diff options
Diffstat (limited to 'src/backend')
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 */ |
