mirror of
https://github.com/postgres/postgres.git
synced 2025-04-22 23:02:54 +03:00
This extends the work done in commit 2f5c9d9c9 to provide a more nearly complete abstraction layer hiding the details of index updating for catalog changes. That commit only invented abstractions for catalog inserts and updates, leaving nearby code for catalog deletes still calling the heap-level routines directly. That seems rather ugly from here, and it does little to help if we ever want to shift to a storage system in which indexing work is needed at delete time. Hence, create a wrapper function CatalogTupleDelete(), and replace calls of simple_heap_delete() on catalog tuples with it. There are now very few direct calls of [simple_]heap_delete remaining in the tree. Discussion: https://postgr.es/m/462.1485902736@sss.pgh.pa.us
1386 lines
38 KiB
C
1386 lines
38 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* policy.c
|
|
* Commands for manipulating policies.
|
|
*
|
|
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* src/backend/commands/policy.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/genam.h"
|
|
#include "access/heapam.h"
|
|
#include "access/htup.h"
|
|
#include "access/htup_details.h"
|
|
#include "access/sysattr.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/dependency.h"
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/objectaccess.h"
|
|
#include "catalog/pg_authid.h"
|
|
#include "catalog/pg_policy.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "commands/policy.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/pg_list.h"
|
|
#include "parser/parse_clause.h"
|
|
#include "parser/parse_collate.h"
|
|
#include "parser/parse_node.h"
|
|
#include "parser/parse_relation.h"
|
|
#include "rewrite/rewriteManip.h"
|
|
#include "rewrite/rowsecurity.h"
|
|
#include "storage/lock.h"
|
|
#include "utils/acl.h"
|
|
#include "utils/array.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/inval.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/syscache.h"
|
|
|
|
static void RangeVarCallbackForPolicy(const RangeVar *rv,
|
|
Oid relid, Oid oldrelid, void *arg);
|
|
static char parse_policy_command(const char *cmd_name);
|
|
static Datum *policy_role_list_to_array(List *roles, int *num_roles);
|
|
|
|
/*
|
|
* Callback to RangeVarGetRelidExtended().
|
|
*
|
|
* Checks the following:
|
|
* - the relation specified is a table.
|
|
* - current user owns the table.
|
|
* - the table is not a system table.
|
|
*
|
|
* If any of these checks fails then an error is raised.
|
|
*/
|
|
static void
|
|
RangeVarCallbackForPolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
|
|
void *arg)
|
|
{
|
|
HeapTuple tuple;
|
|
Form_pg_class classform;
|
|
char relkind;
|
|
|
|
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
|
|
if (!HeapTupleIsValid(tuple))
|
|
return;
|
|
|
|
classform = (Form_pg_class) GETSTRUCT(tuple);
|
|
relkind = classform->relkind;
|
|
|
|
/* Must own relation. */
|
|
if (!pg_class_ownercheck(relid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
|
|
|
|
/* No system table modifications unless explicitly allowed. */
|
|
if (!allowSystemTableMods && IsSystemClass(relid, classform))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: \"%s\" is a system catalog",
|
|
rv->relname)));
|
|
|
|
/* Relation type MUST be a table. */
|
|
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a table", rv->relname)));
|
|
|
|
ReleaseSysCache(tuple);
|
|
}
|
|
|
|
/*
|
|
* parse_policy_command -
|
|
* helper function to convert full command strings to their char
|
|
* representation.
|
|
*
|
|
* cmd_name - full string command name. Valid values are 'all', 'select',
|
|
* 'insert', 'update' and 'delete'.
|
|
*
|
|
*/
|
|
static char
|
|
parse_policy_command(const char *cmd_name)
|
|
{
|
|
char polcmd;
|
|
|
|
if (!cmd_name)
|
|
elog(ERROR, "unrecognized policy command");
|
|
|
|
if (strcmp(cmd_name, "all") == 0)
|
|
polcmd = '*';
|
|
else if (strcmp(cmd_name, "select") == 0)
|
|
polcmd = ACL_SELECT_CHR;
|
|
else if (strcmp(cmd_name, "insert") == 0)
|
|
polcmd = ACL_INSERT_CHR;
|
|
else if (strcmp(cmd_name, "update") == 0)
|
|
polcmd = ACL_UPDATE_CHR;
|
|
else if (strcmp(cmd_name, "delete") == 0)
|
|
polcmd = ACL_DELETE_CHR;
|
|
else
|
|
elog(ERROR, "unrecognized policy command");
|
|
|
|
return polcmd;
|
|
}
|
|
|
|
/*
|
|
* policy_role_list_to_array
|
|
* helper function to convert a list of RoleSpecs to an array of
|
|
* role id Datums.
|
|
*/
|
|
static Datum *
|
|
policy_role_list_to_array(List *roles, int *num_roles)
|
|
{
|
|
Datum *role_oids;
|
|
ListCell *cell;
|
|
int i = 0;
|
|
|
|
/* Handle no roles being passed in as being for public */
|
|
if (roles == NIL)
|
|
{
|
|
*num_roles = 1;
|
|
role_oids = (Datum *) palloc(*num_roles * sizeof(Datum));
|
|
role_oids[0] = ObjectIdGetDatum(ACL_ID_PUBLIC);
|
|
|
|
return role_oids;
|
|
}
|
|
|
|
*num_roles = list_length(roles);
|
|
role_oids = (Datum *) palloc(*num_roles * sizeof(Datum));
|
|
|
|
foreach(cell, roles)
|
|
{
|
|
RoleSpec *spec = lfirst(cell);
|
|
|
|
/*
|
|
* PUBLIC covers all roles, so it only makes sense alone.
|
|
*/
|
|
if (spec->roletype == ROLESPEC_PUBLIC)
|
|
{
|
|
if (*num_roles != 1)
|
|
{
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("ignoring specified roles other than PUBLIC"),
|
|
errhint("All roles are members of the PUBLIC role.")));
|
|
*num_roles = 1;
|
|
}
|
|
role_oids[0] = ObjectIdGetDatum(ACL_ID_PUBLIC);
|
|
|
|
return role_oids;
|
|
}
|
|
else
|
|
role_oids[i++] =
|
|
ObjectIdGetDatum(get_rolespec_oid(spec, false));
|
|
}
|
|
|
|
return role_oids;
|
|
}
|
|
|
|
/*
|
|
* Load row security policy from the catalog, and store it in
|
|
* the relation's relcache entry.
|
|
*/
|
|
void
|
|
RelationBuildRowSecurity(Relation relation)
|
|
{
|
|
MemoryContext rscxt;
|
|
MemoryContext oldcxt = CurrentMemoryContext;
|
|
RowSecurityDesc *volatile rsdesc = NULL;
|
|
|
|
/*
|
|
* Create a memory context to hold everything associated with this
|
|
* relation's row security policy. This makes it easy to clean up during
|
|
* a relcache flush.
|
|
*/
|
|
rscxt = AllocSetContextCreate(CacheMemoryContext,
|
|
"row security descriptor",
|
|
ALLOCSET_SMALL_SIZES);
|
|
|
|
/*
|
|
* Since rscxt lives under CacheMemoryContext, it is long-lived. Use a
|
|
* PG_TRY block to ensure it'll get freed if we fail partway through.
|
|
*/
|
|
PG_TRY();
|
|
{
|
|
Relation catalog;
|
|
ScanKeyData skey;
|
|
SysScanDesc sscan;
|
|
HeapTuple tuple;
|
|
|
|
rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
|
|
rsdesc->rscxt = rscxt;
|
|
|
|
catalog = heap_open(PolicyRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&skey,
|
|
Anum_pg_policy_polrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(RelationGetRelid(relation)));
|
|
|
|
sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true,
|
|
NULL, 1, &skey);
|
|
|
|
/*
|
|
* Loop through the row level security policies for this relation, if
|
|
* any.
|
|
*/
|
|
while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
|
|
{
|
|
Datum value_datum;
|
|
char cmd_value;
|
|
bool permissive_value;
|
|
Datum roles_datum;
|
|
char *qual_value;
|
|
Expr *qual_expr;
|
|
char *with_check_value;
|
|
Expr *with_check_qual;
|
|
char *policy_name_value;
|
|
bool isnull;
|
|
RowSecurityPolicy *policy;
|
|
|
|
/*
|
|
* Note: all the pass-by-reference data we collect here is either
|
|
* still stored in the tuple, or constructed in the caller's
|
|
* short-lived memory context. We must copy it into rscxt
|
|
* explicitly below.
|
|
*/
|
|
|
|
/* Get policy command */
|
|
value_datum = heap_getattr(tuple, Anum_pg_policy_polcmd,
|
|
RelationGetDescr(catalog), &isnull);
|
|
Assert(!isnull);
|
|
cmd_value = DatumGetChar(value_datum);
|
|
|
|
/* Get policy permissive or restrictive */
|
|
value_datum = heap_getattr(tuple, Anum_pg_policy_polpermissive,
|
|
RelationGetDescr(catalog), &isnull);
|
|
Assert(!isnull);
|
|
permissive_value = DatumGetBool(value_datum);
|
|
|
|
/* Get policy name */
|
|
value_datum = heap_getattr(tuple, Anum_pg_policy_polname,
|
|
RelationGetDescr(catalog), &isnull);
|
|
Assert(!isnull);
|
|
policy_name_value = NameStr(*(DatumGetName(value_datum)));
|
|
|
|
/* Get policy roles */
|
|
roles_datum = heap_getattr(tuple, Anum_pg_policy_polroles,
|
|
RelationGetDescr(catalog), &isnull);
|
|
/* shouldn't be null, but initdb doesn't mark it so, so check */
|
|
if (isnull)
|
|
elog(ERROR, "unexpected null value in pg_policy.polroles");
|
|
|
|
/* Get policy qual */
|
|
value_datum = heap_getattr(tuple, Anum_pg_policy_polqual,
|
|
RelationGetDescr(catalog), &isnull);
|
|
if (!isnull)
|
|
{
|
|
qual_value = TextDatumGetCString(value_datum);
|
|
qual_expr = (Expr *) stringToNode(qual_value);
|
|
}
|
|
else
|
|
qual_expr = NULL;
|
|
|
|
/* Get WITH CHECK qual */
|
|
value_datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
|
|
RelationGetDescr(catalog), &isnull);
|
|
if (!isnull)
|
|
{
|
|
with_check_value = TextDatumGetCString(value_datum);
|
|
with_check_qual = (Expr *) stringToNode(with_check_value);
|
|
}
|
|
else
|
|
with_check_qual = NULL;
|
|
|
|
/* Now copy everything into the cache context */
|
|
MemoryContextSwitchTo(rscxt);
|
|
|
|
policy = palloc0(sizeof(RowSecurityPolicy));
|
|
policy->policy_name = pstrdup(policy_name_value);
|
|
policy->polcmd = cmd_value;
|
|
policy->permissive = permissive_value;
|
|
policy->roles = DatumGetArrayTypePCopy(roles_datum);
|
|
policy->qual = copyObject(qual_expr);
|
|
policy->with_check_qual = copyObject(with_check_qual);
|
|
policy->hassublinks = checkExprHasSubLink((Node *) qual_expr) ||
|
|
checkExprHasSubLink((Node *) with_check_qual);
|
|
|
|
rsdesc->policies = lcons(policy, rsdesc->policies);
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
|
|
/* clean up some (not all) of the junk ... */
|
|
if (qual_expr != NULL)
|
|
pfree(qual_expr);
|
|
if (with_check_qual != NULL)
|
|
pfree(with_check_qual);
|
|
}
|
|
|
|
systable_endscan(sscan);
|
|
heap_close(catalog, AccessShareLock);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
/* Delete rscxt, first making sure it isn't active */
|
|
MemoryContextSwitchTo(oldcxt);
|
|
MemoryContextDelete(rscxt);
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
|
|
/* Success --- attach the policy descriptor to the relcache entry */
|
|
relation->rd_rsdesc = rsdesc;
|
|
}
|
|
|
|
/*
|
|
* RemovePolicyById -
|
|
* remove a policy by its OID. If a policy does not exist with the provided
|
|
* oid, then an error is raised.
|
|
*
|
|
* policy_id - the oid of the policy.
|
|
*/
|
|
void
|
|
RemovePolicyById(Oid policy_id)
|
|
{
|
|
Relation pg_policy_rel;
|
|
SysScanDesc sscan;
|
|
ScanKeyData skey[1];
|
|
HeapTuple tuple;
|
|
Oid relid;
|
|
Relation rel;
|
|
|
|
pg_policy_rel = heap_open(PolicyRelationId, RowExclusiveLock);
|
|
|
|
/*
|
|
* Find the policy to delete.
|
|
*/
|
|
ScanKeyInit(&skey[0],
|
|
ObjectIdAttributeNumber,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(policy_id));
|
|
|
|
sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true,
|
|
NULL, 1, skey);
|
|
|
|
tuple = systable_getnext(sscan);
|
|
|
|
/* If the policy exists, then remove it, otherwise raise an error. */
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "could not find tuple for policy %u", policy_id);
|
|
|
|
/*
|
|
* Open and exclusive-lock the relation the policy belongs to. (We need
|
|
* exclusive lock to lock out queries that might otherwise depend on the
|
|
* set of policies the rel has; furthermore we've got to hold the lock
|
|
* till commit.)
|
|
*/
|
|
relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
|
|
|
|
rel = heap_open(relid, AccessExclusiveLock);
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION &&
|
|
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a table",
|
|
RelationGetRelationName(rel))));
|
|
|
|
if (!allowSystemTableMods && IsSystemRelation(rel))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: \"%s\" is a system catalog",
|
|
RelationGetRelationName(rel))));
|
|
|
|
CatalogTupleDelete(pg_policy_rel, &tuple->t_self);
|
|
|
|
systable_endscan(sscan);
|
|
|
|
/*
|
|
* Note that, unlike some of the other flags in pg_class, relrowsecurity
|
|
* is not just an indication of if policies exist. When relrowsecurity is
|
|
* set by a user, then all access to the relation must be through a
|
|
* policy. If no policy is defined for the relation then a default-deny
|
|
* policy is created and all records are filtered (except for queries from
|
|
* the owner).
|
|
*/
|
|
CacheInvalidateRelcache(rel);
|
|
|
|
heap_close(rel, NoLock);
|
|
|
|
/* Clean up */
|
|
heap_close(pg_policy_rel, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* RemoveRoleFromObjectPolicy -
|
|
* remove a role from a policy by its OID. If the role is not a member of
|
|
* the policy then an error is raised. False is returned to indicate that
|
|
* the role could not be removed due to being the only role on the policy
|
|
* and therefore the entire policy should be removed.
|
|
*
|
|
* Note that a warning will be thrown and true will be returned on a
|
|
* permission error, as the policy should not be removed in that case.
|
|
*
|
|
* roleid - the oid of the role to remove
|
|
* classid - should always be PolicyRelationId
|
|
* policy_id - the oid of the policy.
|
|
*/
|
|
bool
|
|
RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid policy_id)
|
|
{
|
|
Relation pg_policy_rel;
|
|
SysScanDesc sscan;
|
|
ScanKeyData skey[1];
|
|
HeapTuple tuple;
|
|
Oid relid;
|
|
Relation rel;
|
|
ArrayType *policy_roles;
|
|
int num_roles;
|
|
Datum roles_datum;
|
|
bool attr_isnull;
|
|
bool noperm = true;
|
|
|
|
Assert(classid == PolicyRelationId);
|
|
|
|
pg_policy_rel = heap_open(PolicyRelationId, RowExclusiveLock);
|
|
|
|
/*
|
|
* Find the policy to update.
|
|
*/
|
|
ScanKeyInit(&skey[0],
|
|
ObjectIdAttributeNumber,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(policy_id));
|
|
|
|
sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true,
|
|
NULL, 1, skey);
|
|
|
|
tuple = systable_getnext(sscan);
|
|
|
|
/* Raise an error if we don't find the policy. */
|
|
if (!HeapTupleIsValid(tuple))
|
|
elog(ERROR, "could not find tuple for policy %u", policy_id);
|
|
|
|
/*
|
|
* Open and exclusive-lock the relation the policy belongs to.
|
|
*/
|
|
relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
|
|
|
|
rel = relation_open(relid, AccessExclusiveLock);
|
|
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a table",
|
|
RelationGetRelationName(rel))));
|
|
|
|
if (!allowSystemTableMods && IsSystemRelation(rel))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: \"%s\" is a system catalog",
|
|
RelationGetRelationName(rel))));
|
|
|
|
/* Get the current set of roles */
|
|
roles_datum = heap_getattr(tuple,
|
|
Anum_pg_policy_polroles,
|
|
RelationGetDescr(pg_policy_rel),
|
|
&attr_isnull);
|
|
|
|
Assert(!attr_isnull);
|
|
|
|
policy_roles = DatumGetArrayTypePCopy(roles_datum);
|
|
|
|
/* We should be removing exactly one entry from the roles array */
|
|
num_roles = ARR_DIMS(policy_roles)[0] - 1;
|
|
|
|
Assert(num_roles >= 0);
|
|
|
|
/* Must own relation. */
|
|
if (pg_class_ownercheck(relid, GetUserId()))
|
|
noperm = false; /* user is allowed to modify this policy */
|
|
else
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED),
|
|
errmsg("role \"%s\" could not be removed from policy \"%s\" on \"%s\"",
|
|
GetUserNameFromId(roleid, false),
|
|
NameStr(((Form_pg_policy) GETSTRUCT(tuple))->polname),
|
|
RelationGetRelationName(rel))));
|
|
|
|
/*
|
|
* If multiple roles exist on this policy, then remove the one we were
|
|
* asked to and leave the rest.
|
|
*/
|
|
if (!noperm && num_roles > 0)
|
|
{
|
|
int i,
|
|
j;
|
|
Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles);
|
|
Datum *role_oids;
|
|
char *qual_value;
|
|
Node *qual_expr;
|
|
List *qual_parse_rtable = NIL;
|
|
char *with_check_value;
|
|
Node *with_check_qual;
|
|
List *with_check_parse_rtable = NIL;
|
|
Datum values[Natts_pg_policy];
|
|
bool isnull[Natts_pg_policy];
|
|
bool replaces[Natts_pg_policy];
|
|
Datum value_datum;
|
|
ArrayType *role_ids;
|
|
HeapTuple new_tuple;
|
|
ObjectAddress target;
|
|
ObjectAddress myself;
|
|
|
|
/* zero-clear */
|
|
memset(values, 0, sizeof(values));
|
|
memset(replaces, 0, sizeof(replaces));
|
|
memset(isnull, 0, sizeof(isnull));
|
|
|
|
/*
|
|
* All of the dependencies will be removed from the policy and then
|
|
* re-added. In order to get them correct, we need to extract out the
|
|
* expressions in the policy and construct a parsestate just enough to
|
|
* build the range table(s) to then pass to recordDependencyOnExpr().
|
|
*/
|
|
|
|
/* Get policy qual, to update dependencies */
|
|
value_datum = heap_getattr(tuple, Anum_pg_policy_polqual,
|
|
RelationGetDescr(pg_policy_rel), &attr_isnull);
|
|
if (!attr_isnull)
|
|
{
|
|
ParseState *qual_pstate;
|
|
|
|
/* parsestate is built just to build the range table */
|
|
qual_pstate = make_parsestate(NULL);
|
|
|
|
qual_value = TextDatumGetCString(value_datum);
|
|
qual_expr = stringToNode(qual_value);
|
|
|
|
/* Add this rel to the parsestate's rangetable, for dependencies */
|
|
addRangeTableEntryForRelation(qual_pstate, rel, NULL, false, false);
|
|
|
|
qual_parse_rtable = qual_pstate->p_rtable;
|
|
free_parsestate(qual_pstate);
|
|
}
|
|
else
|
|
qual_expr = NULL;
|
|
|
|
/* Get WITH CHECK qual, to update dependencies */
|
|
value_datum = heap_getattr(tuple, Anum_pg_policy_polwithcheck,
|
|
RelationGetDescr(pg_policy_rel), &attr_isnull);
|
|
if (!attr_isnull)
|
|
{
|
|
ParseState *with_check_pstate;
|
|
|
|
/* parsestate is built just to build the range table */
|
|
with_check_pstate = make_parsestate(NULL);
|
|
|
|
with_check_value = TextDatumGetCString(value_datum);
|
|
with_check_qual = stringToNode(with_check_value);
|
|
|
|
/* Add this rel to the parsestate's rangetable, for dependencies */
|
|
addRangeTableEntryForRelation(with_check_pstate, rel, NULL, false,
|
|
false);
|
|
|
|
with_check_parse_rtable = with_check_pstate->p_rtable;
|
|
free_parsestate(with_check_pstate);
|
|
}
|
|
else
|
|
with_check_qual = NULL;
|
|
|
|
/* Rebuild the roles array to then update the pg_policy tuple with */
|
|
role_oids = (Datum *) palloc(num_roles * sizeof(Datum));
|
|
for (i = 0, j = 0; i < ARR_DIMS(policy_roles)[0]; i++)
|
|
/* Copy over all of the roles which are not the one being removed */
|
|
if (roles[i] != roleid)
|
|
role_oids[j++] = ObjectIdGetDatum(roles[i]);
|
|
|
|
/* We should have only removed the one role */
|
|
Assert(j == num_roles);
|
|
|
|
/* This is the array for the new tuple */
|
|
role_ids = construct_array(role_oids, num_roles, OIDOID,
|
|
sizeof(Oid), true, 'i');
|
|
|
|
replaces[Anum_pg_policy_polroles - 1] = true;
|
|
values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids);
|
|
|
|
new_tuple = heap_modify_tuple(tuple,
|
|
RelationGetDescr(pg_policy_rel),
|
|
values, isnull, replaces);
|
|
CatalogTupleUpdate(pg_policy_rel, &new_tuple->t_self, new_tuple);
|
|
|
|
/* Remove all old dependencies. */
|
|
deleteDependencyRecordsFor(PolicyRelationId, policy_id, false);
|
|
|
|
/* Record the new set of dependencies */
|
|
target.classId = RelationRelationId;
|
|
target.objectId = relid;
|
|
target.objectSubId = 0;
|
|
|
|
myself.classId = PolicyRelationId;
|
|
myself.objectId = policy_id;
|
|
myself.objectSubId = 0;
|
|
|
|
recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
|
|
|
|
if (qual_expr)
|
|
recordDependencyOnExpr(&myself, qual_expr, qual_parse_rtable,
|
|
DEPENDENCY_NORMAL);
|
|
|
|
if (with_check_qual)
|
|
recordDependencyOnExpr(&myself, with_check_qual,
|
|
with_check_parse_rtable,
|
|
DEPENDENCY_NORMAL);
|
|
|
|
/* Remove all the old shared dependencies (roles) */
|
|
deleteSharedDependencyRecordsFor(PolicyRelationId, policy_id, 0);
|
|
|
|
/* Record the new shared dependencies (roles) */
|
|
target.classId = AuthIdRelationId;
|
|
target.objectSubId = 0;
|
|
for (i = 0; i < num_roles; i++)
|
|
{
|
|
target.objectId = DatumGetObjectId(role_oids[i]);
|
|
/* no need for dependency on the public role */
|
|
if (target.objectId != ACL_ID_PUBLIC)
|
|
recordSharedDependencyOn(&myself, &target,
|
|
SHARED_DEPENDENCY_POLICY);
|
|
}
|
|
|
|
InvokeObjectPostAlterHook(PolicyRelationId, policy_id, 0);
|
|
|
|
heap_freetuple(new_tuple);
|
|
|
|
/* Invalidate Relation Cache */
|
|
CacheInvalidateRelcache(rel);
|
|
}
|
|
|
|
/* Clean up. */
|
|
systable_endscan(sscan);
|
|
|
|
relation_close(rel, NoLock);
|
|
|
|
heap_close(pg_policy_rel, RowExclusiveLock);
|
|
|
|
return (noperm || num_roles > 0);
|
|
}
|
|
|
|
/*
|
|
* CreatePolicy -
|
|
* handles the execution of the CREATE POLICY command.
|
|
*
|
|
* stmt - the CreatePolicyStmt that describes the policy to create.
|
|
*/
|
|
ObjectAddress
|
|
CreatePolicy(CreatePolicyStmt *stmt)
|
|
{
|
|
Relation pg_policy_rel;
|
|
Oid policy_id;
|
|
Relation target_table;
|
|
Oid table_id;
|
|
char polcmd;
|
|
Datum *role_oids;
|
|
int nitems = 0;
|
|
ArrayType *role_ids;
|
|
ParseState *qual_pstate;
|
|
ParseState *with_check_pstate;
|
|
RangeTblEntry *rte;
|
|
Node *qual;
|
|
Node *with_check_qual;
|
|
ScanKeyData skey[2];
|
|
SysScanDesc sscan;
|
|
HeapTuple policy_tuple;
|
|
Datum values[Natts_pg_policy];
|
|
bool isnull[Natts_pg_policy];
|
|
ObjectAddress target;
|
|
ObjectAddress myself;
|
|
int i;
|
|
|
|
/* Parse command */
|
|
polcmd = parse_policy_command(stmt->cmd_name);
|
|
|
|
/*
|
|
* If the command is SELECT or DELETE then WITH CHECK should be NULL.
|
|
*/
|
|
if ((polcmd == ACL_SELECT_CHR || polcmd == ACL_DELETE_CHR)
|
|
&& stmt->with_check != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("WITH CHECK cannot be applied to SELECT or DELETE")));
|
|
|
|
/*
|
|
* If the command is INSERT then WITH CHECK should be the only expression
|
|
* provided.
|
|
*/
|
|
if (polcmd == ACL_INSERT_CHR && stmt->qual != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("only WITH CHECK expression allowed for INSERT")));
|
|
|
|
/* Collect role ids */
|
|
role_oids = policy_role_list_to_array(stmt->roles, &nitems);
|
|
role_ids = construct_array(role_oids, nitems, OIDOID,
|
|
sizeof(Oid), true, 'i');
|
|
|
|
/* Parse the supplied clause */
|
|
qual_pstate = make_parsestate(NULL);
|
|
with_check_pstate = make_parsestate(NULL);
|
|
|
|
/* zero-clear */
|
|
memset(values, 0, sizeof(values));
|
|
memset(isnull, 0, sizeof(isnull));
|
|
|
|
/* Get id of table. Also handles permissions checks. */
|
|
table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
|
|
false, false,
|
|
RangeVarCallbackForPolicy,
|
|
(void *) stmt);
|
|
|
|
/* Open target_table to build quals. No additional lock is necessary. */
|
|
target_table = relation_open(table_id, NoLock);
|
|
|
|
/* Add for the regular security quals */
|
|
rte = addRangeTableEntryForRelation(qual_pstate, target_table,
|
|
NULL, false, false);
|
|
addRTEtoQuery(qual_pstate, rte, false, true, true);
|
|
|
|
/* Add for the with-check quals */
|
|
rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
|
|
NULL, false, false);
|
|
addRTEtoQuery(with_check_pstate, rte, false, true, true);
|
|
|
|
qual = transformWhereClause(qual_pstate,
|
|
copyObject(stmt->qual),
|
|
EXPR_KIND_POLICY,
|
|
"POLICY");
|
|
|
|
with_check_qual = transformWhereClause(with_check_pstate,
|
|
copyObject(stmt->with_check),
|
|
EXPR_KIND_POLICY,
|
|
"POLICY");
|
|
|
|
/* Fix up collation information */
|
|
assign_expr_collations(qual_pstate, qual);
|
|
assign_expr_collations(with_check_pstate, with_check_qual);
|
|
|
|
/* Open pg_policy catalog */
|
|
pg_policy_rel = heap_open(PolicyRelationId, RowExclusiveLock);
|
|
|
|
/* Set key - policy's relation id. */
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_policy_polrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(table_id));
|
|
|
|
/* Set key - policy's name. */
|
|
ScanKeyInit(&skey[1],
|
|
Anum_pg_policy_polname,
|
|
BTEqualStrategyNumber, F_NAMEEQ,
|
|
CStringGetDatum(stmt->policy_name));
|
|
|
|
sscan = systable_beginscan(pg_policy_rel,
|
|
PolicyPolrelidPolnameIndexId, true, NULL, 2,
|
|
skey);
|
|
|
|
policy_tuple = systable_getnext(sscan);
|
|
|
|
/* Complain if the policy name already exists for the table */
|
|
if (HeapTupleIsValid(policy_tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("policy \"%s\" for table \"%s\" already exists",
|
|
stmt->policy_name, RelationGetRelationName(target_table))));
|
|
|
|
values[Anum_pg_policy_polrelid - 1] = ObjectIdGetDatum(table_id);
|
|
values[Anum_pg_policy_polname - 1] = DirectFunctionCall1(namein,
|
|
CStringGetDatum(stmt->policy_name));
|
|
values[Anum_pg_policy_polcmd - 1] = CharGetDatum(polcmd);
|
|
values[Anum_pg_policy_polpermissive - 1] = BoolGetDatum(stmt->permissive);
|
|
values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids);
|
|
|
|
/* Add qual if present. */
|
|
if (qual)
|
|
values[Anum_pg_policy_polqual - 1] = CStringGetTextDatum(nodeToString(qual));
|
|
else
|
|
isnull[Anum_pg_policy_polqual - 1] = true;
|
|
|
|
/* Add WITH CHECK qual if present */
|
|
if (with_check_qual)
|
|
values[Anum_pg_policy_polwithcheck - 1] = CStringGetTextDatum(nodeToString(with_check_qual));
|
|
else
|
|
isnull[Anum_pg_policy_polwithcheck - 1] = true;
|
|
|
|
policy_tuple = heap_form_tuple(RelationGetDescr(pg_policy_rel), values,
|
|
isnull);
|
|
|
|
policy_id = CatalogTupleInsert(pg_policy_rel, policy_tuple);
|
|
|
|
/* Record Dependencies */
|
|
target.classId = RelationRelationId;
|
|
target.objectId = table_id;
|
|
target.objectSubId = 0;
|
|
|
|
myself.classId = PolicyRelationId;
|
|
myself.objectId = policy_id;
|
|
myself.objectSubId = 0;
|
|
|
|
recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
|
|
|
|
recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable,
|
|
DEPENDENCY_NORMAL);
|
|
|
|
recordDependencyOnExpr(&myself, with_check_qual,
|
|
with_check_pstate->p_rtable, DEPENDENCY_NORMAL);
|
|
|
|
/* Register role dependencies */
|
|
target.classId = AuthIdRelationId;
|
|
target.objectSubId = 0;
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
target.objectId = DatumGetObjectId(role_oids[i]);
|
|
/* no dependency if public */
|
|
if (target.objectId != ACL_ID_PUBLIC)
|
|
recordSharedDependencyOn(&myself, &target,
|
|
SHARED_DEPENDENCY_POLICY);
|
|
}
|
|
|
|
InvokeObjectPostCreateHook(PolicyRelationId, policy_id, 0);
|
|
|
|
/* Invalidate Relation Cache */
|
|
CacheInvalidateRelcache(target_table);
|
|
|
|
/* Clean up. */
|
|
heap_freetuple(policy_tuple);
|
|
free_parsestate(qual_pstate);
|
|
free_parsestate(with_check_pstate);
|
|
systable_endscan(sscan);
|
|
relation_close(target_table, NoLock);
|
|
heap_close(pg_policy_rel, RowExclusiveLock);
|
|
|
|
return myself;
|
|
}
|
|
|
|
/*
|
|
* AlterPolicy -
|
|
* handles the execution of the ALTER POLICY command.
|
|
*
|
|
* stmt - the AlterPolicyStmt that describes the policy and how to alter it.
|
|
*/
|
|
ObjectAddress
|
|
AlterPolicy(AlterPolicyStmt *stmt)
|
|
{
|
|
Relation pg_policy_rel;
|
|
Oid policy_id;
|
|
Relation target_table;
|
|
Oid table_id;
|
|
Datum *role_oids = NULL;
|
|
int nitems = 0;
|
|
ArrayType *role_ids = NULL;
|
|
List *qual_parse_rtable = NIL;
|
|
List *with_check_parse_rtable = NIL;
|
|
Node *qual = NULL;
|
|
Node *with_check_qual = NULL;
|
|
ScanKeyData skey[2];
|
|
SysScanDesc sscan;
|
|
HeapTuple policy_tuple;
|
|
HeapTuple new_tuple;
|
|
Datum values[Natts_pg_policy];
|
|
bool isnull[Natts_pg_policy];
|
|
bool replaces[Natts_pg_policy];
|
|
ObjectAddress target;
|
|
ObjectAddress myself;
|
|
Datum polcmd_datum;
|
|
char polcmd;
|
|
bool polcmd_isnull;
|
|
int i;
|
|
|
|
/* Parse role_ids */
|
|
if (stmt->roles != NULL)
|
|
{
|
|
role_oids = policy_role_list_to_array(stmt->roles, &nitems);
|
|
role_ids = construct_array(role_oids, nitems, OIDOID,
|
|
sizeof(Oid), true, 'i');
|
|
}
|
|
|
|
/* Get id of table. Also handles permissions checks. */
|
|
table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
|
|
false, false,
|
|
RangeVarCallbackForPolicy,
|
|
(void *) stmt);
|
|
|
|
target_table = relation_open(table_id, NoLock);
|
|
|
|
/* Parse the using policy clause */
|
|
if (stmt->qual)
|
|
{
|
|
RangeTblEntry *rte;
|
|
ParseState *qual_pstate = make_parsestate(NULL);
|
|
|
|
rte = addRangeTableEntryForRelation(qual_pstate, target_table,
|
|
NULL, false, false);
|
|
|
|
addRTEtoQuery(qual_pstate, rte, false, true, true);
|
|
|
|
qual = transformWhereClause(qual_pstate, copyObject(stmt->qual),
|
|
EXPR_KIND_POLICY,
|
|
"POLICY");
|
|
|
|
/* Fix up collation information */
|
|
assign_expr_collations(qual_pstate, qual);
|
|
|
|
qual_parse_rtable = qual_pstate->p_rtable;
|
|
free_parsestate(qual_pstate);
|
|
}
|
|
|
|
/* Parse the with-check policy clause */
|
|
if (stmt->with_check)
|
|
{
|
|
RangeTblEntry *rte;
|
|
ParseState *with_check_pstate = make_parsestate(NULL);
|
|
|
|
rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
|
|
NULL, false, false);
|
|
|
|
addRTEtoQuery(with_check_pstate, rte, false, true, true);
|
|
|
|
with_check_qual = transformWhereClause(with_check_pstate,
|
|
copyObject(stmt->with_check),
|
|
EXPR_KIND_POLICY,
|
|
"POLICY");
|
|
|
|
/* Fix up collation information */
|
|
assign_expr_collations(with_check_pstate, with_check_qual);
|
|
|
|
with_check_parse_rtable = with_check_pstate->p_rtable;
|
|
free_parsestate(with_check_pstate);
|
|
}
|
|
|
|
/* zero-clear */
|
|
memset(values, 0, sizeof(values));
|
|
memset(replaces, 0, sizeof(replaces));
|
|
memset(isnull, 0, sizeof(isnull));
|
|
|
|
/* Find policy to update. */
|
|
pg_policy_rel = heap_open(PolicyRelationId, RowExclusiveLock);
|
|
|
|
/* Set key - policy's relation id. */
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_policy_polrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(table_id));
|
|
|
|
/* Set key - policy's name. */
|
|
ScanKeyInit(&skey[1],
|
|
Anum_pg_policy_polname,
|
|
BTEqualStrategyNumber, F_NAMEEQ,
|
|
CStringGetDatum(stmt->policy_name));
|
|
|
|
sscan = systable_beginscan(pg_policy_rel,
|
|
PolicyPolrelidPolnameIndexId, true, NULL, 2,
|
|
skey);
|
|
|
|
policy_tuple = systable_getnext(sscan);
|
|
|
|
/* Check that the policy is found, raise an error if not. */
|
|
if (!HeapTupleIsValid(policy_tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("policy \"%s\" for table \"%s\" does not exist",
|
|
stmt->policy_name,
|
|
RelationGetRelationName(target_table))));
|
|
|
|
/* Get policy command */
|
|
polcmd_datum = heap_getattr(policy_tuple, Anum_pg_policy_polcmd,
|
|
RelationGetDescr(pg_policy_rel),
|
|
&polcmd_isnull);
|
|
Assert(!polcmd_isnull);
|
|
polcmd = DatumGetChar(polcmd_datum);
|
|
|
|
/*
|
|
* If the command is SELECT or DELETE then WITH CHECK should be NULL.
|
|
*/
|
|
if ((polcmd == ACL_SELECT_CHR || polcmd == ACL_DELETE_CHR)
|
|
&& stmt->with_check != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("only USING expression allowed for SELECT, DELETE")));
|
|
|
|
/*
|
|
* If the command is INSERT then WITH CHECK should be the only expression
|
|
* provided.
|
|
*/
|
|
if ((polcmd == ACL_INSERT_CHR)
|
|
&& stmt->qual != NULL)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("only WITH CHECK expression allowed for INSERT")));
|
|
|
|
policy_id = HeapTupleGetOid(policy_tuple);
|
|
|
|
if (role_ids != NULL)
|
|
{
|
|
replaces[Anum_pg_policy_polroles - 1] = true;
|
|
values[Anum_pg_policy_polroles - 1] = PointerGetDatum(role_ids);
|
|
}
|
|
else
|
|
{
|
|
Oid *roles;
|
|
Datum roles_datum;
|
|
bool attr_isnull;
|
|
ArrayType *policy_roles;
|
|
|
|
/*
|
|
* We need to pull the set of roles this policy applies to from what's
|
|
* in the catalog, so that we can recreate the dependencies correctly
|
|
* for the policy.
|
|
*/
|
|
|
|
roles_datum = heap_getattr(policy_tuple, Anum_pg_policy_polroles,
|
|
RelationGetDescr(pg_policy_rel),
|
|
&attr_isnull);
|
|
Assert(!attr_isnull);
|
|
|
|
policy_roles = DatumGetArrayTypePCopy(roles_datum);
|
|
|
|
roles = (Oid *) ARR_DATA_PTR(policy_roles);
|
|
|
|
nitems = ARR_DIMS(policy_roles)[0];
|
|
|
|
role_oids = (Datum *) palloc(nitems * sizeof(Datum));
|
|
|
|
for (i = 0; i < nitems; i++)
|
|
role_oids[i] = ObjectIdGetDatum(roles[i]);
|
|
}
|
|
|
|
if (qual != NULL)
|
|
{
|
|
replaces[Anum_pg_policy_polqual - 1] = true;
|
|
values[Anum_pg_policy_polqual - 1]
|
|
= CStringGetTextDatum(nodeToString(qual));
|
|
}
|
|
else
|
|
{
|
|
Datum value_datum;
|
|
bool attr_isnull;
|
|
|
|
/*
|
|
* We need to pull the USING expression and build the range table for
|
|
* the policy from what's in the catalog, so that we can recreate the
|
|
* dependencies correctly for the policy.
|
|
*/
|
|
|
|
/* Check if the policy has a USING expr */
|
|
value_datum = heap_getattr(policy_tuple, Anum_pg_policy_polqual,
|
|
RelationGetDescr(pg_policy_rel),
|
|
&attr_isnull);
|
|
if (!attr_isnull)
|
|
{
|
|
char *qual_value;
|
|
ParseState *qual_pstate;
|
|
|
|
/* parsestate is built just to build the range table */
|
|
qual_pstate = make_parsestate(NULL);
|
|
|
|
qual_value = TextDatumGetCString(value_datum);
|
|
qual = stringToNode(qual_value);
|
|
|
|
/* Add this rel to the parsestate's rangetable, for dependencies */
|
|
addRangeTableEntryForRelation(qual_pstate, target_table, NULL,
|
|
false, false);
|
|
|
|
qual_parse_rtable = qual_pstate->p_rtable;
|
|
free_parsestate(qual_pstate);
|
|
}
|
|
}
|
|
|
|
if (with_check_qual != NULL)
|
|
{
|
|
replaces[Anum_pg_policy_polwithcheck - 1] = true;
|
|
values[Anum_pg_policy_polwithcheck - 1]
|
|
= CStringGetTextDatum(nodeToString(with_check_qual));
|
|
}
|
|
else
|
|
{
|
|
Datum value_datum;
|
|
bool attr_isnull;
|
|
|
|
/*
|
|
* We need to pull the WITH CHECK expression and build the range table
|
|
* for the policy from what's in the catalog, so that we can recreate
|
|
* the dependencies correctly for the policy.
|
|
*/
|
|
|
|
/* Check if the policy has a WITH CHECK expr */
|
|
value_datum = heap_getattr(policy_tuple, Anum_pg_policy_polwithcheck,
|
|
RelationGetDescr(pg_policy_rel),
|
|
&attr_isnull);
|
|
if (!attr_isnull)
|
|
{
|
|
char *with_check_value;
|
|
ParseState *with_check_pstate;
|
|
|
|
/* parsestate is built just to build the range table */
|
|
with_check_pstate = make_parsestate(NULL);
|
|
|
|
with_check_value = TextDatumGetCString(value_datum);
|
|
with_check_qual = stringToNode(with_check_value);
|
|
|
|
/* Add this rel to the parsestate's rangetable, for dependencies */
|
|
addRangeTableEntryForRelation(with_check_pstate, target_table, NULL,
|
|
false, false);
|
|
|
|
with_check_parse_rtable = with_check_pstate->p_rtable;
|
|
free_parsestate(with_check_pstate);
|
|
}
|
|
}
|
|
|
|
new_tuple = heap_modify_tuple(policy_tuple,
|
|
RelationGetDescr(pg_policy_rel),
|
|
values, isnull, replaces);
|
|
CatalogTupleUpdate(pg_policy_rel, &new_tuple->t_self, new_tuple);
|
|
|
|
/* Update Dependencies. */
|
|
deleteDependencyRecordsFor(PolicyRelationId, policy_id, false);
|
|
|
|
/* Record Dependencies */
|
|
target.classId = RelationRelationId;
|
|
target.objectId = table_id;
|
|
target.objectSubId = 0;
|
|
|
|
myself.classId = PolicyRelationId;
|
|
myself.objectId = policy_id;
|
|
myself.objectSubId = 0;
|
|
|
|
recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
|
|
|
|
recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL);
|
|
|
|
recordDependencyOnExpr(&myself, with_check_qual, with_check_parse_rtable,
|
|
DEPENDENCY_NORMAL);
|
|
|
|
/* Register role dependencies */
|
|
deleteSharedDependencyRecordsFor(PolicyRelationId, policy_id, 0);
|
|
target.classId = AuthIdRelationId;
|
|
target.objectSubId = 0;
|
|
for (i = 0; i < nitems; i++)
|
|
{
|
|
target.objectId = DatumGetObjectId(role_oids[i]);
|
|
/* no dependency if public */
|
|
if (target.objectId != ACL_ID_PUBLIC)
|
|
recordSharedDependencyOn(&myself, &target,
|
|
SHARED_DEPENDENCY_POLICY);
|
|
}
|
|
|
|
InvokeObjectPostAlterHook(PolicyRelationId, policy_id, 0);
|
|
|
|
heap_freetuple(new_tuple);
|
|
|
|
/* Invalidate Relation Cache */
|
|
CacheInvalidateRelcache(target_table);
|
|
|
|
/* Clean up. */
|
|
systable_endscan(sscan);
|
|
relation_close(target_table, NoLock);
|
|
heap_close(pg_policy_rel, RowExclusiveLock);
|
|
|
|
return myself;
|
|
}
|
|
|
|
/*
|
|
* rename_policy -
|
|
* change the name of a policy on a relation
|
|
*/
|
|
ObjectAddress
|
|
rename_policy(RenameStmt *stmt)
|
|
{
|
|
Relation pg_policy_rel;
|
|
Relation target_table;
|
|
Oid table_id;
|
|
Oid opoloid;
|
|
ScanKeyData skey[2];
|
|
SysScanDesc sscan;
|
|
HeapTuple policy_tuple;
|
|
ObjectAddress address;
|
|
|
|
/* Get id of table. Also handles permissions checks. */
|
|
table_id = RangeVarGetRelidExtended(stmt->relation, AccessExclusiveLock,
|
|
false, false,
|
|
RangeVarCallbackForPolicy,
|
|
(void *) stmt);
|
|
|
|
target_table = relation_open(table_id, NoLock);
|
|
|
|
pg_policy_rel = heap_open(PolicyRelationId, RowExclusiveLock);
|
|
|
|
/* First pass -- check for conflict */
|
|
|
|
/* Add key - policy's relation id. */
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_policy_polrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(table_id));
|
|
|
|
/* Add key - policy's name. */
|
|
ScanKeyInit(&skey[1],
|
|
Anum_pg_policy_polname,
|
|
BTEqualStrategyNumber, F_NAMEEQ,
|
|
CStringGetDatum(stmt->newname));
|
|
|
|
sscan = systable_beginscan(pg_policy_rel,
|
|
PolicyPolrelidPolnameIndexId, true, NULL, 2,
|
|
skey);
|
|
|
|
if (HeapTupleIsValid(systable_getnext(sscan)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("policy \"%s\" for table \"%s\" already exists",
|
|
stmt->newname, RelationGetRelationName(target_table))));
|
|
|
|
systable_endscan(sscan);
|
|
|
|
/* Second pass -- find existing policy and update */
|
|
/* Add key - policy's relation id. */
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_policy_polrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(table_id));
|
|
|
|
/* Add key - policy's name. */
|
|
ScanKeyInit(&skey[1],
|
|
Anum_pg_policy_polname,
|
|
BTEqualStrategyNumber, F_NAMEEQ,
|
|
CStringGetDatum(stmt->subname));
|
|
|
|
sscan = systable_beginscan(pg_policy_rel,
|
|
PolicyPolrelidPolnameIndexId, true, NULL, 2,
|
|
skey);
|
|
|
|
policy_tuple = systable_getnext(sscan);
|
|
|
|
/* Complain if we did not find the policy */
|
|
if (!HeapTupleIsValid(policy_tuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("policy \"%s\" for table \"%s\" does not exist",
|
|
stmt->subname, RelationGetRelationName(target_table))));
|
|
|
|
opoloid = HeapTupleGetOid(policy_tuple);
|
|
|
|
policy_tuple = heap_copytuple(policy_tuple);
|
|
|
|
namestrcpy(&((Form_pg_policy) GETSTRUCT(policy_tuple))->polname,
|
|
stmt->newname);
|
|
|
|
CatalogTupleUpdate(pg_policy_rel, &policy_tuple->t_self, policy_tuple);
|
|
|
|
InvokeObjectPostAlterHook(PolicyRelationId,
|
|
HeapTupleGetOid(policy_tuple), 0);
|
|
|
|
ObjectAddressSet(address, PolicyRelationId, opoloid);
|
|
|
|
/*
|
|
* Invalidate relation's relcache entry so that other backends (and this
|
|
* one too!) are sent SI message to make them rebuild relcache entries.
|
|
* (Ideally this should happen automatically...)
|
|
*/
|
|
CacheInvalidateRelcache(target_table);
|
|
|
|
/* Clean up. */
|
|
systable_endscan(sscan);
|
|
heap_close(pg_policy_rel, RowExclusiveLock);
|
|
relation_close(target_table, NoLock);
|
|
|
|
return address;
|
|
}
|
|
|
|
/*
|
|
* get_relation_policy_oid - Look up a policy by name to find its OID
|
|
*
|
|
* If missing_ok is false, throw an error if policy not found. If
|
|
* true, just return InvalidOid.
|
|
*/
|
|
Oid
|
|
get_relation_policy_oid(Oid relid, const char *policy_name, bool missing_ok)
|
|
{
|
|
Relation pg_policy_rel;
|
|
ScanKeyData skey[2];
|
|
SysScanDesc sscan;
|
|
HeapTuple policy_tuple;
|
|
Oid policy_oid;
|
|
|
|
pg_policy_rel = heap_open(PolicyRelationId, AccessShareLock);
|
|
|
|
/* Add key - policy's relation id. */
|
|
ScanKeyInit(&skey[0],
|
|
Anum_pg_policy_polrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(relid));
|
|
|
|
/* Add key - policy's name. */
|
|
ScanKeyInit(&skey[1],
|
|
Anum_pg_policy_polname,
|
|
BTEqualStrategyNumber, F_NAMEEQ,
|
|
CStringGetDatum(policy_name));
|
|
|
|
sscan = systable_beginscan(pg_policy_rel,
|
|
PolicyPolrelidPolnameIndexId, true, NULL, 2,
|
|
skey);
|
|
|
|
policy_tuple = systable_getnext(sscan);
|
|
|
|
if (!HeapTupleIsValid(policy_tuple))
|
|
{
|
|
if (!missing_ok)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("policy \"%s\" for table \"%s\" does not exist",
|
|
policy_name, get_rel_name(relid))));
|
|
|
|
policy_oid = InvalidOid;
|
|
}
|
|
else
|
|
policy_oid = HeapTupleGetOid(policy_tuple);
|
|
|
|
/* Clean up. */
|
|
systable_endscan(sscan);
|
|
heap_close(pg_policy_rel, AccessShareLock);
|
|
|
|
return policy_oid;
|
|
}
|
|
|
|
/*
|
|
* relation_has_policies - Determine if relation has any policies
|
|
*/
|
|
bool
|
|
relation_has_policies(Relation rel)
|
|
{
|
|
Relation catalog;
|
|
ScanKeyData skey;
|
|
SysScanDesc sscan;
|
|
HeapTuple policy_tuple;
|
|
bool ret = false;
|
|
|
|
catalog = heap_open(PolicyRelationId, AccessShareLock);
|
|
ScanKeyInit(&skey,
|
|
Anum_pg_policy_polrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(RelationGetRelid(rel)));
|
|
sscan = systable_beginscan(catalog, PolicyPolrelidPolnameIndexId, true,
|
|
NULL, 1, &skey);
|
|
policy_tuple = systable_getnext(sscan);
|
|
if (HeapTupleIsValid(policy_tuple))
|
|
ret = true;
|
|
|
|
systable_endscan(sscan);
|
|
heap_close(catalog, AccessShareLock);
|
|
|
|
return ret;
|
|
}
|