diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index 6d30b9b05d258825857c2dabb3398992fe739931..014823a3d73c3c5610b4a969ab2a3bd54adbafb9 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -24,9 +24,11 @@ #include "catalog/index.h" #include "miscadmin.h" #include "storage/bufmgr.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" +#include "utils/syscache.h" #include "utils/tqual.h" @@ -152,6 +154,11 @@ IndexScanEnd(IndexScanDesc scan) * form "(key_name, ...)=(key_value, ...)". This is currently used * for building unique-constraint and exclusion-constraint error messages. * + * Note that if the user does not have permissions to view all of the + * columns involved then a NULL is returned. Returning a partial key seems + * unlikely to be useful and we have no way to know which of the columns the + * user provided (unlike in ExecBuildSlotValueDescription). + * * The passed-in values/nulls arrays are the "raw" input to the index AM, * e.g. results of FormIndexDatum --- this is not necessarily what is stored * in the index, but it's what the user perceives to be stored. @@ -161,13 +168,62 @@ BuildIndexValueDescription(Relation indexRelation, Datum *values, bool *isnull) { StringInfoData buf; + Form_pg_index idxrec; + HeapTuple ht_idx; int natts = indexRelation->rd_rel->relnatts; int i; + int keyno; + Oid indexrelid = RelationGetRelid(indexRelation); + Oid indrelid; + AclResult aclresult; + + /* + * Check permissions- if the user does not have access to view all of the + * key columns then return NULL to avoid leaking data. + * + * First we need to check table-level SELECT access and then, if + * there is no access there, check column-level permissions. + */ + + /* + * Fetch the pg_index tuple by the Oid of the index + */ + ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid)); + if (!HeapTupleIsValid(ht_idx)) + elog(ERROR, "cache lookup failed for index %u", indexrelid); + idxrec = (Form_pg_index) GETSTRUCT(ht_idx); + + indrelid = idxrec->indrelid; + Assert(indexrelid == idxrec->indexrelid); + + /* Table-level SELECT is enough, if the user has it */ + aclresult = pg_class_aclcheck(indrelid, GetUserId(), ACL_SELECT); + if (aclresult != ACLCHECK_OK) + { + /* + * No table-level access, so step through the columns in the + * index and make sure the user has SELECT rights on all of them. + */ + for (keyno = 0; keyno < idxrec->indnatts; keyno++) + { + AttrNumber attnum = idxrec->indkey.values[keyno]; + + aclresult = pg_attribute_aclcheck(indrelid, attnum, GetUserId(), + ACL_SELECT); + + if (aclresult != ACLCHECK_OK) + { + /* No access, so clean up and return */ + ReleaseSysCache(ht_idx); + return NULL; + } + } + } + ReleaseSysCache(ht_idx); initStringInfo(&buf); appendStringInfo(&buf, "(%s)=(", - pg_get_indexdef_columns(RelationGetRelid(indexRelation), - true)); + pg_get_indexdef_columns(indexrelid, true)); for (i = 0; i < natts; i++) { diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index ac5f50652b5d3f02902621c643e39994dea3077e..9cb4b4a6dcfaac4232d2af122780ee4899f04cd6 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -384,16 +384,20 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, { Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; + char *key_desc; index_deform_tuple(itup, RelationGetDescr(rel), values, isnull); + + key_desc = BuildIndexValueDescription(rel, values, + isnull); + ereport(ERROR, (errcode(ERRCODE_UNIQUE_VIOLATION), errmsg("duplicate key value violates unique constraint \"%s\"", RelationGetRelationName(rel)), - errdetail("Key %s already exists.", - BuildIndexValueDescription(rel, - values, isnull)))); + key_desc ? errdetail("Key %s already exists.", + key_desc) : 0)); } } else if (all_dead) diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index c245c019262daefaf21475471f2c9f7731e5ad11..adabc929b2ef6fafadb3602cde401408abb3507a 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -151,6 +151,7 @@ typedef struct CopyStateData int *defmap; /* array of default att numbers */ ExprState **defexprs; /* array of default att expressions */ bool volatile_defexprs; /* is any of defexprs volatile? */ + List *range_table; /* * These variables are used to reduce overhead in textual COPY FROM. @@ -747,6 +748,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) bool pipe = (stmt->filename == NULL); Relation rel; uint64 processed; + RangeTblEntry *rte; /* Disallow file COPY except to superusers. */ if (!pipe && !superuser()) @@ -760,7 +762,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString) { TupleDesc tupDesc; AclMode required_access = (is_from ? ACL_INSERT : ACL_SELECT); - RangeTblEntry *rte; List *attnums; ListCell *cur; @@ -807,6 +808,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) cstate = BeginCopyFrom(rel, stmt->filename, stmt->attlist, stmt->options); + cstate->range_table = list_make1(rte); processed = CopyFrom(cstate); /* copy from file to database */ EndCopyFrom(cstate); } @@ -814,6 +816,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString) { cstate = BeginCopyTo(rel, stmt->query, queryString, stmt->filename, stmt->attlist, stmt->options); + cstate->range_table = list_make1(rte); processed = DoCopyTo(cstate); /* copy from database to file */ EndCopyTo(cstate); } @@ -1957,6 +1960,7 @@ CopyFrom(CopyState cstate) estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; + estate->es_range_table = cstate->range_table; /* Set up a tuple slot too */ myslot = ExecInitExtraTupleSlot(estate); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 4239dd33c4a3f0feb4a7d34695e45082294b56f4..e9222edede1964590abae9390fd964a0ea05edea 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -63,6 +63,12 @@ int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN; /* How many levels deep into trigger execution are we? */ static int MyTriggerDepth = 0; +/* + * Note that this macro also exists in executor/execMain.c. There does not + * appear to be any good header to put it into, given the structures that + * it uses, so we let them be duplicated. Be sure to update both if one needs + * to be changed, however. + */ #define GetModifiedColumns(relinfo, estate) \ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index d744a4bfad38c688af159f8dcc41cd8fd42f164a..ea81c361eda67f47add9d19f150fa60b983b28e4 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -79,12 +79,23 @@ static void ExecutePlan(EState *estate, PlanState *planstate, DestReceiver *dest); static bool ExecCheckRTEPerms(RangeTblEntry *rte); static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt); -static char *ExecBuildSlotValueDescription(TupleTableSlot *slot, +static char *ExecBuildSlotValueDescription(Oid reloid, + TupleTableSlot *slot, TupleDesc tupdesc, + Bitmapset *modifiedCols, int maxfieldlen); static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree); +/* + * Note that this macro also exists in commands/trigger.c. There does not + * appear to be any good header to put it into, given the structures that + * it uses, so we let them be duplicated. Be sure to update both if one needs + * to be changed, however. + */ +#define GetModifiedColumns(relinfo, estate) \ + (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols) + /* end of local decls */ @@ -1521,14 +1532,23 @@ ExecConstraints(ResultRelInfo *resultRelInfo, { if (tupdesc->attrs[attrChk - 1]->attnotnull && slot_attisnull(slot, attrChk)) + { + char *val_desc; + Bitmapset *modifiedCols; + + modifiedCols = GetModifiedColumns(resultRelInfo, estate); + val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), + slot, + tupdesc, + modifiedCols, + 64); + ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("null value in column \"%s\" violates not-null constraint", - NameStr(tupdesc->attrs[attrChk - 1]->attname)), - errdetail("Failing row contains %s.", - ExecBuildSlotValueDescription(slot, - tupdesc, - 64)))); + NameStr(tupdesc->attrs[attrChk - 1]->attname)), + val_desc ? errdetail("Failing row contains %s.", val_desc) : 0)); + } } } @@ -1537,14 +1557,22 @@ ExecConstraints(ResultRelInfo *resultRelInfo, const char *failed; if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL) + { + char *val_desc; + Bitmapset *modifiedCols; + + modifiedCols = GetModifiedColumns(resultRelInfo, estate); + val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel), + slot, + tupdesc, + modifiedCols, + 64); ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("new row for relation \"%s\" violates check constraint \"%s\"", RelationGetRelationName(rel), failed), - errdetail("Failing row contains %s.", - ExecBuildSlotValueDescription(slot, - tupdesc, - 64)))); + val_desc ? errdetail("Failing row contains %s.", val_desc) : 0)); + } } } @@ -1560,25 +1588,56 @@ ExecConstraints(ResultRelInfo *resultRelInfo, * dropped columns. We used to use the slot's tuple descriptor to decode the * data, but the slot's descriptor doesn't identify dropped columns, so we * now need to be passed the relation's descriptor. + * + * Note that, like BuildIndexValueDescription, if the user does not have + * permission to view any of the columns involved, a NULL is returned. Unlike + * BuildIndexValueDescription, if the user has access to view a subset of the + * column involved, that subset will be returned with a key identifying which + * columns they are. */ static char * -ExecBuildSlotValueDescription(TupleTableSlot *slot, +ExecBuildSlotValueDescription(Oid reloid, + TupleTableSlot *slot, TupleDesc tupdesc, + Bitmapset *modifiedCols, int maxfieldlen) { StringInfoData buf; + StringInfoData collist; bool write_comma = false; + bool write_comma_collist = false; int i; - - /* Make sure the tuple is fully deconstructed */ - slot_getallattrs(slot); + AclResult aclresult; + bool table_perm = false; + bool any_perm = false; initStringInfo(&buf); appendStringInfoChar(&buf, '('); + /* + * Check if the user has permissions to see the row. Table-level SELECT + * allows access to all columns. If the user does not have table-level + * SELECT then we check each column and include those the user has SELECT + * rights on. Additionally, we always include columns the user provided + * data for. + */ + aclresult = pg_class_aclcheck(reloid, GetUserId(), ACL_SELECT); + if (aclresult != ACLCHECK_OK) + { + /* Set up the buffer for the column list */ + initStringInfo(&collist); + appendStringInfoChar(&collist, '('); + } + else + table_perm = any_perm = true; + + /* Make sure the tuple is fully deconstructed */ + slot_getallattrs(slot); + for (i = 0; i < tupdesc->natts; i++) { + bool column_perm = false; char *val; int vallen; @@ -1586,37 +1645,76 @@ ExecBuildSlotValueDescription(TupleTableSlot *slot, if (tupdesc->attrs[i]->attisdropped) continue; - if (slot->tts_isnull[i]) - val = "null"; - else + if (!table_perm) { - Oid foutoid; - bool typisvarlena; + /* + * No table-level SELECT, so need to make sure they either have + * SELECT rights on the column or that they have provided the + * data for the column. If not, omit this column from the error + * message. + */ + aclresult = pg_attribute_aclcheck(reloid, tupdesc->attrs[i]->attnum, + GetUserId(), ACL_SELECT); + if (bms_is_member(tupdesc->attrs[i]->attnum - FirstLowInvalidHeapAttributeNumber, + modifiedCols) || aclresult == ACLCHECK_OK) + { + column_perm = any_perm = true; - getTypeOutputInfo(tupdesc->attrs[i]->atttypid, - &foutoid, &typisvarlena); - val = OidOutputFunctionCall(foutoid, slot->tts_values[i]); - } + if (write_comma_collist) + appendStringInfoString(&collist, ", "); + else + write_comma_collist = true; - if (write_comma) - appendStringInfoString(&buf, ", "); - else - write_comma = true; + appendStringInfoString(&collist, NameStr(tupdesc->attrs[i]->attname)); + } + } - /* truncate if needed */ - vallen = strlen(val); - if (vallen <= maxfieldlen) - appendStringInfoString(&buf, val); - else + if (table_perm || column_perm) { - vallen = pg_mbcliplen(val, vallen, maxfieldlen); - appendBinaryStringInfo(&buf, val, vallen); - appendStringInfoString(&buf, "..."); + if (slot->tts_isnull[i]) + val = "null"; + else + { + Oid foutoid; + bool typisvarlena; + + getTypeOutputInfo(tupdesc->attrs[i]->atttypid, + &foutoid, &typisvarlena); + val = OidOutputFunctionCall(foutoid, slot->tts_values[i]); + } + + if (write_comma) + appendStringInfoString(&buf, ", "); + else + write_comma = true; + + /* truncate if needed */ + vallen = strlen(val); + if (vallen <= maxfieldlen) + appendStringInfoString(&buf, val); + else + { + vallen = pg_mbcliplen(val, vallen, maxfieldlen); + appendBinaryStringInfo(&buf, val, vallen); + appendStringInfoString(&buf, "..."); + } } } + /* If we end up with zero columns being returned, then return NULL. */ + if (!any_perm) + return NULL; + appendStringInfoChar(&buf, ')'); + if (!table_perm) + { + appendStringInfoString(&collist, ") = "); + appendStringInfoString(&collist, buf.data); + + return collist.data; + } + return buf.data; } diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 029ce10c35ef8930da24e587e3aecf40137b3b96..e4e70c41b630e23c73d6545f42642153a8b39c6b 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -1306,15 +1306,19 @@ retry: (errcode(ERRCODE_EXCLUSION_VIOLATION), errmsg("could not create exclusion constraint \"%s\"", RelationGetRelationName(index)), - errdetail("Key %s conflicts with key %s.", - error_new, error_existing))); + error_new && error_existing ? + errdetail("Key %s conflicts with key %s.", + error_new, error_existing) : + errdetail("Key conflicts exist."))); else ereport(ERROR, (errcode(ERRCODE_EXCLUSION_VIOLATION), errmsg("conflicting key value violates exclusion constraint \"%s\"", RelationGetRelationName(index)), - errdetail("Key %s conflicts with existing key %s.", - error_new, error_existing))); + error_new && error_existing ? + errdetail("Key %s conflicts with existing key %s.", + error_new, error_existing) : + errdetail("Key conflicts with existing key."))); } index_endscan(index_scan); diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index b391888b5eec4499f30c9f3ef950de33faf3c816..977349a94fda8eb11010f671f2946d13fa645a36 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -42,6 +42,7 @@ #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" @@ -3496,6 +3497,9 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname, bool onfk; int idx, key_idx; + Oid rel_oid; + AclResult aclresult; + bool has_perm = true; if (spi_err) ereport(ERROR, @@ -3514,12 +3518,14 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname, if (onfk) { key_idx = RI_KEYPAIR_FK_IDX; + rel_oid = fk_rel->rd_id; if (tupdesc == NULL) tupdesc = fk_rel->rd_att; } else { key_idx = RI_KEYPAIR_PK_IDX; + rel_oid = pk_rel->rd_id; if (tupdesc == NULL) tupdesc = pk_rel->rd_att; } @@ -3539,45 +3545,81 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname, RelationGetRelationName(pk_rel)))); } - /* Get printable versions of the keys involved */ - initStringInfo(&key_names); - initStringInfo(&key_values); - for (idx = 0; idx < qkey->nkeypairs; idx++) + /* + * Check permissions- if the user does not have access to view the data in + * any of the key columns then we don't include the errdetail() below. + * + * Check table-level permissions first and, failing that, column-level + * privileges. + */ + aclresult = pg_class_aclcheck(rel_oid, GetUserId(), ACL_SELECT); + if (aclresult != ACLCHECK_OK) { - int fnum = qkey->keypair[idx][key_idx]; - char *name, - *val; - - name = SPI_fname(tupdesc, fnum); - val = SPI_getvalue(violator, tupdesc, fnum); - if (!val) - val = "null"; + /* Try for column-level permissions */ + for (idx = 0; idx < qkey->nkeypairs; idx++) + { + aclresult = pg_attribute_aclcheck(rel_oid, qkey->keypair[idx][key_idx], + GetUserId(), + ACL_SELECT); + /* No access to the key */ + if (aclresult != ACLCHECK_OK) + { + has_perm = false; + break; + } + } + } - if (idx > 0) + if (has_perm) + { + /* Get printable versions of the keys involved */ + initStringInfo(&key_names); + initStringInfo(&key_values); + for (idx = 0; idx < qkey->nkeypairs; idx++) { - appendStringInfoString(&key_names, ", "); - appendStringInfoString(&key_values, ", "); + int fnum = qkey->keypair[idx][key_idx]; + char *name, + *val; + + name = SPI_fname(tupdesc, fnum); + val = SPI_getvalue(violator, tupdesc, fnum); + if (!val) + val = "null"; + + if (idx > 0) + { + appendStringInfoString(&key_names, ", "); + appendStringInfoString(&key_values, ", "); + } + appendStringInfoString(&key_names, name); + appendStringInfoString(&key_values, val); } - appendStringInfoString(&key_names, name); - appendStringInfoString(&key_values, val); } if (onfk) ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", - RelationGetRelationName(fk_rel), constrname), - errdetail("Key (%s)=(%s) is not present in table \"%s\".", - key_names.data, key_values.data, - RelationGetRelationName(pk_rel)))); + RelationGetRelationName(fk_rel), + constrname), + has_perm ? + errdetail("Key (%s)=(%s) is not present in table \"%s\".", + key_names.data, key_values.data, + RelationGetRelationName(pk_rel)) : + errdetail("Key is not present in table \"%s\".", + RelationGetRelationName(pk_rel)))); else ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"", RelationGetRelationName(pk_rel), - constrname, RelationGetRelationName(fk_rel)), + constrname, + RelationGetRelationName(fk_rel)), + has_perm ? errdetail("Key (%s)=(%s) is still referenced from table \"%s\".", key_names.data, key_values.data, + RelationGetRelationName(fk_rel)) : + errdetail("Key is still referenced from table \"%s\".", RelationGetRelationName(fk_rel)))); } diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index 3d3781b5211fb77f7a1e129d182a0d04c7466a97..8d9f1e79416a1a583d7c421b6747191683c4e4c9 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -3074,6 +3074,7 @@ comparetup_index_btree(const SortTuple *a, const SortTuple *b, { Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; + char *key_desc; /* * Some rather brain-dead implementations of qsort (such as the one in @@ -3084,13 +3085,15 @@ comparetup_index_btree(const SortTuple *a, const SortTuple *b, Assert(tuple1 != tuple2); index_deform_tuple(tuple1, tupDes, values, isnull); + + key_desc = BuildIndexValueDescription(state->indexRel, values, isnull); + ereport(ERROR, (errcode(ERRCODE_UNIQUE_VIOLATION), errmsg("could not create unique index \"%s\"", RelationGetRelationName(state->indexRel)), - errdetail("Key %s is duplicated.", - BuildIndexValueDescription(state->indexRel, - values, isnull)))); + key_desc ? errdetail("Key %s is duplicated.", key_desc) : + errdetail("Duplicate keys exist."))); } /* diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index bc6d7318da8622ceb74b88b10672cfc572caf880..266a905066ab6b56adb517b1fb141d3337e88350 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -362,6 +362,38 @@ SELECT atest6 FROM atest6; -- ok (0 rows) COPY atest6 TO stdout; -- ok +-- check error reporting with column privs +SET SESSION AUTHORIZATION regressuser1; +CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2)); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1" +GRANT SELECT (c1) ON t1 TO regressuser2; +GRANT INSERT (c1, c2, c3) ON t1 TO regressuser2; +GRANT UPDATE (c1, c2, c3) ON t1 TO regressuser2; +-- seed data +INSERT INTO t1 VALUES (1, 1, 1); +INSERT INTO t1 VALUES (1, 2, 1); +INSERT INTO t1 VALUES (2, 1, 2); +INSERT INTO t1 VALUES (2, 2, 2); +INSERT INTO t1 VALUES (3, 1, 3); +SET SESSION AUTHORIZATION regressuser2; +INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown +ERROR: duplicate key value violates unique constraint "t1_pkey" +UPDATE t1 SET c2 = 1; -- fail, but row not shown +ERROR: duplicate key value violates unique constraint "t1_pkey" +INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted +ERROR: null value in column "c1" violates not-null constraint +DETAIL: Failing row contains (c1, c2) = (null, null). +INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT +ERROR: null value in column "c1" violates not-null constraint +DETAIL: Failing row contains (c1, c3) = (null, null). +INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT +ERROR: null value in column "c2" violates not-null constraint +DETAIL: Failing row contains (c1) = (5). +UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified +ERROR: new row for relation "t1" violates check constraint "t1_c3_check" +DETAIL: Failing row contains (c1, c3) = (1, 10). +SET SESSION AUTHORIZATION regressuser1; +DROP TABLE t1; -- test column-level privileges when involved with DELETE SET SESSION AUTHORIZATION regressuser1; ALTER TABLE atest6 ADD COLUMN three integer; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 5f1018ac7c8b60db451193a31610d627caa52ab3..16786ffbdce6f250cbf3369ffad8ea49f4642865 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -238,6 +238,31 @@ UPDATE atest5 SET one = 1; -- fail SELECT atest6 FROM atest6; -- ok COPY atest6 TO stdout; -- ok +-- check error reporting with column privs +SET SESSION AUTHORIZATION regressuser1; +CREATE TABLE t1 (c1 int, c2 int, c3 int check (c3 < 5), primary key (c1, c2)); +GRANT SELECT (c1) ON t1 TO regressuser2; +GRANT INSERT (c1, c2, c3) ON t1 TO regressuser2; +GRANT UPDATE (c1, c2, c3) ON t1 TO regressuser2; + +-- seed data +INSERT INTO t1 VALUES (1, 1, 1); +INSERT INTO t1 VALUES (1, 2, 1); +INSERT INTO t1 VALUES (2, 1, 2); +INSERT INTO t1 VALUES (2, 2, 2); +INSERT INTO t1 VALUES (3, 1, 3); + +SET SESSION AUTHORIZATION regressuser2; +INSERT INTO t1 (c1, c2) VALUES (1, 1); -- fail, but row not shown +UPDATE t1 SET c2 = 1; -- fail, but row not shown +INSERT INTO t1 (c1, c2) VALUES (null, null); -- fail, but see columns being inserted +INSERT INTO t1 (c3) VALUES (null); -- fail, but see columns being inserted or have SELECT +INSERT INTO t1 (c1) VALUES (5); -- fail, but see columns being inserted or have SELECT +UPDATE t1 SET c3 = 10; -- fail, but see columns with SELECT rights, or being modified + +SET SESSION AUTHORIZATION regressuser1; +DROP TABLE t1; + -- test column-level privileges when involved with DELETE SET SESSION AUTHORIZATION regressuser1; ALTER TABLE atest6 ADD COLUMN three integer;