summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/access/common/tupdesc.c16
-rw-r--r--src/backend/bootstrap/bootstrap.c15
-rw-r--r--src/backend/catalog/aclchk.c810
-rw-r--r--src/backend/catalog/dependency.c16
-rw-r--r--src/backend/catalog/heap.c23
-rw-r--r--src/backend/catalog/index.c6
-rw-r--r--src/backend/catalog/pg_operator.c4
-rw-r--r--src/backend/catalog/pg_proc.c4
-rw-r--r--src/backend/catalog/pg_shdepend.c111
-rw-r--r--src/backend/catalog/pg_type.c4
-rw-r--r--src/backend/commands/analyze.c9
-rw-r--r--src/backend/commands/tablecmds.c62
-rw-r--r--src/backend/commands/tablespace.c4
-rw-r--r--src/backend/commands/trigger.c33
-rw-r--r--src/backend/commands/tsearchcmds.c4
-rw-r--r--src/backend/commands/user.c14
-rw-r--r--src/backend/executor/execMain.c115
-rw-r--r--src/backend/nodes/copyfuncs.c18
-rw-r--r--src/backend/nodes/equalfuncs.c16
-rw-r--r--src/backend/nodes/outfuncs.c6
-rw-r--r--src/backend/nodes/readfuncs.c49
-rw-r--r--src/backend/optimizer/plan/setrefs.c5
-rw-r--r--src/backend/parser/analyze.c32
-rw-r--r--src/backend/parser/gram.y97
-rw-r--r--src/backend/parser/parse_clause.c31
-rw-r--r--src/backend/parser/parse_expr.c5
-rw-r--r--src/backend/parser/parse_relation.c173
-rw-r--r--src/backend/parser/parse_target.c32
-rw-r--r--src/backend/rewrite/rewriteHandler.c6
-rw-r--r--src/backend/tcop/utility.c4
-rw-r--r--src/backend/utils/adt/acl.c66
-rw-r--r--src/backend/utils/cache/relcache.c12
32 files changed, 1489 insertions, 313 deletions
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 4958299a2e..ea16913c8e 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.124 2009/01/01 17:23:34 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.125 2009/01/22 20:16:00 tgl Exp $
*
* NOTES
* some of the executor utility code such as "ExecTypeFromTL" should be
@@ -53,10 +53,14 @@ CreateTemplateTupleDesc(int natts, bool hasoid)
* struct pointer alignment requirement, and hence we don't need to insert
* alignment padding between the struct and the array of attribute row
* pointers.
+ *
+ * Note: Only the fixed part of pg_attribute rows is included in tuple
+ * descriptors, so we only need ATTRIBUTE_FIXED_PART_SIZE space
+ * per attr. That might need alignment padding, however.
*/
attroffset = sizeof(struct tupleDesc) + natts * sizeof(Form_pg_attribute);
attroffset = MAXALIGN(attroffset);
- stg = palloc(attroffset + natts * MAXALIGN(ATTRIBUTE_TUPLE_SIZE));
+ stg = palloc(attroffset + natts * MAXALIGN(ATTRIBUTE_FIXED_PART_SIZE));
desc = (TupleDesc) stg;
if (natts > 0)
@@ -70,7 +74,7 @@ CreateTemplateTupleDesc(int natts, bool hasoid)
for (i = 0; i < natts; i++)
{
attrs[i] = (Form_pg_attribute) stg;
- stg += MAXALIGN(ATTRIBUTE_TUPLE_SIZE);
+ stg += MAXALIGN(ATTRIBUTE_FIXED_PART_SIZE);
}
}
else
@@ -139,7 +143,7 @@ CreateTupleDescCopy(TupleDesc tupdesc)
for (i = 0; i < desc->natts; i++)
{
- memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_TUPLE_SIZE);
+ memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
desc->attrs[i]->attnotnull = false;
desc->attrs[i]->atthasdef = false;
}
@@ -166,7 +170,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
for (i = 0; i < desc->natts; i++)
{
- memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_TUPLE_SIZE);
+ memcpy(desc->attrs[i], tupdesc->attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
}
if (constr)
@@ -356,6 +360,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
return false;
if (attr1->attinhcount != attr2->attinhcount)
return false;
+ /* attacl is ignored, since it's not even present... */
}
if (tupdesc1->constr != NULL)
@@ -471,6 +476,7 @@ TupleDescInitEntry(TupleDesc desc,
att->attisdropped = false;
att->attislocal = true;
att->attinhcount = 0;
+ /* attacl is not set because it's not present in tupledescs */
tuple = SearchSysCache(TYPEOID,
ObjectIdGetDatum(oidtypeid),
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 00b52dce80..19aab42554 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.248 2009/01/01 17:23:36 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/bootstrap/bootstrap.c,v 1.249 2009/01/22 20:16:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -633,7 +633,7 @@ boot_openrel(char *relname)
closerel(NULL);
elog(DEBUG4, "open relation %s, attrsize %d",
- relname, (int) ATTRIBUTE_TUPLE_SIZE);
+ relname, (int) ATTRIBUTE_FIXED_PART_SIZE);
boot_reldesc = heap_openrv(makeRangeVar(NULL, relname, -1), NoLock);
numattr = boot_reldesc->rd_rel->relnatts;
@@ -643,7 +643,7 @@ boot_openrel(char *relname)
attrtypes[i] = AllocateAttribute();
memmove((char *) attrtypes[i],
(char *) boot_reldesc->rd_att->attrs[i],
- ATTRIBUTE_TUPLE_SIZE);
+ ATTRIBUTE_FIXED_PART_SIZE);
{
Form_pg_attribute at = attrtypes[i];
@@ -709,7 +709,7 @@ DefineAttr(char *name, char *type, int attnum)
if (attrtypes[attnum] == NULL)
attrtypes[attnum] = AllocateAttribute();
- MemSet(attrtypes[attnum], 0, ATTRIBUTE_TUPLE_SIZE);
+ MemSet(attrtypes[attnum], 0, ATTRIBUTE_FIXED_PART_SIZE);
namestrcpy(&attrtypes[attnum]->attname, name);
elog(DEBUG4, "column %s %s", NameStr(attrtypes[attnum]->attname), type);
@@ -1017,16 +1017,19 @@ boot_get_type_io_data(Oid typid,
/* ----------------
* AllocateAttribute
+ *
+ * Note: bootstrap never sets any per-column ACLs, so we only need
+ * ATTRIBUTE_FIXED_PART_SIZE space per attribute.
* ----------------
*/
static Form_pg_attribute
AllocateAttribute(void)
{
- Form_pg_attribute attribute = (Form_pg_attribute) malloc(ATTRIBUTE_TUPLE_SIZE);
+ Form_pg_attribute attribute = (Form_pg_attribute) malloc(ATTRIBUTE_FIXED_PART_SIZE);
if (!PointerIsValid(attribute))
elog(FATAL, "out of memory");
- MemSet(attribute, 0, ATTRIBUTE_TUPLE_SIZE);
+ MemSet(attribute, 0, ATTRIBUTE_FIXED_PART_SIZE);
return attribute;
}
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index ad32f49e7d..b49c80e485 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.151 2009/01/01 17:23:36 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.152 2009/01/22 20:16:00 tgl Exp $
*
* NOTES
* See acl.h.
@@ -61,14 +61,23 @@ static void ExecGrant_Namespace(InternalGrant *grantStmt);
static void ExecGrant_Tablespace(InternalGrant *grantStmt);
static List *objectNamesToOids(GrantObjectType objtype, List *objnames);
+static void expand_col_privileges(List *colnames, Oid table_oid,
+ AclMode this_privileges,
+ AclMode *col_privileges,
+ int num_col_privileges);
+static void expand_all_col_privileges(Oid table_oid, Form_pg_class classForm,
+ AclMode this_privileges,
+ AclMode *col_privileges,
+ int num_col_privileges);
static AclMode string_to_privilege(const char *privname);
static const char *privilege_to_string(AclMode privilege);
static AclMode restrict_and_check_grant(bool is_grant, AclMode avail_goptions,
bool all_privs, AclMode privileges,
Oid objectId, Oid grantorId,
- AclObjectKind objkind, char *objname);
-static AclMode pg_aclmask(AclObjectKind objkind, Oid table_oid, Oid roleid,
- AclMode mask, AclMaskHow how);
+ AclObjectKind objkind, const char *objname,
+ AttrNumber att_number, const char *colname);
+static AclMode pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum,
+ Oid roleid, AclMode mask, AclMaskHow how);
#ifdef ACLDEBUG
@@ -118,7 +127,7 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
AclItem aclitem;
Acl *newer_acl;
- aclitem. ai_grantee = lfirst_oid(j);
+ aclitem.ai_grantee = lfirst_oid(j);
/*
* Grant options can only be granted to individual roles, not PUBLIC.
@@ -131,7 +140,7 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("grant options can only be granted to roles")));
- aclitem. ai_grantor = grantorId;
+ aclitem.ai_grantor = grantorId;
/*
* The asymmetry in the conditions here comes from the spec. In
@@ -165,13 +174,17 @@ merge_acl_with_grant(Acl *old_acl, bool is_grant,
static AclMode
restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
AclMode privileges, Oid objectId, Oid grantorId,
- AclObjectKind objkind, char *objname)
+ AclObjectKind objkind, const char *objname,
+ AttrNumber att_number, const char *colname)
{
AclMode this_privileges;
AclMode whole_mask;
switch (objkind)
{
+ case ACL_KIND_COLUMN:
+ whole_mask = ACL_ALL_RIGHTS_COLUMN;
+ break;
case ACL_KIND_CLASS:
whole_mask = ACL_ALL_RIGHTS_RELATION;
break;
@@ -212,10 +225,15 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
*/
if (avail_goptions == ACL_NO_RIGHTS)
{
- if (pg_aclmask(objkind, objectId, grantorId,
+ if (pg_aclmask(objkind, objectId, att_number, grantorId,
whole_mask | ACL_GRANT_OPTION_FOR(whole_mask),
ACLMASK_ANY) == ACL_NO_RIGHTS)
- aclcheck_error(ACLCHECK_NO_PRIV, objkind, objname);
+ {
+ if (objkind == ACL_KIND_COLUMN && colname)
+ aclcheck_error_col(ACLCHECK_NO_PRIV, objkind, objname, colname);
+ else
+ aclcheck_error(ACLCHECK_NO_PRIV, objkind, objname);
+ }
}
/*
@@ -271,12 +289,11 @@ ExecuteGrantStmt(GrantStmt *stmt)
istmt.objects = objectNamesToOids(stmt->objtype, stmt->objects);
/* all_privs to be filled below */
/* privileges to be filled below */
- istmt.grantees = NIL;
- /* filled below */
+ istmt.col_privs = NIL; /* may get filled below */
+ istmt.grantees = NIL; /* filled below */
istmt.grant_option = stmt->grant_option;
istmt.behavior = stmt->behavior;
-
/*
* Convert the PrivGrantee list into an Oid list. Note that at this point
* we insert an ACL_ID_PUBLIC into the list if an empty role name is
@@ -297,7 +314,8 @@ ExecuteGrantStmt(GrantStmt *stmt)
}
/*
- * Convert stmt->privileges, a textual list, into an AclMode bitmask.
+ * Convert stmt->privileges, a list of AccessPriv nodes, into an
+ * AclMode bitmask. Note: objtype can't be ACL_OBJECT_COLUMN.
*/
switch (stmt->objtype)
{
@@ -367,8 +385,26 @@ ExecuteGrantStmt(GrantStmt *stmt)
foreach(cell, stmt->privileges)
{
- char *privname = strVal(lfirst(cell));
- AclMode priv = string_to_privilege(privname);
+ AccessPriv *privnode = (AccessPriv *) lfirst(cell);
+ AclMode priv;
+
+ /*
+ * If it's a column-level specification, we just set it aside
+ * in col_privs for the moment; but insist it's for a relation.
+ */
+ if (privnode->cols)
+ {
+ if (stmt->objtype != ACL_OBJECT_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("column privileges are only valid for relations")));
+ istmt.col_privs = lappend(istmt.col_privs, privnode);
+ continue;
+ }
+
+ if (privnode->priv_name == NULL) /* parser mistake? */
+ elog(ERROR, "AccessPriv node must specify privilege or columns");
+ priv = string_to_privilege(privnode->priv_name);
if (priv & ~((AclMode) all_privileges))
ereport(ERROR,
@@ -385,7 +421,9 @@ ExecuteGrantStmt(GrantStmt *stmt)
/*
* ExecGrantStmt_oids
*
- * "Internal" entrypoint for granting and revoking privileges.
+ * "Internal" entrypoint for granting and revoking privileges. This is
+ * exported for pg_shdepend.c to use in revoking privileges when dropping
+ * a role.
*/
void
ExecGrantStmt_oids(InternalGrant *istmt)
@@ -572,15 +610,245 @@ objectNamesToOids(GrantObjectType objtype, List *objnames)
}
/*
+ * expand_col_privileges
+ *
+ * OR the specified privilege(s) into per-column array entries for each
+ * specified attribute. The per-column array is indexed starting at
+ * FirstLowInvalidHeapAttributeNumber, up to relation's last attribute.
+ */
+static void
+expand_col_privileges(List *colnames, Oid table_oid,
+ AclMode this_privileges,
+ AclMode *col_privileges,
+ int num_col_privileges)
+{
+ ListCell *cell;
+
+ foreach(cell, colnames)
+ {
+ char *colname = strVal(lfirst(cell));
+ AttrNumber attnum;
+
+ attnum = get_attnum(table_oid, colname);
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ colname, get_rel_name(table_oid))));
+ attnum -= FirstLowInvalidHeapAttributeNumber;
+ if (attnum <= 0 || attnum >= num_col_privileges)
+ elog(ERROR, "column number out of range"); /* safety check */
+ col_privileges[attnum] |= this_privileges;
+ }
+}
+
+/*
+ * expand_all_col_privileges
+ *
+ * OR the specified privilege(s) into per-column array entries for each valid
+ * attribute of a relation. The per-column array is indexed starting at
+ * FirstLowInvalidHeapAttributeNumber, up to relation's last attribute.
+ */
+static void
+expand_all_col_privileges(Oid table_oid, Form_pg_class classForm,
+ AclMode this_privileges,
+ AclMode *col_privileges,
+ int num_col_privileges)
+{
+ AttrNumber curr_att;
+
+ Assert(classForm->relnatts - FirstLowInvalidHeapAttributeNumber < num_col_privileges);
+ for (curr_att = FirstLowInvalidHeapAttributeNumber + 1;
+ curr_att <= classForm->relnatts;
+ curr_att++)
+ {
+ HeapTuple attTuple;
+ bool isdropped;
+
+ if (curr_att == InvalidAttrNumber)
+ continue;
+
+ /* Skip OID column if it doesn't exist */
+ if (curr_att == ObjectIdAttributeNumber && !classForm->relhasoids)
+ continue;
+
+ /* Views don't have any system columns at all */
+ if (classForm->relkind == RELKIND_VIEW && curr_att < 0)
+ continue;
+
+ attTuple = SearchSysCache(ATTNUM,
+ ObjectIdGetDatum(table_oid),
+ Int16GetDatum(curr_att),
+ 0, 0);
+ if (!HeapTupleIsValid(attTuple))
+ elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ curr_att, table_oid);
+
+ isdropped = ((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped;
+
+ ReleaseSysCache(attTuple);
+
+ /* ignore dropped columns */
+ if (isdropped)
+ continue;
+
+ col_privileges[curr_att - FirstLowInvalidHeapAttributeNumber] |= this_privileges;
+ }
+}
+
+/*
+ * This processes attributes, but expects to be called from
+ * ExecGrant_Relation, not directly from ExecGrantStmt.
+ */
+static void
+ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname,
+ AttrNumber attnum, Oid ownerId, AclMode col_privileges,
+ Relation attRelation, const Acl *old_rel_acl)
+{
+ HeapTuple attr_tuple;
+ Form_pg_attribute pg_attribute_tuple;
+ Acl *old_acl;
+ Acl *new_acl;
+ Acl *merged_acl;
+ Datum aclDatum;
+ bool isNull;
+ Oid grantorId;
+ AclMode avail_goptions;
+ bool need_update;
+ HeapTuple newtuple;
+ Datum values[Natts_pg_attribute];
+ bool nulls[Natts_pg_attribute];
+ bool replaces[Natts_pg_attribute];
+ int noldmembers;
+ int nnewmembers;
+ Oid *oldmembers;
+ Oid *newmembers;
+
+ attr_tuple = SearchSysCache(ATTNUM,
+ ObjectIdGetDatum(relOid),
+ Int16GetDatum(attnum),
+ 0, 0);
+ if (!HeapTupleIsValid(attr_tuple))
+ elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ attnum, relOid);
+ pg_attribute_tuple = (Form_pg_attribute) GETSTRUCT(attr_tuple);
+
+ /*
+ * Get working copy of existing ACL. If there's no ACL,
+ * substitute the proper default.
+ */
+ aclDatum = SysCacheGetAttr(ATTNUM, attr_tuple, Anum_pg_attribute_attacl,
+ &isNull);
+ if (isNull)
+ old_acl = acldefault(ACL_OBJECT_COLUMN, ownerId);
+ else
+ old_acl = DatumGetAclPCopy(aclDatum);
+
+ /*
+ * In select_best_grantor we should consider existing table-level ACL bits
+ * as well as the per-column ACL. Build a new ACL that is their
+ * concatenation. (This is a bit cheap and dirty compared to merging
+ * them properly with no duplications, but it's all we need here.)
+ */
+ merged_acl = aclconcat(old_rel_acl, old_acl);
+
+ /* Determine ID to do the grant as, and available grant options */
+ select_best_grantor(GetUserId(), col_privileges,
+ merged_acl, ownerId,
+ &grantorId, &avail_goptions);
+
+ pfree(merged_acl);
+
+ /*
+ * Restrict the privileges to what we can actually grant, and emit
+ * the standards-mandated warning and error messages. Note: we don't
+ * track whether the user actually used the ALL PRIVILEGES(columns)
+ * syntax for each column; we just approximate it by whether all the
+ * possible privileges are specified now. Since the all_privs flag only
+ * determines whether a warning is issued, this seems close enough.
+ */
+ col_privileges =
+ restrict_and_check_grant(istmt->is_grant, avail_goptions,
+ (col_privileges == ACL_ALL_RIGHTS_COLUMN),
+ col_privileges,
+ relOid, grantorId, ACL_KIND_COLUMN,
+ relname, attnum,
+ NameStr(pg_attribute_tuple->attname));
+
+ /*
+ * Generate new ACL.
+ *
+ * We need the members of both old and new ACLs so we can correct
+ * the shared dependency information.
+ */
+ noldmembers = aclmembers(old_acl, &oldmembers);
+
+ new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
+ istmt->grant_option,
+ istmt->behavior, istmt->grantees,
+ col_privileges, grantorId,
+ ownerId);
+
+ nnewmembers = aclmembers(new_acl, &newmembers);
+
+ /* finished building new ACL value, now insert it */
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, false, sizeof(nulls));
+ MemSet(replaces, false, sizeof(replaces));
+
+ /*
+ * If the updated ACL is empty, we can set attacl to null, and maybe
+ * even avoid an update of the pg_attribute row. This is worth testing
+ * because we'll come through here multiple times for any relation-level
+ * REVOKE, even if there were never any column GRANTs. Note we are
+ * assuming that the "default" ACL state for columns is empty.
+ */
+ if (ACL_NUM(new_acl) > 0)
+ {
+ values[Anum_pg_attribute_attacl - 1] = PointerGetDatum(new_acl);
+ need_update = true;
+ }
+ else
+ {
+ nulls[Anum_pg_attribute_attacl - 1] = true;
+ need_update = !isNull;
+ }
+ replaces[Anum_pg_attribute_attacl - 1] = true;
+
+ if (need_update)
+ {
+ newtuple = heap_modify_tuple(attr_tuple, RelationGetDescr(attRelation),
+ values, nulls, replaces);
+
+ simple_heap_update(attRelation, &newtuple->t_self, newtuple);
+
+ /* keep the catalog indexes up to date */
+ CatalogUpdateIndexes(attRelation, newtuple);
+
+ /* Update the shared dependency ACL info */
+ updateAclDependencies(RelationRelationId, relOid, attnum,
+ ownerId, istmt->is_grant,
+ noldmembers, oldmembers,
+ nnewmembers, newmembers);
+ }
+
+ pfree(new_acl);
+
+ ReleaseSysCache(attr_tuple);
+}
+
+/*
* This processes both sequences and non-sequences.
*/
static void
ExecGrant_Relation(InternalGrant *istmt)
{
Relation relation;
+ Relation attRelation;
ListCell *cell;
relation = heap_open(RelationRelationId, RowExclusiveLock);
+ attRelation = heap_open(AttributeRelationId, RowExclusiveLock);
foreach(cell, istmt->objects)
{
@@ -588,21 +856,15 @@ ExecGrant_Relation(InternalGrant *istmt)
Datum aclDatum;
Form_pg_class pg_class_tuple;
bool isNull;
- AclMode avail_goptions;
AclMode this_privileges;
+ AclMode *col_privileges;
+ int num_col_privileges;
+ bool have_col_privileges;
Acl *old_acl;
- Acl *new_acl;
- Oid grantorId;
+ Acl *old_rel_acl;
Oid ownerId;
HeapTuple tuple;
- HeapTuple newtuple;
- Datum values[Natts_pg_class];
- bool nulls[Natts_pg_class];
- bool replaces[Natts_pg_class];
- int noldmembers;
- int nnewmembers;
- Oid *oldmembers;
- Oid *newmembers;
+ ListCell *cell_colprivs;
tuple = SearchSysCache(RELOID,
ObjectIdGetDatum(relOid),
@@ -655,9 +917,9 @@ ExecGrant_Relation(InternalGrant *istmt)
if (pg_class_tuple->relkind == RELKIND_SEQUENCE)
{
/*
- * For backward compatibility, throw just a warning for
+ * For backward compatibility, just throw a warning for
* invalid sequence permissions when using the non-sequence
- * GRANT syntax is used.
+ * GRANT syntax.
*/
if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_SEQUENCE))
{
@@ -668,7 +930,7 @@ ExecGrant_Relation(InternalGrant *istmt)
*/
ereport(WARNING,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
- errmsg("sequence \"%s\" only supports USAGE, SELECT, and UPDATE",
+ errmsg("sequence \"%s\" only supports USAGE, SELECT, and UPDATE privileges",
NameStr(pg_class_tuple->relname))));
this_privileges &= (AclMode) ACL_ALL_RIGHTS_SEQUENCE;
}
@@ -676,7 +938,7 @@ ExecGrant_Relation(InternalGrant *istmt)
else
{
if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_RELATION))
-
+ {
/*
* USAGE is the only permission supported by sequences but
* not by non-sequences. Don't mention the object name
@@ -686,10 +948,37 @@ ExecGrant_Relation(InternalGrant *istmt)
ereport(ERROR,
(errcode(ERRCODE_INVALID_GRANT_OPERATION),
errmsg("invalid privilege type USAGE for table")));
+ }
}
}
/*
+ * Set up array in which we'll accumulate any column privilege bits
+ * that need modification. The array is indexed such that entry [0]
+ * corresponds to FirstLowInvalidHeapAttributeNumber.
+ */
+ num_col_privileges = pg_class_tuple->relnatts - FirstLowInvalidHeapAttributeNumber + 1;
+ col_privileges = (AclMode *) palloc0(num_col_privileges * sizeof(AclMode));
+ have_col_privileges = false;
+
+ /*
+ * If we are revoking relation privileges that are also column
+ * privileges, we must implicitly revoke them from each column too,
+ * per SQL spec. (We don't need to implicitly add column privileges
+ * during GRANT because the permissions-checking code always checks
+ * both relation and per-column privileges.)
+ */
+ if (!istmt->is_grant &&
+ (this_privileges & ACL_ALL_RIGHTS_COLUMN) != 0)
+ {
+ expand_all_col_privileges(relOid, pg_class_tuple,
+ this_privileges & ACL_ALL_RIGHTS_COLUMN,
+ col_privileges,
+ num_col_privileges);
+ have_col_privileges = true;
+ }
+
+ /*
* Get owner ID and working copy of existing ACL. If there's no ACL,
* substitute the proper default.
*/
@@ -703,67 +992,160 @@ ExecGrant_Relation(InternalGrant *istmt)
else
old_acl = DatumGetAclPCopy(aclDatum);
- /* Determine ID to do the grant as, and available grant options */
- select_best_grantor(GetUserId(), this_privileges,
- old_acl, ownerId,
- &grantorId, &avail_goptions);
+ /* Need an extra copy of original rel ACL for column handling */
+ old_rel_acl = aclcopy(old_acl);
/*
- * Restrict the privileges to what we can actually grant, and emit the
- * standards-mandated warning and error messages.
+ * Handle relation-level privileges, if any were specified
*/
- this_privileges =
- restrict_and_check_grant(istmt->is_grant, avail_goptions,
- istmt->all_privs, this_privileges,
- relOid, grantorId,
- pg_class_tuple->relkind == RELKIND_SEQUENCE
- ? ACL_KIND_SEQUENCE : ACL_KIND_CLASS,
- NameStr(pg_class_tuple->relname));
+ if (this_privileges != ACL_NO_RIGHTS)
+ {
+ AclMode avail_goptions;
+ Acl *new_acl;
+ Oid grantorId;
+ HeapTuple newtuple;
+ Datum values[Natts_pg_class];
+ bool nulls[Natts_pg_class];
+ bool replaces[Natts_pg_class];
+ int noldmembers;
+ int nnewmembers;
+ Oid *oldmembers;
+ Oid *newmembers;
+
+ /* Determine ID to do the grant as, and available grant options */
+ select_best_grantor(GetUserId(), this_privileges,
+ old_acl, ownerId,
+ &grantorId, &avail_goptions);
+
+ /*
+ * Restrict the privileges to what we can actually grant, and emit
+ * the standards-mandated warning and error messages.
+ */
+ this_privileges =
+ restrict_and_check_grant(istmt->is_grant, avail_goptions,
+ istmt->all_privs, this_privileges,
+ relOid, grantorId,
+ pg_class_tuple->relkind == RELKIND_SEQUENCE
+ ? ACL_KIND_SEQUENCE : ACL_KIND_CLASS,
+ NameStr(pg_class_tuple->relname),
+ 0, NULL);
+
+ /*
+ * Generate new ACL.
+ *
+ * We need the members of both old and new ACLs so we can correct
+ * the shared dependency information.
+ */
+ noldmembers = aclmembers(old_acl, &oldmembers);
+
+ new_acl = merge_acl_with_grant(old_acl,
+ istmt->is_grant,
+ istmt->grant_option,
+ istmt->behavior,
+ istmt->grantees,
+ this_privileges,
+ grantorId,
+ ownerId);
+
+ nnewmembers = aclmembers(new_acl, &newmembers);
+
+ /* finished building new ACL value, now insert it */
+ MemSet(values, 0, sizeof(values));
+ MemSet(nulls, false, sizeof(nulls));
+ MemSet(replaces, false, sizeof(replaces));
+
+ replaces[Anum_pg_class_relacl - 1] = true;
+ values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl);
+
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation),
+ values, nulls, replaces);
+
+ simple_heap_update(relation, &newtuple->t_self, newtuple);
+
+ /* keep the catalog indexes up to date */
+ CatalogUpdateIndexes(relation, newtuple);
+
+ /* Update the shared dependency ACL info */
+ updateAclDependencies(RelationRelationId, relOid, 0,
+ ownerId, istmt->is_grant,
+ noldmembers, oldmembers,
+ nnewmembers, newmembers);
+
+ pfree(new_acl);
+ }
/*
- * Generate new ACL.
- *
- * We need the members of both old and new ACLs so we can correct the
- * shared dependency information.
+ * Handle column-level privileges, if any were specified or implied.
+ * We first expand the user-specified column privileges into the
+ * array, and then iterate over all nonempty array entries.
*/
- noldmembers = aclmembers(old_acl, &oldmembers);
+ foreach(cell_colprivs, istmt->col_privs)
+ {
+ AccessPriv *col_privs = (AccessPriv *) lfirst(cell_colprivs);
- new_acl = merge_acl_with_grant(old_acl, istmt->is_grant,
- istmt->grant_option, istmt->behavior,
- istmt->grantees, this_privileges,
- grantorId, ownerId);
+ if (col_privs->priv_name == NULL)
+ this_privileges = ACL_ALL_RIGHTS_COLUMN;
+ else
+ this_privileges = string_to_privilege(col_privs->priv_name);
- nnewmembers = aclmembers(new_acl, &newmembers);
+ if (this_privileges & ~((AclMode) ACL_ALL_RIGHTS_COLUMN))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("invalid privilege type %s for column",
+ privilege_to_string(this_privileges))));
- /* finished building new ACL value, now insert it */
- MemSet(values, 0, sizeof(values));
- MemSet(nulls, false, sizeof(nulls));
- MemSet(replaces, false, sizeof(replaces));
+ if (pg_class_tuple->relkind == RELKIND_SEQUENCE &&
+ this_privileges & ~((AclMode) ACL_SELECT))
+ {
+ /*
+ * The only column privilege allowed on sequences is SELECT.
+ * This is a warning not error because we do it that way
+ * for relation-level privileges.
+ */
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("sequence \"%s\" only supports SELECT column privileges",
+ NameStr(pg_class_tuple->relname))));
- replaces[Anum_pg_class_relacl - 1] = true;
- values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl);
+ this_privileges &= (AclMode) ACL_SELECT;
+ }
- newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
+ expand_col_privileges(col_privs->cols, relOid,
+ this_privileges,
+ col_privileges,
+ num_col_privileges);
+ have_col_privileges = true;
+ }
- simple_heap_update(relation, &newtuple->t_self, newtuple);
+ if (have_col_privileges)
+ {
+ AttrNumber i;
- /* keep the catalog indexes up to date */
- CatalogUpdateIndexes(relation, newtuple);
+ for (i = 0; i < num_col_privileges; i++)
+ {
+ if (col_privileges[i] == ACL_NO_RIGHTS)
+ continue;
+ ExecGrant_Attribute(istmt,
+ relOid,
+ NameStr(pg_class_tuple->relname),
+ i + FirstLowInvalidHeapAttributeNumber,
+ ownerId,
+ col_privileges[i],
+ attRelation,
+ old_rel_acl);
+ }
+ }
- /* Update the shared dependency ACL info */
- updateAclDependencies(RelationRelationId, relOid,
- ownerId, istmt->is_grant,
- noldmembers, oldmembers,
- nnewmembers, newmembers);
+ pfree(old_rel_acl);
+ pfree(col_privileges);
ReleaseSysCache(tuple);
- pfree(new_acl);
-
/* prevent error when processing duplicate objects */
CommandCounterIncrement();
}
+ heap_close(attRelation, RowExclusiveLock);
heap_close(relation, RowExclusiveLock);
}
@@ -833,7 +1215,8 @@ ExecGrant_Database(InternalGrant *istmt)
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, istmt->privileges,
datId, grantorId, ACL_KIND_DATABASE,
- NameStr(pg_database_tuple->datname));
+ NameStr(pg_database_tuple->datname),
+ 0, NULL);
/*
* Generate new ACL.
@@ -867,7 +1250,7 @@ ExecGrant_Database(InternalGrant *istmt)
CatalogUpdateIndexes(relation, newtuple);
/* Update the shared dependency ACL info */
- updateAclDependencies(DatabaseRelationId, HeapTupleGetOid(tuple),
+ updateAclDependencies(DatabaseRelationId, HeapTupleGetOid(tuple), 0,
ownerId, istmt->is_grant,
noldmembers, oldmembers,
nnewmembers, newmembers);
@@ -950,7 +1333,8 @@ ExecGrant_Fdw(InternalGrant *istmt)
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, istmt->privileges,
fdwid, grantorId, ACL_KIND_FDW,
- NameStr(pg_fdw_tuple->fdwname));
+ NameStr(pg_fdw_tuple->fdwname),
+ 0, NULL);
/*
* Generate new ACL.
@@ -984,7 +1368,8 @@ ExecGrant_Fdw(InternalGrant *istmt)
CatalogUpdateIndexes(relation, newtuple);
/* Update the shared dependency ACL info */
- updateAclDependencies(ForeignDataWrapperRelationId, HeapTupleGetOid(tuple),
+ updateAclDependencies(ForeignDataWrapperRelationId,
+ HeapTupleGetOid(tuple), 0,
ownerId, istmt->is_grant,
noldmembers, oldmembers,
nnewmembers, newmembers);
@@ -1066,7 +1451,8 @@ static void ExecGrant_ForeignServer(InternalGrant *istmt)
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, istmt->privileges,
srvid, grantorId, ACL_KIND_FOREIGN_SERVER,
- NameStr(pg_server_tuple->srvname));
+ NameStr(pg_server_tuple->srvname),
+ 0, NULL);
/*
* Generate new ACL.
@@ -1100,7 +1486,8 @@ static void ExecGrant_ForeignServer(InternalGrant *istmt)
CatalogUpdateIndexes(relation, newtuple);
/* Update the shared dependency ACL info */
- updateAclDependencies(ForeignServerRelationId, HeapTupleGetOid(tuple),
+ updateAclDependencies(ForeignServerRelationId,
+ HeapTupleGetOid(tuple), 0,
ownerId, istmt->is_grant,
noldmembers, oldmembers,
nnewmembers, newmembers);
@@ -1182,7 +1569,8 @@ ExecGrant_Function(InternalGrant *istmt)
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, istmt->privileges,
funcId, grantorId, ACL_KIND_PROC,
- NameStr(pg_proc_tuple->proname));
+ NameStr(pg_proc_tuple->proname),
+ 0, NULL);
/*
* Generate new ACL.
@@ -1216,7 +1604,7 @@ ExecGrant_Function(InternalGrant *istmt)
CatalogUpdateIndexes(relation, newtuple);
/* Update the shared dependency ACL info */
- updateAclDependencies(ProcedureRelationId, funcId,
+ updateAclDependencies(ProcedureRelationId, funcId, 0,
ownerId, istmt->is_grant,
noldmembers, oldmembers,
nnewmembers, newmembers);
@@ -1305,7 +1693,8 @@ ExecGrant_Language(InternalGrant *istmt)
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, istmt->privileges,
langId, grantorId, ACL_KIND_LANGUAGE,
- NameStr(pg_language_tuple->lanname));
+ NameStr(pg_language_tuple->lanname),
+ 0, NULL);
/*
* Generate new ACL.
@@ -1339,7 +1728,7 @@ ExecGrant_Language(InternalGrant *istmt)
CatalogUpdateIndexes(relation, newtuple);
/* Update the shared dependency ACL info */
- updateAclDependencies(LanguageRelationId, HeapTupleGetOid(tuple),
+ updateAclDependencies(LanguageRelationId, HeapTupleGetOid(tuple), 0,
ownerId, istmt->is_grant,
noldmembers, oldmembers,
nnewmembers, newmembers);
@@ -1422,7 +1811,8 @@ ExecGrant_Namespace(InternalGrant *istmt)
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, istmt->privileges,
nspid, grantorId, ACL_KIND_NAMESPACE,
- NameStr(pg_namespace_tuple->nspname));
+ NameStr(pg_namespace_tuple->nspname),
+ 0, NULL);
/*
* Generate new ACL.
@@ -1456,7 +1846,7 @@ ExecGrant_Namespace(InternalGrant *istmt)
CatalogUpdateIndexes(relation, newtuple);
/* Update the shared dependency ACL info */
- updateAclDependencies(NamespaceRelationId, HeapTupleGetOid(tuple),
+ updateAclDependencies(NamespaceRelationId, HeapTupleGetOid(tuple), 0,
ownerId, istmt->is_grant,
noldmembers, oldmembers,
nnewmembers, newmembers);
@@ -1545,7 +1935,8 @@ ExecGrant_Tablespace(InternalGrant *istmt)
restrict_and_check_grant(istmt->is_grant, avail_goptions,
istmt->all_privs, istmt->privileges,
tblId, grantorId, ACL_KIND_TABLESPACE,
- NameStr(pg_tablespace_tuple->spcname));
+ NameStr(pg_tablespace_tuple->spcname),
+ 0, NULL);
/*
* Generate new ACL.
@@ -1579,7 +1970,7 @@ ExecGrant_Tablespace(InternalGrant *istmt)
CatalogUpdateIndexes(relation, newtuple);
/* Update the shared dependency ACL info */
- updateAclDependencies(TableSpaceRelationId, tblId,
+ updateAclDependencies(TableSpaceRelationId, tblId, 0,
ownerId, istmt->is_grant,
noldmembers, oldmembers,
nnewmembers, newmembers);
@@ -1677,6 +2068,8 @@ privilege_to_string(AclMode privilege)
static const char *const no_priv_msg[MAX_ACL_KIND] =
{
+ /* ACL_KIND_COLUMN */
+ gettext_noop("permission denied for column %s"),
/* ACL_KIND_CLASS */
gettext_noop("permission denied for relation %s"),
/* ACL_KIND_SEQUENCE */
@@ -1713,6 +2106,8 @@ static const char *const no_priv_msg[MAX_ACL_KIND] =
static const char *const not_owner_msg[MAX_ACL_KIND] =
{
+ /* ACL_KIND_COLUMN */
+ gettext_noop("must be owner of relation %s"),
/* ACL_KIND_CLASS */
gettext_noop("must be owner of relation %s"),
/* ACL_KIND_SEQUENCE */
@@ -1774,6 +2169,34 @@ aclcheck_error(AclResult aclerr, AclObjectKind objectkind,
}
+void
+aclcheck_error_col(AclResult aclerr, AclObjectKind objectkind,
+ const char *objectname, const char *colname)
+{
+ switch (aclerr)
+ {
+ case ACLCHECK_OK:
+ /* no error, so return to caller */
+ break;
+ case ACLCHECK_NO_PRIV:
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for column %s of relation %s",
+ colname, objectname)));
+ break;
+ case ACLCHECK_NOT_OWNER:
+ /* relation msg is OK since columns don't have separate owners */
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg(not_owner_msg[objectkind], objectname)));
+ break;
+ default:
+ elog(ERROR, "unrecognized AclResult: %d", (int) aclerr);
+ break;
+ }
+}
+
+
/* Check if given user has rolcatupdate privilege according to pg_authid */
static bool
has_rolcatupdate(Oid roleid)
@@ -1800,11 +2223,15 @@ has_rolcatupdate(Oid roleid)
* Relay for the various pg_*_mask routines depending on object kind
*/
static AclMode
-pg_aclmask(AclObjectKind objkind, Oid table_oid, Oid roleid,
+pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum, Oid roleid,
AclMode mask, AclMaskHow how)
{
switch (objkind)
{
+ case ACL_KIND_COLUMN:
+ return
+ pg_class_aclmask(table_oid, roleid, mask, how) |
+ pg_attribute_aclmask(table_oid, attnum, roleid, mask, how);
case ACL_KIND_CLASS:
case ACL_KIND_SEQUENCE:
return pg_class_aclmask(table_oid, roleid, mask, how);
@@ -1830,15 +2257,105 @@ pg_aclmask(AclObjectKind objkind, Oid table_oid, Oid roleid,
}
}
-/*
- * Exported routine for examining a user's privileges for a table
+
+/* ****************************************************************
+ * Exported routines for examining a user's privileges for various objects
*
- * See aclmask() for a description of the API.
+ * See aclmask() for a description of the common API for these functions.
*
* Note: we give lookup failure the full ereport treatment because the
- * has_table_privilege() family of functions allow users to pass
- * any random OID to this function. Likewise for the sibling functions
- * below.
+ * has_xxx_privilege() family of functions allow users to pass any random
+ * OID to these functions.
+ * ****************************************************************
+ */
+
+/*
+ * Exported routine for examining a user's privileges for a column
+ *
+ * Note: this considers only privileges granted specifically on the column.
+ * It is caller's responsibility to take relation-level privileges into account
+ * as appropriate. (For the same reason, we have no special case for
+ * superuser-ness here.)
+ */
+AclMode
+pg_attribute_aclmask(Oid table_oid, AttrNumber attnum, Oid roleid,
+ AclMode mask, AclMaskHow how)
+{
+ AclMode result;
+ HeapTuple classTuple;
+ HeapTuple attTuple;
+ Form_pg_class classForm;
+ Form_pg_attribute attributeForm;
+ Datum aclDatum;
+ bool isNull;
+ Acl *acl;
+ Oid ownerId;
+
+ /*
+ * Must get the relation's tuple from pg_class (only needed for ownerId)
+ */
+ classTuple = SearchSysCache(RELOID,
+ ObjectIdGetDatum(table_oid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(classTuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation with OID %u does not exist",
+ table_oid)));
+ classForm = (Form_pg_class) GETSTRUCT(classTuple);
+
+ ownerId = classForm->relowner;
+
+ /*
+ * Next, get the column's ACL from pg_attribute
+ */
+ attTuple = SearchSysCache(ATTNUM,
+ ObjectIdGetDatum(table_oid),
+ Int16GetDatum(attnum),
+ 0, 0);
+ if (!HeapTupleIsValid(attTuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("attribute %d of relation with OID %u does not exist",
+ attnum, table_oid)));
+ attributeForm = (Form_pg_attribute) GETSTRUCT(attTuple);
+
+ /* Throw error on dropped columns, too */
+ if (attributeForm->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("attribute %d of relation with OID %u does not exist",
+ attnum, table_oid)));
+
+ aclDatum = SysCacheGetAttr(ATTNUM, attTuple, Anum_pg_attribute_attacl,
+ &isNull);
+
+ if (isNull)
+ {
+ /* No ACL, so build default ACL */
+ acl = acldefault(ACL_OBJECT_COLUMN, ownerId);
+ aclDatum = (Datum) 0;
+ }
+ else
+ {
+ /* detoast column's ACL if necessary */
+ acl = DatumGetAclP(aclDatum);
+ }
+
+ result = aclmask(acl, roleid, ownerId, mask, how);
+
+ /* if we have a detoasted copy, free it */
+ if (acl && (Pointer) acl != DatumGetPointer(aclDatum))
+ pfree(acl);
+
+ ReleaseSysCache(attTuple);
+ ReleaseSysCache(classTuple);
+
+ return result;
+}
+
+/*
+ * Exported routine for examining a user's privileges for a table
*/
AclMode
pg_class_aclmask(Oid table_oid, Oid roleid,
@@ -2377,6 +2894,115 @@ pg_foreign_server_aclmask(Oid srv_oid, Oid roleid,
}
/*
+ * Exported routine for checking a user's access privileges to a column
+ *
+ * Returns ACLCHECK_OK if the user has any of the privileges identified by
+ * 'mode'; otherwise returns a suitable error code (in practice, always
+ * ACLCHECK_NO_PRIV).
+ *
+ * As with pg_attribute_aclmask, only privileges granted directly on the
+ * column are considered here.
+ */
+AclResult
+pg_attribute_aclcheck(Oid table_oid, AttrNumber attnum,
+ Oid roleid, AclMode mode)
+{
+ if (pg_attribute_aclmask(table_oid, attnum, roleid, mode, ACLMASK_ANY) != 0)
+ return ACLCHECK_OK;
+ else
+ return ACLCHECK_NO_PRIV;
+}
+
+/*
+ * Exported routine for checking a user's access privileges to any/all columns
+ *
+ * If 'how' is ACLMASK_ANY, then returns ACLCHECK_OK if user has any of the
+ * privileges identified by 'mode' on any non-dropped column in the relation;
+ * otherwise returns a suitable error code (in practice, always
+ * ACLCHECK_NO_PRIV).
+ *
+ * If 'how' is ACLMASK_ALL, then returns ACLCHECK_OK if user has any of the
+ * privileges identified by 'mode' on all non-dropped columns in the relation
+ * (and there must be at least one such column); otherwise returns a suitable
+ * error code (in practice, always ACLCHECK_NO_PRIV).
+ *
+ * As with pg_attribute_aclmask, only privileges granted directly on the
+ * column(s) are considered here.
+ *
+ * Note: system columns are not considered here; there are cases where that
+ * might be appropriate but there are also cases where it wouldn't.
+ */
+AclResult
+pg_attribute_aclcheck_all(Oid table_oid, Oid roleid, AclMode mode,
+ AclMaskHow how)
+{
+ AclResult result;
+ HeapTuple classTuple;
+ Form_pg_class classForm;
+ AttrNumber nattrs;
+ AttrNumber curr_att;
+
+ /* Must fetch pg_class row to check number of attributes */
+ classTuple = SearchSysCache(RELOID,
+ ObjectIdGetDatum(table_oid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(classTuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation with OID %u does not exist",
+ table_oid)));
+ classForm = (Form_pg_class) GETSTRUCT(classTuple);
+
+ nattrs = classForm->relnatts;
+
+ ReleaseSysCache(classTuple);
+
+ /*
+ * Initialize result in case there are no non-dropped columns. We want
+ * to report failure in such cases for either value of 'how'.
+ */
+ result = ACLCHECK_NO_PRIV;
+
+ for (curr_att = 1; curr_att <= nattrs; curr_att++)
+ {
+ HeapTuple attTuple;
+ bool isdropped;
+
+ attTuple = SearchSysCache(ATTNUM,
+ ObjectIdGetDatum(table_oid),
+ Int16GetDatum(curr_att),
+ 0, 0);
+ if (!HeapTupleIsValid(attTuple))
+ elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ curr_att, table_oid);
+
+ isdropped = ((Form_pg_attribute) GETSTRUCT(attTuple))->attisdropped;
+
+ ReleaseSysCache(attTuple);
+
+ /* ignore dropped columns */
+ if (isdropped)
+ continue;
+
+ if (pg_attribute_aclmask(table_oid, curr_att, roleid,
+ mode, ACLMASK_ANY) != 0)
+ {
+ result = ACLCHECK_OK;
+ if (how == ACLMASK_ANY)
+ break; /* succeed on any success */
+ }
+ else
+ {
+ result = ACLCHECK_NO_PRIV;
+ if (how == ACLMASK_ALL)
+ break; /* fail on any failure */
+ }
+ }
+
+ return result;
+}
+
+/*
* Exported routine for checking a user's access privileges to a table
*
* Returns ACLCHECK_OK if the user has any of the privileges identified by
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 6f63232ff5..70c43cdec0 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.85 2009/01/01 17:23:36 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.86 2009/01/22 20:16:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -977,6 +977,13 @@ deleteOneObject(const ObjectAddress *object, Relation depRel)
systable_endscan(scan);
/*
+ * Delete shared dependency references related to this object. Again,
+ * if subId = 0, remove records for sub-objects too.
+ */
+ deleteSharedDependencyRecordsFor(object->classId, object->objectId,
+ object->objectSubId);
+
+ /*
* Now delete the object itself, in an object-type-dependent way.
*/
doDeletion(object);
@@ -988,13 +995,6 @@ deleteOneObject(const ObjectAddress *object, Relation depRel)
DeleteComments(object->objectId, object->classId, object->objectSubId);
/*
- * Delete shared dependency references related to this object. Sub-objects
- * (columns) don't have dependencies on global objects, so skip them.
- */
- if (object->objectSubId == 0)
- deleteSharedDependencyRecordsFor(object->classId, object->objectId);
-
- /*
* CommandCounterIncrement here to ensure that preceding changes are all
* visible to the next deletion step.
*/
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 462a1aac61..b01edbe017 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.349 2009/01/01 17:23:36 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.350 2009/01/22 20:16:01 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -112,37 +112,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum);
static FormData_pg_attribute a1 = {
0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
SelfItemPointerAttributeNumber, 0, -1, -1,
- false, 'p', 's', true, false, false, true, 0
+ false, 'p', 's', true, false, false, true, 0, { 0 }
};
static FormData_pg_attribute a2 = {
0, {"oid"}, OIDOID, 0, sizeof(Oid),
ObjectIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, false, true, 0
+ true, 'p', 'i', true, false, false, true, 0, { 0 }
};
static FormData_pg_attribute a3 = {
0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
MinTransactionIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, false, true, 0
+ true, 'p', 'i', true, false, false, true, 0, { 0 }
};
static FormData_pg_attribute a4 = {
0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
MinCommandIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, false, true, 0
+ true, 'p', 'i', true, false, false, true, 0, { 0 }
};
static FormData_pg_attribute a5 = {
0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
MaxTransactionIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, false, true, 0
+ true, 'p', 'i', true, false, false, true, 0, { 0 }
};
static FormData_pg_attribute a6 = {
0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
MaxCommandIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, false, true, 0
+ true, 'p', 'i', true, false, false, true, 0, { 0 }
};
/*
@@ -154,7 +154,7 @@ static FormData_pg_attribute a6 = {
static FormData_pg_attribute a7 = {
0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
TableOidAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, false, true, 0
+ true, 'p', 'i', true, false, false, true, 0, { 0 }
};
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
@@ -475,11 +475,13 @@ CheckAttributeType(const char *attname, Oid atttypid)
* Construct and insert a new tuple in pg_attribute.
*
* Caller has already opened and locked pg_attribute. new_attribute is the
- * attribute to insert.
+ * attribute to insert (but we ignore its attacl, if indeed it has one).
*
* indstate is the index state for CatalogIndexInsert. It can be passed as
* NULL, in which case we'll fetch the necessary info. (Don't do this when
* inserting multiple attributes, because it's a tad more expensive.)
+ *
+ * We always initialize attacl to NULL (i.e., default permissions).
*/
void
InsertPgAttributeTuple(Relation pg_attribute_rel,
@@ -512,6 +514,9 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
+ /* start out with empty permissions */
+ nulls[Anum_pg_attribute_attacl - 1] = true;
+
tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
/* finally insert the new tuple, update the indexes, and clean up */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index dee85b6716..e53f4f52dc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.311 2009/01/01 17:23:37 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.312 2009/01/22 20:16:01 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -178,7 +178,7 @@ ConstructTupleDescriptor(Relation heapRelation,
* now that we've determined the "from", let's copy the tuple desc
* data...
*/
- memcpy(to, from, ATTRIBUTE_TUPLE_SIZE);
+ memcpy(to, from, ATTRIBUTE_FIXED_PART_SIZE);
/*
* Fix the stuff that should not be the same as the underlying
@@ -198,7 +198,7 @@ ConstructTupleDescriptor(Relation heapRelation,
/* Expressional index */
Node *indexkey;
- MemSet(to, 0, ATTRIBUTE_TUPLE_SIZE);
+ MemSet(to, 0, ATTRIBUTE_FIXED_PART_SIZE);
if (indexpr_item == NULL) /* shouldn't happen */
elog(ERROR, "too few entries in indexprs list");
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 989f2470af..12ffd74256 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/pg_operator.c,v 1.107 2009/01/01 17:23:37 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/pg_operator.c,v 1.108 2009/01/22 20:16:01 tgl Exp $
*
* NOTES
* these routines moved here from commands/define.c and somewhat cleaned up.
@@ -774,7 +774,7 @@ makeOperatorDependencies(HeapTuple tuple)
/* In case we are updating a shell, delete any existing entries */
deleteDependencyRecordsFor(myself.classId, myself.objectId);
- deleteSharedDependencyRecordsFor(myself.classId, myself.objectId);
+ deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
/* Dependency on namespace */
if (OidIsValid(oper->oprnamespace))
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 191b8b8a9e..f9b2716b9f 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.160 2009/01/01 17:23:37 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.161 2009/01/22 20:16:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -499,7 +499,7 @@ ProcedureCreate(const char *procedureName,
if (is_update)
{
deleteDependencyRecordsFor(ProcedureRelationId, retval);
- deleteSharedDependencyRecordsFor(ProcedureRelationId, retval);
+ deleteSharedDependencyRecordsFor(ProcedureRelationId, retval, 0);
}
myself.classId = ProcedureRelationId;
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 113c72f324..ee5329b1d7 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.30 2009/01/01 17:23:37 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/pg_shdepend.c,v 1.31 2009/01/22 20:16:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -55,15 +55,19 @@ static int getOidListDiff(Oid *list1, int nlist1, Oid *list2, int nlist2,
Oid **diff);
static Oid classIdGetDbId(Oid classId);
static void shdepLockAndCheckObject(Oid classId, Oid objectId);
-static void shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
- Oid refclassid, Oid refobjid,
- SharedDependencyType deptype);
-static void shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
- Oid refclassId, Oid refobjId,
- SharedDependencyType deptype);
-static void shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId,
- Oid refclassId, Oid refobjId,
- SharedDependencyType deptype);
+static void shdepChangeDep(Relation sdepRel,
+ Oid classid, Oid objid, int32 objsubid,
+ Oid refclassid, Oid refobjid,
+ SharedDependencyType deptype);
+static void shdepAddDependency(Relation sdepRel,
+ Oid classId, Oid objectId, int32 objsubId,
+ Oid refclassId, Oid refobjId,
+ SharedDependencyType deptype);
+static void shdepDropDependency(Relation sdepRel,
+ Oid classId, Oid objectId, int32 objsubId,
+ bool drop_subobjects,
+ Oid refclassId, Oid refobjId,
+ SharedDependencyType deptype);
static void storeObjectDescription(StringInfo descs, objectType type,
ObjectAddress *object,
SharedDependencyType deptype,
@@ -111,6 +115,7 @@ recordSharedDependencyOn(ObjectAddress *depender,
sdepRel))
{
shdepAddDependency(sdepRel, depender->classId, depender->objectId,
+ depender->objectSubId,
referenced->classId, referenced->objectId,
deptype);
}
@@ -163,14 +168,15 @@ recordDependencyOnOwner(Oid classId, Oid objectId, Oid owner)
* locked.
*/
static void
-shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
+shdepChangeDep(Relation sdepRel,
+ Oid classid, Oid objid, int32 objsubid,
Oid refclassid, Oid refobjid,
SharedDependencyType deptype)
{
Oid dbid = classIdGetDbId(classid);
HeapTuple oldtup = NULL;
HeapTuple scantup;
- ScanKeyData key[3];
+ ScanKeyData key[4];
SysScanDesc scan;
/*
@@ -194,9 +200,13 @@ shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
Anum_pg_shdepend_objid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objid));
+ ScanKeyInit(&key[3],
+ Anum_pg_shdepend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(objsubid));
scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
- SnapshotNow, 3, key);
+ SnapshotNow, 4, key);
while ((scantup = systable_getnext(scan)) != NULL)
{
@@ -206,8 +216,8 @@ shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
/* Caller screwed up if multiple matches */
if (oldtup)
elog(ERROR,
- "multiple pg_shdepend entries for object %u/%u deptype %c",
- classid, objid, deptype);
+ "multiple pg_shdepend entries for object %u/%u/%d deptype %c",
+ classid, objid, objsubid, deptype);
oldtup = heap_copytuple(scantup);
}
@@ -244,6 +254,7 @@ shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(dbid);
values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classid);
values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objid);
+ values[Anum_pg_shdepend_objsubid - 1] = Int32GetDatum(objsubid);
values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassid);
values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjid);
@@ -268,6 +279,9 @@ shdepChangeDep(Relation sdepRel, Oid classid, Oid objid,
* changeDependencyOnOwner
*
* Update the shared dependencies to account for the new owner.
+ *
+ * Note: we don't need an objsubid argument because only whole objects
+ * have owners.
*/
void
changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
@@ -277,7 +291,8 @@ changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
sdepRel = heap_open(SharedDependRelationId, RowExclusiveLock);
/* Adjust the SHARED_DEPENDENCY_OWNER entry */
- shdepChangeDep(sdepRel, classId, objectId,
+ shdepChangeDep(sdepRel,
+ classId, objectId, 0,
AuthIdRelationId, newOwnerId,
SHARED_DEPENDENCY_OWNER);
@@ -300,7 +315,7 @@ changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
* to make the various ALTER OWNER routines each know about it.
*----------
*/
- shdepDropDependency(sdepRel, classId, objectId,
+ shdepDropDependency(sdepRel, classId, objectId, 0, true,
AuthIdRelationId, newOwnerId,
SHARED_DEPENDENCY_ACL);
@@ -368,7 +383,7 @@ getOidListDiff(Oid *list1, int nlist1, Oid *list2, int nlist2, Oid **diff)
* updateAclDependencies
* Update the pg_shdepend info for an object's ACL during GRANT/REVOKE.
*
- * classId, objectId: identify the object whose ACL this is
+ * classId, objectId, objsubId: identify the object whose ACL this is
* ownerId: role owning the object
* isGrant: are we adding or removing ACL entries?
* noldmembers, oldmembers: array of roleids appearing in old ACL
@@ -388,7 +403,8 @@ getOidListDiff(Oid *list1, int nlist1, Oid *list2, int nlist2, Oid **diff)
* before return.
*/
void
-updateAclDependencies(Oid classId, Oid objectId, Oid ownerId, bool isGrant,
+updateAclDependencies(Oid classId, Oid objectId, int32 objsubId,
+ Oid ownerId, bool isGrant,
int noldmembers, Oid *oldmembers,
int nnewmembers, Oid *newmembers)
{
@@ -429,11 +445,12 @@ updateAclDependencies(Oid classId, Oid objectId, Oid ownerId, bool isGrant,
continue;
if (isGrant)
- shdepAddDependency(sdepRel, classId, objectId,
+ shdepAddDependency(sdepRel, classId, objectId, objsubId,
AuthIdRelationId, roleid,
SHARED_DEPENDENCY_ACL);
else
- shdepDropDependency(sdepRel, classId, objectId,
+ shdepDropDependency(sdepRel, classId, objectId, objsubId,
+ false, /* exact match on objsubId */
AuthIdRelationId, roleid,
SHARED_DEPENDENCY_ACL);
}
@@ -533,7 +550,7 @@ checkSharedDependencies(Oid classId, Oid objectId,
object.classId = sdepForm->classid;
object.objectId = sdepForm->objid;
- object.objectSubId = 0;
+ object.objectSubId = sdepForm->objsubid;
/*
* If it's a dependency local to this database or it's a shared
@@ -755,7 +772,7 @@ dropDatabaseDependencies(Oid databaseId)
systable_endscan(scan);
/* Now delete all entries corresponding to the database itself */
- shdepDropDependency(sdepRel, DatabaseRelationId, databaseId,
+ shdepDropDependency(sdepRel, DatabaseRelationId, databaseId, 0, true,
InvalidOid, InvalidOid,
SHARED_DEPENDENCY_INVALID);
@@ -768,15 +785,19 @@ dropDatabaseDependencies(Oid databaseId)
* Delete all pg_shdepend entries corresponding to an object that's being
* dropped or modified. The object is assumed to be either a shared object
* or local to the current database (the classId tells us which).
+ *
+ * If objectSubId is zero, we are deleting a whole object, so get rid of
+ * pg_shdepend entries for subobjects as well.
*/
void
-deleteSharedDependencyRecordsFor(Oid classId, Oid objectId)
+deleteSharedDependencyRecordsFor(Oid classId, Oid objectId, int32 objectSubId)
{
Relation sdepRel;
sdepRel = heap_open(SharedDependRelationId, RowExclusiveLock);
- shdepDropDependency(sdepRel, classId, objectId,
+ shdepDropDependency(sdepRel, classId, objectId, objectSubId,
+ (objectSubId == 0),
InvalidOid, InvalidOid,
SHARED_DEPENDENCY_INVALID);
@@ -791,7 +812,8 @@ deleteSharedDependencyRecordsFor(Oid classId, Oid objectId)
* locked.
*/
static void
-shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
+shdepAddDependency(Relation sdepRel,
+ Oid classId, Oid objectId, int32 objsubId,
Oid refclassId, Oid refobjId,
SharedDependencyType deptype)
{
@@ -814,6 +836,7 @@ shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
values[Anum_pg_shdepend_dbid - 1] = ObjectIdGetDatum(classIdGetDbId(classId));
values[Anum_pg_shdepend_classid - 1] = ObjectIdGetDatum(classId);
values[Anum_pg_shdepend_objid - 1] = ObjectIdGetDatum(objectId);
+ values[Anum_pg_shdepend_objsubid - 1] = Int32GetDatum(objsubId);
values[Anum_pg_shdepend_refclassid - 1] = ObjectIdGetDatum(refclassId);
values[Anum_pg_shdepend_refobjid - 1] = ObjectIdGetDatum(refobjId);
@@ -835,20 +858,26 @@ shdepAddDependency(Relation sdepRel, Oid classId, Oid objectId,
* Internal workhorse for deleting entries from pg_shdepend.
*
* We drop entries having the following properties:
- * dependent object is the one identified by classId/objectId
+ * dependent object is the one identified by classId/objectId/objsubId
* if refclassId isn't InvalidOid, it must match the entry's refclassid
* if refobjId isn't InvalidOid, it must match the entry's refobjid
* if deptype isn't SHARED_DEPENDENCY_INVALID, it must match entry's deptype
*
+ * If drop_subobjects is true, we ignore objsubId and consider all entries
+ * matching classId/objectId.
+ *
* sdepRel must be the pg_shdepend relation, already opened and suitably
* locked.
*/
static void
-shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId,
+shdepDropDependency(Relation sdepRel,
+ Oid classId, Oid objectId, int32 objsubId,
+ bool drop_subobjects,
Oid refclassId, Oid refobjId,
SharedDependencyType deptype)
{
- ScanKeyData key[3];
+ ScanKeyData key[4];
+ int nkeys;
SysScanDesc scan;
HeapTuple tup;
@@ -865,9 +894,19 @@ shdepDropDependency(Relation sdepRel, Oid classId, Oid objectId,
Anum_pg_shdepend_objid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(objectId));
+ if (drop_subobjects)
+ nkeys = 3;
+ else
+ {
+ ScanKeyInit(&key[3],
+ Anum_pg_shdepend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(objsubId));
+ nkeys = 4;
+ }
scan = systable_beginscan(sdepRel, SharedDependDependerIndexId, true,
- SnapshotNow, 3, key);
+ SnapshotNow, nkeys, key);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
@@ -1067,7 +1106,7 @@ isSharedObjectPinned(Oid classId, Oid objectId, Relation sdepRel)
*
* Drop the objects owned by any one of the given RoleIds. If a role has
* access to an object, the grant will be removed as well (but the object
- * will not, of course.)
+ * will not, of course).
*
* We can revoke grants immediately while doing the scan, but drops are
* saved up and done all at once with performMultipleDeletions. This
@@ -1131,10 +1170,9 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
while ((tuple = systable_getnext(scan)) != NULL)
{
- ObjectAddress obj;
- GrantObjectType objtype;
- InternalGrant istmt;
Form_pg_shdepend sdepForm = (Form_pg_shdepend) GETSTRUCT(tuple);
+ InternalGrant istmt;
+ ObjectAddress obj;
/* We only operate on objects in the current database */
if (sdepForm->dbid != MyDatabaseId)
@@ -1172,14 +1210,13 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
default:
elog(ERROR, "unexpected object type %d",
sdepForm->classid);
- /* keep compiler quiet */
- objtype = (GrantObjectType) 0;
break;
}
istmt.is_grant = false;
istmt.objects = list_make1_oid(sdepForm->objid);
istmt.all_privs = true;
istmt.privileges = ACL_NO_RIGHTS;
+ istmt.col_privs = NIL;
istmt.grantees = list_make1_oid(roleid);
istmt.grant_option = false;
istmt.behavior = DROP_CASCADE;
@@ -1190,7 +1227,7 @@ shdepDropOwned(List *roleids, DropBehavior behavior)
/* Save it for deletion below */
obj.classId = sdepForm->classid;
obj.objectId = sdepForm->objid;
- obj.objectSubId = 0;
+ obj.objectSubId = sdepForm->objsubid;
add_exact_object_address(&obj, deleteobjs);
break;
}
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index d39a66b58d..6d28b1df2f 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/catalog/pg_type.c,v 1.123 2009/01/01 17:23:37 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/catalog/pg_type.c,v 1.124 2009/01/22 20:16:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -482,7 +482,7 @@ GenerateTypeDependencies(Oid typeNamespace,
if (rebuild)
{
deleteDependencyRecordsFor(TypeRelationId, typeObjectId);
- deleteSharedDependencyRecordsFor(TypeRelationId, typeObjectId);
+ deleteSharedDependencyRecordsFor(TypeRelationId, typeObjectId, 0);
}
myself.classId = TypeRelationId;
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 5f6a2c42de..33447b671f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.132 2009/01/06 23:46:06 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.133 2009/01/22 20:16:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -705,11 +705,12 @@ examine_attribute(Relation onerel, int attnum)
return NULL;
/*
- * Create the VacAttrStats struct.
+ * Create the VacAttrStats struct. Note that we only have a copy of
+ * the fixed fields of the pg_attribute tuple.
*/
stats = (VacAttrStats *) palloc0(sizeof(VacAttrStats));
- stats->attr = (Form_pg_attribute) palloc(ATTRIBUTE_TUPLE_SIZE);
- memcpy(stats->attr, attr, ATTRIBUTE_TUPLE_SIZE);
+ stats->attr = (Form_pg_attribute) palloc(ATTRIBUTE_FIXED_PART_SIZE);
+ memcpy(stats->attr, attr, ATTRIBUTE_FIXED_PART_SIZE);
typtuple = SearchSysCache(TYPEOID,
ObjectIdGetDatum(attr->atttypid),
0, 0, 0);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 061d45c30a..397e010aca 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.277 2009/01/12 08:54:26 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.278 2009/01/22 20:16:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -246,6 +246,7 @@ static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
static Oid transformFkeyCheckAttrs(Relation pkrel,
int numattrs, int16 *attnums,
Oid *opclasses);
+static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
static void validateForeignKeyConstraint(FkConstraint *fkconstraint,
Relation rel, Relation pkrel, Oid constraintOid);
static void createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
@@ -3589,6 +3590,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
attribute.attisdropped = false;
attribute.attislocal = colDef->is_local;
attribute.attinhcount = colDef->inhcount;
+ /* attribute.attacl is handled by InsertPgAttributeTuple */
ReleaseSysCache(typeTuple);
@@ -4473,15 +4475,14 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
* Add a foreign-key constraint to a single table
*
* Subroutine for ATExecAddConstraint. Must already hold exclusive
- * lock on the rel, and have done appropriate validity/permissions checks
- * for it.
+ * lock on the rel, and have done appropriate validity checks for it.
+ * We do permissions checks here, however.
*/
static void
ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
FkConstraint *fkconstraint)
{
Relation pkrel;
- AclResult aclresult;
int16 pkattnum[INDEX_MAX_KEYS];
int16 fkattnum[INDEX_MAX_KEYS];
Oid pktypoid[INDEX_MAX_KEYS];
@@ -4506,10 +4507,7 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
pkrel = heap_openrv(fkconstraint->pktable, AccessExclusiveLock);
/*
- * Validity and permissions checks
- *
- * Note: REFERENCES permissions checks are redundant with CREATE TRIGGER,
- * but we may as well error out sooner instead of later.
+ * Validity checks (permission checks wait till we have the column numbers)
*/
if (pkrel->rd_rel->relkind != RELKIND_RELATION)
ereport(ERROR,
@@ -4517,24 +4515,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
errmsg("referenced relation \"%s\" is not a table",
RelationGetRelationName(pkrel))));
- aclresult = pg_class_aclcheck(RelationGetRelid(pkrel), GetUserId(),
- ACL_REFERENCES);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_CLASS,
- RelationGetRelationName(pkrel));
-
if (!allowSystemTableMods && IsSystemRelation(pkrel))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(pkrel))));
- aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
- ACL_REFERENCES);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
-
/*
* Disallow reference from permanent table to temp table or vice versa.
* (The ban on perm->temp is for fairly obvious reasons. The ban on
@@ -4599,6 +4585,12 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
}
/*
+ * Now we can check permissions.
+ */
+ checkFkeyPermissions(pkrel, pkattnum, numpks);
+ checkFkeyPermissions(rel, fkattnum, numfks);
+
+ /*
* Look up the equality operators to use in the constraint.
*
* Note that we have to be careful about the difference between the actual
@@ -5016,6 +5008,30 @@ transformFkeyCheckAttrs(Relation pkrel,
return indexoid;
}
+/* Permissions checks for ADD FOREIGN KEY */
+static void
+checkFkeyPermissions(Relation rel, int16 *attnums, int natts)
+{
+ Oid roleid = GetUserId();
+ AclResult aclresult;
+ int i;
+
+ /* Okay if we have relation-level REFERENCES permission */
+ aclresult = pg_class_aclcheck(RelationGetRelid(rel), roleid,
+ ACL_REFERENCES);
+ if (aclresult == ACLCHECK_OK)
+ return;
+ /* Else we must have REFERENCES on each column */
+ for (i = 0; i < natts; i++)
+ {
+ aclresult = pg_attribute_aclcheck(RelationGetRelid(rel), attnums[i],
+ roleid, ACL_REFERENCES);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_CLASS,
+ RelationGetRelationName(rel));
+ }
+}
+
/*
* Scan the existing rows in a table to verify they meet a proposed FK
* constraint.
@@ -5123,7 +5139,7 @@ CreateFKCheckTrigger(RangeVar *myRel, FkConstraint *fkconstraint,
fk_trigger->constrrel = fkconstraint->pktable;
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, constraintOid);
+ (void) CreateTrigger(fk_trigger, constraintOid, false);
/* Make changes-so-far visible */
CommandCounterIncrement();
@@ -5204,7 +5220,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
}
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, constraintOid);
+ (void) CreateTrigger(fk_trigger, constraintOid, false);
/* Make changes-so-far visible */
CommandCounterIncrement();
@@ -5256,7 +5272,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
}
fk_trigger->args = NIL;
- (void) CreateTrigger(fk_trigger, constraintOid);
+ (void) CreateTrigger(fk_trigger, constraintOid, false);
}
/*
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 54818ff34d..b81381a6ea 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -37,7 +37,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.60 2009/01/20 18:59:37 heikki Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.61 2009/01/22 20:16:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -455,7 +455,7 @@ DropTableSpace(DropTableSpaceStmt *stmt)
/*
* Remove dependency on owner.
*/
- deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid);
+ deleteSharedDependencyRecordsFor(TableSpaceRelationId, tablespaceoid, 0);
/*
* Acquire TablespaceCreateLock to ensure that no TablespaceCreateDbspace
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 699493c335..ce276e5fe5 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.245 2009/01/22 19:16:31 heikki Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.246 2009/01/22 20:16:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -74,11 +74,16 @@ static void AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event,
* be made to link the trigger to that constraint. constraintOid is zero when
* executing a user-entered CREATE TRIGGER command.
*
+ * If checkPermissions is true we require ACL_TRIGGER permissions on the
+ * relation. If not, the caller already checked permissions. (This is
+ * currently redundant with constraintOid being zero, but it's clearer to
+ * have a separate argument.)
+ *
* Note: can return InvalidOid if we decided to not create a trigger at all,
* but a foreign-key constraint. This is a kluge for backwards compatibility.
*/
Oid
-CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid)
+CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid, bool checkPermissions)
{
int16 tgtype;
int2vector *tgattr;
@@ -117,37 +122,27 @@ CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid)
errmsg("permission denied: \"%s\" is a system catalog",
RelationGetRelationName(rel))));
- /* permission checks */
+ if (stmt->isconstraint && stmt->constrrel != NULL)
+ constrrelid = RangeVarGetRelid(stmt->constrrel, false);
- if (stmt->isconstraint)
+ /* permission checks */
+ if (checkPermissions)
{
- /* constraint trigger */
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
- ACL_REFERENCES);
+ ACL_TRIGGER);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
RelationGetRelationName(rel));
- if (stmt->constrrel != NULL)
+ if (OidIsValid(constrrelid))
{
- constrrelid = RangeVarGetRelid(stmt->constrrel, false);
-
aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
- ACL_REFERENCES);
+ ACL_TRIGGER);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_CLASS,
get_rel_name(constrrelid));
}
}
- else
- {
- /* regular trigger */
- aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
- ACL_TRIGGER);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_CLASS,
- RelationGetRelationName(rel));
- }
/* Compute tgtype */
TRIGGER_CLEAR_TYPE(tgtype);
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index f9248a2b67..7276cd50d4 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.15 2009/01/01 17:23:40 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.16 2009/01/22 20:16:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1255,7 +1255,7 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
if (removeOld)
{
deleteDependencyRecordsFor(myself.classId, myself.objectId);
- deleteSharedDependencyRecordsFor(myself.classId, myself.objectId);
+ deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
}
/*
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index a013e80ac9..7c1da42bc3 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.184 2009/01/01 17:23:40 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.185 2009/01/22 20:16:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1123,9 +1123,17 @@ GrantRole(GrantRoleStmt *stmt)
*/
foreach(item, stmt->granted_roles)
{
- char *rolename = strVal(lfirst(item));
- Oid roleid = get_roleid_checked(rolename);
+ AccessPriv *priv = (AccessPriv *) lfirst(item);
+ char *rolename = priv->priv_name;
+ Oid roleid;
+
+ /* Must reject priv(columns) and ALL PRIVILEGES(columns) */
+ if (rolename == NULL || priv->cols != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_GRANT_OPERATION),
+ errmsg("column names cannot be included in GRANT/REVOKE ROLE")));
+ roleid = get_roleid_checked(rolename);
if (stmt->is_grant)
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index bb05b5d482..0352d9a5e4 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.320 2009/01/01 17:23:41 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.321 2009/01/22 20:16:03 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -34,6 +34,7 @@
#include "access/heapam.h"
#include "access/reloptions.h"
+#include "access/sysattr.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/heap.h"
@@ -453,8 +454,12 @@ static void
ExecCheckRTEPerms(RangeTblEntry *rte)
{
AclMode requiredPerms;
+ AclMode relPerms;
+ AclMode remainingPerms;
Oid relOid;
Oid userid;
+ Bitmapset *tmpset;
+ int col;
/*
* Only plain-relation RTEs need to be checked here. Function RTEs are
@@ -484,12 +489,110 @@ ExecCheckRTEPerms(RangeTblEntry *rte)
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
/*
- * We must have *all* the requiredPerms bits, so use aclmask not aclcheck.
+ * We must have *all* the requiredPerms bits, but some of the bits can be
+ * satisfied from column-level rather than relation-level permissions.
+ * First, remove any bits that are satisfied by relation permissions.
*/
- if (pg_class_aclmask(relOid, userid, requiredPerms, ACLMASK_ALL)
- != requiredPerms)
- aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
- get_rel_name(relOid));
+ relPerms = pg_class_aclmask(relOid, userid, requiredPerms, ACLMASK_ALL);
+ remainingPerms = requiredPerms & ~relPerms;
+ if (remainingPerms != 0)
+ {
+ /*
+ * If we lack any permissions that exist only as relation permissions,
+ * we can fail straight away.
+ */
+ if (remainingPerms & ~(ACL_SELECT | ACL_INSERT | ACL_UPDATE))
+ aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ get_rel_name(relOid));
+
+ /*
+ * Check to see if we have the needed privileges at column level.
+ *
+ * Note: failures just report a table-level error; it would be nicer
+ * to report a column-level error if we have some but not all of the
+ * column privileges.
+ */
+ if (remainingPerms & ACL_SELECT)
+ {
+ /*
+ * When the query doesn't explicitly reference any columns (for
+ * 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 (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
+ ACLMASK_ANY) != ACLCHECK_OK)
+ aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ get_rel_name(relOid));
+ }
+
+ tmpset = bms_copy(rte->selectedCols);
+ while ((col = bms_first_member(tmpset)) >= 0)
+ {
+ /* remove the column number offset */
+ col += FirstLowInvalidHeapAttributeNumber;
+ if (col == InvalidAttrNumber)
+ {
+ /* Whole-row reference, must have priv on all cols */
+ if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
+ ACLMASK_ALL) != ACLCHECK_OK)
+ aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ get_rel_name(relOid));
+ }
+ else
+ {
+ if (pg_attribute_aclcheck(relOid, col, userid, ACL_SELECT)
+ != ACLCHECK_OK)
+ aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ get_rel_name(relOid));
+ }
+ }
+ bms_free(tmpset);
+ }
+
+ /*
+ * Basically the same for the mod columns, with either INSERT or UPDATE
+ * privilege as specified by remainingPerms.
+ */
+ remainingPerms &= ~ACL_SELECT;
+ if (remainingPerms != 0)
+ {
+ /*
+ * When the query doesn't explicitly change any columns, allow
+ * the query if we have permission on any column of the rel. This
+ * is to handle SELECT FOR UPDATE as well as possible corner cases
+ * in INSERT and UPDATE.
+ */
+ if (bms_is_empty(rte->modifiedCols))
+ {
+ if (pg_attribute_aclcheck_all(relOid, userid, remainingPerms,
+ ACLMASK_ANY) != ACLCHECK_OK)
+ aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ get_rel_name(relOid));
+ }
+
+ tmpset = bms_copy(rte->modifiedCols);
+ while ((col = bms_first_member(tmpset)) >= 0)
+ {
+ /* remove the column number offset */
+ col += FirstLowInvalidHeapAttributeNumber;
+ if (col == InvalidAttrNumber)
+ {
+ /* whole-row reference can't happen here */
+ elog(ERROR, "whole-row update is not implemented");
+ }
+ else
+ {
+ if (pg_attribute_aclcheck(relOid, col, userid, remainingPerms)
+ != ACLCHECK_OK)
+ aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ get_rel_name(relOid));
+ }
+ }
+ bms_free(tmpset);
+ }
+ }
}
/*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index da1c65cfcc..06f89b16a3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.420 2009/01/16 13:27:23 heikki Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.421 2009/01/22 20:16:03 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1740,6 +1740,8 @@ _copyRangeTblEntry(RangeTblEntry *from)
COPY_SCALAR_FIELD(inFromCl);
COPY_SCALAR_FIELD(requiredPerms);
COPY_SCALAR_FIELD(checkAsUser);
+ COPY_BITMAPSET_FIELD(selectedCols);
+ COPY_BITMAPSET_FIELD(modifiedCols);
return newnode;
}
@@ -2342,6 +2344,17 @@ _copyFuncWithArgs(FuncWithArgs *from)
return newnode;
}
+static AccessPriv *
+_copyAccessPriv(AccessPriv *from)
+{
+ AccessPriv *newnode = makeNode(AccessPriv);
+
+ COPY_STRING_FIELD(priv_name);
+ COPY_NODE_FIELD(cols);
+
+ return newnode;
+}
+
static GrantRoleStmt *
_copyGrantRoleStmt(GrantRoleStmt *from)
{
@@ -4096,6 +4109,9 @@ copyObject(void *from)
case T_FuncWithArgs:
retval = _copyFuncWithArgs(from);
break;
+ case T_AccessPriv:
+ retval = _copyAccessPriv(from);
+ break;
case T_XmlSerialize:
retval = _copyXmlSerialize(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 190750f2e1..4e101dd23b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -22,7 +22,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.345 2009/01/16 13:27:23 heikki Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.346 2009/01/22 20:16:03 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1014,6 +1014,15 @@ _equalFuncWithArgs(FuncWithArgs *a, FuncWithArgs *b)
}
static bool
+_equalAccessPriv(AccessPriv *a, AccessPriv *b)
+{
+ COMPARE_STRING_FIELD(priv_name);
+ COMPARE_NODE_FIELD(cols);
+
+ return true;
+}
+
+static bool
_equalGrantRoleStmt(GrantRoleStmt *a, GrantRoleStmt *b)
{
COMPARE_NODE_FIELD(granted_roles);
@@ -2122,6 +2131,8 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
COMPARE_SCALAR_FIELD(inFromCl);
COMPARE_SCALAR_FIELD(requiredPerms);
COMPARE_SCALAR_FIELD(checkAsUser);
+ COMPARE_BITMAPSET_FIELD(selectedCols);
+ COMPARE_BITMAPSET_FIELD(modifiedCols);
return true;
}
@@ -2874,6 +2885,9 @@ equal(void *a, void *b)
case T_FuncWithArgs:
retval = _equalFuncWithArgs(a, b);
break;
+ case T_AccessPriv:
+ retval = _equalAccessPriv(a, b);
+ break;
case T_XmlSerialize:
retval = _equalXmlSerialize(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index af427fe7e6..0efd84dae8 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.349 2009/01/01 17:23:43 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.350 2009/01/22 20:16:04 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@@ -180,8 +180,6 @@ _outList(StringInfo str, List *node)
* converts a bitmap set of integers
*
* Note: the output format is "(b int int ...)", similar to an integer List.
- * Currently bitmapsets do not appear in any node type that is stored in
- * rules, so there is no support in readfuncs.c for reading this format.
*/
static void
_outBitmapset(StringInfo str, Bitmapset *bms)
@@ -2060,6 +2058,8 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
WRITE_BOOL_FIELD(inFromCl);
WRITE_UINT_FIELD(requiredPerms);
WRITE_OID_FIELD(checkAsUser);
+ WRITE_BITMAPSET_FIELD(selectedCols);
+ WRITE_BITMAPSET_FIELD(modifiedCols);
}
static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d5f4677c2a..de62e53d1d 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.220 2009/01/01 17:23:43 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.221 2009/01/22 20:16:04 tgl Exp $
*
* NOTES
* Path and Plan nodes do not have any readfuncs support, because we
@@ -114,6 +114,11 @@
token = pg_strtok(&length); /* skip :fldname */ \
local_node->fldname = nodeRead(NULL, 0)
+/* Read a bitmapset field */
+#define READ_BITMAPSET_FIELD(fldname) \
+ token = pg_strtok(&length); /* skip :fldname */ \
+ local_node->fldname = _readBitmapset()
+
/* Routine exit */
#define READ_DONE() \
return local_node
@@ -137,6 +142,46 @@
static Datum readDatum(bool typbyval);
+/*
+ * _readBitmapset
+ */
+static Bitmapset *
+_readBitmapset(void)
+{
+ Bitmapset *result = NULL;
+ READ_TEMP_LOCALS();
+
+ token = pg_strtok(&length);
+ if (token == NULL)
+ elog(ERROR, "incomplete Bitmapset structure");
+ if (length != 1 || token[0] != '(')
+ elog(ERROR, "unrecognized token: \"%.*s\"", length, token);
+
+ token = pg_strtok(&length);
+ if (token == NULL)
+ elog(ERROR, "incomplete Bitmapset structure");
+ if (length != 1 || token[0] != 'b')
+ elog(ERROR, "unrecognized token: \"%.*s\"", length, token);
+
+ for (;;)
+ {
+ int val;
+ char *endptr;
+
+ token = pg_strtok(&length);
+ if (token == NULL)
+ elog(ERROR, "unterminated Bitmapset structure");
+ if (length == 1 && token[0] == ')')
+ break;
+ val = (int) strtol(token, &endptr, 10);
+ if (endptr != token + length)
+ elog(ERROR, "unrecognized integer: \"%.*s\"", length, token);
+ result = bms_add_member(result, val);
+ }
+
+ return result;
+}
+
/*
* _readQuery
@@ -1102,6 +1147,8 @@ _readRangeTblEntry(void)
READ_BOOL_FIELD(inFromCl);
READ_UINT_FIELD(requiredPerms);
READ_OID_FIELD(checkAsUser);
+ READ_BITMAPSET_FIELD(selectedCols);
+ READ_BITMAPSET_FIELD(modifiedCols);
READ_DONE();
}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 1a64a7742d..17016d5f3b 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.148 2009/01/01 17:23:44 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.149 2009/01/22 20:16:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -189,7 +189,8 @@ set_plan_references(PlannerGlobal *glob, Plan *plan, List *rtable)
* 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 alias and eref Alias fields, which
- * are needed by EXPLAIN.
+ * are needed by EXPLAIN, and the selectedCols and modifiedCols bitmaps,
+ * which are needed for executor-startup permissions checking.
*/
foreach(lc, rtable)
{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6b54e9ba17..397e951c71 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -17,13 +17,14 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.387 2009/01/08 13:42:33 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.388 2009/01/22 20:16:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
+#include "access/sysattr.h"
#include "catalog/pg_type.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -422,6 +423,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
* bugs of just that nature...)
*/
sub_pstate->p_rtable = sub_rtable;
+ sub_pstate->p_joinexprs = NIL; /* sub_rtable has no joins */
sub_pstate->p_relnamespace = sub_relnamespace;
sub_pstate->p_varnamespace = sub_varnamespace;
@@ -629,7 +631,9 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/*
* Generate query's target list using the computed list of expressions.
+ * Also, mark all the target columns as needing insert permissions.
*/
+ rte = pstate->p_target_rangetblentry;
qry->targetList = NIL;
icols = list_head(icolumns);
attnos = list_head(attrnos);
@@ -637,17 +641,22 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
{
Expr *expr = (Expr *) lfirst(lc);
ResTarget *col;
+ AttrNumber attr_num;
TargetEntry *tle;
col = (ResTarget *) lfirst(icols);
Assert(IsA(col, ResTarget));
+ attr_num = (AttrNumber) lfirst_int(attnos);
tle = makeTargetEntry(expr,
- (AttrNumber) lfirst_int(attnos),
+ attr_num,
col->name,
false);
qry->targetList = lappend(qry->targetList, tle);
+ rte->modifiedCols = bms_add_member(rte->modifiedCols,
+ attr_num - FirstLowInvalidHeapAttributeNumber);
+
icols = lnext(icols);
attnos = lnext(attnos);
}
@@ -1129,8 +1138,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
List *targetvars,
*targetnames,
*sv_relnamespace,
- *sv_varnamespace,
- *sv_rtable;
+ *sv_varnamespace;
+ int sv_rtable_length;
RangeTblEntry *jrte;
int tllen;
@@ -1254,16 +1263,15 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
* "ORDER BY upper(foo)" will draw the right error message rather than
* "foo not found".
*/
- jrte = addRangeTableEntryForJoin(NULL,
+ sv_rtable_length = list_length(pstate->p_rtable);
+
+ jrte = addRangeTableEntryForJoin(pstate,
targetnames,
JOIN_INNER,
targetvars,
NULL,
false);
- sv_rtable = pstate->p_rtable;
- pstate->p_rtable = list_make1(jrte);
-
sv_relnamespace = pstate->p_relnamespace;
pstate->p_relnamespace = NIL; /* no qualified names allowed */
@@ -1283,7 +1291,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
&qry->targetList,
false /* no unknowns expected */ );
- pstate->p_rtable = sv_rtable;
+ pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
pstate->p_relnamespace = sv_relnamespace;
pstate->p_varnamespace = sv_varnamespace;
@@ -1618,6 +1626,7 @@ static Query *
transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
{
Query *qry = makeNode(Query);
+ RangeTblEntry *target_rte;
Node *qual;
ListCell *origTargetList;
ListCell *tl;
@@ -1675,6 +1684,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1;
/* Prepare non-junk columns for assignment to target table */
+ target_rte = pstate->p_target_rangetblentry;
origTargetList = list_head(stmt->targetList);
foreach(tl, qry->targetList)
@@ -1715,6 +1725,10 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
origTarget->indirection,
origTarget->location);
+ /* Mark the target column as requiring update permissions */
+ target_rte->modifiedCols = bms_add_member(target_rte->modifiedCols,
+ attrno - FirstLowInvalidHeapAttributeNumber);
+
origTargetList = lnext(origTargetList);
}
if (origTargetList != NULL)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index cc1f812bd9..7f9e5e5b98 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.655 2009/01/16 13:27:23 heikki Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.656 2009/01/22 20:16:05 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -94,6 +94,13 @@ extern List *parsetree; /* final parse result is delivered here */
static bool QueryIsRule = FALSE;
+/* Private struct for the result of privilege_target production */
+typedef struct PrivTarget
+{
+ GrantObjectType objtype;
+ List *objs;
+} PrivTarget;
+
/*
* If you need access to certain yacc-generated variables and find that
* they're static by default, uncomment the next line. (this is not a
@@ -167,7 +174,8 @@ static TypeName *TableFuncTypeName(List *columns);
WithClause *with;
A_Indices *aind;
ResTarget *target;
- PrivTarget *privtarget;
+ struct PrivTarget *privtarget;
+ AccessPriv *accesspriv;
InsertStmt *istmt;
VariableSetStmt *vsetstmt;
@@ -254,7 +262,7 @@ static TypeName *TableFuncTypeName(List *columns);
%type <str> iso_level opt_encoding
%type <node> grantee
%type <list> grantee_list
-%type <str> privilege
+%type <accesspriv> privilege
%type <list> privileges privilege_list
%type <privtarget> privilege_target
%type <funwithargs> function_with_argtypes
@@ -4210,12 +4218,11 @@ RevokeStmt:
/*
- * A privilege list is represented as a list of strings; the validity of
- * the privilege names gets checked at execution. This is a bit annoying
- * but we have little choice because of the syntactic conflict with lists
- * of role names in GRANT/REVOKE. What's more, we have to call out in
- * the "privilege" production any reserved keywords that need to be usable
- * as privilege names.
+ * Privilege names are represented as strings; the validity of the privilege
+ * names gets checked at execution. This is a bit annoying but we have little
+ * choice because of the syntactic conflict with lists of role names in
+ * GRANT/REVOKE. What's more, we have to call out in the "privilege"
+ * production any reserved keywords that need to be usable as privilege names.
*/
/* either ALL [PRIVILEGES] or a list of individual privileges */
@@ -4225,18 +4232,54 @@ privileges: privilege_list
{ $$ = NIL; }
| ALL PRIVILEGES
{ $$ = NIL; }
+ | ALL '(' columnList ')'
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = NULL;
+ n->cols = $3;
+ $$ = list_make1(n);
+ }
+ | ALL PRIVILEGES '(' columnList ')'
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = NULL;
+ n->cols = $4;
+ $$ = list_make1(n);
+ }
;
-privilege_list: privilege
- { $$ = list_make1(makeString($1)); }
- | privilege_list ',' privilege
- { $$ = lappend($1, makeString($3)); }
+privilege_list: privilege { $$ = list_make1($1); }
+ | privilege_list ',' privilege { $$ = lappend($1, $3); }
;
-privilege: SELECT { $$ = pstrdup($1); }
- | REFERENCES { $$ = pstrdup($1); }
- | CREATE { $$ = pstrdup($1); }
- | ColId { $$ = $1; }
+privilege: SELECT opt_column_list
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup($1);
+ n->cols = $2;
+ $$ = n;
+ }
+ | REFERENCES opt_column_list
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup($1);
+ n->cols = $2;
+ $$ = n;
+ }
+ | CREATE opt_column_list
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = pstrdup($1);
+ n->cols = $2;
+ $$ = n;
+ }
+ | ColId opt_column_list
+ {
+ AccessPriv *n = makeNode(AccessPriv);
+ n->priv_name = $1;
+ n->cols = $2;
+ $$ = n;
+ }
;
@@ -4246,70 +4289,70 @@ privilege: SELECT { $$ = pstrdup($1); }
privilege_target:
qualified_name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_RELATION;
n->objs = $1;
$$ = n;
}
| TABLE qualified_name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_RELATION;
n->objs = $2;
$$ = n;
}
| SEQUENCE qualified_name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_SEQUENCE;
n->objs = $2;
$$ = n;
}
| FOREIGN DATA_P WRAPPER name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_FDW;
n->objs = $4;
$$ = n;
}
| FOREIGN SERVER name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_FOREIGN_SERVER;
n->objs = $3;
$$ = n;
}
| FUNCTION function_with_argtypes_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_FUNCTION;
n->objs = $2;
$$ = n;
}
| DATABASE name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_DATABASE;
n->objs = $2;
$$ = n;
}
| LANGUAGE name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_LANGUAGE;
n->objs = $2;
$$ = n;
}
| SCHEMA name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_NAMESPACE;
n->objs = $2;
$$ = n;
}
| TABLESPACE name_list
{
- PrivTarget *n = makeNode(PrivTarget);
+ PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
n->objtype = ACL_OBJECT_TABLESPACE;
n->objs = $2;
$$ = n;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 93e393f837..7e9fb9c071 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.185 2009/01/01 17:23:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_clause.c,v 1.186 2009/01/22 20:16:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -53,6 +53,7 @@ static void extractRemainingColumns(List *common_colnames,
List *src_colnames, List *src_colvars,
List **res_colnames, List **res_colvars);
static Node *transformJoinUsingClause(ParseState *pstate,
+ RangeTblEntry *leftRTE, RangeTblEntry *rightRTE,
List *leftVars, List *rightVars);
static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j,
RangeTblEntry *l_rte,
@@ -194,8 +195,7 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
*
* If we find an explicit reference to the rel later during parse
* analysis, we will add the ACL_SELECT bit back again; see
- * scanRTEForColumn (for simple field references), ExpandColumnRefStar
- * (for foo.*) and ExpandAllTables (for *).
+ * markVarForSelectPriv and its callers.
*/
rte->requiredPerms = requiredPerms;
@@ -305,7 +305,9 @@ extractRemainingColumns(List *common_colnames,
* Result is a transformed qualification expression.
*/
static Node *
-transformJoinUsingClause(ParseState *pstate, List *leftVars, List *rightVars)
+transformJoinUsingClause(ParseState *pstate,
+ RangeTblEntry *leftRTE, RangeTblEntry *rightRTE,
+ List *leftVars, List *rightVars)
{
Node *result = NULL;
ListCell *lvars,
@@ -315,17 +317,25 @@ transformJoinUsingClause(ParseState *pstate, List *leftVars, List *rightVars)
* We cheat a little bit here by building an untransformed operator tree
* whose leaves are the already-transformed Vars. This is OK because
* transformExpr() won't complain about already-transformed subnodes.
+ * However, this does mean that we have to mark the columns as requiring
+ * SELECT privilege for ourselves; transformExpr() won't do it.
*/
forboth(lvars, leftVars, rvars, rightVars)
{
- Node *lvar = (Node *) lfirst(lvars);
- Node *rvar = (Node *) lfirst(rvars);
+ Var *lvar = (Var *) lfirst(lvars);
+ Var *rvar = (Var *) lfirst(rvars);
A_Expr *e;
+ /* Require read access to the join variables */
+ markVarForSelectPriv(pstate, lvar, leftRTE);
+ markVarForSelectPriv(pstate, rvar, rightRTE);
+
+ /* Now create the lvar = rvar join condition */
e = makeSimpleA_Expr(AEXPR_OP, "=",
copyObject(lvar), copyObject(rvar),
-1);
+ /* And combine into an AND clause, if multiple join columns */
if (result == NULL)
result = (Node *) e;
else
@@ -728,6 +738,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
*r_colvars,
*res_colvars;
RangeTblEntry *rte;
+ int k;
/*
* Recursively process the left and right subtrees
@@ -912,6 +923,8 @@ transformFromClauseItem(ParseState *pstate, Node *n,
}
j->quals = transformJoinUsingClause(pstate,
+ l_rte,
+ r_rte,
l_usingvars,
r_usingvars);
}
@@ -972,6 +985,12 @@ transformFromClauseItem(ParseState *pstate, Node *n,
*top_rte = rte;
*top_rti = j->rtindex;
+ /* make a matching link to the JoinExpr for later use */
+ for (k = list_length(pstate->p_joinexprs) + 1; k < j->rtindex; k++)
+ pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL);
+ pstate->p_joinexprs = lappend(pstate->p_joinexprs, j);
+ Assert(list_length(pstate->p_joinexprs) == j->rtindex);
+
/*
* Prepare returned namespace list. If the JOIN has an alias then it
* hides the contained RTEs as far as the relnamespace goes;
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 1d0f77a5b3..2bf6174866 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.239 2009/01/01 17:23:45 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.240 2009/01/22 20:16:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1977,6 +1977,9 @@ transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname,
/* location is not filled in by makeVar */
result->location = location;
+ /* mark relation as requiring whole-row SELECT access */
+ markVarForSelectPriv(pstate, result, rte);
+
return (Node *) result;
}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 49ad024039..eb98f470ee 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.140 2009/01/01 17:23:46 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.141 2009/01/22 20:16:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -39,6 +39,8 @@ static RangeTblEntry *scanNameSpaceForRefname(ParseState *pstate,
const char *refname, int location);
static RangeTblEntry *scanNameSpaceForRelid(ParseState *pstate, Oid relid,
int location);
+static void markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
+ int rtindex, AttrNumber col);
static bool isLockedRel(ParseState *pstate, char *refname);
static void expandRelation(Oid relid, Alias *eref,
int rtindex, int sublevels_up,
@@ -435,14 +437,8 @@ GetCTEForRTE(ParseState *pstate, RangeTblEntry *rte, int rtelevelsup)
* If found, return an appropriate Var node, else return NULL.
* If the name proves ambiguous within this RTE, raise error.
*
- * Side effect: if we find a match, mark the RTE as requiring read access.
- * See comments in setTargetTable().
- *
- * NOTE: if the RTE is for a join, marking it as requiring read access does
- * nothing. It might seem that we need to propagate the mark to all the
- * contained RTEs, but that is not necessary. This is so because a join
- * expression can only appear in a FROM clause, and any table named in
- * FROM will be marked as requiring read access from the beginning.
+ * Side effect: if we find a match, mark the RTE as requiring read access
+ * for the column.
*/
Node *
scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
@@ -450,6 +446,7 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
{
Node *result = NULL;
int attnum = 0;
+ Var *var;
ListCell *c;
/*
@@ -476,9 +473,10 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
errmsg("column reference \"%s\" is ambiguous",
colname),
parser_errposition(pstate, location)));
- result = (Node *) make_var(pstate, rte, attnum, location);
- /* Require read access */
- rte->requiredPerms |= ACL_SELECT;
+ var = make_var(pstate, rte, attnum, location);
+ /* Require read access to the column */
+ markVarForSelectPriv(pstate, var, rte);
+ result = (Node *) var;
}
}
@@ -504,9 +502,10 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
Int16GetDatum(attnum),
0, 0))
{
- result = (Node *) make_var(pstate, rte, attnum, location);
- /* Require read access */
- rte->requiredPerms |= ACL_SELECT;
+ var = make_var(pstate, rte, attnum, location);
+ /* Require read access to the column */
+ markVarForSelectPriv(pstate, var, rte);
+ result = (Node *) var;
}
}
}
@@ -595,6 +594,122 @@ qualifiedNameToVar(ParseState *pstate,
}
/*
+ * markRTEForSelectPriv
+ * Mark the specified column of an RTE as requiring SELECT privilege
+ *
+ * col == InvalidAttrNumber means a "whole row" reference
+ *
+ * The caller should pass the actual RTE if it has it handy; otherwise pass
+ * NULL, and we'll look it up here. (This uglification of the API is
+ * worthwhile because nearly all external callers have the RTE at hand.)
+ */
+static void
+markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
+ int rtindex, AttrNumber col)
+{
+ if (rte == NULL)
+ rte = rt_fetch(rtindex, pstate->p_rtable);
+
+ if (rte->rtekind == RTE_RELATION)
+ {
+ /* Make sure the rel as a whole is marked for SELECT access */
+ rte->requiredPerms |= ACL_SELECT;
+ /* Must offset the attnum to fit in a bitmapset */
+ rte->selectedCols = bms_add_member(rte->selectedCols,
+ col - FirstLowInvalidHeapAttributeNumber);
+ }
+ else if (rte->rtekind == RTE_JOIN)
+ {
+ if (col == InvalidAttrNumber)
+ {
+ /*
+ * A whole-row reference to a join has to be treated as
+ * whole-row references to the two inputs.
+ */
+ JoinExpr *j;
+
+ if (rtindex > 0 && rtindex <= list_length(pstate->p_joinexprs))
+ j = (JoinExpr *) list_nth(pstate->p_joinexprs, rtindex - 1);
+ else
+ j = NULL;
+ if (j == NULL)
+ elog(ERROR, "could not find JoinExpr for whole-row reference");
+ Assert(IsA(j, JoinExpr));
+
+ /* Note: we can't see FromExpr here */
+ if (IsA(j->larg, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) j->larg)->rtindex;
+
+ markRTEForSelectPriv(pstate, NULL, varno, InvalidAttrNumber);
+ }
+ else if (IsA(j->larg, JoinExpr))
+ {
+ int varno = ((JoinExpr *) j->larg)->rtindex;
+
+ markRTEForSelectPriv(pstate, NULL, varno, InvalidAttrNumber);
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(j->larg));
+ if (IsA(j->rarg, RangeTblRef))
+ {
+ int varno = ((RangeTblRef *) j->rarg)->rtindex;
+
+ markRTEForSelectPriv(pstate, NULL, varno, InvalidAttrNumber);
+ }
+ else if (IsA(j->rarg, JoinExpr))
+ {
+ int varno = ((JoinExpr *) j->rarg)->rtindex;
+
+ markRTEForSelectPriv(pstate, NULL, varno, InvalidAttrNumber);
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(j->rarg));
+ }
+ else
+ {
+ /*
+ * Regular join attribute, look at the alias-variable list.
+ *
+ * The aliasvar could be either a Var or a COALESCE expression,
+ * but in the latter case we should already have marked the two
+ * referent variables as being selected, due to their use in the
+ * JOIN clause. So we need only be concerned with the simple
+ * Var case.
+ */
+ Var *aliasvar;
+
+ Assert(col > 0 && col <= list_length(rte->joinaliasvars));
+ aliasvar = (Var *) list_nth(rte->joinaliasvars, col - 1);
+ if (IsA(aliasvar, Var))
+ markVarForSelectPriv(pstate, aliasvar, NULL);
+ }
+ }
+ /* other RTE types don't require privilege marking */
+}
+
+/*
+ * markVarForSelectPriv
+ * Mark the RTE referenced by a Var as requiring SELECT privilege
+ *
+ * The caller should pass the Var's referenced RTE if it has it handy
+ * (nearly all do); otherwise pass NULL.
+ */
+void
+markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
+{
+ Index lv;
+
+ Assert(IsA(var, Var));
+ /* Find the appropriate pstate if it's an uplevel Var */
+ for (lv = 0; lv < var->varlevelsup; lv++)
+ pstate = pstate->parentParseState;
+ markRTEForSelectPriv(pstate, rte, var->varno, var->varattno);
+}
+
+/*
* buildRelationAliases
* Construct the eref column name list for a relation RTE.
* This code is also used for the case of a function RTE returning
@@ -838,6 +953,8 @@ addRangeTableEntry(ParseState *pstate,
rte->requiredPerms = ACL_SELECT;
rte->checkAsUser = InvalidOid; /* not set-uid by default, either */
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
* Add completed RTE to pstate's range table list, but not to join list
@@ -891,6 +1008,8 @@ addRangeTableEntryForRelation(ParseState *pstate,
rte->requiredPerms = ACL_SELECT;
rte->checkAsUser = InvalidOid; /* not set-uid by default, either */
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
* Add completed RTE to pstate's range table list, but not to join list
@@ -969,6 +1088,8 @@ addRangeTableEntryForSubquery(ParseState *pstate,
rte->requiredPerms = 0;
rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
* Add completed RTE to pstate's range table list, but not to join list
@@ -1101,6 +1222,8 @@ addRangeTableEntryForFunction(ParseState *pstate,
rte->requiredPerms = 0;
rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
* Add completed RTE to pstate's range table list, but not to join list
@@ -1168,8 +1291,11 @@ addRangeTableEntryForValues(ParseState *pstate,
*/
rte->inh = false; /* never true for values RTEs */
rte->inFromCl = inFromCl;
+
rte->requiredPerms = 0;
rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
* Add completed RTE to pstate's range table list, but not to join list
@@ -1239,6 +1365,8 @@ addRangeTableEntryForJoin(ParseState *pstate,
rte->requiredPerms = 0;
rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
* Add completed RTE to pstate's range table list, but not to join list
@@ -1320,6 +1448,8 @@ addRangeTableEntryForCTE(ParseState *pstate,
rte->requiredPerms = 0;
rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
* Add completed RTE to pstate's range table list, but not to join list
@@ -1803,6 +1933,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref,
* As with expandRTE, rtindex/sublevels_up determine the varno/varlevelsup
* fields of the Vars produced, and location sets their location.
* pstate->p_next_resno determines the resnos assigned to the TLEs.
+ * The referenced columns are marked as requiring SELECT access.
*/
List *
expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
@@ -1817,10 +1948,17 @@ expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
expandRTE(rte, rtindex, sublevels_up, location, false,
&names, &vars);
+ /*
+ * Require read access to the table. This is normally redundant with the
+ * markVarForSelectPriv calls below, but not if the table has zero
+ * columns.
+ */
+ rte->requiredPerms |= ACL_SELECT;
+
forboth(name, names, var, vars)
{
char *label = strVal(lfirst(name));
- Node *varnode = (Node *) lfirst(var);
+ Var *varnode = (Var *) lfirst(var);
TargetEntry *te;
te = makeTargetEntry((Expr *) varnode,
@@ -1828,6 +1966,9 @@ expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
label,
false);
te_list = lappend(te_list, te);
+
+ /* Require read access to each column */
+ markVarForSelectPriv(pstate, varnode, rte);
}
Assert(name == NULL && var == NULL); /* lists not the same length? */
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2765751edb..3f804472c7 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.169 2009/01/01 17:23:46 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.170 2009/01/22 20:16:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -850,6 +850,8 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
* in a SELECT target list (where we want TargetEntry nodes in the result)
* and foo.* in a ROW() or VALUES() construct (where we want just bare
* expressions).
+ *
+ * The referenced columns are marked as requiring SELECT access.
*/
static List *
ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
@@ -929,20 +931,37 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
makeRangeVar(schemaname, relname,
cref->location));
- /* Require read access --- see comments in setTargetTable() */
- rte->requiredPerms |= ACL_SELECT;
-
rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up);
if (targetlist)
+ {
+ /* expandRelAttrs handles permissions marking */
return expandRelAttrs(pstate, rte, rtindex, sublevels_up,
cref->location);
+ }
else
{
List *vars;
+ ListCell *l;
expandRTE(rte, rtindex, sublevels_up, cref->location, false,
NULL, &vars);
+
+ /*
+ * Require read access to the table. This is normally redundant
+ * with the markVarForSelectPriv calls below, but not if the table
+ * has zero columns.
+ */
+ rte->requiredPerms |= ACL_SELECT;
+
+ /* Require read access to each column */
+ foreach(l, vars)
+ {
+ Var *var = (Var *) lfirst(l);
+
+ markVarForSelectPriv(pstate, var, rte);
+ }
+
return vars;
}
}
@@ -956,6 +975,8 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref,
* varnamespace. We do not consider relnamespace because that would include
* input tables of aliasless JOINs, NEW/OLD pseudo-entries, implicit RTEs,
* etc.
+ *
+ * The referenced relations/columns are marked as requiring SELECT access.
*/
static List *
ExpandAllTables(ParseState *pstate, int location)
@@ -975,9 +996,6 @@ ExpandAllTables(ParseState *pstate, int location)
RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
int rtindex = RTERangeTablePosn(pstate, rte, NULL);
- /* Require read access --- see comments in setTargetTable() */
- rte->requiredPerms |= ACL_SELECT;
-
target = list_concat(target,
expandRelAttrs(pstate, rte, rtindex, 0,
location));
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 56549a5509..7cfeacb730 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.183 2009/01/22 17:27:54 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.184 2009/01/22 20:16:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1178,9 +1178,13 @@ ApplyRetrieveRule(Query *parsetree,
Assert(subrte->relid == relation->rd_id);
subrte->requiredPerms = rte->requiredPerms;
subrte->checkAsUser = rte->checkAsUser;
+ subrte->selectedCols = rte->selectedCols;
+ subrte->modifiedCols = rte->modifiedCols;
rte->requiredPerms = 0; /* no permission check on subquery itself */
rte->checkAsUser = InvalidOid;
+ rte->selectedCols = NULL;
+ rte->modifiedCols = NULL;
/*
* FOR UPDATE/SHARE of view?
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 61f23213c4..7e7d07e4f0 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.304 2009/01/01 17:23:48 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.305 2009/01/22 20:16:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -910,7 +910,7 @@ ProcessUtility(Node *parsetree,
break;
case T_CreateTrigStmt:
- CreateTrigger((CreateTrigStmt *) parsetree, InvalidOid);
+ CreateTrigger((CreateTrigStmt *) parsetree, InvalidOid, true);
break;
case T_DropPropertyStmt:
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index b270077e8d..cb0ebf4694 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.145 2009/01/01 17:23:48 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.146 2009/01/22 20:16:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -367,6 +367,47 @@ allocacl(int n)
}
/*
+ * Copy an ACL
+ */
+Acl *
+aclcopy(const Acl *orig_acl)
+{
+ Acl *result_acl;
+
+ result_acl = allocacl(ACL_NUM(orig_acl));
+
+ memcpy(ACL_DAT(result_acl),
+ ACL_DAT(orig_acl),
+ ACL_NUM(orig_acl) * sizeof(AclItem));
+
+ return result_acl;
+}
+
+/*
+ * Concatenate two ACLs
+ *
+ * This is a bit cheesy, since we may produce an ACL with redundant entries.
+ * Be careful what the result is used for!
+ */
+Acl *
+aclconcat(const Acl *left_acl, const Acl *right_acl)
+{
+ Acl *result_acl;
+
+ result_acl = allocacl(ACL_NUM(left_acl) + ACL_NUM(right_acl));
+
+ memcpy(ACL_DAT(result_acl),
+ ACL_DAT(left_acl),
+ ACL_NUM(left_acl) * sizeof(AclItem));
+
+ memcpy(ACL_DAT(result_acl) + ACL_NUM(left_acl),
+ ACL_DAT(right_acl),
+ ACL_NUM(right_acl) * sizeof(AclItem));
+
+ return result_acl;
+}
+
+/*
* Verify that an ACL array is acceptable (one-dimensional and has no nulls)
*/
static void
@@ -542,11 +583,17 @@ acldefault(GrantObjectType objtype, Oid ownerId)
{
AclMode world_default;
AclMode owner_default;
+ int nacl;
Acl *acl;
AclItem *aip;
switch (objtype)
{
+ case ACL_OBJECT_COLUMN:
+ /* by default, columns have no extra privileges */
+ world_default = ACL_NO_RIGHTS;
+ owner_default = ACL_NO_RIGHTS;
+ break;
case ACL_OBJECT_RELATION:
world_default = ACL_NO_RIGHTS;
owner_default = ACL_ALL_RIGHTS_RELATION;
@@ -593,7 +640,13 @@ acldefault(GrantObjectType objtype, Oid ownerId)
break;
}
- acl = allocacl((world_default != ACL_NO_RIGHTS) ? 2 : 1);
+ nacl = 0;
+ if (world_default != ACL_NO_RIGHTS)
+ nacl++;
+ if (owner_default != ACL_NO_RIGHTS)
+ nacl++;
+
+ acl = allocacl(nacl);
aip = ACL_DAT(acl);
if (world_default != ACL_NO_RIGHTS)
@@ -614,9 +667,12 @@ acldefault(GrantObjectType objtype, Oid ownerId)
* "_SYSTEM"-like ACL entry, by internally special-casing the owner
* whereever we are testing grant options.
*/
- aip->ai_grantee = ownerId;
- aip->ai_grantor = ownerId;
- ACLITEM_SET_PRIVS_GOPTIONS(*aip, owner_default, ACL_NO_RIGHTS);
+ if (owner_default != ACL_NO_RIGHTS)
+ {
+ aip->ai_grantee = ownerId;
+ aip->ai_grantor = ownerId;
+ ACLITEM_SET_PRIVS_GOPTIONS(*aip, owner_default, ACL_NO_RIGHTS);
+ }
return acl;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 4be6e08606..ecfe59e3a8 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.281 2009/01/22 17:27:54 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.282 2009/01/22 20:16:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -480,7 +480,7 @@ RelationBuildTupleDesc(Relation relation)
memcpy(relation->rd_att->attrs[attp->attnum - 1],
attp,
- ATTRIBUTE_TUPLE_SIZE);
+ ATTRIBUTE_FIXED_PART_SIZE);
/* Update constraint/default info */
if (attp->attnotnull)
@@ -1449,7 +1449,7 @@ formrdesc(const char *relationName, Oid relationReltype,
{
memcpy(relation->rd_att->attrs[i],
&att[i],
- ATTRIBUTE_TUPLE_SIZE);
+ ATTRIBUTE_FIXED_PART_SIZE);
has_not_null |= att[i].attnotnull;
/* make sure attcacheoff is valid */
relation->rd_att->attrs[i]->attcacheoff = -1;
@@ -2714,7 +2714,7 @@ BuildHardcodedDescriptor(int natts, Form_pg_attribute attrs, bool hasoids)
for (i = 0; i < natts; i++)
{
- memcpy(result->attrs[i], &attrs[i], ATTRIBUTE_TUPLE_SIZE);
+ memcpy(result->attrs[i], &attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
/* make sure attcacheoff is valid */
result->attrs[i]->attcacheoff = -1;
}
@@ -3441,7 +3441,7 @@ load_relcache_init_file(void)
{
if ((nread = fread(&len, 1, sizeof(len), fp)) != sizeof(len))
goto read_failed;
- if (len != ATTRIBUTE_TUPLE_SIZE)
+ if (len != ATTRIBUTE_FIXED_PART_SIZE)
goto read_failed;
if ((nread = fread(rel->rd_att->attrs[i], 1, len, fp)) != len)
goto read_failed;
@@ -3751,7 +3751,7 @@ write_relcache_init_file(void)
/* next, do all the attribute tuple form data entries */
for (i = 0; i < relform->relnatts; i++)
{
- write_item(rel->rd_att->attrs[i], ATTRIBUTE_TUPLE_SIZE, fp);
+ write_item(rel->rd_att->attrs[i], ATTRIBUTE_FIXED_PART_SIZE, fp);
}
/* next, do the access method specific field */