]> granicus.if.org Git - postgresql/commitdiff
Perform RLS WITH CHECK before constraints, etc
authorStephen Frost <sfrost@snowman.net>
Sat, 25 Apr 2015 00:34:26 +0000 (20:34 -0400)
committerStephen Frost <sfrost@snowman.net>
Sat, 25 Apr 2015 00:34:26 +0000 (20:34 -0400)
The RLS capability is built on top of the WITH CHECK OPTION
system which was added for auto-updatable views, however, unlike
WCOs on views (which are mandated by the SQL spec to not fire until
after all other constraints and checks are done), it makes much more
sense for RLS checks to happen earlier than constraint and uniqueness
checks.

This patch reworks the structure which holds the WCOs a bit to be
explicitly either VIEW or RLS checks and the RLS-related checks are
done prior to the constraint and uniqueness checks.  This also allows
better error reporting as we are now reporting when a violation is due
to a WITH CHECK OPTION and when it's due to an RLS policy violation,
which was independently noted by Craig Ringer as being confusing.

The documentation is also updated to include a paragraph about when RLS
WITH CHECK handling is performed, as there have been a number of
questions regarding that and the documentation was previously silent on
the matter.

Author: Dean Rasheed, with some kabitzing and comment changes by me.

15 files changed:
doc/src/sgml/ref/create_policy.sgml
src/backend/executor/execMain.c
src/backend/executor/nodeModifyTable.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/rewrite/rewriteHandler.c
src/backend/rewrite/rowsecurity.c
src/include/executor/executor.h
src/include/nodes/execnodes.h
src/include/nodes/parsenodes.h
src/test/modules/test_rls_hooks/expected/test_rls_hooks.out
src/test/regress/expected/rowsecurity.out
src/test/regress/sql/rowsecurity.sql

index 868a6c1cd34daeddaa14fe7ded8128a6ba07a547..49eaadc259806996980bb92c6d56fcf22b076d45 100644 (file)
@@ -60,6 +60,14 @@ CREATE POLICY <replaceable class="parameter">name</replaceable> ON <replaceable
    expressions, as they are assumed to be trustworthy.
   </para>
 
+  <para>
+    For INSERT and UPDATE queries, WITH CHECK expressions are enforced after
+    BEFORE triggers are fired, and before any data modifications are made.
+    Thus a BEFORE ROW trigger may modify the data to be inserted, affecting
+    the result of the security policy check.  WITH CHECK expressions are
+    enforced before any other constraints.
+  </para>
+
   <para>
    Policy names are per-table, therefore one policy name can be used for many
    different tables and have a definition for each table which is appropriate to
index 90d37b566ae6e2549cd97077c87989a22b7798ae..df4da3faa974c4794d8fefc26fb20f183163cb9a 100644 (file)
@@ -1673,9 +1673,15 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 
 /*
  * ExecWithCheckOptions -- check that tuple satisfies any WITH CHECK OPTIONs
+ * of the specified kind.
+ *
+ * Note that this needs to be called multiple times to ensure that all kinds of
+ * WITH CHECK OPTIONs are handled (both those from views which have the WITH
+ * CHECK OPTION set and from row level security policies).  See ExecInsert()
+ * and ExecUpdate().
  */
 void
-ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
+ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
                                         TupleTableSlot *slot, EState *estate)
 {
        Relation        rel = resultRelInfo->ri_RelationDesc;
@@ -1700,6 +1706,13 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
                WithCheckOption *wco = (WithCheckOption *) lfirst(l1);
                ExprState  *wcoExpr = (ExprState *) lfirst(l2);
 
+               /*
+                * Skip any WCOs which are not the kind we are looking for at this
+                * time.
+                */
+               if (wco->kind != kind)
+                       continue;
+
                /*
                 * WITH CHECK OPTION checks are intended to ensure that the new tuple
                 * is visible (in the case of a view) or that it passes the
@@ -1714,19 +1727,42 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
                        char       *val_desc;
                        Bitmapset  *modifiedCols;
 
-                       modifiedCols = GetModifiedColumns(resultRelInfo, estate);
-                       val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
-                                                                                                        slot,
-                                                                                                        tupdesc,
-                                                                                                        modifiedCols,
-                                                                                                        64);
-
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
-                                errmsg("new row violates WITH CHECK OPTION for \"%s\"",
-                                               wco->viewname),
-                                       val_desc ? errdetail("Failing row contains %s.", val_desc) :
-                                                          0));
+                       switch (wco->kind)
+                       {
+                               /*
+                                * For WITH CHECK OPTIONs coming from views, we might be able to
+                                * provide the details on the row, depending on the permissions
+                                * on the relation (that is, if the user could view it directly
+                                * anyway).  For RLS violations, we don't include the data since
+                                * we don't know if the user should be able to view the tuple as
+                                * as that depends on the USING policy.
+                                */
+                               case WCO_VIEW_CHECK:
+                                       modifiedCols = GetModifiedColumns(resultRelInfo, estate);
+                                       val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
+                                                                                                                        slot,
+                                                                                                                        tupdesc,
+                                                                                                                        modifiedCols,
+                                                                                                                        64);
+
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION),
+                                               errmsg("new row violates WITH CHECK OPTION for \"%s\"",
+                                                          wco->relname),
+                                                        val_desc ? errdetail("Failing row contains %s.",
+                                                                                                 val_desc) : 0));
+                                       break;
+                               case WCO_RLS_INSERT_CHECK:
+                               case WCO_RLS_UPDATE_CHECK:
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                                errmsg("new row violates row level security policy for \"%s\"",
+                                                               wco->relname)));
+                                       break;
+                               default:
+                                       elog(ERROR, "unrecognized WCO kind: %u", wco->kind);
+                                       break;
+                       }
                }
        }
 }
index f96fb2432b6a60a17931201013ca9027e2d6cd64..06ec82e2461852fa84de41964212cfb2e26b8694 100644 (file)
@@ -252,6 +252,16 @@ ExecInsert(TupleTableSlot *slot,
                 */
                tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 
+               /*
+                * Check any RLS INSERT WITH CHECK policies
+                *
+                * ExecWithCheckOptions() will skip any WCOs which are not of
+                * the kind we are looking for at this point.
+                */
+               if (resultRelInfo->ri_WithCheckOptions != NIL)
+                       ExecWithCheckOptions(WCO_RLS_INSERT_CHECK,
+                                                                resultRelInfo, slot, estate);
+
                /*
                 * Check the constraints of the tuple
                 */
@@ -287,9 +297,21 @@ ExecInsert(TupleTableSlot *slot,
 
        list_free(recheckIndexes);
 
-       /* Check any WITH CHECK OPTION constraints */
+       /*
+        * Check any WITH CHECK OPTION constraints from parent views.  We
+        * are required to do this after testing all constraints and
+        * uniqueness violations per the SQL spec, so we do it after actually
+        * inserting the record into the heap and all indexes.
+        *
+        * ExecWithCheckOptions will elog(ERROR) if a violation is found, so
+        * the tuple will never be seen, if it violates the the WITH CHECK
+        * OPTION.
+        *
+        * ExecWithCheckOptions() will skip any WCOs which are not of
+        * the kind we are looking for at this point.
+        */
        if (resultRelInfo->ri_WithCheckOptions != NIL)
-               ExecWithCheckOptions(resultRelInfo, slot, estate);
+               ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
 
        /* Process RETURNING if present */
        if (resultRelInfo->ri_projectReturning)
@@ -653,15 +675,25 @@ ExecUpdate(ItemPointer tupleid,
                tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
 
                /*
-                * Check the constraints of the tuple
+                * Check any RLS UPDATE WITH CHECK policies
                 *
                 * If we generate a new candidate tuple after EvalPlanQual testing, we
-                * must loop back here and recheck constraints.  (We don't need to
-                * redo triggers, however.  If there are any BEFORE triggers then
-                * trigger.c will have done heap_lock_tuple to lock the correct tuple,
-                * so there's no need to do them again.)
+                * must loop back here and recheck any RLS policies and constraints.
+                * (We don't need to redo triggers, however.  If there are any BEFORE
+                * triggers then trigger.c will have done heap_lock_tuple to lock the
+                * correct tuple, so there's no need to do them again.)
+                *
+                * ExecWithCheckOptions() will skip any WCOs which are not of
+                * the kind we are looking for at this point.
                 */
 lreplace:;
+               if (resultRelInfo->ri_WithCheckOptions != NIL)
+                       ExecWithCheckOptions(WCO_RLS_UPDATE_CHECK,
+                                                                resultRelInfo, slot, estate);
+
+               /*
+                * Check the constraints of the tuple
+                */
                if (resultRelationDesc->rd_att->constr)
                        ExecConstraints(resultRelInfo, slot, estate);
 
@@ -780,9 +812,17 @@ lreplace:;
 
        list_free(recheckIndexes);
 
-       /* Check any WITH CHECK OPTION constraints */
+       /*
+        * Check any WITH CHECK OPTION constraints from parent views.  We
+        * are required to do this after testing all constraints and
+        * uniqueness violations per the SQL spec, so we do it after actually
+        * updating the record in the heap and all indexes.
+        *
+        * ExecWithCheckOptions() will skip any WCOs which are not of
+        * the kind we are looking for at this point.
+        */
        if (resultRelInfo->ri_WithCheckOptions != NIL)
-               ExecWithCheckOptions(resultRelInfo, slot, estate);
+               ExecWithCheckOptions(WCO_VIEW_CHECK, resultRelInfo, slot, estate);
 
        /* Process RETURNING if present */
        if (resultRelInfo->ri_projectReturning)
index 029761e74f8d7cbfebb4edbdad53e9f9ad0c508e..59c755d7a5952ead543a79cc36b9b6be00637a51 100644 (file)
@@ -2064,7 +2064,8 @@ _copyWithCheckOption(const WithCheckOption *from)
 {
        WithCheckOption *newnode = makeNode(WithCheckOption);
 
-       COPY_STRING_FIELD(viewname);
+       COPY_SCALAR_FIELD(kind);
+       COPY_STRING_FIELD(relname);
        COPY_NODE_FIELD(qual);
        COPY_SCALAR_FIELD(cascaded);
 
index 190e50ab8c6909d62a7dbb4b40b0c6f53ff48e34..3bc81762af5fca909d35ec3fce8e2f589e786238 100644 (file)
@@ -2363,7 +2363,8 @@ _equalRangeTblFunction(const RangeTblFunction *a, const RangeTblFunction *b)
 static bool
 _equalWithCheckOption(const WithCheckOption *a, const WithCheckOption *b)
 {
-       COMPARE_STRING_FIELD(viewname);
+       COMPARE_SCALAR_FIELD(kind);
+       COMPARE_STRING_FIELD(relname);
        COMPARE_NODE_FIELD(qual);
        COMPARE_SCALAR_FIELD(cascaded);
 
index 385b289bed2ff17e4ec4382aca632be16ffad384..e0dca56ea6cfaefc1ad8ee4d7d8036895a1e8f2f 100644 (file)
@@ -2332,7 +2332,8 @@ _outWithCheckOption(StringInfo str, const WithCheckOption *node)
 {
        WRITE_NODE_TYPE("WITHCHECKOPTION");
 
-       WRITE_STRING_FIELD(viewname);
+       WRITE_ENUM_FIELD(kind, WCOKind);
+       WRITE_STRING_FIELD(relname);
        WRITE_NODE_FIELD(qual);
        WRITE_BOOL_FIELD(cascaded);
 }
index 563209c56150d4de00bf93ad03a1de0b74741acf..b0cd95da063bf382f605801ff60ebc0c77a63caa 100644 (file)
@@ -266,7 +266,8 @@ _readWithCheckOption(void)
 {
        READ_LOCALS(WithCheckOption);
 
-       READ_STRING_FIELD(viewname);
+       READ_ENUM_FIELD(kind, WCOKind);
+       READ_STRING_FIELD(relname);
        READ_NODE_FIELD(qual);
        READ_BOOL_FIELD(cascaded);
 
index 08ec13ccdd03a03e342936a3618298f6c1ac88a7..60c60caf3964f09bcd065f7900e6f3e68b73f63a 100644 (file)
@@ -2947,7 +2947,8 @@ rewriteTargetView(Query *parsetree, Relation view)
                        WithCheckOption *wco;
 
                        wco = makeNode(WithCheckOption);
-                       wco->viewname = pstrdup(RelationGetRelationName(view));
+                       wco->kind = WCO_VIEW_CHECK;
+                       wco->relname = pstrdup(RelationGetRelationName(view));
                        wco->qual = NULL;
                        wco->cascaded = cascaded;
 
index e060353bff8aa4b37481ba2a3e97e8d5fb527c23..b0b308118f4d013c786743b07bceb065d4d718d0 100644 (file)
@@ -259,7 +259,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
                        WithCheckOption    *wco;
 
                        wco = (WithCheckOption *) makeNode(WithCheckOption);
-                       wco->viewname = pstrdup(RelationGetRelationName(rel));
+                       wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
+                                                                                                                 WCO_RLS_UPDATE_CHECK;
+                       wco->relname = pstrdup(RelationGetRelationName(rel));
                        wco->qual = (Node *) hook_with_check_expr_restrictive;
                        wco->cascaded = false;
                        *withCheckOptions = lappend(*withCheckOptions, wco);
@@ -274,7 +276,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
                        WithCheckOption    *wco;
 
                        wco = (WithCheckOption *) makeNode(WithCheckOption);
-                       wco->viewname = pstrdup(RelationGetRelationName(rel));
+                       wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
+                                                                                                                 WCO_RLS_UPDATE_CHECK;
+                       wco->relname = pstrdup(RelationGetRelationName(rel));
                        wco->qual = (Node *) rowsec_with_check_expr;
                        wco->cascaded = false;
                        *withCheckOptions = lappend(*withCheckOptions, wco);
@@ -285,7 +289,9 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
                        WithCheckOption    *wco;
 
                        wco = (WithCheckOption *) makeNode(WithCheckOption);
-                       wco->viewname = pstrdup(RelationGetRelationName(rel));
+                       wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
+                                                                                                                 WCO_RLS_UPDATE_CHECK;
+                       wco->relname = pstrdup(RelationGetRelationName(rel));
                        wco->qual = (Node *) hook_with_check_expr_permissive;
                        wco->cascaded = false;
                        *withCheckOptions = lappend(*withCheckOptions, wco);
@@ -297,13 +303,18 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
                        List                       *combined_quals = NIL;
                        Expr                       *combined_qual_eval;
 
-                       combined_quals = lcons(copyObject(rowsec_with_check_expr), combined_quals);
-                       combined_quals = lcons(copyObject(hook_with_check_expr_permissive), combined_quals);
+                       combined_quals = lcons(copyObject(rowsec_with_check_expr),
+                                                                  combined_quals);
+
+                       combined_quals = lcons(copyObject(hook_with_check_expr_permissive),
+                                                                  combined_quals);
 
                        combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1);
 
                        wco = (WithCheckOption *) makeNode(WithCheckOption);
-                       wco->viewname = pstrdup(RelationGetRelationName(rel));
+                       wco->kind = root->commandType == CMD_INSERT ? WCO_RLS_INSERT_CHECK :
+                                                                                                                 WCO_RLS_UPDATE_CHECK;
+                       wco->relname = pstrdup(RelationGetRelationName(rel));
                        wco->qual = (Node *) combined_qual_eval;
                        wco->cascaded = false;
                        *withCheckOptions = lappend(*withCheckOptions, wco);
@@ -332,7 +343,8 @@ get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
                        Expr   *combined_qual_eval;
 
                        combined_quals = lcons(copyObject(rowsec_expr), combined_quals);
-                       combined_quals = lcons(copyObject(hook_expr_permissive), combined_quals);
+                       combined_quals = lcons(copyObject(hook_expr_permissive),
+                                                                  combined_quals);
 
                        combined_qual_eval = makeBoolExpr(OR_EXPR, combined_quals, -1);
 
index c2bf9a2940d1d1dd3ac80b9918e2e3eb78e09ed3..33c8fad844c662fe67fb4dec4fa37246fa87fc33 100644 (file)
@@ -193,7 +193,7 @@ extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
                                TupleTableSlot *slot, EState *estate);
-extern void ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
+extern void ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
                                         TupleTableSlot *slot, EState *estate);
 extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti);
 extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist);
index ac75f86fef7e5a25f7ece603d4bd88275120d3b9..dac542fbc14ab4888525275ebd1e1eca3de69df7 100644 (file)
@@ -303,7 +303,7 @@ typedef struct JunkFilter
  *             TrigInstrument                  optional runtime measurements for triggers
  *             FdwRoutine                              FDW callback functions, if foreign table
  *             FdwState                                available to save private state of FDW
- *             WithCheckOptions                list of WithCheckOption's for views
+ *             WithCheckOptions                list of WithCheckOption's to be checked
  *             WithCheckOptionExprs    list of WithCheckOption expr states
  *             ConstraintExprs                 array of constraint-checking expr states
  *             junkFilter                              for removing junk attributes from tuples
index 0e257ac46ce2104d2d410d6eb347609f5df51d78..36e36d563162857ab0c082d8c4ed06e57e55135d 100644 (file)
@@ -872,14 +872,23 @@ typedef struct RangeTblFunction
 /*
  * WithCheckOption -
  *             representation of WITH CHECK OPTION checks to be applied to new tuples
- *             when inserting/updating an auto-updatable view.
+ *             when inserting/updating an auto-updatable view, or RLS WITH CHECK
+ *             policies to be applied when inserting/updating a relation with RLS.
  */
+typedef enum WCOKind
+{
+       WCO_VIEW_CHECK,                         /* WCO on an auto-updatable view */
+       WCO_RLS_INSERT_CHECK,           /* RLS INSERT WITH CHECK policy */
+       WCO_RLS_UPDATE_CHECK            /* RLS UPDATE WITH CHECK policy */
+} WCOKind;
+
 typedef struct WithCheckOption
 {
        NodeTag         type;
-       char       *viewname;           /* name of view that specified the WCO */
+       WCOKind         kind;                   /* kind of WCO */
+       char       *relname;            /* name of relation that specified the WCO */
        Node       *qual;                       /* constraint qual to check */
-       bool            cascaded;               /* true = WITH CASCADED CHECK OPTION */
+       bool            cascaded;               /* true for a cascaded WCO on a view */
 } WithCheckOption;
 
 /*
index 54d3bb7a8ccd84aae4179b6cf483d02167bb23a5..9427a6fae80e35623723f6bd8198076a872b7932 100644 (file)
@@ -57,7 +57,7 @@ SELECT * FROM rls_test_permissive;
 INSERT INTO rls_test_permissive VALUES ('r1','s1',10);
 -- failure
 INSERT INTO rls_test_permissive VALUES ('r4','s4',10);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_permissive"
+ERROR:  new row violates row level security policy for "rls_test_permissive"
 SET ROLE s1;
 -- With only the hook's policies, restrictive
 -- hook's policy is current_user = supervisor
@@ -78,7 +78,7 @@ SELECT * FROM rls_test_restrictive;
 INSERT INTO rls_test_restrictive VALUES ('r1','s1',10);
 -- failure
 INSERT INTO rls_test_restrictive VALUES ('r4','s4',10);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_restrictive"
+ERROR:  new row violates row level security policy for "rls_test_restrictive"
 SET ROLE s1;
 -- With only the hook's policies, both
 -- permissive hook's policy is current_user = username
@@ -100,13 +100,13 @@ SELECT * FROM rls_test_both;
 
 -- failure
 INSERT INTO rls_test_both VALUES ('r1','s1',10);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_both"
+ERROR:  new row violates row level security policy for "rls_test_both"
 -- failure
 INSERT INTO rls_test_both VALUES ('r4','s1',10);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_both"
+ERROR:  new row violates row level security policy for "rls_test_both"
 -- failure
 INSERT INTO rls_test_both VALUES ('r4','s4',10);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_both"
+ERROR:  new row violates row level security policy for "rls_test_both"
 RESET ROLE;
 -- Create "internal" policies, to check that the policies from
 -- the hooks are combined correctly.
@@ -136,7 +136,7 @@ INSERT INTO rls_test_permissive VALUES ('r1','s1',7);
 INSERT INTO rls_test_permissive VALUES ('r3','s3',10);
 -- failure
 INSERT INTO rls_test_permissive VALUES ('r4','s4',7);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_permissive"
+ERROR:  new row violates row level security policy for "rls_test_permissive"
 SET ROLE s1;
 -- With both internal and hook policies, restrictive
 EXPLAIN (costs off) SELECT * FROM rls_test_restrictive;
@@ -158,13 +158,13 @@ SELECT * FROM rls_test_restrictive;
 INSERT INTO rls_test_restrictive VALUES ('r1','s1',8);
 -- failure
 INSERT INTO rls_test_restrictive VALUES ('r3','s3',10);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_restrictive"
+ERROR:  new row violates row level security policy for "rls_test_restrictive"
 -- failure
 INSERT INTO rls_test_restrictive VALUES ('r1','s1',7);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_restrictive"
+ERROR:  new row violates row level security policy for "rls_test_restrictive"
 -- failure
 INSERT INTO rls_test_restrictive VALUES ('r4','s4',7);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_restrictive"
+ERROR:  new row violates row level security policy for "rls_test_restrictive"
 -- With both internal and hook policies, both permissive
 -- and restrictive hook policies
 EXPLAIN (costs off) SELECT * FROM rls_test_both;
@@ -185,13 +185,13 @@ SELECT * FROM rls_test_both;
 INSERT INTO rls_test_both VALUES ('r1','s1',8);
 -- failure
 INSERT INTO rls_test_both VALUES ('r3','s3',10);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_both"
+ERROR:  new row violates row level security policy for "rls_test_both"
 -- failure
 INSERT INTO rls_test_both VALUES ('r1','s1',7);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_both"
+ERROR:  new row violates row level security policy for "rls_test_both"
 -- failure
 INSERT INTO rls_test_both VALUES ('r4','s4',7);
-ERROR:  new row violates WITH CHECK OPTION for "rls_test_both"
+ERROR:  new row violates row level security policy for "rls_test_both"
 RESET ROLE;
 DROP TABLE rls_test_restrictive;
 DROP TABLE rls_test_permissive;
index 56760796edd8558687b640afdddc94fc190f1933..1ea65a7d8a1a7a543fba5189289f28c931e6d888 100644 (file)
@@ -300,6 +300,11 @@ SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
 -----+-----+--------+---------+--------
 (0 rows)
 
+-- RLS policies are checked before constraints
+INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user2', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation
+ERROR:  new row violates row level security policy for "document"
+UPDATE document SET did = 8, dauthor = 'rls_regress_user2' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation
+ERROR:  new row violates row level security policy for "document"
 -- database superuser does bypass RLS policy when enabled
 RESET SESSION AUTHORIZATION;
 SET row_security TO ON;
@@ -1426,9 +1431,9 @@ NOTICE:  f_leak => d3d9446802a44259755d38e6d163e820
 (5 rows)
 
 INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
-ERROR:  new row violates WITH CHECK OPTION for "b1"
+ERROR:  new row violates row level security policy for "b1"
 INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
-ERROR:  new row violates WITH CHECK OPTION for "b1"
+ERROR:  new row violates row level security policy for "b1"
 INSERT INTO bv1 VALUES (12, 'xxx'); -- ok
 EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
                                 QUERY PLAN                                 
@@ -1988,7 +1993,7 @@ EXPLAIN (COSTS OFF) WITH cte1 AS (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FRO
 (6 rows)
 
 WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
-ERROR:  new row violates WITH CHECK OPTION for "t1"
+ERROR:  new row violates row level security policy for "t1"
 WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
  a  |                b                 
 ----+----------------------------------
@@ -2006,7 +2011,7 @@ WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
 (11 rows)
 
 WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail
-ERROR:  new row violates WITH CHECK OPTION for "t1"
+ERROR:  new row violates row level security policy for "t1"
 WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
  a  |    b    
 ----+---------
index 4da5035f5be8d484fae64a4cf2b9279217cee2e7..f38b4438fdfc15c0a4778fc69c67b51c1bbe950b 100644 (file)
@@ -146,6 +146,10 @@ SET SESSION AUTHORIZATION rls_regress_user1;
 INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user1', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see
 SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
 
+-- RLS policies are checked before constraints
+INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user2', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation
+UPDATE document SET did = 8, dauthor = 'rls_regress_user2' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation
+
 -- database superuser does bypass RLS policy when enabled
 RESET SESSION AUTHORIZATION;
 SET row_security TO ON;