uint64 numberTuples,
                        ScanDirection direction,
                        DestReceiver *dest);
-static bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
 static bool ExecCheckPermissionsModified(Oid relOid, Oid userid,
                                         Bitmapset *modifiedCols,
                                         AclMode requiredPerms);
  * ExecCheckOneRelPerms
  *     Check access permissions for a single relation.
  */
-static bool
+bool
 ExecCheckOneRelPerms(RTEPermissionInfo *perminfo)
 {
    AclMode     requiredPerms;
 
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/acl.h"
 #include "utils/backend_status.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
                bms_make_singleton(parse->resultRelation);
    }
 
+   /*
+    * This would be a convenient time to check access permissions for all
+    * relations mentioned in the query, since it would be better to fail now,
+    * before doing any detailed planning.  However, for historical reasons,
+    * we leave this to be done at executor startup.
+    *
+    * Note, however, that we do need to check access permissions for any view
+    * relations mentioned in the query, in order to prevent information being
+    * leaked by selectivity estimation functions, which only check view owner
+    * permissions on underlying tables (see all_rows_selectable() and its
+    * callers).  This is a little ugly, because it means that access
+    * permissions for views will be checked twice, which is another reason
+    * why it would be better to do all the ACL checks here.
+    */
+   foreach(l, parse->rtable)
+   {
+       RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+       if (rte->perminfoindex != 0 &&
+           rte->relkind == RELKIND_VIEW)
+       {
+           RTEPermissionInfo *perminfo;
+           bool        result;
+
+           perminfo = getRTEPermissionInfo(parse->rteperminfos, rte);
+           result = ExecCheckOneRelPerms(perminfo);
+           if (!result)
+               aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_VIEW,
+                              get_rel_name(perminfo->relid));
+       }
+   }
+
    /*
     * Preprocess RowMark information.  We need to do this after subquery
     * pullup, so that all base relations are present.
 
  *     so we can't cope with system columns.
  * *exprs: input/output parameter collecting primitive subclauses within
  *     the clause tree
+ * *leakproof: input/output parameter recording the leakproofness of the
+ *     clause tree.  This should be true initially, and will be set to false
+ *     if any operator function used in an OpExpr is not leakproof.
  *
  * Returns false if there is something we definitively can't handle.
  * On true return, we can proceed to match the *exprs against statistics.
 static bool
 statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
                                      Index relid, Bitmapset **attnums,
-                                     List **exprs)
+                                     List **exprs, bool *leakproof)
 {
    /* Look inside any binary-compatible relabeling (as in examine_variable) */
    if (IsA(clause, RelabelType))
    /* (Var/Expr op Const) or (Const op Var/Expr) */
    if (is_opclause(clause))
    {
-       RangeTblEntry *rte = root->simple_rte_array[relid];
        OpExpr     *expr = (OpExpr *) clause;
        Node       *clause_expr;
 
                return false;
        }
 
-       /*
-        * If there are any securityQuals on the RTE from security barrier
-        * views or RLS policies, then the user may not have access to all the
-        * table's data, and we must check that the operator is leakproof.
-        *
-        * If the operator is leaky, then we must ignore this clause for the
-        * purposes of estimating with MCV lists, otherwise the operator might
-        * reveal values from the MCV list that the user doesn't have
-        * permission to see.
-        */
-       if (rte->securityQuals != NIL &&
-           !get_func_leakproof(get_opcode(expr->opno)))
-           return false;
+       /* Check if the operator is leakproof */
+       if (*leakproof)
+           *leakproof = get_func_leakproof(get_opcode(expr->opno));
 
        /* Check (Var op Const) or (Const op Var) clauses by recursing. */
        if (IsA(clause_expr, Var))
            return statext_is_compatible_clause_internal(root, clause_expr,
-                                                        relid, attnums, exprs);
+                                                        relid, attnums,
+                                                        exprs, leakproof);
 
        /* Otherwise we have (Expr op Const) or (Const op Expr). */
        *exprs = lappend(*exprs, clause_expr);
    /* Var/Expr IN Array */
    if (IsA(clause, ScalarArrayOpExpr))
    {
-       RangeTblEntry *rte = root->simple_rte_array[relid];
        ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause;
        Node       *clause_expr;
        bool        expronleft;
                return false;
        }
 
-       /*
-        * If there are any securityQuals on the RTE from security barrier
-        * views or RLS policies, then the user may not have access to all the
-        * table's data, and we must check that the operator is leakproof.
-        *
-        * If the operator is leaky, then we must ignore this clause for the
-        * purposes of estimating with MCV lists, otherwise the operator might
-        * reveal values from the MCV list that the user doesn't have
-        * permission to see.
-        */
-       if (rte->securityQuals != NIL &&
-           !get_func_leakproof(get_opcode(expr->opno)))
-           return false;
+       /* Check if the operator is leakproof */
+       if (*leakproof)
+           *leakproof = get_func_leakproof(get_opcode(expr->opno));
 
        /* Check Var IN Array clauses by recursing. */
        if (IsA(clause_expr, Var))
            return statext_is_compatible_clause_internal(root, clause_expr,
-                                                        relid, attnums, exprs);
+                                                        relid, attnums,
+                                                        exprs, leakproof);
 
        /* Otherwise we have Expr IN Array. */
        *exprs = lappend(*exprs, clause_expr);
             */
            if (!statext_is_compatible_clause_internal(root,
                                                       (Node *) lfirst(lc),
-                                                      relid, attnums, exprs))
+                                                      relid, attnums, exprs,
+                                                      leakproof))
                return false;
        }
 
 
        /* Check Var IS NULL clauses by recursing. */
        if (IsA(nt->arg, Var))
-           return statext_is_compatible_clause_internal(root, (Node *) (nt->arg),
-                                                        relid, attnums, exprs);
+           return statext_is_compatible_clause_internal(root,
+                                                        (Node *) (nt->arg),
+                                                        relid, attnums,
+                                                        exprs, leakproof);
 
        /* Otherwise we have Expr IS NULL. */
        *exprs = lappend(*exprs, nt->arg);
 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;
+   bool        leakproof;
 
    /*
     * Special-case handling for bare BoolExpr AND clauses, because the
        clause_relid != relid)
        return false;
 
-   /* Check the clause and determine what attributes it references. */
+   /*
+    * Check the clause, determine what attributes it references, and whether
+    * it includes any non-leakproof operators.
+    */
+   leakproof = true;
    if (!statext_is_compatible_clause_internal(root, (Node *) rinfo->clause,
-                                              relid, attnums, exprs))
+                                              relid, attnums, exprs,
+                                              &leakproof))
        return false;
 
    /*
-    * Check that the user has permission to read all required attributes.
+    * If the clause includes any non-leakproof operators, check that the user
+    * has permission to read all required attributes, otherwise the operators
+    * might reveal values from the MCV list that the user doesn't have
+    * permission to see.  We require all rows to be selectable --- there must
+    * be no securityQuals from security barrier views or RLS policies.  See
+    * similar code in examine_variable(), examine_simple_variable(), and
+    * statistic_proc_security_check().
+    *
+    * Note that for an inheritance child, the permission checks are performed
+    * on the inheritance root parent, and whole-table select privilege on the
+    * parent doesn't guarantee that the user could read all columns of the
+    * child. Therefore we must check all referenced columns.
     */
-   userid = OidIsValid(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)
+   if (!leakproof)
    {
        Bitmapset  *clause_attnums = NULL;
        int         attnum = -1;
        if (*exprs != NIL)
            pull_varattnos((Node *) *exprs, relid, &clause_attnums);
 
-       attnum = -1;
-       while ((attnum = bms_next_member(clause_attnums, attnum)) >= 0)
-       {
-           /* Undo the offset */
-           AttrNumber  attno = attnum + FirstLowInvalidHeapAttributeNumber;
-
-           if (attno == InvalidAttrNumber)
-           {
-               /* Whole-row reference, so must have access to all columns */
-               if (pg_attribute_aclcheck_all(rte->relid, userid, ACL_SELECT,
-                                             ACLMASK_ALL) != ACLCHECK_OK)
-                   return false;
-           }
-           else
-           {
-               if (pg_attribute_aclcheck(rte->relid, attno, userid,
-                                         ACL_SELECT) != ACLCHECK_OK)
-                   return false;
-           }
-       }
+       /* Must have permission to read all rows from these columns */
+       if (!all_rows_selectable(root, relid, clause_attnums))
+           return false;
    }
 
    /* If we reach here, the clause is OK */
 
  *     unique for this query.  (Caution: this should be trusted for
  *     statistical purposes only, since we do not check indimmediate nor
  *     verify that the exact same definition of equality applies.)
- * acl_ok: true if current user has permission to read the column(s)
- *     underlying the pg_statistic entry.  This is consulted by
+ * acl_ok: true if current user has permission to read all table rows from
+ *     the column(s) underlying the pg_statistic entry.  This is consulted by
  *     statistic_proc_security_check().
  *
  * Caller is responsible for doing ReleaseVariableStats() before exiting.
         */
        ListCell   *ilist;
        ListCell   *slist;
-       Oid         userid;
 
        /*
         * The nullingrels bits within the expression could prevent us from
        if (bms_overlap(varnos, root->outer_join_rels))
            node = remove_nulling_relids(node, root->outer_join_rels, NULL);
 
-       /*
-        * Determine the user ID to use for privilege checks: either
-        * onerel->userid if it's set (e.g., in case we're accessing the table
-        * via a view), or the current user otherwise.
-        *
-        * If we drill down to child relations, we keep using the same userid:
-        * it's going to be the same anyway, due to how we set up the relation
-        * tree (q.v. build_simple_rel).
-        */
-       userid = OidIsValid(onerel->userid) ? onerel->userid : GetUserId();
-
        foreach(ilist, onerel->indexlist)
        {
            IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
 
                            if (HeapTupleIsValid(vardata->statsTuple))
                            {
-                               /* Get index's table for permission check */
-                               RangeTblEntry *rte;
-
-                               rte = planner_rt_fetch(index->rel->relid, root);
-                               Assert(rte->rtekind == RTE_RELATION);
-
                                /*
+                                * Test if user has permission to access all
+                                * rows from the index's table.
+                                *
                                 * For simplicity, we insist on the whole
                                 * table being selectable, rather than trying
                                 * to identify which column(s) the index
-                                * depends on.  Also require all rows to be
-                                * selectable --- there must be no
-                                * securityQuals from security barrier views
-                                * or RLS policies.
+                                * depends on.
+                                *
+                                * Note that for an inheritance child,
+                                * permissions are checked on the inheritance
+                                * root parent, and whole-table select
+                                * privilege on the parent doesn't quite
+                                * guarantee that the user could read all
+                                * columns of the child.  But in practice it's
+                                * unlikely that any interesting security
+                                * violation could result from allowing access
+                                * to the expression index's stats, so we
+                                * allow it anyway.  See similar code in
+                                * examine_simple_variable() for additional
+                                * comments.
                                 */
                                vardata->acl_ok =
-                                   rte->securityQuals == NIL &&
-                                   (pg_class_aclcheck(rte->relid, userid,
-                                                      ACL_SELECT) == ACLCHECK_OK);
-
-                               /*
-                                * If the user doesn't have permissions to
-                                * access an inheritance child relation, check
-                                * the permissions of the table actually
-                                * mentioned in the query, since most likely
-                                * the user does have that permission.  Note
-                                * that whole-table select privilege on the
-                                * parent doesn't quite guarantee that the
-                                * user could read all columns of the child.
-                                * But in practice it's unlikely that any
-                                * interesting security violation could result
-                                * from allowing access to the expression
-                                * index's stats, so we allow it anyway.  See
-                                * similar code in examine_simple_variable()
-                                * for additional comments.
-                                */
-                               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);
-
-                                       vardata->acl_ok =
-                                           rte->securityQuals == NIL &&
-                                           (pg_class_aclcheck(rte->relid,
-                                                              userid,
-                                                              ACL_SELECT) == ACLCHECK_OK);
-                                   }
-                               }
+                                   all_rows_selectable(root,
+                                                       index->rel->relid,
+                                                       NULL);
                            }
                            else
                            {
                    vardata->freefunc = ReleaseDummy;
 
                    /*
+                    * Test if user has permission to access all rows from the
+                    * table.
+                    *
                     * For simplicity, we insist on the whole table being
                     * selectable, rather than trying to identify which
-                    * column(s) the statistics object depends on.  Also
-                    * require all rows to be selectable --- there must be no
-                    * securityQuals from security barrier views or RLS
-                    * policies.
+                    * column(s) the statistics object depends on.
+                    *
+                    * Note that for an inheritance child, permissions are
+                    * checked on the inheritance root parent, and whole-table
+                    * select privilege on the parent doesn't quite guarantee
+                    * that the user could read all columns of the child.  But
+                    * in practice it's unlikely that any interesting security
+                    * violation could result from allowing access to the
+                    * expression stats, so we allow it anyway.  See similar
+                    * code in examine_simple_variable() for additional
+                    * comments.
                     */
-                   vardata->acl_ok =
-                       rte->securityQuals == NIL &&
-                       (pg_class_aclcheck(rte->relid, userid,
-                                          ACL_SELECT) == ACLCHECK_OK);
-
-                   /*
-                    * If the user doesn't have permissions to access an
-                    * inheritance child relation, check the permissions of
-                    * the table actually mentioned in the query, since most
-                    * likely the user does have that permission.  Note that
-                    * whole-table select privilege on the parent doesn't
-                    * quite guarantee that the user could read all columns of
-                    * the child. But in practice it's unlikely that any
-                    * interesting security violation could result from
-                    * allowing access to the expression stats, so we allow it
-                    * anyway.  See similar code in examine_simple_variable()
-                    * for additional comments.
-                    */
-                   if (!vardata->acl_ok &&
-                       root->append_rel_array != NULL)
-                   {
-                       AppendRelInfo *appinfo;
-                       Index       varno = onerel->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 != onerel->relid)
-                       {
-                           /* Repeat access check on this rel */
-                           rte = planner_rt_fetch(varno, root);
-                           Assert(rte->rtekind == RTE_RELATION);
-
-                           vardata->acl_ok =
-                               rte->securityQuals == NIL &&
-                               (pg_class_aclcheck(rte->relid,
-                                                  userid,
-                                                  ACL_SELECT) == ACLCHECK_OK);
-                       }
-                   }
+                   vardata->acl_ok = all_rows_selectable(root,
+                                                         onerel->relid,
+                                                         NULL);
 
                    break;
                }
 
        if (HeapTupleIsValid(vardata->statsTuple))
        {
-           RelOptInfo *onerel = find_base_rel_noerr(root, var->varno);
-           Oid         userid;
-
            /*
-            * Check if user has permission to read this column.  We require
-            * all rows to be accessible, so there must be no securityQuals
-            * from security barrier views or RLS policies.
+            * Test if user has permission to read all rows from this column.
             *
-            * Normally the Var will have an associated RelOptInfo from which
-            * we can find out which userid to do the check as; but it might
-            * not if it's a RETURNING Var for an INSERT target relation.  In
-            * that case use the RTEPermissionInfo associated with the RTE.
+            * This requires that the user has the appropriate SELECT
+            * privileges and that there are no securityQuals from security
+            * barrier views or RLS policies.  If that's not the case, then we
+            * only permit leakproof functions to be passed pg_statistic data
+            * in vardata, otherwise the functions might reveal data that the
+            * user doesn't have permission to see --- see
+            * statistic_proc_security_check().
             */
-           if (onerel)
-               userid = onerel->userid;
-           else
-           {
-               RTEPermissionInfo *perminfo;
-
-               perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
-               userid = perminfo->checkAsUser;
-           }
-           if (!OidIsValid(userid))
-               userid = GetUserId();
-
            vardata->acl_ok =
-               rte->securityQuals == NIL &&
-               ((pg_class_aclcheck(rte->relid, userid,
-                                   ACL_SELECT) == ACLCHECK_OK) ||
-                (pg_attribute_aclcheck(rte->relid, var->varattno, userid,
-                                       ACL_SELECT) == ACLCHECK_OK));
-
-           /*
-            * If the user doesn't have permissions to access an inheritance
-            * child relation or specifically this attribute, check the
-            * permissions of the table/column actually mentioned in the
-            * query, since most likely the user does have that permission
-            * (else the query will fail at runtime), and if the user can read
-            * the column there then he can get the values of the child table
-            * too.  To do that, we must find out which of the root parent's
-            * attributes the child relation's attribute corresponds to.
-            */
-           if (!vardata->acl_ok && var->varattno > 0 &&
-               root->append_rel_array != NULL)
-           {
-               AppendRelInfo *appinfo;
-               Index       varno = var->varno;
-               int         varattno = var->varattno;
-               bool        found = false;
-
-               appinfo = root->append_rel_array[varno];
-
-               /*
-                * Partitions are mapped to their immediate parent, not the
-                * root parent, so must be ready to walk up multiple
-                * AppendRelInfos.  But stop if we hit a parent that is not
-                * RTE_RELATION --- that's a flattened UNION ALL subquery, not
-                * an inheritance parent.
-                */
-               while (appinfo &&
-                      planner_rt_fetch(appinfo->parent_relid,
-                                       root)->rtekind == RTE_RELATION)
-               {
-                   int         parent_varattno;
-
-                   found = false;
-                   if (varattno <= 0 || varattno > appinfo->num_child_cols)
-                       break;  /* safety check */
-                   parent_varattno = appinfo->parent_colnos[varattno - 1];
-                   if (parent_varattno == 0)
-                       break;  /* Var is local to child */
-
-                   varno = appinfo->parent_relid;
-                   varattno = parent_varattno;
-                   found = true;
-
-                   /* If the parent is itself a child, continue up. */
-                   appinfo = root->append_rel_array[varno];
-               }
-
-               /*
-                * In rare cases, the Var may be local to the child table, in
-                * which case, we've got to live with having no access to this
-                * column's stats.
-                */
-               if (!found)
-                   return;
-
-               /* Repeat the access check on this parent rel & column */
-               rte = planner_rt_fetch(varno, root);
-               Assert(rte->rtekind == RTE_RELATION);
-
-               /*
-                * 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,
-                                       ACL_SELECT) == ACLCHECK_OK) ||
-                    (pg_attribute_aclcheck(rte->relid, varattno, userid,
-                                           ACL_SELECT) == ACLCHECK_OK));
-           }
+               all_rows_selectable(root, var->varno,
+                                   bms_make_singleton(var->varattno - FirstLowInvalidHeapAttributeNumber));
        }
        else
        {
    }
 }
 
+/*
+ * all_rows_selectable
+ *     Test whether the user has permission to select all rows from a given
+ *     relation.
+ *
+ * Inputs:
+ * root: the planner info
+ * varno: the index of the relation (assumed to be an RTE_RELATION)
+ * varattnos: the attributes for which permission is required, or NULL if
+ *     whole-table access is required
+ *
+ * Returns true if the user has the required select permissions, and there are
+ * no securityQuals from security barrier views or RLS policies.
+ *
+ * Note that if the relation is an inheritance child relation, securityQuals
+ * and access permissions are checked against the inheritance root parent (the
+ * relation actually mentioned in the query) --- see the comments in
+ * expand_single_inheritance_child() for an explanation of why it has to be
+ * done this way.
+ *
+ * If varattnos is non-NULL, its attribute numbers should be offset by
+ * FirstLowInvalidHeapAttributeNumber so that system attributes can be
+ * checked.  If varattnos is NULL, only table-level SELECT privileges are
+ * checked, not any column-level privileges.
+ *
+ * Note: if the relation is accessed via a view, this function actually tests
+ * whether the view owner has permission to select from the relation.  To
+ * ensure that the current user has permission, it is also necessary to check
+ * that the current user has permission to select from the view, which we do
+ * at planner-startup --- see subquery_planner().
+ *
+ * This is exported so that other estimation functions can use it.
+ */
+bool
+all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset *varattnos)
+{
+   RelOptInfo *rel = find_base_rel_noerr(root, varno);
+   RangeTblEntry *rte = planner_rt_fetch(varno, root);
+   Oid         userid;
+   int         varattno;
+
+   Assert(rte->rtekind == RTE_RELATION);
+
+   /*
+    * Determine the user ID to use for privilege checks (either the current
+    * user or the view owner, if we're accessing the table via a view).
+    *
+    * Normally the relation will have an associated RelOptInfo from which we
+    * can find the userid, but it might not if it's a RETURNING Var for an
+    * INSERT target relation.  In that case use the RTEPermissionInfo
+    * associated with the RTE.
+    *
+    * If we navigate up to a parent relation, we keep using the same userid,
+    * since it's the same in all relations of a given inheritance tree.
+    */
+   if (rel)
+       userid = rel->userid;
+   else
+   {
+       RTEPermissionInfo *perminfo;
+
+       perminfo = getRTEPermissionInfo(root->parse->rteperminfos, rte);
+       userid = perminfo->checkAsUser;
+   }
+   if (!OidIsValid(userid))
+       userid = GetUserId();
+
+   /*
+    * Permissions and securityQuals must be checked on the table actually
+    * mentioned in the query, so if this is an inheritance child, navigate up
+    * to the inheritance root parent.  If the user can read the whole table
+    * or the required columns there, then they can read from the child table
+    * too.  For per-column checks, we must find out which of the root
+    * parent's attributes the child relation's attributes correspond to.
+    */
+   if (root->append_rel_array != NULL)
+   {
+       AppendRelInfo *appinfo;
+
+       appinfo = root->append_rel_array[varno];
+
+       /*
+        * Partitions are mapped to their immediate parent, not the root
+        * parent, so must be ready to walk up multiple AppendRelInfos.  But
+        * stop if we hit a parent that is not RTE_RELATION --- that's a
+        * flattened UNION ALL subquery, not an inheritance parent.
+        */
+       while (appinfo &&
+              planner_rt_fetch(appinfo->parent_relid,
+                               root)->rtekind == RTE_RELATION)
+       {
+           Bitmapset  *parent_varattnos = NULL;
+
+           /*
+            * For each child attribute, find the corresponding parent
+            * attribute.  In rare cases, the attribute may be local to the
+            * child table, in which case, we've got to live with having no
+            * access to this column.
+            */
+           varattno = -1;
+           while ((varattno = bms_next_member(varattnos, varattno)) >= 0)
+           {
+               AttrNumber  attno;
+               AttrNumber  parent_attno;
+
+               attno = varattno + FirstLowInvalidHeapAttributeNumber;
+
+               if (attno == InvalidAttrNumber)
+               {
+                   /*
+                    * Whole-row reference, so must map each column of the
+                    * child to the parent table.
+                    */
+                   for (attno = 1; attno <= appinfo->num_child_cols; attno++)
+                   {
+                       parent_attno = appinfo->parent_colnos[attno - 1];
+                       if (parent_attno == 0)
+                           return false;   /* attr is local to child */
+                       parent_varattnos =
+                           bms_add_member(parent_varattnos,
+                                          parent_attno - FirstLowInvalidHeapAttributeNumber);
+                   }
+               }
+               else
+               {
+                   if (attno < 0)
+                   {
+                       /* System attnos are the same in all tables */
+                       parent_attno = attno;
+                   }
+                   else
+                   {
+                       if (attno > appinfo->num_child_cols)
+                           return false;   /* safety check */
+                       parent_attno = appinfo->parent_colnos[attno - 1];
+                       if (parent_attno == 0)
+                           return false;   /* attr is local to child */
+                   }
+                   parent_varattnos =
+                       bms_add_member(parent_varattnos,
+                                      parent_attno - FirstLowInvalidHeapAttributeNumber);
+               }
+           }
+
+           /* If the parent is itself a child, continue up */
+           varno = appinfo->parent_relid;
+           varattnos = parent_varattnos;
+           appinfo = root->append_rel_array[varno];
+       }
+
+       /* Perform the access check on this parent rel */
+       rte = planner_rt_fetch(varno, root);
+       Assert(rte->rtekind == RTE_RELATION);
+   }
+
+   /*
+    * For all rows to be accessible, there must be no securityQuals from
+    * security barrier views or RLS policies.
+    */
+   if (rte->securityQuals != NIL)
+       return false;
+
+   /*
+    * Test for table-level SELECT privilege.
+    *
+    * If varattnos is non-NULL, this is sufficient to give access to all
+    * requested attributes, even for a child table, since we have verified
+    * that all required child columns have matching parent columns.
+    *
+    * If varattnos is NULL (whole-table access requested), this doesn't
+    * necessarily guarantee that the user can read all columns of a child
+    * table, but we allow it anyway (see comments in examine_variable()) and
+    * don't bother checking any column privileges.
+    */
+   if (pg_class_aclcheck(rte->relid, userid, ACL_SELECT) == ACLCHECK_OK)
+       return true;
+
+   if (varattnos == NULL)
+       return false;           /* whole-table access requested */
+
+   /*
+    * Don't have table-level SELECT privilege, so check per-column
+    * privileges.
+    */
+   varattno = -1;
+   while ((varattno = bms_next_member(varattnos, varattno)) >= 0)
+   {
+       AttrNumber  attno = varattno + FirstLowInvalidHeapAttributeNumber;
+
+       if (attno == InvalidAttrNumber)
+       {
+           /* Whole-row reference, so must have access to all columns */
+           if (pg_attribute_aclcheck_all(rte->relid, userid, ACL_SELECT,
+                                         ACLMASK_ALL) != ACLCHECK_OK)
+               return false;
+       }
+       else
+       {
+           if (pg_attribute_aclcheck(rte->relid, attno, userid,
+                                     ACL_SELECT) != ACLCHECK_OK)
+               return false;
+       }
+   }
+
+   /* If we reach here, have all required column privileges */
+   return true;
+}
+
 /*
  * examine_indexcol_variable
  *     Try to look up statistical data about an index column/expression.
 
 /*
  * Check whether it is permitted to call func_oid passing some of the
- * pg_statistic data in vardata.  We allow this either if the user has SELECT
- * privileges on the table or column underlying the pg_statistic data or if
- * the function is marked leakproof.
+ * pg_statistic data in vardata.  We allow this if either of the following
+ * conditions is met: (1) the user has SELECT privileges on the table or
+ * column underlying the pg_statistic data and there are no securityQuals from
+ * security barrier views or RLS policies, or (2) the function is marked
+ * leakproof.
  */
 bool
 statistic_proc_security_check(VariableStatData *vardata, Oid func_oid)
 {
    if (vardata->acl_ok)
-       return true;
+       return true;            /* have SELECT privs and no securityQuals */
 
    if (!OidIsValid(func_oid))
        return false;
 
 extern void ExecutorRewind(QueryDesc *queryDesc);
 extern bool ExecCheckPermissions(List *rangeTable,
                                 List *rteperminfos, bool ereport_on_violation);
+extern bool ExecCheckOneRelPerms(RTEPermissionInfo *perminfo);
 extern void CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation,
                                List *mergeActions);
 extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
 
    int32       atttypmod;      /* actual typmod (after stripping relabel) */
    bool        isunique;       /* matches unique index, DISTINCT or GROUP-BY
                                 * clause */
-   bool        acl_ok;         /* result of ACL check on table or column */
+   bool        acl_ok;         /* true if user has SELECT privilege on all
+                                * rows from the table or column */
 } VariableStatData;
 
 #define ReleaseVariableStats(vardata)  \
 
 extern void examine_variable(PlannerInfo *root, Node *node, int varRelid,
                             VariableStatData *vardata);
+extern bool all_rows_selectable(PlannerInfo *root, Index varno, Bitmapset *varattnos);
 extern bool statistic_proc_security_check(VariableStatData *vardata, Oid func_oid);
 extern bool get_restriction_variable(PlannerInfo *root, List *args,
                                     int varRelid,
 
   SELECT * FROM atest12 WHERE b <<< 5;
 CREATE VIEW atest12sbv WITH (security_barrier=true) AS
   SELECT * FROM atest12 WHERE b <<< 5;
-GRANT SELECT ON atest12v TO PUBLIC;
-GRANT SELECT ON atest12sbv TO PUBLIC;
 -- This plan should use nestloop, knowing that few rows will be selected.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
                    QUERY PLAN                    
   LANGUAGE plpgsql immutable;
 CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
                      restrict = scalargtsel);
--- This should not show any "leak" notices before failing.
+-- These should not show any "leak" notices before failing.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
 ERROR:  permission denied for table atest12
+EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0;
+ERROR:  permission denied for view atest12v
+EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0;
+ERROR:  permission denied for view atest12sbv
+-- Now regress_priv_user1 grants access to regress_priv_user2 via the views.
+SET SESSION AUTHORIZATION regress_priv_user1;
+GRANT SELECT ON atest12v TO PUBLIC;
+GRANT SELECT ON atest12sbv TO PUBLIC;
+SET SESSION AUTHORIZATION regress_priv_user2;
 -- These plans should continue to use a nestloop, since they execute with the
 -- privileges of the view owner.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
 
 DROP VIEW rls_view;
 DROP TABLE rls_tbl;
 DROP TABLE ref_tbl;
--- Leaky operator test
+-- Leaky operator tests
 CREATE TABLE rls_tbl (a int);
 INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
 ANALYZE rls_tbl;
    One-Time Filter: false
 (2 rows)
 
+RESET SESSION AUTHORIZATION;
+CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
+INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_child_tbl;
+CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
+CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
+INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_ptbl, rls_part;
+ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_ptbl TO regress_rls_alice;
+GRANT SELECT ON rls_part TO regress_rls_alice;
+CREATE POLICY p1 ON rls_tbl USING (a < 0);
+CREATE POLICY p2 ON rls_ptbl USING (a < 0);
+CREATE POLICY p3 ON rls_part USING (a < 0);
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+ERROR:  permission denied for table rls_child_tbl
+SELECT * FROM rls_ptbl WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM rls_part WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+               SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+               SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+ERROR:  permission denied for table rls_child_tbl
+RESET SESSION AUTHORIZATION;
+REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
+CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
+ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
+CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+ERROR:  permission denied for table rls_tbl
+SELECT * FROM rls_tbl_view WHERE a <<< 1000;
+ERROR:  permission denied for view rls_tbl_view
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+               SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+ERROR:  permission denied for table rls_tbl
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+               SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+ a 
+---
+(0 rows)
+
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
+DROP TABLE rls_part;
+DROP TABLE rls_ptbl;
+DROP TABLE rls_child_tbl;
+DROP VIEW rls_tbl_view;
 DROP TABLE rls_tbl;
 -- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
 SET SESSION AUTHORIZATION regress_rls_alice;
 
     LANGUAGE plpgsql;
 CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
+CREATE FUNCTION op_leak(record, record) RETURNS bool
+    AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
+    LANGUAGE plpgsql;
+CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
+                     restrict = scalarltsel);
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
 ERROR:  permission denied for table priv_test_tbl
-SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
+SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0; -- Permission denied
+ERROR:  permission denied for table priv_test_tbl
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
 ERROR:  permission denied for table priv_test_tbl
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
 ERROR:  permission denied for table priv_test_tbl
 ---+---
 (0 rows)
 
+SELECT * FROM tststats.priv_test_view t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
 DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
 -- Grant table access, but hide all data with RLS
 RESET SESSION AUTHORIZATION;
 ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
 GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
 -- Should now have direct table access, but see nothing and leak nothing
 SET SESSION AUTHORIZATION regress_stats_user1;
 ---+---
 (0 rows)
 
-SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
+SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0; -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
  a | b 
 ---+---
 (0 rows)
 
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+-- Create plain inheritance parent table with no access permissions
+RESET SESSION AUTHORIZATION;
+CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
+ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
+-- Should not have access to parent, and should leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+ERROR:  permission denied for table priv_test_parent_tbl
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 OR b <<< 0; -- Permission denied
+ERROR:  permission denied for table priv_test_parent_tbl
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
+ERROR:  permission denied for table priv_test_parent_tbl
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+ERROR:  permission denied for table priv_test_parent_tbl
+-- Grant table access to parent, but hide all data with RLS
+RESET SESSION AUTHORIZATION;
+ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0);
+GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
+-- Should now have direct table access to parent, but see nothing and leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 OR b <<< 0; -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+ a | b 
+---+---
+(0 rows)
+
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
 -- privilege checks for pg_stats_ext and pg_stats_ext_exprs
 RESET SESSION AUTHORIZATION;
 CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT);
 -- Tidy up
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
+DROP OPERATOR <<< (record, record);
+DROP FUNCTION op_leak(record, record);
 RESET SESSION AUTHORIZATION;
 DROP TABLE stats_ext_tbl;
 DROP SCHEMA tststats CASCADE;
-NOTICE:  drop cascades to 2 other objects
-DETAIL:  drop cascades to table tststats.priv_test_tbl
+NOTICE:  drop cascades to 3 other objects
+DETAIL:  drop cascades to table tststats.priv_test_parent_tbl
+drop cascades to table tststats.priv_test_tbl
 drop cascades to view tststats.priv_test_view
 DROP USER regress_stats_user1;
 CREATE TABLE grouping_unique (x integer);
 
   SELECT * FROM atest12 WHERE b <<< 5;
 CREATE VIEW atest12sbv WITH (security_barrier=true) AS
   SELECT * FROM atest12 WHERE b <<< 5;
-GRANT SELECT ON atest12v TO PUBLIC;
-GRANT SELECT ON atest12sbv TO PUBLIC;
 
 -- This plan should use nestloop, knowing that few rows will be selected.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12v x, atest12v y WHERE x.a = y.b;
 CREATE OPERATOR >>> (procedure = leak2, leftarg = integer, rightarg = integer,
                      restrict = scalargtsel);
 
--- This should not show any "leak" notices before failing.
+-- These should not show any "leak" notices before failing.
 EXPLAIN (COSTS OFF) SELECT * FROM atest12 WHERE a >>> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM atest12v WHERE a >>> 0;
+EXPLAIN (COSTS OFF) SELECT * FROM atest12sbv WHERE a >>> 0;
+
+-- Now regress_priv_user1 grants access to regress_priv_user2 via the views.
+SET SESSION AUTHORIZATION regress_priv_user1;
+GRANT SELECT ON atest12v TO PUBLIC;
+GRANT SELECT ON atest12sbv TO PUBLIC;
+SET SESSION AUTHORIZATION regress_priv_user2;
 
 -- These plans should continue to use a nestloop, since they execute with the
 -- privileges of the view owner.
 
 DROP TABLE rls_tbl;
 DROP TABLE ref_tbl;
 
--- Leaky operator test
+-- Leaky operator tests
 CREATE TABLE rls_tbl (a int);
 INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
 ANALYZE rls_tbl;
                      restrict = scalarltsel);
 SELECT * FROM rls_tbl WHERE a <<< 1000;
 EXPLAIN (COSTS OFF) SELECT * FROM rls_tbl WHERE a <<< 1000 or a <<< 900;
+RESET SESSION AUTHORIZATION;
+
+CREATE TABLE rls_child_tbl () INHERITS (rls_tbl);
+INSERT INTO rls_child_tbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_child_tbl;
+
+CREATE TABLE rls_ptbl (a int) PARTITION BY RANGE (a);
+CREATE TABLE rls_part PARTITION OF rls_ptbl FOR VALUES FROM (-100) TO (100);
+INSERT INTO rls_ptbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_ptbl, rls_part;
+
+ALTER TABLE rls_ptbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_part ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_ptbl TO regress_rls_alice;
+GRANT SELECT ON rls_part TO regress_rls_alice;
+CREATE POLICY p1 ON rls_tbl USING (a < 0);
+CREATE POLICY p2 ON rls_ptbl USING (a < 0);
+CREATE POLICY p3 ON rls_part USING (a < 0);
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+SELECT * FROM rls_ptbl WHERE a <<< 1000;
+SELECT * FROM rls_part WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+               SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+               SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
+RESET SESSION AUTHORIZATION;
+
+REVOKE SELECT ON rls_tbl FROM regress_rls_alice;
+CREATE VIEW rls_tbl_view AS SELECT * FROM rls_tbl;
+
+ALTER TABLE rls_child_tbl ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_child_tbl TO regress_rls_alice;
+CREATE POLICY p4 ON rls_child_tbl USING (a < 0);
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+SELECT * FROM rls_tbl_view WHERE a <<< 1000;
+SELECT * FROM rls_child_tbl WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_tbl UNION ALL
+               SELECT * FROM rls_tbl) t WHERE a <<< 1000;
+SELECT * FROM (SELECT * FROM rls_child_tbl UNION ALL
+               SELECT * FROM rls_child_tbl) t WHERE a <<< 1000;
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
 RESET SESSION AUTHORIZATION;
+DROP TABLE rls_part;
+DROP TABLE rls_ptbl;
+DROP TABLE rls_child_tbl;
+DROP VIEW rls_tbl_view;
 DROP TABLE rls_tbl;
 
 -- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
 
     LANGUAGE plpgsql;
 CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
                      restrict = scalarltsel);
+CREATE FUNCTION op_leak(record, record) RETURNS bool
+    AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
+    LANGUAGE plpgsql;
+CREATE OPERATOR <<< (procedure = op_leak, leftarg = record, rightarg = record,
+                     restrict = scalarltsel);
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
-SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
+SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0; -- Permission denied
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
 
 -- Grant access via a security barrier view, but hide all data
 SET SESSION AUTHORIZATION regress_stats_user1;
 SELECT * FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
 SELECT * FROM tststats.priv_test_view WHERE a <<< 0 OR b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_view t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
 DELETE FROM tststats.priv_test_view WHERE a <<< 0 AND b <<< 0; -- Should not leak
 
 -- Grant table access, but hide all data with RLS
 RESET SESSION AUTHORIZATION;
 ALTER TABLE tststats.priv_test_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_tbl_pol ON tststats.priv_test_tbl USING (2 * a < 0);
 GRANT SELECT, DELETE ON tststats.priv_test_tbl TO regress_stats_user1;
 
 -- Should now have direct table access, but see nothing and leak nothing
 SET SESSION AUTHORIZATION regress_stats_user1;
 SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
-SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0;
+SELECT * FROM tststats.priv_test_tbl WHERE a <<< 0 OR b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
 DELETE FROM tststats.priv_test_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
 
+-- Create plain inheritance parent table with no access permissions
+RESET SESSION AUTHORIZATION;
+CREATE TABLE tststats.priv_test_parent_tbl (a int, b int);
+ALTER TABLE tststats.priv_test_tbl INHERIT tststats.priv_test_parent_tbl;
+
+-- Should not have access to parent, and should leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 OR b <<< 0; -- Permission denied
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Permission denied
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Permission denied
+
+-- Grant table access to parent, but hide all data with RLS
+RESET SESSION AUTHORIZATION;
+ALTER TABLE tststats.priv_test_parent_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY priv_test_parent_tbl_pol ON tststats.priv_test_parent_tbl USING (2 * a < 0);
+GRANT SELECT, DELETE ON tststats.priv_test_parent_tbl TO regress_stats_user1;
+
+-- Should now have direct table access to parent, but see nothing and leak nothing
+SET SESSION AUTHORIZATION regress_stats_user1;
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_parent_tbl WHERE a <<< 0 OR b <<< 0; -- Should not leak
+SELECT * FROM tststats.priv_test_parent_tbl t
+ WHERE a <<< 0 AND (b <<< 0 OR t.* <<< (1, 1) IS NOT NULL); -- Should not leak
+DELETE FROM tststats.priv_test_parent_tbl WHERE a <<< 0 AND b <<< 0; -- Should not leak
+
 -- privilege checks for pg_stats_ext and pg_stats_ext_exprs
 RESET SESSION AUTHORIZATION;
 CREATE TABLE stats_ext_tbl (id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, col TEXT);
 -- Tidy up
 DROP OPERATOR <<< (int, int);
 DROP FUNCTION op_leak(int, int);
+DROP OPERATOR <<< (record, record);
+DROP FUNCTION op_leak(record, record);
 RESET SESSION AUTHORIZATION;
 DROP TABLE stats_ext_tbl;
 DROP SCHEMA tststats CASCADE;