#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"
/*
* 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)
{
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)
/*
* Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
+ * ExecCheckPermissions() does.
*/
userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
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));
#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"
* 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;
/*
* 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)
{
* 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
* 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;
/*
* 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
{
LOCKMODE lockmode = is_from ? RowExclusiveLock : AccessShareLock;
ParseNamespaceItem *nsitem;
- RangeTblEntry *rte;
+ RTEPermissionInfo *perminfo;
TupleDesc tupDesc;
List *attnums;
ListCell *cur;
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)
{
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.
* 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;
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);
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;
* 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 *
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
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));
#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"
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 */
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,
/*
- * 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.
* 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,
+ result = (*ExecutorCheckPerms_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
* 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;
* 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;
* 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));
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
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;
#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"
static bool tlist_matches_tupdesc(PlanState *ps, List *tlist, int varno, TupleDesc tupdesc);
static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
+static RTEPermissionInfo *GetResultRTEPermissionInfo(ResultRelInfo *relinfo, 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);
return bms_union(ExecGetUpdatedCols(relinfo, estate),
ExecGetExtraUpdatedCols(relinfo, estate));
}
+
+/*
+ * GetResultRTEPermissionInfo
+ * Looks up RTEPermissionInfo for ExecGet*Cols() routines
+ */
+static 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 = getRTEPermissionInfo(estate->es_rteperminfos, rte);
+ }
+
+ return perminfo;
+}
+
+/*
+ * 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();
+}
WRITE_CHAR_FIELD(relkind);
WRITE_INT_FIELD(rellockmode);
WRITE_NODE_FIELD(tablesample);
+ WRITE_UINT_FIELD(perminfoindex);
break;
case RTE_SUBQUERY:
WRITE_NODE_FIELD(subquery);
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);
}
READ_CHAR_FIELD(relkind);
READ_INT_FIELD(rellockmode);
READ_NODE_FIELD(tablesample);
+ READ_UINT_FIELD(perminfoindex);
break;
case RTE_SUBQUERY:
READ_NODE_FIELD(subquery);
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);
#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"
glob->subroots = NIL;
glob->rewindPlanIDs = NULL;
glob->finalrtable = NIL;
+ glob->finalrteperminfos = NIL;
glob->finalrowmarks = NIL;
glob->resultRelations = NIL;
glob->appendRelations = NIL;
/* 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);
result->planTree = top_plan;
result->partPruneInfos = glob->partPruneInfos;
result->rtable = glob->finalrtable;
+ result->permInfos = glob->finalrteperminfos;
result->resultRelations = glob->resultRelations;
result->appendRelations = glob->appendRelations;
result->subplans = glob->subplans;
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);
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);
#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"
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
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,
* 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)
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);
}
/*
/*
* 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;
/* 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;
*/
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));
+ }
}
/*
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.
+ */
+ CombineRangeTables(&parse->rtable, &parse->rteperminfos,
+ subselect->rtable, subselect->rteperminfos);
/*
* And finally, build the JoinExpr node.
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
}
/*
- * 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.)
+ * Now append the adjusted rtable entries and their perminfos to upper
+ * query. (We hold off until after fixing the upper rtable entries; no
+ * point in running that code on the subquery ones too.)
*/
- parse->rtable = list_concat(parse->rtable, subquery->rtable);
+ CombineRangeTables(&parse->rtable, &parse->rteperminfos,
+ subquery->rtable, subquery->rteperminfos);
/*
* Pull up any FOR UPDATE/SHARE markers, too. (OffsetVarNodes already
}
/*
- * Append child RTEs to parent rtable.
+ * Append child RTEs (and their perminfos) to parent rtable.
*/
- root->parse->rtable = list_concat(root->parse->rtable, rtable);
+ CombineRangeTables(&root->parse->rtable, &root->parse->rteperminfos,
+ rtable, subquery->rteperminfos);
/*
* Recursively scan the subquery's setOperations tree and add
#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"
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,
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);
/* 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.
*/
* 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
{
expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
RangeTblEntry *parentrte,
Index parentRTindex, Relation parentrel,
+ Bitmapset *parent_updatedCols,
PlanRowMark *top_parentrc, LOCKMODE lockmode)
{
PartitionDesc 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.
+ * 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.
/* 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);
/*
* 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));
}
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);
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
}
}
+/*
+ * 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
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;
+}
#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"
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;
/* 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;
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;
/*
* 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;
}
* 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;
* 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)
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);
}
/*
/* 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;
* (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,
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;
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;
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;
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;
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;
transformUpdateTargetList(ParseState *pstate, List *origTlist)
{
List *tlist = NIL;
- RangeTblEntry *target_rte;
+ RTEPermissionInfo *target_perminfo;
ListCell *orig_tl;
ListCell *tl;
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)
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);
}
&qry->targetList);
qry->rtable = pstate->p_rtable;
+ qry->rteperminfos = pstate->p_rteperminfos;
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
qry->hasSubLinks = pstate->p_hasSubLinks;
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,
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,
* 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.
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);
}
}
*/
qry->targetList = NIL;
qry->rtable = pstate->p_rtable;
+ qry->rteperminfos = pstate->p_rteperminfos;
/*
* Transform the join condition. This includes references to the target
{
List *exprList = NIL;
ListCell *lc;
- RangeTblEntry *rte;
+ RTEPermissionInfo *perminfo;
ListCell *icols;
ListCell *attnos;
List *icolumns;
* 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);
false);
action->targetList = lappend(action->targetList, tle);
- rte->insertedCols =
- bms_add_member(rte->insertedCols,
+ perminfo->insertedCols =
+ bms_add_member(perminfo->insertedCols,
attr_num - FirstLowInvalidHeapAttributeNumber);
}
}
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)
{
*
* 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;
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;
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
+ RTEPermissionInfo *perminfo;
char *refname = alias ? alias->aliasname : relation->relname;
LOCKMODE lockmode;
Relation rel;
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.
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
* 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
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
+ RTEPermissionInfo *perminfo;
char *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
Assert(pstate != NULL);
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.
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
* list --- caller must do that if appropriate.
*/
return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
- rel->rd_att);
+ perminfo, rel->rd_att);
}
/*
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
/*
* 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 addRTEPermissionInfo().
*/
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
* 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);
}
/*
* 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 addRTEPermissionInfo().
*/
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
/*
* 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
/*
* 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
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 */
/*
* 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
/*
* 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
* 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);
}
int sublevels_up, bool require_col_privs, int location)
{
RangeTblEntry *rte = nsitem->p_rte;
+ RTEPermissionInfo *perminfo = nsitem->p_perminfo;
List *names,
*vars;
ListCell *name,
* 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)
{
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;
+}
*
* 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
else
{
RangeTblEntry *rte = nsitem->p_rte;
+ RTEPermissionInfo *perminfo = nsitem->p_perminfo;
List *vars;
ListCell *l;
* 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)
/*
* 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)
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
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);
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);
#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"
rte->rellockmode = AccessShareLock;
ExecInitRangeTable(estate, list_make1(rte));
+ addRTEPermissionInfo(&estate->es_rteperminfos, rte);
+
edata->targetRelInfo = resultRelInfo = makeNode(ResultRelInfo);
/*
bool has_oldtup;
TupleTableSlot *remoteslot;
RangeTblEntry *target_rte;
+ RTEPermissionInfo *target_perminfo;
MemoryContext oldctx;
/*
* 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);
{
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));
/*
* 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)
{
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 */
* 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 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);
+ {
+ List *rtable_tail = sub_action->rtable;
+ List *perminfos_tail = sub_action->rteperminfos;
+
+ /*
+ * RewriteQuery relies on the fact that RT entries from the original
+ * query appear at the start of the expanded rtable, so we put the
+ * action's original table at the end of the list.
+ */
+ sub_action->rtable = copyObject(parsetree->rtable);
+ sub_action->rteperminfos = copyObject(parsetree->rteperminfos);
+ CombineRangeTables(&sub_action->rtable, &sub_action->rteperminfos,
+ rtable_tail, perminfos_tail);
+ }
/*
* There could have been some SubLinks in parsetree's rtable, in which
/*
* 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;
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);
Query *rule_action;
RangeTblEntry *rte,
*subrte;
+ RTEPermissionInfo *perminfo,
+ *sub_perminfo;
RowMarkClause *rc;
if (list_length(rule->actions) != 1)
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.
/*
* 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);
* 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;
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 */
/*
*/
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;
}
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)
{
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;
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.
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
* 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;
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 =
{
int result_relation;
RangeTblEntry *rt_entry;
+ RTEPermissionInfo *rt_perminfo;
Relation rt_entry_relation;
List *locks;
int product_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
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)
{
return expression_tree_walker(node, contains_multiexpr_param, context);
}
+/*
+ * 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)
+{
+ ListCell *l;
+ int offset = list_length(*dst_perminfos);
+
+ if (offset > 0)
+ {
+ foreach(l, src_rtable)
+ {
+ RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+ if (rte->perminfoindex > 0)
+ rte->perminfoindex += offset;
+ }
+ }
+
+ *dst_perminfos = list_concat(*dst_perminfos, src_perminfos);
+ *dst_rtable = list_concat(*dst_rtable, src_rtable);
+}
/*
* OffsetVarNodes - adjust Vars when appending one query's RT to another
#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"
CmdType commandType;
List *permissive_policies;
List *restrictive_policies;
+ RTEPermissionInfo *perminfo;
/* Defaults for the return values */
*securityQuals = NIL;
*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)
* 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;
*/
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;
* 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;
* 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,
* 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,
* 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
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;
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;
/*
/*
* 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
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202212011
+#define CATALOG_VERSION_NO 202212061
#endif
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;
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);
+/* Hook for plugins to get control in ExecCheckPermissions() */
+typedef bool (*ExecutorCheckPerms_hook_type) (List *rangeTable,
+ List *rtePermInfos,
+ bool ereport_on_violation);
extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
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,
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);
* 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_infos; /* PlannedStmt.partPruneInfos */
const char *es_sourceText; /* Source text from QueryDesc */
JunkFilter *es_junkFilter; /* top-level junk filter, if any */
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 */
* 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
* 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):
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 -
/* "flat" rangetable for executor */
List *finalrtable;
+ /* "flat" list of RTEPermissionInfos */
+ List *finalrteperminfos;
+
/* "flat" list of PlanRowMarks */
List *finalrowmarks;
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 */
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);
* 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.
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) */
* 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.
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? */
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,
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);
} ReplaceVarsNoMatchOption;
+extern void CombineRangeTables(List **dst_rtable, List **dst_perminfos,
+ List *src_rtable, List *src_perminfos);
extern void OffsetVarNodes(Node *node, int offset, int sublevels_up);
extern void ChangeVarNodes(Node *node, int rt_index, int new_index,
int sublevels_up);
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,
}
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;
/* 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)
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
---
(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;
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;
RI_QueryHashEntry
RI_QueryKey
RTEKind
+RTEPermissionInfo
RWConflict
RWConflictPoolHeader
Range
fix_upper_expr_context
fix_windowagg_cond_context
flatten_join_alias_vars_context
+flatten_rtes_walker_context
float4
float4KEY
float8