]> granicus.if.org Git - postgresql/commitdiff
Fix column-privilege leak in error-message paths
authorStephen Frost <sfrost@snowman.net>
Mon, 12 Jan 2015 22:04:11 +0000 (17:04 -0500)
committerStephen Frost <sfrost@snowman.net>
Wed, 28 Jan 2015 17:32:56 +0000 (12:32 -0500)
While building error messages to return to the user,
BuildIndexValueDescription, ExecBuildSlotValueDescription and
ri_ReportViolation would happily include the entire key or entire row in
the result returned to the user, even if the user didn't have access to
view all of the columns being included.

Instead, include only those columns which the user is providing or which
the user has select rights on.  If the user does not have any rights
to view the table or any of the columns involved then no detail is
provided and a NULL value is returned from BuildIndexValueDescription
and ExecBuildSlotValueDescription.  Note that, for key cases, the user
must have access to all of the columns for the key to be shown; a
partial key will not be returned.

Back-patch all the way, as column-level privileges are now in all
supported versions.

This has been assigned CVE-2014-8161, but since the issue and the patch
have already been publicized on pgsql-hackers, there's no point in trying
to hide this commit.

src/backend/access/index/genam.c
src/backend/access/nbtree/nbtinsert.c
src/backend/commands/copy.c
src/backend/commands/trigger.c
src/backend/executor/execMain.c
src/backend/executor/execUtils.c
src/backend/utils/adt/ri_triggers.c
src/backend/utils/sort/tuplesort.c
src/test/regress/expected/privileges.out
src/test/regress/sql/privileges.sql

index 6d30b9b05d258825857c2dabb3398992fe739931..014823a3d73c3c5610b4a969ab2a3bd54adbafb9 100644 (file)
 #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++)
        {
index ac5f50652b5d3f02902621c643e39994dea3077e..9cb4b4a6dcfaac4232d2af122780ee4899f04cd6 100644 (file)
@@ -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)
index c245c019262daefaf21475471f2c9f7731e5ad11..adabc929b2ef6fafadb3602cde401408abb3507a 100644 (file)
@@ -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);
index 4239dd33c4a3f0feb4a7d34695e45082294b56f4..e9222edede1964590abae9390fd964a0ea05edea 100644 (file)
@@ -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)
 
index d744a4bfad38c688af159f8dcc41cd8fd42f164a..ea81c361eda67f47add9d19f150fa60b983b28e4 100644 (file)
@@ -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;
 }
 
index 029ce10c35ef8930da24e587e3aecf40137b3b96..e4e70c41b630e23c73d6545f42642153a8b39c6b 100644 (file)
@@ -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);
index b391888b5eec4499f30c9f3ef950de33faf3c816..977349a94fda8eb11010f671f2946d13fa645a36 100644 (file)
@@ -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))));
 }
 
index 3d3781b5211fb77f7a1e129d182a0d04c7466a97..8d9f1e79416a1a583d7c421b6747191683c4e4c9 100644 (file)
@@ -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.")));
        }
 
        /*
index bc6d7318da8622ceb74b88b10672cfc572caf880..266a905066ab6b56adb517b1fb141d3337e88350 100644 (file)
@@ -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;
index 5f1018ac7c8b60db451193a31610d627caa52ab3..16786ffbdce6f250cbf3369ffad8ea49f4642865 100644 (file)
@@ -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;