diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml
index 868a6c1cd34..49eaadc2598 100644
--- a/doc/src/sgml/ref/create_policy.sgml
+++ b/doc/src/sgml/ref/create_policy.sgml
@@ -60,6 +60,14 @@ CREATE POLICY name ON
+
+ 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.
+
+
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
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 90d37b566ae..df4da3faa97 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -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);
+ 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->viewname),
- val_desc ? errdetail("Failing row contains %s.", val_desc) :
- 0));
+ 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;
+ }
}
}
}
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f96fb2432b6..06ec82e2461 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -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)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 029761e74f8..59c755d7a59 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -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);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 190e50ab8c6..3bc81762af5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -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);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 385b289bed2..e0dca56ea6c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -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);
}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 563209c5615..b0cd95da063 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -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);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 08ec13ccdd0..60c60caf396 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -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;
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e060353bff8..b0b308118f4 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -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);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index c2bf9a2940d..33c8fad844c 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -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);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index ac75f86fef7..dac542fbc14 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -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
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0e257ac46ce..36e36d56316 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -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;
/*
diff --git a/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out
index 54d3bb7a8cc..9427a6fae80 100644
--- a/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out
+++ b/src/test/modules/test_rls_hooks/expected/test_rls_hooks.out
@@ -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;
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 56760796edd..1ea65a7d8a1 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -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
----+---------
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
index 4da5035f5be..f38b4438fdf 100644
--- a/src/test/regress/sql/rowsecurity.sql
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -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;