Re: WITH CHECK and Column-Level Privileges

Поиск
Список
Период
Сортировка
От Stephen Frost
Тема Re: WITH CHECK and Column-Level Privileges
Дата
Msg-id 20150112223947.GN3062@tamriel.snowman.net
обсуждение исходный текст
Ответ на Re: WITH CHECK and Column-Level Privileges  (Stephen Frost <sfrost@snowman.net>)
Список pgsql-hackers
All,

Apologies, forgot to mention- this is again 9.4.

Thanks,
Stephen

* Stephen Frost (sfrost@snowman.net) wrote:
> Dean, Robert,
>
> * Dean Rasheed (dean.a.rasheed@gmail.com) wrote:
> > On 29 October 2014 13:04, Stephen Frost <sfrost@snowman.net> wrote:
> > > * Robert Haas (robertmhaas@gmail.com) wrote:
> > >> On Wed, Oct 29, 2014 at 8:16 AM, Stephen Frost <sfrost@snowman.net> wrote:
> > >> > suggestions.  If the user does not have table-level SELECT rights,
> > >> > they'll see for the "Failing row contains" case, they'll get:
> > >> >
> > >> > Failing row contains (col1, col2, col3) = (1, 2, 3).
> > >> >
> > >> > Or, if they have no access to any columns:
> > >> >
> > >> > Failing row contains () = ()
> > >>
> > >> I haven't looked at the code, but that sounds nice, except that if
> > >> they have no access to any columns, I'd leave the message out
> > >> altogether instead of emitting it with no useful content.
> > >
> > > Alright, I can change things around to make that happen without too much
> > > trouble.
> >
> > Yes, skim-reading the patch, it looks like a good approach to me, and
> > also +1 for not emitting anything if no values are visible.
>
> Alright, here's an updated patch which doesn't return any detail if no
> values are visible or if only a partial key is visible.
>
> Please take a look.  I'm not thrilled with simply returning an empty
> string and then checking that for BuildIndexValueDescription and
> ExecBuildSlotValueDescription, but I figured we might have other users
> of BuildIndexValueDescription and I wasn't seeing a particularly better
> solution.  Suggestions welcome, of course.
>
>     Thanks!
>
>         Stephen

> From e00cc2f5be4524989e8d670296f82bb99f3774a1 Mon Sep 17 00:00:00 2001
> From: Stephen Frost <sfrost@snowman.net>
> Date: Mon, 12 Jan 2015 17:04:11 -0500
> Subject: [PATCH] Fix column-privilege leak in error-message paths
>
> While building error messages to return to the user,
> BuildIndexValueDescription and ExecBuildSlotValueDescription 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.  Note that, for duplicate 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.
> ---
>  src/backend/access/index/genam.c         |  59 ++++++++-
>  src/backend/access/nbtree/nbtinsert.c    |  30 +++--
>  src/backend/commands/copy.c              |   6 +-
>  src/backend/executor/execMain.c          | 215 +++++++++++++++++++++++--------
>  src/backend/executor/execUtils.c         |  12 +-
>  src/backend/utils/sort/tuplesort.c       |  28 ++--
>  src/test/regress/expected/privileges.out |  31 +++++
>  src/test/regress/sql/privileges.sql      |  24 ++++
>  8 files changed, 328 insertions(+), 77 deletions(-)
>
> diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
> index 850008b..1090568 100644
> --- a/src/backend/access/index/genam.c
> +++ b/src/backend/access/index/genam.c
> @@ -25,10 +25,12 @@
>  #include "lib/stringinfo.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/snapmgr.h"
> +#include "utils/syscache.h"
>  #include "utils/tqual.h"
>
>
> @@ -154,6 +156,9 @@ 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 the columns
> + * involved, an empty string "" will be returned instead.
> + *
>   * 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.
> @@ -163,13 +168,63 @@ 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 users does not have access to view the
> +     * data in the key columns, we return "" instead, which callers can
> +     * test for and use or not accordingly.
> +     *
> +     * 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 just return "" */
> +                ReleaseSysCache(ht_idx);
> +                return "";
> +            }
> +        }
> +    }
> +    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 59d7006..4279416 100644
> --- a/src/backend/access/nbtree/nbtinsert.c
> +++ b/src/backend/access/nbtree/nbtinsert.c
> @@ -388,18 +388,30 @@ _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);
> -                        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)),
> -                                 errtableconstraint(heapRel,
> -                                             RelationGetRelationName(rel))));
> +
> +                        key_desc = BuildIndexValueDescription(rel, values,
> +                                                              isnull);
> +
> +                        if (!strlen(key_desc))
> +                            ereport(ERROR,
> +                                    (errcode(ERRCODE_UNIQUE_VIOLATION),
> +                                     errmsg("duplicate key value violates unique constraint \"%s\"",
> +                                            RelationGetRelationName(rel)),
> +                                     errtableconstraint(heapRel,
> +                                                RelationGetRelationName(rel))));
> +                        else
> +                            ereport(ERROR,
> +                                    (errcode(ERRCODE_UNIQUE_VIOLATION),
> +                                     errmsg("duplicate key value violates unique constraint \"%s\"",
> +                                            RelationGetRelationName(rel)),
> +                                     errdetail("Key %s already exists.",
> +                                                key_desc),
> +                                     errtableconstraint(heapRel,
> +                                                RelationGetRelationName(rel))));
>                      }
>                  }
>                  else if (all_dead)
> diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
> index fbd7492..9ab1e19 100644
> --- a/src/backend/commands/copy.c
> +++ b/src/backend/commands/copy.c
> @@ -160,6 +160,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.
> @@ -784,6 +785,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
>      bool        pipe = (stmt->filename == NULL);
>      Relation    rel;
>      Oid            relid;
> +    RangeTblEntry *rte;
>
>      /* Disallow COPY to/from file or program except to superusers. */
>      if (!pipe && !superuser())
> @@ -806,7 +808,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
>      {
>          TupleDesc    tupDesc;
>          AclMode        required_access = (is_from ? ACL_INSERT : ACL_SELECT);
> -        RangeTblEntry *rte;
>          List       *attnums;
>          ListCell   *cur;
>
> @@ -856,6 +857,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
>
>          cstate = BeginCopyFrom(rel, stmt->filename, stmt->is_program,
>                                 stmt->attlist, stmt->options);
> +        cstate->range_table = list_make1(rte);
>          *processed = CopyFrom(cstate);    /* copy from file to database */
>          EndCopyFrom(cstate);
>      }
> @@ -864,6 +866,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
>          cstate = BeginCopyTo(rel, stmt->query, queryString,
>                               stmt->filename, stmt->is_program,
>                               stmt->attlist, stmt->options);
> +        cstate->range_table = list_make1(rte);
>          *processed = DoCopyTo(cstate);    /* copy from database to file */
>          EndCopyTo(cstate);
>      }
> @@ -2184,6 +2187,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/executor/execMain.c b/src/backend/executor/execMain.c
> index 01eda70..49e4c81 100644
> --- a/src/backend/executor/execMain.c
> +++ b/src/backend/executor/execMain.c
> @@ -82,12 +82,17 @@ 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);
>
> +#define GetModifiedColumns(relinfo, estate) \
> +    (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols)
> +
>  /* end of local decls */
>
>
> @@ -1590,9 +1595,12 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
>      Relation    rel = resultRelInfo->ri_RelationDesc;
>      TupleDesc    tupdesc = RelationGetDescr(rel);
>      TupleConstr *constr = tupdesc->constr;
> +    Bitmapset  *modifiedCols;
>
>      Assert(constr);
>
> +    modifiedCols = GetModifiedColumns(resultRelInfo, estate);
> +
>      if (constr->has_not_null)
>      {
>          int            natts = tupdesc->natts;
> @@ -1602,15 +1610,29 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
>          {
>              if (tupdesc->attrs[attrChk - 1]->attnotnull &&
>                  slot_attisnull(slot, attrChk))
> -                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)),
> -                         errtablecol(rel, attrChk)));
> +            {
> +                char       *val_desc;
> +
> +                val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
> +                                                         slot,
> +                                                         tupdesc,
> +                                                         modifiedCols,
> +                                                         64);
> +
> +                if (!strlen(val_desc))
> +                    ereport(ERROR,
> +                            (errcode(ERRCODE_NOT_NULL_VIOLATION),
> +                             errmsg("null value in column \"%s\" violates not-null constraint",
> +                                  NameStr(tupdesc->attrs[attrChk - 1]->attname)),
> +                             errtablecol(rel, attrChk)));
> +                else
> +                    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.", val_desc),
> +                             errtablecol(rel, attrChk)));
> +            }
>          }
>      }
>
> @@ -1619,15 +1641,28 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
>          const char *failed;
>
>          if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
> -            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)),
> -                     errtableconstraint(rel, failed)));
> +        {
> +            char       *val_desc;
> +
> +            val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
> +                                                     slot,
> +                                                     tupdesc,
> +                                                     modifiedCols,
> +                                                     64);
> +            if (!strlen(val_desc))
> +                ereport(ERROR,
> +                        (errcode(ERRCODE_CHECK_VIOLATION),
> +                         errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
> +                                RelationGetRelationName(rel), failed),
> +                         errtableconstraint(rel, failed)));
> +            else
> +                ereport(ERROR,
> +                        (errcode(ERRCODE_CHECK_VIOLATION),
> +                         errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
> +                                RelationGetRelationName(rel), failed),
> +                         errdetail("Failing row contains %s.", val_desc),
> +                         errtableconstraint(rel, failed)));
> +        }
>      }
>  }
>
> @@ -1638,9 +1673,13 @@ void
>  ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
>                       TupleTableSlot *slot, EState *estate)
>  {
> +    Relation    rel = resultRelInfo->ri_RelationDesc;
>      ExprContext *econtext;
>      ListCell   *l1,
>                 *l2;
> +    Bitmapset  *modifiedCols;
> +
> +    modifiedCols = GetModifiedColumns(resultRelInfo, estate);
>
>      /*
>       * We will use the EState's per-tuple context for evaluating constraint
> @@ -1666,14 +1705,27 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
>           * above for CHECK constraints).
>           */
>          if (!ExecQual((List *) wcoExpr, econtext, false))
> -            ereport(ERROR,
> -                    (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
> -                 errmsg("new row violates WITH CHECK OPTION for view \"%s\"",
> -                        wco->viewname),
> -                     errdetail("Failing row contains %s.",
> -                               ExecBuildSlotValueDescription(slot,
> -                            RelationGetDescr(resultRelInfo->ri_RelationDesc),
> -                                                             64))));
> +        {
> +            char       *val_desc;
> +
> +            val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
> +                                                     slot,
> +                             RelationGetDescr(resultRelInfo->ri_RelationDesc),
> +                                                      modifiedCols,
> +                                                     64);
> +
> +            if (!strlen(val_desc))
> +                ereport(ERROR,
> +                        (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
> +                     errmsg("new row violates WITH CHECK OPTION for view \"%s\"",
> +                            wco->viewname)));
> +            else
> +                ereport(ERROR,
> +                        (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
> +                     errmsg("new row violates WITH CHECK OPTION for view \"%s\"",
> +                            wco->viewname),
> +                         errdetail("Failing row contains %s.", val_desc)));
> +        }
>      }
>  }
>
> @@ -1689,25 +1741,51 @@ ExecWithCheckOptions(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, an empty string is returned.
>   */
>  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 that the user has permissions to see the row.  If the user
> +     * has table-level SELECT then that is good enough.  If not, we have
> +     * to check all the columns.
> +     */
> +    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;
>
> @@ -1715,37 +1793,70 @@ ExecBuildSlotValueDescription(TupleTableSlot *slot,
>          if (tupdesc->attrs[i]->attisdropped)
>              continue;
>
> -        if (slot->tts_isnull[i])
> -            val = "null";
> -        else
> +        if (!table_perm)
>          {
> -            Oid            foutoid;
> -            bool        typisvarlena;
> +            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 there were no permissions found, return an empty string. */
> +    if (!any_perm)
> +        return "";
> +
>      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 d5e1273..4860356 100644
> --- a/src/backend/executor/execUtils.c
> +++ b/src/backend/executor/execUtils.c
> @@ -1323,8 +1323,10 @@ 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),
> +                     strlen(error_new) == 0 || strlen(error_existing) == 0 ?
> +                         errdetail("Key conflicts exist.") :
> +                         errdetail("Key %s conflicts with key %s.",
> +                                  error_new, error_existing),
>                       errtableconstraint(heap,
>                                          RelationGetRelationName(index))));
>          else
> @@ -1332,8 +1334,10 @@ retry:
>                      (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),
> +                     strlen(error_new) == 0 || strlen(error_existing) == 0 ?
> +                         errdetail("Key conflicts with existing key.") :
> +                         errdetail("Key %s conflicts with existing key %s.",
> +                                  error_new, error_existing),
>                       errtableconstraint(heap,
>                                          RelationGetRelationName(index))));
>      }
> diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
> index aa0f6d8..6aba499 100644
> --- a/src/backend/utils/sort/tuplesort.c
> +++ b/src/backend/utils/sort/tuplesort.c
> @@ -3240,6 +3240,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
> @@ -3250,15 +3251,24 @@ comparetup_index_btree(const SortTuple *a, const SortTuple *b,
>          Assert(tuple1 != tuple2);
>
>          index_deform_tuple(tuple1, tupDes, 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)),
> -                 errtableconstraint(state->heapRel,
> -                                 RelationGetRelationName(state->indexRel))));
> +
> +        key_desc = BuildIndexValueDescription(state->indexRel, values, isnull);
> +
> +        if (!strlen(key_desc))
> +            ereport(ERROR,
> +                    (errcode(ERRCODE_UNIQUE_VIOLATION),
> +                     errmsg("could not create unique index \"%s\"",
> +                            RelationGetRelationName(state->indexRel)),
> +                     errtableconstraint(state->heapRel,
> +                                     RelationGetRelationName(state->indexRel))));
> +        else
> +            ereport(ERROR,
> +                    (errcode(ERRCODE_UNIQUE_VIOLATION),
> +                     errmsg("could not create unique index \"%s\"",
> +                            RelationGetRelationName(state->indexRel)),
> +                     errdetail("Key %s is duplicated.", key_desc),
> +                     errtableconstraint(state->heapRel,
> +                                     RelationGetRelationName(state->indexRel))));
>      }
>
>      /*
> diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
> index 1675075..b1ecd39 100644
> --- a/src/test/regress/expected/privileges.out
> +++ b/src/test/regress/expected/privileges.out
> @@ -381,6 +381,37 @@ 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));
> +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).
> +DROP TABLE t1;
> +ERROR:  must be owner of relation 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 a0ff953..c850a2e 100644
> --- a/src/test/regress/sql/privileges.sql
> +++ b/src/test/regress/sql/privileges.sql
> @@ -256,6 +256,30 @@ 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
> +
> +DROP TABLE t1;
> +
>  -- test column-level privileges when involved with DELETE
>  SET SESSION AUTHORIZATION regressuser1;
>  ALTER TABLE atest6 ADD COLUMN three integer;
> --
> 1.9.1
>




В списке pgsql-hackers по дате отправления:

Предыдущее
От: Jim Nasby
Дата:
Сообщение: Re: Removing INNER JOINs
Следующее
От: Michael Paquier
Дата:
Сообщение: Re: Table-level log_autovacuum_min_duration