*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 21,26 ****
--- 21,27 ----
  #include <arpa/inet.h>
  
  #include "access/heapam.h"
+ #include "access/sysattr.h"
  #include "access/xact.h"
  #include "catalog/namespace.h"
  #include "catalog/pg_type.h"
***************
*** 41,46 ****
--- 42,48 ----
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
+ #include "utils/security.h"
  #include "utils/snapmgr.h"
  
  
***************
*** 725,733 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  	List	   *force_notnull = NIL;
  	bool		force_quote_all = false;
  	bool		format_specified = false;
- 	AclMode		required_access = (is_from ? ACL_INSERT : ACL_SELECT);
- 	AclMode		relPerms;
- 	AclMode		remainingPerms;
  	ListCell   *option;
  	TupleDesc	tupDesc;
  	int			num_phys_attrs;
--- 727,732 ----
***************
*** 988,993 **** DoCopy(const CopyStmt *stmt, const char *queryString)
--- 987,996 ----
  
  	if (stmt->relation)
  	{
+ 		Bitmapset  *columnsSet = NULL;
+ 		List	   *attnums;
+ 		ListCell   *cur;
+ 
  		Assert(!stmt->query);
  		cstate->queryDesc = NULL;
  
***************
*** 998,1026 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  		tupDesc = RelationGetDescr(cstate->rel);
  
  		/* Check relation permissions. */
! 		relPerms = pg_class_aclmask(RelationGetRelid(cstate->rel), GetUserId(),
! 									required_access, ACLMASK_ALL);
! 		remainingPerms = required_access & ~relPerms;
! 		if (remainingPerms != 0)
  		{
! 			/* We don't have table permissions, check per-column permissions */
! 			List	   *attnums;
! 			ListCell   *cur;
! 
! 			attnums = CopyGetAttnums(tupDesc, cstate->rel, attnamelist);
! 			foreach(cur, attnums)
! 			{
! 				int			attnum = lfirst_int(cur);
  
! 				if (pg_attribute_aclcheck(RelationGetRelid(cstate->rel),
! 										  attnum,
! 										  GetUserId(),
! 										  remainingPerms) != ACLCHECK_OK)
! 					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
! 								   RelationGetRelationName(cstate->rel));
! 			}
  		}
  
  		/* check read-only transaction */
  		if (XactReadOnly && is_from && !cstate->rel->rd_islocaltemp)
  			PreventCommandIfReadOnly("COPY FROM");
--- 1001,1021 ----
  		tupDesc = RelationGetDescr(cstate->rel);
  
  		/* Check relation permissions. */
! 		attnums = CopyGetAttnums(tupDesc, cstate->rel, attnamelist);
! 		foreach (cur, attnums)
  		{
! 			int	attnum = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;
  
! 			columnsSet = bms_add_member(columnsSet, attnum);
  		}
  
+ 		if (is_from)
+ 			check_dml_permissions(RelationGetRelid(cstate->rel), GetUserId(),
+ 								  ACL_INSERT, NULL, columnsSet, true);
+ 		else
+ 			check_dml_permissions(RelationGetRelid(cstate->rel), GetUserId(),
+ 								  ACL_SELECT, columnsSet, NULL, true);
+ 
  		/* check read-only transaction */
  		if (XactReadOnly && is_from && !cstate->rel->rd_islocaltemp)
  			PreventCommandIfReadOnly("COPY FROM");
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 54,59 ****
--- 54,60 ----
  #include "utils/acl.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
+ #include "utils/security.h"
  #include "utils/snapmgr.h"
  #include "utils/tqual.h"
  
***************
*** 73,79 **** static void ExecutePlan(EState *estate, PlanState *planstate,
  			ScanDirection direction,
  			DestReceiver *dest);
  static void ExecCheckRTPerms(List *rangeTable);
- static void ExecCheckRTEPerms(RangeTblEntry *rte);
  static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
  static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
  				  Plan *planTree);
--- 74,79 ----
***************
*** 414,569 **** ExecCheckRTPerms(List *rangeTable)
  
  	foreach(l, rangeTable)
  	{
! 		ExecCheckRTEPerms((RangeTblEntry *) lfirst(l));
! 	}
! }
! 
! /*
!  * ExecCheckRTEPerms
!  *		Check access permissions for a single RTE.
!  */
! 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
! 	 * checked by init_fcache when the function is prepared for execution.
! 	 * Join, subquery, and special RTEs need no checks.
! 	 */
! 	if (rte->rtekind != RTE_RELATION)
! 		return;
! 
! 	/*
! 	 * No work if requiredPerms is empty.
! 	 */
! 	requiredPerms = rte->requiredPerms;
! 	if (requiredPerms == 0)
! 		return;
  
! 	relOid = rte->relid;
! 
! 	/*
! 	 * userid to check as: current user unless we have a setuid indication.
! 	 *
! 	 * Note: GetUserId() is presently fast enough that there's no harm in
! 	 * calling it separately for each RTE.	If that stops being true, we could
! 	 * call it once in ExecCheckRTPerms and pass the userid down from there.
! 	 * But for now, no need for the extra clutter.
! 	 */
! 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
  
- 	/*
- 	 * 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.
- 	 */
- 	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);
! 		}
  	}
  }
  
--- 414,452 ----
  
  	foreach(l, rangeTable)
  	{
! 		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
! 		Oid				userId;
  
! 		/*
! 		 * Only plain-relation RTEs need to be checked here.  Function RTEs are
! 		 * checked by init_fcache when the function is prepared for execution.
! 		 * Join, subquery, and special RTEs need no checks.
! 		 */
! 		if (rte->rtekind != RTE_RELATION)
! 			continue;
  
  		/*
! 		 * No work if requiredPerms is empty.
  		 */
! 		if (rte->requiredPerms == 0)
! 			continue;
  
  		/*
! 		 * userid to check as: current user unless we have a setuid indication.
  		 *
! 		 * Note: GetUserId() is presently fast enough that there's no harm in
! 		 * calling it separately for each RTE.	If that stops being true, we
! 		 * could call it once in ExecCheckRTPerms and pass the userid down
! 		 * from there.
! 		 * But for now, no need for the extra clutter.
  		 */
! 		userId = rte->checkAsUser ? rte->checkAsUser : GetUserId();
  
  		/*
! 		 * Priv checks to execute the given DML statement
  		 */
! 		check_dml_permissions(rte->relid, userId, rte->requiredPerms,
! 							  rte->selectedCols, rte->modifiedCols, true);
  	}
  }
  
*** a/src/backend/utils/Makefile
--- b/src/backend/utils/Makefile
***************
*** 9,15 **** top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global
  
  OBJS        = fmgrtab.o
! SUBDIRS     = adt cache error fmgr hash init mb misc mmgr resowner sort time
  
  # location of Catalog.pm
  catalogdir  = $(top_srcdir)/src/backend/catalog
--- 9,15 ----
  include $(top_builddir)/src/Makefile.global
  
  OBJS        = fmgrtab.o
! SUBDIRS     = adt cache error fmgr hash init mb misc mmgr resowner security sort time
  
  # location of Catalog.pm
  catalogdir  = $(top_srcdir)/src/backend/catalog
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 30,35 ****
--- 30,36 ----
  
  #include "postgres.h"
  
+ #include "access/sysattr.h"
  #include "access/xact.h"
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_operator.h"
***************
*** 39,50 ****
  #include "parser/parse_coerce.h"
  #include "parser/parse_relation.h"
  #include "miscadmin.h"
- #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/snapmgr.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
--- 40,51 ----
  #include "parser/parse_coerce.h"
  #include "parser/parse_relation.h"
  #include "miscadmin.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
+ #include "utils/security.h"
  #include "utils/snapmgr.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
***************
*** 2624,2629 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
--- 2625,2632 ----
  	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
  	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
  	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+ 	Bitmapset  *pkColumns = NULL;
+ 	Bitmapset  *fkColumns = NULL;
  	const char *sep;
  	int			i;
  	int			old_work_mem;
***************
*** 2638,2650 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
  	 *
  	 * XXX are there any other show-stopper conditions to check?
  	 */
! 	if (pg_class_aclcheck(RelationGetRelid(fk_rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
  		return false;
! 	if (pg_class_aclcheck(RelationGetRelid(pk_rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
  		return false;
  
- 	ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
- 
  	/*----------
  	 * The query string built is:
  	 *	SELECT fk.keycols FROM ONLY relname fk
--- 2641,2663 ----
  	 *
  	 * XXX are there any other show-stopper conditions to check?
  	 */
! 	ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
! 
! 	for (i = 0; i < riinfo.nkeys; i++)
! 	{
! 		fkColumns = bms_add_member(fkColumns, riinfo.fk_attnums[i]
! 								   - FirstLowInvalidHeapAttributeNumber);
! 		pkColumns = bms_add_member(pkColumns, riinfo.pk_attnums[i]
! 								   - FirstLowInvalidHeapAttributeNumber);
! 	}
! 
! 	if (!check_dml_permissions(RelationGetRelid(fk_rel), GetUserId(),
! 							   ACL_SELECT, fkColumns, NULL, false))
  		return false;
! 	if (!check_dml_permissions(RelationGetRelid(pk_rel), GetUserId(),
! 							   ACL_SELECT, pkColumns, NULL, false))
  		return false;
  
  	/*----------
  	 * The query string built is:
  	 *	SELECT fk.keycols FROM ONLY relname fk
*** /dev/null
--- b/src/backend/utils/security/Makefile
***************
*** 0 ****
--- 1,17 ----
+ #-------------------------------------------------------------------------
+ #
+ # Makefile--
+ #    Makefile for backend/utils/security
+ #
+ # IDENTIFICATION
+ #    $PostgreSQL$
+ #
+ #-------------------------------------------------------------------------
+ 
+ subdir = src/backend/utils/security
+ top_builddir = ../../../..
+ include $(top_builddir)/src/Makefile.global
+ 
+ OBJS = dml.o
+ 
+ include $(top_srcdir)/src/backend/common.mk
*** /dev/null
--- b/src/backend/utils/security/dml.c
***************
*** 0 ****
--- 1,179 ----
+ /*
+  * dml.c
+  *
+  * privileges checker functions corresponding to DML statements
+  *
+  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  */
+ #include "postgres.h"
+ 
+ #include "access/sysattr.h"
+ #include "nodes/bitmapset.h"
+ #include "utils/lsyscache.h"
+ #include "utils/security.h"
+ 
+ /* Hooks for plugin to get control */
+ check_dml_permissions_hook_type check_dml_permissions_hook = NULL;
+ 
+ /*
+  * check_dml_permissions
+  *
+  * It checks access permissions to execute DML statement on a certain
+  * relation and attributes.
+  * The caller shall correctly provide OID of the relation and Bitmapset of
+  * the attributes to be accessed, OID of the database role, a bitmask of
+  * required permissions and a behavior hint on access violations.
+  */
+ bool
+ check_dml_permissions(Oid relOid, Oid userId, AclMode requiredPerms,
+ 					  Bitmapset *selCols, Bitmapset *modCols, bool abort)
+ {
+ 	AclMode		relPerms;
+ 	AclMode		remainingPerms;
+ 	Bitmapset  *tmpset;
+ 	int			col;
+ 	bool		retval = true;
+ 
+ 	/*
+ 	 * 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.
+ 	 */
+ 	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))
+ 		{
+ 			if (!abort)
+ 				return false;
+ 
+ 			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(selCols))
+ 			{
+ 				if (pg_attribute_aclcheck_all(relOid, userId, ACL_SELECT,
+ 											  ACLMASK_ANY) != ACLCHECK_OK)
+ 				{
+ 					if (!abort)
+ 						return false;
+ 
+ 					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 								   get_rel_name(relOid));
+ 				}
+ 			}
+ 
+ 			tmpset = bms_copy(selCols);
+ 			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)
+ 					{
+ 						if (!abort)
+ 							return false;
+ 
+ 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 									   get_rel_name(relOid));
+ 					}
+ 				}
+ 				else
+ 				{
+ 					if (pg_attribute_aclcheck(relOid, col, userId, ACL_SELECT)
+ 						!= ACLCHECK_OK)
+ 					{
+ 						if (!abort)
+ 							return false;
+ 
+ 						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(modCols))
+ 			{
+ 				if (pg_attribute_aclcheck_all(relOid, userId, remainingPerms,
+ 											  ACLMASK_ANY) != ACLCHECK_OK)
+ 				{
+ 					if (!abort)
+ 						return false;
+ 
+ 					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 								   get_rel_name(relOid));
+ 				}
+ 			}
+ 
+ 			tmpset = bms_copy(modCols);
+ 			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)
+ 					{
+ 						if (!abort)
+ 							return false;
+ 
+ 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 									   get_rel_name(relOid));
+ 					}
+ 				}
+ 			}
+ 			bms_free(tmpset);
+ 		}
+ 	}
+ 
+ 	if (check_dml_permissions_hook)
+ 		retval = (*check_dml_permissions_hook)(relOid, userId, requiredPerms,
+ 											   selCols, modCols, abort);
+ 
+ 	return retval;
+ }
*** /dev/null
--- b/src/include/utils/security.h
***************
*** 0 ****
--- 1,27 ----
+ /*
+  * security.h
+  *	Definition of access control checker functions
+  *
+  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  */
+ #ifndef SECURITY_H
+ #define SECURITY_H
+ 
+ #include "utils/acl.h"
+ 
+ /*
+  * checker functions corresponding to DML statements
+  */
+ extern bool check_dml_permissions(Oid relOid,
+ 								  Oid userId,
+ 								  AclMode requiredPerms,
+ 								  Bitmapset *selCols,
+ 								  Bitmapset *modCols,
+ 								  bool abort);
+ 
+ typedef bool (*check_dml_permissions_hook_type)
+ 				(Oid, Oid, AclMode, Bitmapset *, Bitmapset *, bool);
+ extern PGDLLIMPORT check_dml_permissions_hook_type check_dml_permissions_hook;
+ 
+ #endif
