summaryrefslogtreecommitdiffstats
path: root/src/test/regress/expected/rowsecurity.out
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/regress/expected/rowsecurity.out')
-rw-r--r--src/test/regress/expected/rowsecurity.out4061
1 files changed, 4061 insertions, 0 deletions
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000..cff2dcc
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,4061 @@
+--
+-- Test of Row-level security feature
+--
+-- Clean up in case a prior regression run failed
+-- Suppress NOTICE messages when users/groups don't exist
+SET client_min_messages TO 'warning';
+DROP USER IF EXISTS regress_rls_alice;
+DROP USER IF EXISTS regress_rls_bob;
+DROP USER IF EXISTS regress_rls_carol;
+DROP USER IF EXISTS regress_rls_dave;
+DROP USER IF EXISTS regress_rls_exempt_user;
+DROP ROLE IF EXISTS regress_rls_group1;
+DROP ROLE IF EXISTS regress_rls_group2;
+DROP SCHEMA IF EXISTS regress_rls_schema CASCADE;
+RESET client_min_messages;
+-- initial setup
+CREATE USER regress_rls_alice NOLOGIN;
+CREATE USER regress_rls_bob NOLOGIN;
+CREATE USER regress_rls_carol NOLOGIN;
+CREATE USER regress_rls_dave NOLOGIN;
+CREATE USER regress_rls_exempt_user BYPASSRLS NOLOGIN;
+CREATE ROLE regress_rls_group1 NOLOGIN;
+CREATE ROLE regress_rls_group2 NOLOGIN;
+GRANT regress_rls_group1 TO regress_rls_bob;
+GRANT regress_rls_group2 TO regress_rls_carol;
+CREATE SCHEMA regress_rls_schema;
+GRANT ALL ON SCHEMA regress_rls_schema to public;
+SET search_path = regress_rls_schema;
+-- setup of malicious function
+CREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool
+ COST 0.0000001 LANGUAGE plpgsql
+ AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+GRANT EXECUTE ON FUNCTION f_leak(text) TO public;
+-- BASIC Row-Level Security Scenario
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE uaccount (
+ pguser name primary key,
+ seclv int
+);
+GRANT SELECT ON uaccount TO public;
+INSERT INTO uaccount VALUES
+ ('regress_rls_alice', 99),
+ ('regress_rls_bob', 1),
+ ('regress_rls_carol', 2),
+ ('regress_rls_dave', 3);
+CREATE TABLE category (
+ cid int primary key,
+ cname text
+);
+GRANT ALL ON category TO public;
+INSERT INTO category VALUES
+ (11, 'novel'),
+ (22, 'science fiction'),
+ (33, 'technology'),
+ (44, 'manga');
+CREATE TABLE document (
+ did int primary key,
+ cid int references category(cid),
+ dlevel int not null,
+ dauthor name,
+ dtitle text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+ ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),
+ ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),
+ ( 3, 22, 2, 'regress_rls_bob', 'my science fiction'),
+ ( 4, 44, 1, 'regress_rls_bob', 'my first manga'),
+ ( 5, 44, 2, 'regress_rls_bob', 'my second manga'),
+ ( 6, 22, 1, 'regress_rls_carol', 'great science fiction'),
+ ( 7, 33, 2, 'regress_rls_carol', 'great technology book'),
+ ( 8, 44, 1, 'regress_rls_carol', 'great manga'),
+ ( 9, 22, 1, 'regress_rls_dave', 'awesome science fiction'),
+ (10, 33, 2, 'regress_rls_dave', 'awesome technology book');
+ALTER TABLE document ENABLE ROW LEVEL SECURITY;
+-- user's security level must be higher than or equal to document's
+CREATE POLICY p1 ON document AS PERMISSIVE
+ USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+-- try to create a policy of bogus type
+CREATE POLICY p1 ON document AS UGLY
+ USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+ERROR: unrecognized row security option "ugly"
+LINE 1: CREATE POLICY p1 ON document AS UGLY
+ ^
+HINT: Only PERMISSIVE or RESTRICTIVE policies are supported currently.
+-- but Dave isn't allowed to anything at cid 50 or above
+-- this is to make sure that we sort the policies by name first
+-- when applying WITH CHECK, a later INSERT by Dave should fail due
+-- to p1r first
+CREATE POLICY p2r ON document AS RESTRICTIVE TO regress_rls_dave
+ USING (cid <> 44 AND cid < 50);
+-- and Dave isn't allowed to see manga documents
+CREATE POLICY p1r ON document AS RESTRICTIVE TO regress_rls_dave
+ USING (cid <> 44);
+\dp
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------------------+----------+-------+---------------------------------------------+-------------------+--------------------------------------------
+ regress_rls_schema | category | table | regress_rls_alice=arwdDxt/regress_rls_alice+| |
+ | | | =arwdDxt/regress_rls_alice | |
+ regress_rls_schema | document | table | regress_rls_alice=arwdDxt/regress_rls_alice+| | p1: +
+ | | | =arwdDxt/regress_rls_alice | | (u): (dlevel <= ( SELECT uaccount.seclv +
+ | | | | | FROM uaccount +
+ | | | | | WHERE (uaccount.pguser = CURRENT_USER)))+
+ | | | | | p2r (RESTRICTIVE): +
+ | | | | | (u): ((cid <> 44) AND (cid < 50)) +
+ | | | | | to: regress_rls_dave +
+ | | | | | p1r (RESTRICTIVE): +
+ | | | | | (u): (cid <> 44) +
+ | | | | | to: regress_rls_dave
+ regress_rls_schema | uaccount | table | regress_rls_alice=arwdDxt/regress_rls_alice+| |
+ | | | =r/regress_rls_alice | |
+(3 rows)
+
+\d document
+ Table "regress_rls_schema.document"
+ Column | Type | Collation | Nullable | Default
+---------+---------+-----------+----------+---------
+ did | integer | | not null |
+ cid | integer | | |
+ dlevel | integer | | not null |
+ dauthor | name | | |
+ dtitle | text | | |
+Indexes:
+ "document_pkey" PRIMARY KEY, btree (did)
+Foreign-key constraints:
+ "document_cid_fkey" FOREIGN KEY (cid) REFERENCES category(cid)
+Policies:
+ POLICY "p1"
+ USING ((dlevel <= ( SELECT uaccount.seclv
+ FROM uaccount
+ WHERE (uaccount.pguser = CURRENT_USER))))
+ POLICY "p1r" AS RESTRICTIVE
+ TO regress_rls_dave
+ USING ((cid <> 44))
+ POLICY "p2r" AS RESTRICTIVE
+ TO regress_rls_dave
+ USING (((cid <> 44) AND (cid < 50)))
+
+SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename = 'document' ORDER BY policyname;
+ schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
+--------------------+-----------+------------+-------------+--------------------+-----+--------------------------------------------+------------
+ regress_rls_schema | document | p1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +|
+ | | | | | | FROM uaccount +|
+ | | | | | | WHERE (uaccount.pguser = CURRENT_USER))) |
+ regress_rls_schema | document | p1r | RESTRICTIVE | {regress_rls_dave} | ALL | (cid <> 44) |
+ regress_rls_schema | document | p2r | RESTRICTIVE | {regress_rls_dave} | ALL | ((cid <> 44) AND (cid < 50)) |
+(3 rows)
+
+-- viewpoint from regress_rls_bob
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO ON;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great manga
+NOTICE: f_leak => awesome science fiction
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+(5 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great manga
+NOTICE: f_leak => awesome science fiction
+ cid | did | dlevel | dauthor | dtitle | cname
+-----+-----+--------+-------------------+-------------------------+-----------------
+ 11 | 1 | 1 | regress_rls_bob | my first novel | novel
+ 44 | 4 | 1 | regress_rls_bob | my first manga | manga
+ 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction
+ 44 | 8 | 1 | regress_rls_carol | great manga | manga
+ 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction
+(5 rows)
+
+-- try a sampled version
+SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
+ WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great manga
+NOTICE: f_leak => awesome science fiction
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+(4 rows)
+
+-- viewpoint from regress_rls_carol
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => my science fiction
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => my second manga
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great technology book
+NOTICE: f_leak => great manga
+NOTICE: f_leak => awesome science fiction
+NOTICE: f_leak => awesome technology book
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 5 | 44 | 2 | regress_rls_bob | my second manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 33 | 2 | regress_rls_dave | awesome technology book
+(10 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => my science fiction
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => my second manga
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great technology book
+NOTICE: f_leak => great manga
+NOTICE: f_leak => awesome science fiction
+NOTICE: f_leak => awesome technology book
+ cid | did | dlevel | dauthor | dtitle | cname
+-----+-----+--------+-------------------+-------------------------+-----------------
+ 11 | 1 | 1 | regress_rls_bob | my first novel | novel
+ 11 | 2 | 2 | regress_rls_bob | my second novel | novel
+ 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction
+ 44 | 4 | 1 | regress_rls_bob | my first manga | manga
+ 44 | 5 | 2 | regress_rls_bob | my second manga | manga
+ 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction
+ 33 | 7 | 2 | regress_rls_carol | great technology book | technology
+ 44 | 8 | 1 | regress_rls_carol | great manga | manga
+ 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction
+ 33 | 10 | 2 | regress_rls_dave | awesome technology book | technology
+(10 rows)
+
+-- try a sampled version
+SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
+ WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => my second manga
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great manga
+NOTICE: f_leak => awesome science fiction
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 5 | 44 | 2 | regress_rls_bob | my second manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+ QUERY PLAN
+----------------------------------------------------
+ Seq Scan on document
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ QUERY PLAN
+-----------------------------------------------------------
+ Hash Join
+ Hash Cond: (category.cid = document.cid)
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+ -> Seq Scan on category
+ -> Hash
+ -> Seq Scan on document
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+(9 rows)
+
+-- viewpoint from regress_rls_dave
+SET SESSION AUTHORIZATION regress_rls_dave;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => my science fiction
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great technology book
+NOTICE: f_leak => awesome science fiction
+NOTICE: f_leak => awesome technology book
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 33 | 2 | regress_rls_dave | awesome technology book
+(7 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => my science fiction
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great technology book
+NOTICE: f_leak => awesome science fiction
+NOTICE: f_leak => awesome technology book
+ cid | did | dlevel | dauthor | dtitle | cname
+-----+-----+--------+-------------------+-------------------------+-----------------
+ 11 | 1 | 1 | regress_rls_bob | my first novel | novel
+ 11 | 2 | 2 | regress_rls_bob | my second novel | novel
+ 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction
+ 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction
+ 33 | 7 | 2 | regress_rls_carol | great technology book | technology
+ 22 | 9 | 1 | regress_rls_dave | awesome science fiction | science fiction
+ 33 | 10 | 2 | regress_rls_dave | awesome technology book | technology
+(7 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+ QUERY PLAN
+----------------------------------------------------------------------------------------------
+ Seq Scan on document
+ Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------
+ Hash Join
+ Hash Cond: (category.cid = document.cid)
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+ -> Seq Scan on category
+ -> Hash
+ -> Seq Scan on document
+ Filter: ((cid <> 44) AND (cid <> 44) AND (cid < 50) AND (dlevel <= $0) AND f_leak(dtitle))
+(9 rows)
+
+-- 44 would technically fail for both p2r and p1r, but we should get an error
+-- back from p1r for this because it sorts first
+INSERT INTO document VALUES (100, 44, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail
+ERROR: new row violates row-level security policy "p1r" for table "document"
+-- Just to see a p2r error
+INSERT INTO document VALUES (100, 55, 1, 'regress_rls_dave', 'testing sorting of policies'); -- fail
+ERROR: new row violates row-level security policy "p2r" for table "document"
+-- only owner can change policies
+ALTER POLICY p1 ON document USING (true); --fail
+ERROR: must be owner of table document
+DROP POLICY p1 ON document; --fail
+ERROR: must be owner of relation document
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER POLICY p1 ON document USING (dauthor = current_user);
+-- viewpoint from regress_rls_bob again
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => my science fiction
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => my second manga
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-----------------+--------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 5 | 44 | 2 | regress_rls_bob | my second manga
+(5 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => my science fiction
+NOTICE: f_leak => my first manga
+NOTICE: f_leak => my second manga
+ cid | did | dlevel | dauthor | dtitle | cname
+-----+-----+--------+-----------------+--------------------+-----------------
+ 11 | 1 | 1 | regress_rls_bob | my first novel | novel
+ 11 | 2 | 2 | regress_rls_bob | my second novel | novel
+ 22 | 3 | 2 | regress_rls_bob | my science fiction | science fiction
+ 44 | 4 | 1 | regress_rls_bob | my first manga | manga
+ 44 | 5 | 2 | regress_rls_bob | my second manga | manga
+(5 rows)
+
+-- viewpoint from rls_regres_carol again
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great technology book
+NOTICE: f_leak => great manga
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book
+ 8 | 44 | 1 | regress_rls_carol | great manga
+(3 rows)
+
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great technology book
+NOTICE: f_leak => great manga
+ cid | did | dlevel | dauthor | dtitle | cname
+-----+-----+--------+-------------------+-----------------------+-----------------
+ 22 | 6 | 1 | regress_rls_carol | great science fiction | science fiction
+ 33 | 7 | 2 | regress_rls_carol | great technology book | technology
+ 44 | 8 | 1 | regress_rls_carol | great manga | manga
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+ QUERY PLAN
+---------------------------------------------------------
+ Seq Scan on document
+ Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ QUERY PLAN
+---------------------------------------------------------------
+ Nested Loop
+ -> Seq Scan on document
+ Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+ -> Index Scan using category_pkey on category
+ Index Cond: (cid = document.cid)
+(5 rows)
+
+-- interaction of FK/PK constraints
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE POLICY p2 ON category
+ USING (CASE WHEN current_user = 'regress_rls_bob' THEN cid IN (11, 33)
+ WHEN current_user = 'regress_rls_carol' THEN cid IN (22, 44)
+ ELSE false END);
+ALTER TABLE category ENABLE ROW LEVEL SECURITY;
+-- cannot delete PK referenced by invisible FK
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;
+ did | cid | dlevel | dauthor | dtitle | cid | cname
+-----+-----+--------+-----------------+--------------------+-----+------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel | 11 | novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel | 11 | novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction | |
+ 4 | 44 | 1 | regress_rls_bob | my first manga | |
+ 5 | 44 | 2 | regress_rls_bob | my second manga | |
+ | | | | | 33 | technology
+(6 rows)
+
+DELETE FROM category WHERE cid = 33; -- fails with FK violation
+ERROR: update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document"
+DETAIL: Key is still referenced from table "document".
+-- can insert FK referencing invisible PK
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid ORDER BY d.did, c.cid;
+ did | cid | dlevel | dauthor | dtitle | cid | cname
+-----+-----+--------+-------------------+-----------------------+-----+-----------------
+ 6 | 22 | 1 | regress_rls_carol | great science fiction | 22 | science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book | |
+ 8 | 44 | 1 | regress_rls_carol | great manga | 44 | manga
+(3 rows)
+
+INSERT INTO document VALUES (11, 33, 1, current_user, 'hoge');
+-- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row
+SET SESSION AUTHORIZATION regress_rls_bob;
+INSERT INTO document VALUES (8, 44, 1, 'regress_rls_bob', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see
+ERROR: duplicate key value violates unique constraint "document_pkey"
+SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+---------+--------
+(0 rows)
+
+-- RLS policies are checked before constraints
+INSERT INTO document VALUES (8, 44, 1, 'regress_rls_carol', 'my third manga'); -- Should fail with RLS check violation, not duplicate key violation
+ERROR: new row violates row-level security policy for table "document"
+UPDATE document SET did = 8, dauthor = 'regress_rls_carol' WHERE did = 5; -- Should fail with RLS check violation, not duplicate key violation
+ERROR: new row violates row-level security policy for table "document"
+-- database superuser does bypass RLS policy when enabled
+RESET SESSION AUTHORIZATION;
+SET row_security TO ON;
+SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 5 | 44 | 2 | regress_rls_bob | my second manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 33 | 2 | regress_rls_dave | awesome technology book
+ 11 | 33 | 1 | regress_rls_carol | hoge
+(11 rows)
+
+SELECT * FROM category;
+ cid | cname
+-----+-----------------
+ 11 | novel
+ 22 | science fiction
+ 33 | technology
+ 44 | manga
+(4 rows)
+
+-- database superuser does bypass RLS policy when disabled
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 5 | 44 | 2 | regress_rls_bob | my second manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 33 | 2 | regress_rls_dave | awesome technology book
+ 11 | 33 | 1 | regress_rls_carol | hoge
+(11 rows)
+
+SELECT * FROM category;
+ cid | cname
+-----+-----------------
+ 11 | novel
+ 22 | science fiction
+ 33 | technology
+ 44 | manga
+(4 rows)
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_rls_exempt_user;
+SET row_security TO OFF;
+SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 5 | 44 | 2 | regress_rls_bob | my second manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 33 | 2 | regress_rls_dave | awesome technology book
+ 11 | 33 | 1 | regress_rls_carol | hoge
+(11 rows)
+
+SELECT * FROM category;
+ cid | cname
+-----+-----------------
+ 11 | novel
+ 22 | science fiction
+ 33 | technology
+ 44 | manga
+(4 rows)
+
+-- RLS policy does not apply to table owner when RLS enabled.
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security TO ON;
+SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 5 | 44 | 2 | regress_rls_bob | my second manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 33 | 2 | regress_rls_dave | awesome technology book
+ 11 | 33 | 1 | regress_rls_carol | hoge
+(11 rows)
+
+SELECT * FROM category;
+ cid | cname
+-----+-----------------
+ 11 | novel
+ 22 | science fiction
+ 33 | technology
+ 44 | manga
+(4 rows)
+
+-- RLS policy does not apply to table owner when RLS disabled.
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security TO OFF;
+SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 22 | 2 | regress_rls_bob | my science fiction
+ 4 | 44 | 1 | regress_rls_bob | my first manga
+ 5 | 44 | 2 | regress_rls_bob | my second manga
+ 6 | 22 | 1 | regress_rls_carol | great science fiction
+ 7 | 33 | 2 | regress_rls_carol | great technology book
+ 8 | 44 | 1 | regress_rls_carol | great manga
+ 9 | 22 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 33 | 2 | regress_rls_dave | awesome technology book
+ 11 | 33 | 1 | regress_rls_carol | hoge
+(11 rows)
+
+SELECT * FROM category;
+ cid | cname
+-----+-----------------
+ 11 | novel
+ 22 | science fiction
+ 33 | technology
+ 44 | manga
+(4 rows)
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security TO ON;
+CREATE TABLE t1 (id int not null primary key, a int, junk1 text, b text);
+ALTER TABLE t1 DROP COLUMN junk1; -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+COPY t1 FROM stdin WITH ;
+CREATE TABLE t2 (c float) INHERITS (t1);
+GRANT ALL ON t2 TO public;
+COPY t2 FROM stdin;
+CREATE TABLE t3 (id int not null primary key, c text, b text, a int);
+ALTER TABLE t3 INHERIT t1;
+GRANT ALL ON t3 TO public;
+COPY t3(id, a,b,c) FROM stdin;
+CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number
+CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE t2 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM t1;
+ id | a | b
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | dad
+ 202 | 2 | bcd
+ 204 | 4 | def
+ 302 | 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+ QUERY PLAN
+-------------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2 t1_2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3 t1_3
+ Filter: ((a % 2) = 0)
+(7 rows)
+
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE: f_leak => bbb
+NOTICE: f_leak => dad
+NOTICE: f_leak => bcd
+NOTICE: f_leak => def
+NOTICE: f_leak => yyy
+ id | a | b
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | dad
+ 202 | 2 | bcd
+ 204 | 4 | def
+ 302 | 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(7 rows)
+
+-- reference to system column
+SELECT tableoid::regclass, * FROM t1;
+ tableoid | id | a | b
+----------+-----+---+-----
+ t1 | 102 | 2 | bbb
+ t1 | 104 | 4 | dad
+ t2 | 202 | 2 | bcd
+ t2 | 204 | 4 | def
+ t3 | 302 | 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+ QUERY PLAN
+-------------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2 t1_2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3 t1_3
+ Filter: ((a % 2) = 0)
+(7 rows)
+
+-- reference to whole-row reference
+SELECT *, t1 FROM t1;
+ id | a | b | t1
+-----+---+-----+-------------
+ 102 | 2 | bbb | (102,2,bbb)
+ 104 | 4 | dad | (104,4,dad)
+ 202 | 2 | bcd | (202,2,bcd)
+ 204 | 4 | def | (204,4,def)
+ 302 | 2 | yyy | (302,2,yyy)
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+ QUERY PLAN
+-------------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2 t1_2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3 t1_3
+ Filter: ((a % 2) = 0)
+(7 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ id | a | b
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | dad
+ 202 | 2 | bcd
+ 204 | 4 | def
+ 302 | 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE;
+ QUERY PLAN
+-------------------------------------
+ LockRows
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2 t1_2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3 t1_3
+ Filter: ((a % 2) = 0)
+(8 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+NOTICE: f_leak => bbb
+NOTICE: f_leak => dad
+NOTICE: f_leak => bcd
+NOTICE: f_leak => def
+NOTICE: f_leak => yyy
+ id | a | b
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | dad
+ 202 | 2 | bcd
+ 204 | 4 | def
+ 302 | 2 | yyy
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+ QUERY PLAN
+-----------------------------------------------------
+ LockRows
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(8 rows)
+
+-- union all query
+SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3;
+ a | b | tableoid
+---+-----+----------
+ 1 | abc | t2
+ 3 | cde | t2
+ 1 | xxx | t3
+ 2 | yyy | t3
+ 3 | zzz | t3
+(5 rows)
+
+EXPLAIN (COSTS OFF) SELECT a, b, tableoid::regclass FROM t2 UNION ALL SELECT a, b, tableoid::regclass FROM t3;
+ QUERY PLAN
+-------------------------------
+ Append
+ -> Seq Scan on t2
+ Filter: ((a % 2) = 1)
+ -> Seq Scan on t3
+(4 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE: f_leak => aba
+NOTICE: f_leak => bbb
+NOTICE: f_leak => ccc
+NOTICE: f_leak => dad
+NOTICE: f_leak => abc
+NOTICE: f_leak => bcd
+NOTICE: f_leak => cde
+NOTICE: f_leak => def
+NOTICE: f_leak => xxx
+NOTICE: f_leak => yyy
+NOTICE: f_leak => zzz
+ id | a | b
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 103 | 3 | ccc
+ 104 | 4 | dad
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 203 | 3 | cde
+ 204 | 4 | def
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+---------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: f_leak(b)
+ -> Seq Scan on t2 t1_2
+ Filter: f_leak(b)
+ -> Seq Scan on t3 t1_3
+ Filter: f_leak(b)
+(7 rows)
+
+-- non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_rls_exempt_user;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE: f_leak => aba
+NOTICE: f_leak => bbb
+NOTICE: f_leak => ccc
+NOTICE: f_leak => dad
+NOTICE: f_leak => abc
+NOTICE: f_leak => bcd
+NOTICE: f_leak => cde
+NOTICE: f_leak => def
+NOTICE: f_leak => xxx
+NOTICE: f_leak => yyy
+NOTICE: f_leak => zzz
+ id | a | b
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 103 | 3 | ccc
+ 104 | 4 | dad
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 203 | 3 | cde
+ 204 | 4 | def
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+---------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: f_leak(b)
+ -> Seq Scan on t2 t1_2
+ Filter: f_leak(b)
+ -> Seq Scan on t3 t1_3
+ Filter: f_leak(b)
+(7 rows)
+
+--
+-- Partitioned Tables
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE part_document (
+ did int,
+ cid int,
+ dlevel int not null,
+ dauthor name,
+ dtitle text
+) PARTITION BY RANGE (cid);
+GRANT ALL ON part_document TO public;
+-- Create partitions for document categories
+CREATE TABLE part_document_fiction PARTITION OF part_document FOR VALUES FROM (11) to (12);
+CREATE TABLE part_document_satire PARTITION OF part_document FOR VALUES FROM (55) to (56);
+CREATE TABLE part_document_nonfiction PARTITION OF part_document FOR VALUES FROM (99) to (100);
+GRANT ALL ON part_document_fiction TO public;
+GRANT ALL ON part_document_satire TO public;
+GRANT ALL ON part_document_nonfiction TO public;
+INSERT INTO part_document VALUES
+ ( 1, 11, 1, 'regress_rls_bob', 'my first novel'),
+ ( 2, 11, 2, 'regress_rls_bob', 'my second novel'),
+ ( 3, 99, 2, 'regress_rls_bob', 'my science textbook'),
+ ( 4, 55, 1, 'regress_rls_bob', 'my first satire'),
+ ( 5, 99, 2, 'regress_rls_bob', 'my history book'),
+ ( 6, 11, 1, 'regress_rls_carol', 'great science fiction'),
+ ( 7, 99, 2, 'regress_rls_carol', 'great technology book'),
+ ( 8, 55, 2, 'regress_rls_carol', 'great satire'),
+ ( 9, 11, 1, 'regress_rls_dave', 'awesome science fiction'),
+ (10, 99, 2, 'regress_rls_dave', 'awesome technology book');
+ALTER TABLE part_document ENABLE ROW LEVEL SECURITY;
+-- Create policy on parent
+-- user's security level must be higher than or equal to document's
+CREATE POLICY pp1 ON part_document AS PERMISSIVE
+ USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+-- Dave is only allowed to see cid < 55
+CREATE POLICY pp1r ON part_document AS RESTRICTIVE TO regress_rls_dave
+ USING (cid < 55);
+\d+ part_document
+ Partitioned table "regress_rls_schema.part_document"
+ Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
+---------+---------+-----------+----------+---------+----------+--------------+-------------
+ did | integer | | | | plain | |
+ cid | integer | | | | plain | |
+ dlevel | integer | | not null | | plain | |
+ dauthor | name | | | | plain | |
+ dtitle | text | | | | extended | |
+Partition key: RANGE (cid)
+Policies:
+ POLICY "pp1"
+ USING ((dlevel <= ( SELECT uaccount.seclv
+ FROM uaccount
+ WHERE (uaccount.pguser = CURRENT_USER))))
+ POLICY "pp1r" AS RESTRICTIVE
+ TO regress_rls_dave
+ USING ((cid < 55))
+Partitions: part_document_fiction FOR VALUES FROM (11) TO (12),
+ part_document_nonfiction FOR VALUES FROM (99) TO (100),
+ part_document_satire FOR VALUES FROM (55) TO (56)
+
+SELECT * FROM pg_policies WHERE schemaname = 'regress_rls_schema' AND tablename like '%part_document%' ORDER BY policyname;
+ schemaname | tablename | policyname | permissive | roles | cmd | qual | with_check
+--------------------+---------------+------------+-------------+--------------------+-----+--------------------------------------------+------------
+ regress_rls_schema | part_document | pp1 | PERMISSIVE | {public} | ALL | (dlevel <= ( SELECT uaccount.seclv +|
+ | | | | | | FROM uaccount +|
+ | | | | | | WHERE (uaccount.pguser = CURRENT_USER))) |
+ regress_rls_schema | part_document | pp1r | RESTRICTIVE | {regress_rls_dave} | ALL | (cid < 55) |
+(2 rows)
+
+-- viewpoint from regress_rls_bob
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO ON;
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => awesome science fiction
+NOTICE: f_leak => my first satire
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+ QUERY PLAN
+------------------------------------------------------------
+ Append
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+ -> Seq Scan on part_document_fiction part_document_1
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_satire part_document_2
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_nonfiction part_document_3
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+(10 rows)
+
+-- viewpoint from regress_rls_carol
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => awesome science fiction
+NOTICE: f_leak => my first satire
+NOTICE: f_leak => great satire
+NOTICE: f_leak => my science textbook
+NOTICE: f_leak => my history book
+NOTICE: f_leak => great technology book
+NOTICE: f_leak => awesome technology book
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 99 | 2 | regress_rls_bob | my science textbook
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 5 | 99 | 2 | regress_rls_bob | my history book
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 7 | 99 | 2 | regress_rls_carol | great technology book
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 99 | 2 | regress_rls_dave | awesome technology book
+(10 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+ QUERY PLAN
+------------------------------------------------------------
+ Append
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+ -> Seq Scan on part_document_fiction part_document_1
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_satire part_document_2
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_nonfiction part_document_3
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+(10 rows)
+
+-- viewpoint from regress_rls_dave
+SET SESSION AUTHORIZATION regress_rls_dave;
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => awesome science fiction
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+ QUERY PLAN
+--------------------------------------------------------------
+ Seq Scan on part_document_fiction part_document
+ Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+(5 rows)
+
+-- pp1 ERROR
+INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail
+ERROR: new row violates row-level security policy for table "part_document"
+-- pp1r ERROR
+INSERT INTO part_document VALUES (100, 99, 1, 'regress_rls_dave', 'testing pp1r'); -- fail
+ERROR: new row violates row-level security policy "pp1r" for table "part_document"
+-- Show that RLS policy does not apply for direct inserts to children
+-- This should fail with RLS POLICY pp1r violation.
+INSERT INTO part_document VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail
+ERROR: new row violates row-level security policy "pp1r" for table "part_document"
+-- But this should succeed.
+INSERT INTO part_document_satire VALUES (100, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- success
+-- We still cannot see the row using the parent
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => awesome science fiction
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+(4 rows)
+
+-- But we can if we look directly
+SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first satire
+NOTICE: f_leak => great satire
+NOTICE: f_leak => testing RLS with partitions
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------------
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions
+(3 rows)
+
+-- Turn on RLS and create policy on child to show RLS is checked before constraints
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER TABLE part_document_satire ENABLE ROW LEVEL SECURITY;
+CREATE POLICY pp3 ON part_document_satire AS RESTRICTIVE
+ USING (cid < 55);
+-- This should fail with RLS violation now.
+SET SESSION AUTHORIZATION regress_rls_dave;
+INSERT INTO part_document_satire VALUES (101, 55, 1, 'regress_rls_dave', 'testing RLS with partitions'); -- fail
+ERROR: new row violates row-level security policy for table "part_document_satire"
+-- And now we cannot see directly into the partition either, due to RLS
+SELECT * FROM part_document_satire WHERE f_leak(dtitle) ORDER BY did;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+---------+--------
+(0 rows)
+
+-- The parent looks same as before
+-- viewpoint from regress_rls_dave
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => awesome science fiction
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+ QUERY PLAN
+--------------------------------------------------------------
+ Seq Scan on part_document_fiction part_document
+ Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+(5 rows)
+
+-- viewpoint from regress_rls_carol
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => awesome science fiction
+NOTICE: f_leak => my first satire
+NOTICE: f_leak => great satire
+NOTICE: f_leak => testing RLS with partitions
+NOTICE: f_leak => my science textbook
+NOTICE: f_leak => my history book
+NOTICE: f_leak => great technology book
+NOTICE: f_leak => awesome technology book
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 99 | 2 | regress_rls_bob | my science textbook
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 5 | 99 | 2 | regress_rls_bob | my history book
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 7 | 99 | 2 | regress_rls_carol | great technology book
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 99 | 2 | regress_rls_dave | awesome technology book
+ 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+ QUERY PLAN
+------------------------------------------------------------
+ Append
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = CURRENT_USER)
+ -> Seq Scan on part_document_fiction part_document_1
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_satire part_document_2
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+ -> Seq Scan on part_document_nonfiction part_document_3
+ Filter: ((dlevel <= $0) AND f_leak(dtitle))
+(10 rows)
+
+-- only owner can change policies
+ALTER POLICY pp1 ON part_document USING (true); --fail
+ERROR: must be owner of table part_document
+DROP POLICY pp1 ON part_document; --fail
+ERROR: must be owner of relation part_document
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER POLICY pp1 ON part_document USING (dauthor = current_user);
+-- viewpoint from regress_rls_bob again
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => my first novel
+NOTICE: f_leak => my second novel
+NOTICE: f_leak => my first satire
+NOTICE: f_leak => my science textbook
+NOTICE: f_leak => my history book
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-----------------+---------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 99 | 2 | regress_rls_bob | my science textbook
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 5 | 99 | 2 | regress_rls_bob | my history book
+(5 rows)
+
+-- viewpoint from rls_regres_carol again
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM part_document WHERE f_leak(dtitle) ORDER BY did;
+NOTICE: f_leak => great science fiction
+NOTICE: f_leak => great satire
+NOTICE: f_leak => great technology book
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 7 | 99 | 2 | regress_rls_carol | great technology book
+ 8 | 55 | 2 | regress_rls_carol | great satire
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
+ QUERY PLAN
+---------------------------------------------------------------
+ Append
+ -> Seq Scan on part_document_fiction part_document_1
+ Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+ -> Seq Scan on part_document_satire part_document_2
+ Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+ -> Seq Scan on part_document_nonfiction part_document_3
+ Filter: ((dauthor = CURRENT_USER) AND f_leak(dtitle))
+(7 rows)
+
+-- database superuser does bypass RLS policy when enabled
+RESET SESSION AUTHORIZATION;
+SET row_security TO ON;
+SELECT * FROM part_document ORDER BY did;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 99 | 2 | regress_rls_bob | my science textbook
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 5 | 99 | 2 | regress_rls_bob | my history book
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 7 | 99 | 2 | regress_rls_carol | great technology book
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 99 | 2 | regress_rls_dave | awesome technology book
+ 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions
+(11 rows)
+
+SELECT * FROM part_document_satire ORDER by did;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------------
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions
+(3 rows)
+
+-- database non-superuser with bypass privilege can bypass RLS policy when disabled
+SET SESSION AUTHORIZATION regress_rls_exempt_user;
+SET row_security TO OFF;
+SELECT * FROM part_document ORDER BY did;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 99 | 2 | regress_rls_bob | my science textbook
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 5 | 99 | 2 | regress_rls_bob | my history book
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 7 | 99 | 2 | regress_rls_carol | great technology book
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 99 | 2 | regress_rls_dave | awesome technology book
+ 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions
+(11 rows)
+
+SELECT * FROM part_document_satire ORDER by did;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------------
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions
+(3 rows)
+
+-- RLS policy does not apply to table owner when RLS enabled.
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security TO ON;
+SELECT * FROM part_document ORDER by did;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------------
+ 1 | 11 | 1 | regress_rls_bob | my first novel
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+ 3 | 99 | 2 | regress_rls_bob | my science textbook
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 5 | 99 | 2 | regress_rls_bob | my history book
+ 6 | 11 | 1 | regress_rls_carol | great science fiction
+ 7 | 99 | 2 | regress_rls_carol | great technology book
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 9 | 11 | 1 | regress_rls_dave | awesome science fiction
+ 10 | 99 | 2 | regress_rls_dave | awesome technology book
+ 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions
+(11 rows)
+
+SELECT * FROM part_document_satire ORDER by did;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-------------------+-----------------------------
+ 4 | 55 | 1 | regress_rls_bob | my first satire
+ 8 | 55 | 2 | regress_rls_carol | great satire
+ 100 | 55 | 1 | regress_rls_dave | testing RLS with partitions
+(3 rows)
+
+-- When RLS disabled, other users get ERROR.
+SET SESSION AUTHORIZATION regress_rls_dave;
+SET row_security TO OFF;
+SELECT * FROM part_document ORDER by did;
+ERROR: query would be affected by row-level security policy for table "part_document"
+SELECT * FROM part_document_satire ORDER by did;
+ERROR: query would be affected by row-level security policy for table "part_document_satire"
+-- Check behavior with a policy that uses a SubPlan not an InitPlan.
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security TO ON;
+CREATE POLICY pp3 ON part_document AS RESTRICTIVE
+ USING ((SELECT dlevel <= seclv FROM uaccount WHERE pguser = current_user));
+SET SESSION AUTHORIZATION regress_rls_carol;
+INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_carol', 'testing pp3'); -- fail
+ERROR: new row violates row-level security policy "pp3" for table "part_document"
+----- Dependencies -----
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security TO ON;
+CREATE TABLE dependee (x integer, y integer);
+CREATE TABLE dependent (x integer, y integer);
+CREATE POLICY d1 ON dependent FOR ALL
+ TO PUBLIC
+ USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));
+DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row security qual?
+ERROR: cannot drop table dependee because other objects depend on it
+DETAIL: policy d1 on table dependent depends on table dependee
+HINT: Use DROP ... CASCADE to drop the dependent objects too.
+DROP TABLE dependee CASCADE;
+NOTICE: drop cascades to policy d1 on table dependent
+EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified
+ QUERY PLAN
+-----------------------
+ Seq Scan on dependent
+(1 row)
+
+----- RECURSION ----
+--
+-- Simple recursion
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE rec1 (x integer, y integer);
+CREATE POLICY r1 ON rec1 USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));
+ALTER TABLE rec1 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rec1; -- fail, direct recursion
+ERROR: infinite recursion detected in policy for relation "rec1"
+--
+-- Mutual recursion
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE rec2 (a integer, b integer);
+ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2 WHERE b = y));
+CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1 WHERE y = b));
+ALTER TABLE rec2 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rec1; -- fail, mutual recursion
+ERROR: infinite recursion detected in policy for relation "rec1"
+--
+-- Mutual recursion via views
+--
+SET SESSION AUTHORIZATION regress_rls_bob;
+CREATE VIEW rec1v AS SELECT * FROM rec1;
+CREATE VIEW rec2v AS SELECT * FROM rec2;
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));
+ALTER POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rec1; -- fail, mutual recursion via views
+ERROR: infinite recursion detected in policy for relation "rec1"
+--
+-- Mutual recursion via .s.b views
+--
+SET SESSION AUTHORIZATION regress_rls_bob;
+DROP VIEW rec1v, rec2v CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to policy r1 on table rec1
+drop cascades to policy r2 on table rec2
+CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;
+CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE POLICY r1 ON rec1 USING (x = (SELECT a FROM rec2v WHERE b = y));
+CREATE POLICY r2 ON rec2 USING (a = (SELECT x FROM rec1v WHERE y = b));
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rec1; -- fail, mutual recursion via s.b. views
+ERROR: infinite recursion detected in policy for relation "rec1"
+--
+-- recursive RLS and VIEWs in policy
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE s1 (a int, b text);
+INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+CREATE TABLE s2 (x int, y text);
+INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);
+GRANT SELECT ON s1, s2 TO regress_rls_bob;
+CREATE POLICY p1 ON s1 USING (a in (select x from s2 where y like '%2f%'));
+CREATE POLICY p2 ON s2 USING (x in (select a from s1 where b like '%22%'));
+CREATE POLICY p3 ON s1 FOR INSERT WITH CHECK (a = (SELECT a FROM s1));
+ALTER TABLE s1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE s2 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
+SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)
+ERROR: infinite recursion detected in policy for relation "s1"
+INSERT INTO s1 VALUES (1, 'foo'); -- fail (infinite recursion)
+ERROR: infinite recursion detected in policy for relation "s1"
+SET SESSION AUTHORIZATION regress_rls_alice;
+DROP POLICY p3 on s1;
+ALTER POLICY p2 ON s2 USING (x % 2 = 0);
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM s1 WHERE f_leak(b); -- OK
+NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+ a | b
+---+----------------------------------
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM only s1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------------------
+ Seq Scan on s1
+ Filter: ((hashed SubPlan 1) AND f_leak(b))
+ SubPlan 1
+ -> Seq Scan on s2
+ Filter: (((x % 2) = 0) AND (y ~~ '%2f%'::text))
+(5 rows)
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER POLICY p1 ON s1 USING (a in (select x from v2)); -- using VIEW in RLS policy
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM s1 WHERE f_leak(b); -- OK
+NOTICE: f_leak => 0267aaf632e87a63288a08331f22c7c3
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+ a | b
+----+----------------------------------
+ -4 | 0267aaf632e87a63288a08331f22c7c3
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------------------
+ Seq Scan on s1
+ Filter: ((hashed SubPlan 1) AND f_leak(b))
+ SubPlan 1
+ -> Seq Scan on s2
+ Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))
+(5 rows)
+
+SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+ xx | x | y
+----+----+----------------------------------
+ -6 | -6 | 596a3d04481816330f07e4f97510c28f
+ -4 | -4 | 0267aaf632e87a63288a08331f22c7c3
+ 2 | 2 | c81e728d9d4c2f636f067f89cc14862c
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Seq Scan on s2
+ Filter: (((x % 2) = 0) AND (y ~~ '%28%'::text))
+ SubPlan 2
+ -> Limit
+ -> Seq Scan on s1
+ Filter: (hashed SubPlan 1)
+ SubPlan 1
+ -> Seq Scan on s2 s2_1
+ Filter: (((x % 2) = 0) AND (y ~~ '%af%'::text))
+(9 rows)
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+ALTER POLICY p2 ON s2 USING (x in (select a from s1 where b like '%d2%'));
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion via view)
+ERROR: infinite recursion detected in policy for relation "s1"
+-- prepared statement with regress_rls_alice privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+ id | a | b
+-----+---+-----
+ 102 | 2 | bbb
+ 202 | 2 | bcd
+ 302 | 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE p1(2);
+ QUERY PLAN
+----------------------------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a <= 2) AND ((a % 2) = 0))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a <= 2) AND ((a % 2) = 0))
+ -> Seq Scan on t3 t1_3
+ Filter: ((a <= 2) AND ((a % 2) = 0))
+(7 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1 WHERE f_leak(b);
+NOTICE: f_leak => aba
+NOTICE: f_leak => bbb
+NOTICE: f_leak => ccc
+NOTICE: f_leak => dad
+NOTICE: f_leak => abc
+NOTICE: f_leak => bcd
+NOTICE: f_leak => cde
+NOTICE: f_leak => def
+NOTICE: f_leak => xxx
+NOTICE: f_leak => yyy
+NOTICE: f_leak => zzz
+ id | a | b
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 103 | 3 | ccc
+ 104 | 4 | dad
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 203 | 3 | cde
+ 204 | 4 | def
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(11 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+---------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: f_leak(b)
+ -> Seq Scan on t2 t1_2
+ Filter: f_leak(b)
+ -> Seq Scan on t3 t1_3
+ Filter: f_leak(b)
+(7 rows)
+
+-- plan cache should be invalidated
+EXECUTE p1(2);
+ id | a | b
+-----+---+-----
+ 101 | 1 | aba
+ 102 | 2 | bbb
+ 201 | 1 | abc
+ 202 | 2 | bcd
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+(6 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE p1(2);
+ QUERY PLAN
+---------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: (a <= 2)
+ -> Seq Scan on t2 t1_2
+ Filter: (a <= 2)
+ -> Seq Scan on t3 t1_3
+ Filter: (a <= 2)
+(7 rows)
+
+PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+EXECUTE p2(2);
+ id | a | b
+-----+---+-----
+ 102 | 2 | bbb
+ 202 | 2 | bcd
+ 302 | 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE p2(2);
+ QUERY PLAN
+---------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: (a = 2)
+ -> Seq Scan on t2 t1_2
+ Filter: (a = 2)
+ -> Seq Scan on t3 t1_3
+ Filter: (a = 2)
+(7 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO ON;
+EXECUTE p2(2);
+ id | a | b
+-----+---+-----
+ 102 | 2 | bbb
+ 202 | 2 | bcd
+ 302 | 2 | yyy
+(3 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE p2(2);
+ QUERY PLAN
+---------------------------------------------
+ Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a = 2) AND ((a % 2) = 0))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a = 2) AND ((a % 2) = 0))
+ -> Seq Scan on t3 t1_3
+ Filter: ((a = 2) AND ((a % 2) = 0))
+(7 rows)
+
+--
+-- UPDATE / DELETE and Row-level security
+--
+SET SESSION AUTHORIZATION regress_rls_bob;
+EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------
+ Update on t1
+ Update on t1
+ Update on t2 t1_1
+ Update on t3 t1_2
+ -> Seq Scan on t1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(10 rows)
+
+UPDATE t1 SET b = b || b WHERE f_leak(b);
+NOTICE: f_leak => bbb
+NOTICE: f_leak => dad
+NOTICE: f_leak => bcd
+NOTICE: f_leak => def
+NOTICE: f_leak => yyy
+EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------
+ Update on t1
+ -> Seq Scan on t1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(3 rows)
+
+UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+NOTICE: f_leak => bbbbbb
+NOTICE: f_leak => daddad
+-- returning clause with system column
+UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
+NOTICE: f_leak => bbbbbb_updt
+NOTICE: f_leak => daddad_updt
+ tableoid | id | a | b | t1
+----------+-----+---+-------------+---------------------
+ t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt)
+ t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt)
+(2 rows)
+
+UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
+NOTICE: f_leak => bbbbbb_updt
+NOTICE: f_leak => daddad_updt
+NOTICE: f_leak => bcdbcd
+NOTICE: f_leak => defdef
+NOTICE: f_leak => yyyyyy
+ id | a | b
+-----+---+-------------
+ 102 | 2 | bbbbbb_updt
+ 104 | 4 | daddad_updt
+ 202 | 2 | bcdbcd
+ 204 | 4 | defdef
+ 302 | 2 | yyyyyy
+(5 rows)
+
+UPDATE t1 SET b = b WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
+NOTICE: f_leak => bbbbbb_updt
+NOTICE: f_leak => daddad_updt
+NOTICE: f_leak => bcdbcd
+NOTICE: f_leak => defdef
+NOTICE: f_leak => yyyyyy
+ tableoid | id | a | b | t1
+----------+-----+---+-------------+---------------------
+ t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt)
+ t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt)
+ t2 | 202 | 2 | bcdbcd | (202,2,bcdbcd)
+ t2 | 204 | 4 | defdef | (204,4,defdef)
+ t3 | 302 | 2 | yyyyyy | (302,2,yyyyyy)
+(5 rows)
+
+-- updates with from clause
+EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3
+WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+ QUERY PLAN
+-----------------------------------------------------------------
+ Update on t2
+ -> Nested Loop
+ -> Seq Scan on t2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Seq Scan on t3
+ Filter: ((a = 2) AND f_leak(b))
+(6 rows)
+
+UPDATE t2 SET b=t2.b FROM t3
+WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+NOTICE: f_leak => cde
+NOTICE: f_leak => yyyyyy
+EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ QUERY PLAN
+-----------------------------------------------------------------
+ Update on t1
+ Update on t1
+ Update on t2 t1_1
+ Update on t3 t1_2
+ -> Nested Loop
+ -> Seq Scan on t1
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Nested Loop
+ -> Seq Scan on t2 t1_1
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Nested Loop
+ -> Seq Scan on t3 t1_2
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+(19 rows)
+
+UPDATE t1 SET b=t1.b FROM t2
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Update on t2
+ -> Nested Loop
+ -> Seq Scan on t2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_3
+ Filter: ((a = 3) AND ((a % 2) = 0) AND f_leak(b))
+(11 rows)
+
+UPDATE t2 SET b=t2.b FROM t1
+WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+NOTICE: f_leak => cde
+-- updates with from clause self join
+EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+ QUERY PLAN
+-----------------------------------------------------------------
+ Update on t2 t2_1
+ -> Nested Loop
+ Join Filter: (t2_1.b = t2_2.b)
+ -> Seq Scan on t2 t2_1
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+ -> Seq Scan on t2 t2_2
+ Filter: ((a = 3) AND ((a % 2) = 1) AND f_leak(b))
+(7 rows)
+
+UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+NOTICE: f_leak => cde
+NOTICE: f_leak => cde
+ id | a | b | c | id | a | b | c | t2_1 | t2_2
+-----+---+-----+-----+-----+---+-----+-----+-----------------+-----------------
+ 203 | 3 | cde | 3.3 | 203 | 3 | cde | 3.3 | (203,3,cde,3.3) | (203,3,cde,3.3)
+(1 row)
+
+EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Update on t1 t1_1
+ Update on t1 t1_1
+ Update on t2 t1_1_1
+ Update on t3 t1_1_2
+ -> Nested Loop
+ Join Filter: (t1_1.b = t1_2.b)
+ -> Seq Scan on t1 t1_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Append
+ -> Seq Scan on t1 t1_2_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2_2
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_2_3
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Nested Loop
+ Join Filter: (t1_1_1.b = t1_2.b)
+ -> Seq Scan on t2 t1_1_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Append
+ -> Seq Scan on t1 t1_2_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2_2
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_2_3
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Nested Loop
+ Join Filter: (t1_1_2.b = t1_2.b)
+ -> Seq Scan on t3 t1_1_2
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Append
+ -> Seq Scan on t1 t1_2_1
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_2_2
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_2_3
+ Filter: ((a = 4) AND ((a % 2) = 0) AND f_leak(b))
+(37 rows)
+
+UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+NOTICE: f_leak => daddad_updt
+NOTICE: f_leak => daddad_updt
+NOTICE: f_leak => defdef
+NOTICE: f_leak => defdef
+NOTICE: f_leak => daddad_updt
+NOTICE: f_leak => defdef
+ id | a | b | id | a | b | t1_1 | t1_2
+-----+---+-------------+-----+---+-------------+---------------------+---------------------
+ 104 | 4 | daddad_updt | 104 | 4 | daddad_updt | (104,4,daddad_updt) | (104,4,daddad_updt)
+ 204 | 4 | defdef | 204 | 4 | defdef | (204,4,defdef) | (204,4,defdef)
+(2 rows)
+
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+SELECT * FROM t1 ORDER BY a,b;
+ id | a | b
+-----+---+-------------
+ 101 | 1 | aba
+ 201 | 1 | abc
+ 301 | 1 | xxx
+ 102 | 2 | bbbbbb_updt
+ 202 | 2 | bcdbcd
+ 302 | 2 | yyyyyy
+ 103 | 3 | ccc
+ 203 | 3 | cde
+ 303 | 3 | zzz
+ 104 | 4 | daddad_updt
+ 204 | 4 | defdef
+(11 rows)
+
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO ON;
+EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------
+ Delete on t1
+ -> Seq Scan on t1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(3 rows)
+
+EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------
+ Delete on t1
+ Delete on t1
+ Delete on t2 t1_1
+ Delete on t3 t1_2
+ -> Seq Scan on t1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t2 t1_1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> Seq Scan on t3 t1_2
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(10 rows)
+
+DELETE FROM only t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
+NOTICE: f_leak => bbbbbb_updt
+NOTICE: f_leak => daddad_updt
+ tableoid | id | a | b | t1
+----------+-----+---+-------------+---------------------
+ t1 | 102 | 2 | bbbbbb_updt | (102,2,bbbbbb_updt)
+ t1 | 104 | 4 | daddad_updt | (104,4,daddad_updt)
+(2 rows)
+
+DELETE FROM t1 WHERE f_leak(b) RETURNING tableoid::regclass, *, t1;
+NOTICE: f_leak => bcdbcd
+NOTICE: f_leak => defdef
+NOTICE: f_leak => yyyyyy
+ tableoid | id | a | b | t1
+----------+-----+---+--------+----------------
+ t2 | 202 | 2 | bcdbcd | (202,2,bcdbcd)
+ t2 | 204 | 4 | defdef | (204,4,defdef)
+ t3 | 302 | 2 | yyyyyy | (302,2,yyyyyy)
+(3 rows)
+
+--
+-- S.b. view on top of Row-level security
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE b1 (a int, b text);
+INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+CREATE POLICY p1 ON b1 USING (a % 2 = 0);
+ALTER TABLE b1 ENABLE ROW LEVEL SECURITY;
+GRANT ALL ON b1 TO regress_rls_bob;
+SET SESSION AUTHORIZATION regress_rls_bob;
+CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;
+GRANT ALL ON bv1 TO regress_rls_carol;
+SET SESSION AUTHORIZATION regress_rls_carol;
+EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);
+ QUERY PLAN
+---------------------------------------------
+ Subquery Scan on bv1
+ Filter: f_leak(bv1.b)
+ -> Seq Scan on b1
+ Filter: ((a > 0) AND ((a % 2) = 0))
+(4 rows)
+
+SELECT * FROM bv1 WHERE f_leak(b);
+NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
+NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
+ a | b
+----+----------------------------------
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+(5 rows)
+
+INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
+ERROR: new row violates row-level security policy for table "b1"
+INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
+ERROR: new row violates row-level security policy for table "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
+-----------------------------------------------------------------------
+ Update on b1
+ -> Seq Scan on b1
+ Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0) AND f_leak(b))
+(3 rows)
+
+UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Delete on b1
+ -> Seq Scan on b1
+ Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0) AND f_leak(b))
+(3 rows)
+
+DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM b1;
+ a | b
+-----+----------------------------------
+ -10 | 1b0fd9efa5279c4203b7c70233f86dbf
+ -9 | 252e691406782824eec43d7eadc3d256
+ -8 | a8d2ec85eaf98407310b72eb73dda247
+ -7 | 74687a12d3915d3c4d83f1af7b3683d5
+ -6 | 596a3d04481816330f07e4f97510c28f
+ -5 | 47c1b025fa18ea96c33fbb6718688c0f
+ -4 | 0267aaf632e87a63288a08331f22c7c3
+ -3 | b3149ecea4628efd23d2f86e5a723472
+ -2 | 5d7b9adcbe1c629ec722529dd12e5129
+ -1 | 6bb61e3b7bce0931da574d19d1d82c88
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 1 | c4ca4238a0b923820dcc509a6f75849b
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ 5 | e4da3b7fbbce2345d7772b0674a318d5
+ 7 | 8f14e45fceea167a5a36dedd4bea2543
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | xxx
+ 4 | yyy
+(21 rows)
+
+--
+-- INSERT ... ON CONFLICT DO UPDATE and Row-level security
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+DROP POLICY p1 ON document;
+DROP POLICY p1r ON document;
+CREATE POLICY p1 ON document FOR SELECT USING (true);
+CREATE POLICY p2 ON document FOR INSERT WITH CHECK (dauthor = current_user);
+CREATE POLICY p3 ON document FOR UPDATE
+ USING (cid = (SELECT cid from category WHERE cname = 'novel'))
+ WITH CHECK (dauthor = current_user);
+SET SESSION AUTHORIZATION regress_rls_bob;
+-- Exists...
+SELECT * FROM document WHERE did = 2;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-----------------+-----------------
+ 2 | 11 | 2 | regress_rls_bob | my second novel
+(1 row)
+
+-- ...so violates actual WITH CHECK OPTION within UPDATE (not INSERT, since
+-- alternative UPDATE path happens to be taken):
+INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, dauthor = EXCLUDED.dauthor;
+ERROR: new row violates row-level security policy for table "document"
+-- Violates USING qual for UPDATE policy p3.
+--
+-- UPDATE path is taken, but UPDATE fails purely because *existing* row to be
+-- updated is not a "novel"/cid 11 (row is not leaked, even though we have
+-- SELECT privileges sufficient to see the row in this instance):
+INSERT INTO document VALUES (33, 22, 1, 'regress_rls_bob', 'okay science fiction'); -- preparation for next statement
+INSERT INTO document VALUES (33, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'Some novel, replaces sci-fi') -- takes UPDATE path
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;
+ERROR: new row violates row-level security policy (USING expression) for table "document"
+-- Fine (we UPDATE, since INSERT WCOs and UPDATE security barrier quals + WCOs
+-- not violated):
+INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-----------------+----------------
+ 2 | 11 | 2 | regress_rls_bob | my first novel
+(1 row)
+
+-- Fine (we INSERT, so "cid = 33" ("technology") isn't evaluated):
+INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-----------------+-----------------------
+ 78 | 11 | 1 | regress_rls_bob | some technology novel
+(1 row)
+
+-- Fine (same query, but we UPDATE, so "cid = 33", ("technology") is not the
+-- case in respect of *existing* tuple):
+INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-----------------+-----------------------
+ 78 | 33 | 1 | regress_rls_bob | some technology novel
+(1 row)
+
+-- Same query a third time, but now fails due to existing tuple finally not
+-- passing quals:
+INSERT INTO document VALUES (78, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'some technology novel')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33 RETURNING *;
+ERROR: new row violates row-level security policy (USING expression) for table "document"
+-- Don't fail just because INSERT doesn't satisfy WITH CHECK option that
+-- originated as a barrier/USING() qual from the UPDATE. Note that the UPDATE
+-- path *isn't* taken, and so UPDATE-related policy does not apply:
+INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
+ did | cid | dlevel | dauthor | dtitle
+-----+-----+--------+-----------------+----------------------------------
+ 79 | 33 | 1 | regress_rls_bob | technology book, can only insert
+(1 row)
+
+-- But this time, the same statement fails, because the UPDATE path is taken,
+-- and updating the row just inserted falls afoul of security barrier qual
+-- (enforced as WCO) -- what we might have updated target tuple to is
+-- irrelevant, in fact.
+INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
+ERROR: new row violates row-level security policy (USING expression) for table "document"
+-- Test default USING qual enforced as WCO
+SET SESSION AUTHORIZATION regress_rls_alice;
+DROP POLICY p1 ON document;
+DROP POLICY p2 ON document;
+DROP POLICY p3 ON document;
+CREATE POLICY p3_with_default ON document FOR UPDATE
+ USING (cid = (SELECT cid from category WHERE cname = 'novel'));
+SET SESSION AUTHORIZATION regress_rls_bob;
+-- Just because WCO-style enforcement of USING quals occurs with
+-- existing/target tuple does not mean that the implementation can be allowed
+-- to fail to also enforce this qual against the final tuple appended to
+-- relation (since in the absence of an explicit WCO, this is also interpreted
+-- as an UPDATE/ALL WCO in general).
+--
+-- UPDATE path is taken here (fails due to existing tuple). Note that this is
+-- not reported as a "USING expression", because it's an RLS UPDATE check that originated as
+-- a USING qual for the purposes of RLS in general, as opposed to an explicit
+-- USING qual that is ordinarily a security barrier. We leave it up to the
+-- UPDATE to make this fail:
+INSERT INTO document VALUES (79, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'technology book, can only insert')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle RETURNING *;
+ERROR: new row violates row-level security policy for table "document"
+-- UPDATE path is taken here. Existing tuple passes, since its cid
+-- corresponds to "novel", but default USING qual is enforced against
+-- post-UPDATE tuple too (as always when updating with a policy that lacks an
+-- explicit WCO), and so this fails:
+INSERT INTO document VALUES (2, (SELECT cid from category WHERE cname = 'technology'), 1, 'regress_rls_bob', 'my first novel')
+ ON CONFLICT (did) DO UPDATE SET cid = EXCLUDED.cid, dtitle = EXCLUDED.dtitle RETURNING *;
+ERROR: new row violates row-level security policy for table "document"
+SET SESSION AUTHORIZATION regress_rls_alice;
+DROP POLICY p3_with_default ON document;
+--
+-- Test ALL policies with ON CONFLICT DO UPDATE (much the same as existing UPDATE
+-- tests)
+--
+CREATE POLICY p3_with_all ON document FOR ALL
+ USING (cid = (SELECT cid from category WHERE cname = 'novel'))
+ WITH CHECK (dauthor = current_user);
+SET SESSION AUTHORIZATION regress_rls_bob;
+-- Fails, since ALL WCO is enforced in insert path:
+INSERT INTO document VALUES (80, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_carol', 'my first novel')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle, cid = 33;
+ERROR: new row violates row-level security policy for table "document"
+-- Fails, since ALL policy USING qual is enforced (existing, target tuple is in
+-- violation, since it has the "manga" cid):
+INSERT INTO document VALUES (4, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
+ ON CONFLICT (did) DO UPDATE SET dtitle = EXCLUDED.dtitle;
+ERROR: new row violates row-level security policy (USING expression) for table "document"
+-- Fails, since ALL WCO are enforced:
+INSERT INTO document VALUES (1, (SELECT cid from category WHERE cname = 'novel'), 1, 'regress_rls_bob', 'my first novel')
+ ON CONFLICT (did) DO UPDATE SET dauthor = 'regress_rls_carol';
+ERROR: new row violates row-level security policy for table "document"
+--
+-- ROLE/GROUP
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE z1 (a int, b text);
+CREATE TABLE z2 (a int, b text);
+GRANT SELECT ON z1,z2 TO regress_rls_group1, regress_rls_group2,
+ regress_rls_bob, regress_rls_carol;
+INSERT INTO z1 VALUES
+ (1, 'aba'),
+ (2, 'bbb'),
+ (3, 'ccc'),
+ (4, 'dad');
+CREATE POLICY p1 ON z1 TO regress_rls_group1 USING (a % 2 = 0);
+CREATE POLICY p2 ON z1 TO regress_rls_group2 USING (a % 2 = 1);
+ALTER TABLE z1 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE: f_leak => bbb
+NOTICE: f_leak => dad
+ a | b
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+PREPARE plancache_test AS SELECT * FROM z1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+PREPARE plancache_test2 AS WITH q AS MATERIALIZED (SELECT * FROM z1 WHERE f_leak(b)) SELECT * FROM q,z2;
+EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z2
+(7 rows)
+
+PREPARE plancache_test3 AS WITH q AS MATERIALIZED (SELECT * FROM z2) SELECT * FROM q,z1 WHERE f_leak(z1.b);
+EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+ QUERY PLAN
+-----------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z2
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(7 rows)
+
+SET ROLE regress_rls_group1;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE: f_leak => bbb
+NOTICE: f_leak => dad
+ a | b
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z2
+(7 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+ QUERY PLAN
+-----------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z2
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(7 rows)
+
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE: f_leak => aba
+NOTICE: f_leak => ccc
+ a | b
+---+-----
+ 1 | aba
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z2
+(7 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+ QUERY PLAN
+-----------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z2
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(7 rows)
+
+SET ROLE regress_rls_group2;
+SELECT * FROM z1 WHERE f_leak(b);
+NOTICE: f_leak => aba
+NOTICE: f_leak => ccc
+ a | b
+---+-----
+ 1 | aba
+ 3 | ccc
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM z1 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(2 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test2;
+ QUERY PLAN
+-------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z2
+(7 rows)
+
+EXPLAIN (COSTS OFF) EXECUTE plancache_test3;
+ QUERY PLAN
+-----------------------------------------------------
+ Nested Loop
+ CTE q
+ -> Seq Scan on z2
+ -> CTE Scan on q
+ -> Materialize
+ -> Seq Scan on z1
+ Filter: (((a % 2) = 1) AND f_leak(b))
+(7 rows)
+
+--
+-- Views should follow policy for view owner.
+--
+-- View and Table owner are the same.
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
+GRANT SELECT ON rls_view TO regress_rls_bob;
+-- Query as role that is not owner of view or table. Should return all records.
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rls_view;
+NOTICE: f_leak => aba
+NOTICE: f_leak => bbb
+NOTICE: f_leak => ccc
+NOTICE: f_leak => dad
+ a | b
+---+-----
+ 1 | aba
+ 2 | bbb
+ 3 | ccc
+ 4 | dad
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+ QUERY PLAN
+---------------------
+ Seq Scan on z1
+ Filter: f_leak(b)
+(2 rows)
+
+-- Query as view/table owner. Should return all records.
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_view;
+NOTICE: f_leak => aba
+NOTICE: f_leak => bbb
+NOTICE: f_leak => ccc
+NOTICE: f_leak => dad
+ a | b
+---+-----
+ 1 | aba
+ 2 | bbb
+ 3 | ccc
+ 4 | dad
+(4 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+ QUERY PLAN
+---------------------
+ Seq Scan on z1
+ Filter: f_leak(b)
+(2 rows)
+
+DROP VIEW rls_view;
+-- View and Table owners are different.
+SET SESSION AUTHORIZATION regress_rls_bob;
+CREATE VIEW rls_view AS SELECT * FROM z1 WHERE f_leak(b);
+GRANT SELECT ON rls_view TO regress_rls_alice;
+-- Query as role that is not owner of view but is owner of table.
+-- Should return records based on view owner policies.
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM rls_view;
+NOTICE: f_leak => bbb
+NOTICE: f_leak => dad
+ a | b
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+-- Query as role that is not owner of table but is owner of view.
+-- Should return records based on view owner policies.
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM rls_view;
+NOTICE: f_leak => bbb
+NOTICE: f_leak => dad
+ a | b
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+-- Query as role that is not the owner of the table or view without permissions.
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM rls_view; --fail - permission denied.
+ERROR: permission denied for view rls_view
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view; --fail - permission denied.
+ERROR: permission denied for view rls_view
+-- Query as role that is not the owner of the table or view with permissions.
+SET SESSION AUTHORIZATION regress_rls_bob;
+GRANT SELECT ON rls_view TO regress_rls_carol;
+SELECT * FROM rls_view;
+NOTICE: f_leak => bbb
+NOTICE: f_leak => dad
+ a | b
+---+-----
+ 2 | bbb
+ 4 | dad
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM rls_view;
+ QUERY PLAN
+-----------------------------------------
+ Seq Scan on z1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(2 rows)
+
+SET SESSION AUTHORIZATION regress_rls_bob;
+DROP VIEW rls_view;
+--
+-- Command specific
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE x1 (a int, b text, c text);
+GRANT ALL ON x1 TO PUBLIC;
+INSERT INTO x1 VALUES
+ (1, 'abc', 'regress_rls_bob'),
+ (2, 'bcd', 'regress_rls_bob'),
+ (3, 'cde', 'regress_rls_carol'),
+ (4, 'def', 'regress_rls_carol'),
+ (5, 'efg', 'regress_rls_bob'),
+ (6, 'fgh', 'regress_rls_bob'),
+ (7, 'fgh', 'regress_rls_carol'),
+ (8, 'fgh', 'regress_rls_carol');
+CREATE POLICY p0 ON x1 FOR ALL USING (c = current_user);
+CREATE POLICY p1 ON x1 FOR SELECT USING (a % 2 = 0);
+CREATE POLICY p2 ON x1 FOR INSERT WITH CHECK (a % 2 = 1);
+CREATE POLICY p3 ON x1 FOR UPDATE USING (a % 2 = 0);
+CREATE POLICY p4 ON x1 FOR DELETE USING (a < 8);
+ALTER TABLE x1 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;
+NOTICE: f_leak => abc
+NOTICE: f_leak => bcd
+NOTICE: f_leak => def
+NOTICE: f_leak => efg
+NOTICE: f_leak => fgh
+NOTICE: f_leak => fgh
+ a | b | c
+---+-----+-------------------
+ 1 | abc | regress_rls_bob
+ 2 | bcd | regress_rls_bob
+ 4 | def | regress_rls_carol
+ 5 | efg | regress_rls_bob
+ 6 | fgh | regress_rls_bob
+ 8 | fgh | regress_rls_carol
+(6 rows)
+
+UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
+NOTICE: f_leak => abc
+NOTICE: f_leak => bcd
+NOTICE: f_leak => def
+NOTICE: f_leak => efg
+NOTICE: f_leak => fgh
+NOTICE: f_leak => fgh
+ a | b | c
+---+----------+-------------------
+ 1 | abc_updt | regress_rls_bob
+ 2 | bcd_updt | regress_rls_bob
+ 4 | def_updt | regress_rls_carol
+ 5 | efg_updt | regress_rls_bob
+ 6 | fgh_updt | regress_rls_bob
+ 8 | fgh_updt | regress_rls_carol
+(6 rows)
+
+SET SESSION AUTHORIZATION regress_rls_carol;
+SELECT * FROM x1 WHERE f_leak(b) ORDER BY a ASC;
+NOTICE: f_leak => cde
+NOTICE: f_leak => fgh
+NOTICE: f_leak => bcd_updt
+NOTICE: f_leak => def_updt
+NOTICE: f_leak => fgh_updt
+NOTICE: f_leak => fgh_updt
+ a | b | c
+---+----------+-------------------
+ 2 | bcd_updt | regress_rls_bob
+ 3 | cde | regress_rls_carol
+ 4 | def_updt | regress_rls_carol
+ 6 | fgh_updt | regress_rls_bob
+ 7 | fgh | regress_rls_carol
+ 8 | fgh_updt | regress_rls_carol
+(6 rows)
+
+UPDATE x1 SET b = b || '_updt' WHERE f_leak(b) RETURNING *;
+NOTICE: f_leak => cde
+NOTICE: f_leak => fgh
+NOTICE: f_leak => bcd_updt
+NOTICE: f_leak => def_updt
+NOTICE: f_leak => fgh_updt
+NOTICE: f_leak => fgh_updt
+ a | b | c
+---+---------------+-------------------
+ 3 | cde_updt | regress_rls_carol
+ 7 | fgh_updt | regress_rls_carol
+ 2 | bcd_updt_updt | regress_rls_bob
+ 4 | def_updt_updt | regress_rls_carol
+ 6 | fgh_updt_updt | regress_rls_bob
+ 8 | fgh_updt_updt | regress_rls_carol
+(6 rows)
+
+DELETE FROM x1 WHERE f_leak(b) RETURNING *;
+NOTICE: f_leak => cde_updt
+NOTICE: f_leak => fgh_updt
+NOTICE: f_leak => bcd_updt_updt
+NOTICE: f_leak => def_updt_updt
+NOTICE: f_leak => fgh_updt_updt
+NOTICE: f_leak => fgh_updt_updt
+ a | b | c
+---+---------------+-------------------
+ 3 | cde_updt | regress_rls_carol
+ 7 | fgh_updt | regress_rls_carol
+ 2 | bcd_updt_updt | regress_rls_bob
+ 4 | def_updt_updt | regress_rls_carol
+ 6 | fgh_updt_updt | regress_rls_bob
+ 8 | fgh_updt_updt | regress_rls_carol
+(6 rows)
+
+--
+-- Duplicate Policy Names
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE y1 (a int, b text);
+CREATE TABLE y2 (a int, b text);
+GRANT ALL ON y1, y2 TO regress_rls_bob;
+CREATE POLICY p1 ON y1 FOR ALL USING (a % 2 = 0);
+CREATE POLICY p2 ON y1 FOR SELECT USING (a > 2);
+CREATE POLICY p1 ON y1 FOR SELECT USING (a % 2 = 1); --fail
+ERROR: policy "p1" for table "y1" already exists
+CREATE POLICY p1 ON y2 FOR ALL USING (a % 2 = 0); --OK
+ALTER TABLE y1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE y2 ENABLE ROW LEVEL SECURITY;
+--
+-- Expression structure with SBV
+--
+-- Create view as table owner. RLS should NOT be applied.
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE VIEW rls_sbv WITH (security_barrier) AS
+ SELECT * FROM y1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
+ QUERY PLAN
+-----------------------------------
+ Seq Scan on y1
+ Filter: (f_leak(b) AND (a = 1))
+(2 rows)
+
+DROP VIEW rls_sbv;
+-- Create view as role that does not own table. RLS should be applied.
+SET SESSION AUTHORIZATION regress_rls_bob;
+CREATE VIEW rls_sbv WITH (security_barrier) AS
+ SELECT * FROM y1 WHERE f_leak(b);
+EXPLAIN (COSTS OFF) SELECT * FROM rls_sbv WHERE (a = 1);
+ QUERY PLAN
+------------------------------------------------------------------
+ Seq Scan on y1
+ Filter: ((a = 1) AND ((a > 2) OR ((a % 2) = 0)) AND f_leak(b))
+(2 rows)
+
+DROP VIEW rls_sbv;
+--
+-- Expression structure
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+INSERT INTO y2 (SELECT x, md5(x::text) FROM generate_series(0,20) x);
+CREATE POLICY p2 ON y2 USING (a % 3 = 0);
+CREATE POLICY p3 ON y2 USING (a % 4 = 0);
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM y2 WHERE f_leak(b);
+NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da
+NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
+NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26
+NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
+NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710
+NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56
+NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3
+NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf
+NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23
+NOTICE: f_leak => 98f13708210194c475687be6106a3b84
+ a | b
+----+----------------------------------
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+(14 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Seq Scan on y2
+ Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))
+(2 rows)
+
+--
+-- Qual push-down of leaky functions, when not referring to table
+--
+SELECT * FROM y2 WHERE f_leak('abc');
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+NOTICE: f_leak => abc
+ a | b
+----+----------------------------------
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+(14 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM y2 WHERE f_leak('abc');
+ QUERY PLAN
+---------------------------------------------------------------------------------------
+ Seq Scan on y2
+ Filter: (f_leak('abc'::text) AND (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)))
+(2 rows)
+
+CREATE TABLE test_qual_pushdown (
+ abc text
+);
+INSERT INTO test_qual_pushdown VALUES ('abc'),('def');
+SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
+NOTICE: f_leak => abc
+NOTICE: f_leak => def
+ a | b | abc
+---+---+-----
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(abc);
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Hash Join
+ Hash Cond: (test_qual_pushdown.abc = y2.b)
+ -> Seq Scan on test_qual_pushdown
+ Filter: f_leak(abc)
+ -> Hash
+ -> Seq Scan on y2
+ Filter: (((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0))
+(7 rows)
+
+SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
+NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da
+NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE: f_leak => eccbc87e4b5ce2fe28308fd9f2a7baf3
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
+NOTICE: f_leak => 45c48cce2e2d7fbdea1afc51c7c6ad26
+NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
+NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710
+NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56
+NOTICE: f_leak => 9bf31c7ff062936a96d3c8bd1f8f2ff3
+NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf
+NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23
+NOTICE: f_leak => 98f13708210194c475687be6106a3b84
+ a | b | abc
+---+---+-----
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM y2 JOIN test_qual_pushdown ON (b = abc) WHERE f_leak(b);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Hash Join
+ Hash Cond: (test_qual_pushdown.abc = y2.b)
+ -> Seq Scan on test_qual_pushdown
+ -> Hash
+ -> Seq Scan on y2
+ Filter: ((((a % 4) = 0) OR ((a % 3) = 0) OR ((a % 2) = 0)) AND f_leak(b))
+(6 rows)
+
+DROP TABLE test_qual_pushdown;
+--
+-- Plancache invalidate on user change.
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE t1 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table t2
+drop cascades to table t3
+CREATE TABLE t1 (a integer);
+GRANT SELECT ON t1 TO regress_rls_bob, regress_rls_carol;
+CREATE POLICY p1 ON t1 TO regress_rls_bob USING ((a % 2) = 0);
+CREATE POLICY p2 ON t1 TO regress_rls_carol USING ((a % 4) = 0);
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+-- Prepare as regress_rls_bob
+SET ROLE regress_rls_bob;
+PREPARE role_inval AS SELECT * FROM t1;
+-- Check plan
+EXPLAIN (COSTS OFF) EXECUTE role_inval;
+ QUERY PLAN
+-------------------------
+ Seq Scan on t1
+ Filter: ((a % 2) = 0)
+(2 rows)
+
+-- Change to regress_rls_carol
+SET ROLE regress_rls_carol;
+-- Check plan- should be different
+EXPLAIN (COSTS OFF) EXECUTE role_inval;
+ QUERY PLAN
+-------------------------
+ Seq Scan on t1
+ Filter: ((a % 4) = 0)
+(2 rows)
+
+-- Change back to regress_rls_bob
+SET ROLE regress_rls_bob;
+-- Check plan- should be back to original
+EXPLAIN (COSTS OFF) EXECUTE role_inval;
+ QUERY PLAN
+-------------------------
+ Seq Scan on t1
+ Filter: ((a % 2) = 0)
+(2 rows)
+
+--
+-- CTE and RLS
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE t1 CASCADE;
+CREATE TABLE t1 (a integer, b text);
+CREATE POLICY p1 ON t1 USING (a % 2 = 0);
+ALTER TABLE t1 ENABLE ROW LEVEL SECURITY;
+GRANT ALL ON t1 TO regress_rls_bob;
+INSERT INTO t1 (SELECT x, md5(x::text) FROM generate_series(0,20) x);
+SET SESSION AUTHORIZATION regress_rls_bob;
+WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
+NOTICE: f_leak => cfcd208495d565ef66e7dff9f98764da
+NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
+NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+NOTICE: f_leak => c9f0f895fb98ab9159f51fd0297e236d
+NOTICE: f_leak => d3d9446802a44259755d38e6d163e820
+NOTICE: f_leak => c20ad4d76fe97759aa27a0c99bff6710
+NOTICE: f_leak => aab3238922bcc25a6f606eb525ffdc56
+NOTICE: f_leak => c74d97b01eae257e44aa9d5bade97baf
+NOTICE: f_leak => 6f4922f45568161a8cdf4ad2299f6d23
+NOTICE: f_leak => 98f13708210194c475687be6106a3b84
+ a | b
+----+----------------------------------
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+(11 rows)
+
+EXPLAIN (COSTS OFF)
+WITH cte1 AS MATERIALIZED (SELECT * FROM t1 WHERE f_leak(b)) SELECT * FROM cte1;
+ QUERY PLAN
+-------------------------------------------------
+ CTE Scan on cte1
+ CTE cte1
+ -> Seq Scan on t1
+ Filter: (((a % 2) = 0) AND f_leak(b))
+(4 rows)
+
+WITH cte1 AS (UPDATE t1 SET a = a + 1 RETURNING *) SELECT * FROM cte1; --fail
+ERROR: new row violates row-level security policy for table "t1"
+WITH cte1 AS (UPDATE t1 SET a = a RETURNING *) SELECT * FROM cte1; --ok
+ a | b
+----+----------------------------------
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+(11 rows)
+
+WITH cte1 AS (INSERT INTO t1 VALUES (21, 'Fail') RETURNING *) SELECT * FROM cte1; --fail
+ERROR: new row violates row-level security policy for table "t1"
+WITH cte1 AS (INSERT INTO t1 VALUES (20, 'Success') RETURNING *) SELECT * FROM cte1; --ok
+ a | b
+----+---------
+ 20 | Success
+(1 row)
+
+--
+-- Rename Policy
+--
+RESET SESSION AUTHORIZATION;
+ALTER POLICY p1 ON t1 RENAME TO p1; --fail
+ERROR: policy "p1" for table "t1" already exists
+SELECT polname, relname
+ FROM pg_policy pol
+ JOIN pg_class pc ON (pc.oid = pol.polrelid)
+ WHERE relname = 't1';
+ polname | relname
+---------+---------
+ p1 | t1
+(1 row)
+
+ALTER POLICY p1 ON t1 RENAME TO p2; --ok
+SELECT polname, relname
+ FROM pg_policy pol
+ JOIN pg_class pc ON (pc.oid = pol.polrelid)
+ WHERE relname = 't1';
+ polname | relname
+---------+---------
+ p2 | t1
+(1 row)
+
+--
+-- Check INSERT SELECT
+--
+SET SESSION AUTHORIZATION regress_rls_bob;
+CREATE TABLE t2 (a integer, b text);
+INSERT INTO t2 (SELECT * FROM t1);
+EXPLAIN (COSTS OFF) INSERT INTO t2 (SELECT * FROM t1);
+ QUERY PLAN
+-------------------------------
+ Insert on t2
+ -> Seq Scan on t1
+ Filter: ((a % 2) = 0)
+(3 rows)
+
+SELECT * FROM t2;
+ a | b
+----+----------------------------------
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(12 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t2;
+ QUERY PLAN
+----------------
+ Seq Scan on t2
+(1 row)
+
+CREATE TABLE t3 AS SELECT * FROM t1;
+SELECT * FROM t3;
+ a | b
+----+----------------------------------
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(12 rows)
+
+SELECT * INTO t4 FROM t1;
+SELECT * FROM t4;
+ a | b
+----+----------------------------------
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(12 rows)
+
+--
+-- RLS with JOIN
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE blog (id integer, author text, post text);
+CREATE TABLE comment (blog_id integer, message text);
+GRANT ALL ON blog, comment TO regress_rls_bob;
+CREATE POLICY blog_1 ON blog USING (id % 2 = 0);
+ALTER TABLE blog ENABLE ROW LEVEL SECURITY;
+INSERT INTO blog VALUES
+ (1, 'alice', 'blog #1'),
+ (2, 'bob', 'blog #1'),
+ (3, 'alice', 'blog #2'),
+ (4, 'alice', 'blog #3'),
+ (5, 'john', 'blog #1');
+INSERT INTO comment VALUES
+ (1, 'cool blog'),
+ (1, 'fun blog'),
+ (3, 'crazy blog'),
+ (5, 'what?'),
+ (4, 'insane!'),
+ (2, 'who did it?');
+SET SESSION AUTHORIZATION regress_rls_bob;
+-- Check RLS JOIN with Non-RLS.
+SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
+ id | author | message
+----+--------+-------------
+ 4 | alice | insane!
+ 2 | bob | who did it?
+(2 rows)
+
+-- Check Non-RLS JOIN with RLS.
+SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
+ id | author | message
+----+--------+-------------
+ 4 | alice | insane!
+ 2 | bob | who did it?
+(2 rows)
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE POLICY comment_1 ON comment USING (blog_id < 4);
+ALTER TABLE comment ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+-- Check RLS JOIN RLS
+SELECT id, author, message FROM blog JOIN comment ON id = blog_id;
+ id | author | message
+----+--------+-------------
+ 2 | bob | who did it?
+(1 row)
+
+SELECT id, author, message FROM comment JOIN blog ON id = blog_id;
+ id | author | message
+----+--------+-------------
+ 2 | bob | who did it?
+(1 row)
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+DROP TABLE blog, comment;
+--
+-- Default Deny Policy
+--
+RESET SESSION AUTHORIZATION;
+DROP POLICY p2 ON t1;
+ALTER TABLE t1 OWNER TO regress_rls_alice;
+-- Check that default deny does not apply to superuser.
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+ a | b
+----+----------------------------------
+ 1 | c4ca4238a0b923820dcc509a6f75849b
+ 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ 5 | e4da3b7fbbce2345d7772b0674a318d5
+ 7 | 8f14e45fceea167a5a36dedd4bea2543
+ 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 11 | 6512bd43d9caa6e02c990b0a82652dca
+ 13 | c51ce410c124a10e0db5e4b97fc2af39
+ 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ 17 | 70efdf2ec9b086079795c442636b55fb
+ 19 | 1f0e3dad99908345f7439f8ffabdffc4
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(22 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+ QUERY PLAN
+----------------
+ Seq Scan on t1
+(1 row)
+
+-- Check that default deny does not apply to table owner.
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM t1;
+ a | b
+----+----------------------------------
+ 1 | c4ca4238a0b923820dcc509a6f75849b
+ 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ 5 | e4da3b7fbbce2345d7772b0674a318d5
+ 7 | 8f14e45fceea167a5a36dedd4bea2543
+ 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ 11 | 6512bd43d9caa6e02c990b0a82652dca
+ 13 | c51ce410c124a10e0db5e4b97fc2af39
+ 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ 17 | 70efdf2ec9b086079795c442636b55fb
+ 19 | 1f0e3dad99908345f7439f8ffabdffc4
+ 0 | cfcd208495d565ef66e7dff9f98764da
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ 8 | c9f0f895fb98ab9159f51fd0297e236d
+ 10 | d3d9446802a44259755d38e6d163e820
+ 12 | c20ad4d76fe97759aa27a0c99bff6710
+ 14 | aab3238922bcc25a6f606eb525ffdc56
+ 16 | c74d97b01eae257e44aa9d5bade97baf
+ 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ 20 | 98f13708210194c475687be6106a3b84
+ 20 | Success
+(22 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+ QUERY PLAN
+----------------
+ Seq Scan on t1
+(1 row)
+
+-- Check that default deny applies to non-owner/non-superuser when RLS on.
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO ON;
+SELECT * FROM t1;
+ a | b
+---+---
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM t1;
+ a | b
+---+---
+(0 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM t1;
+ QUERY PLAN
+--------------------------
+ Result
+ One-Time Filter: false
+(2 rows)
+
+--
+-- COPY TO/FROM
+--
+RESET SESSION AUTHORIZATION;
+DROP TABLE copy_t CASCADE;
+ERROR: table "copy_t" does not exist
+CREATE TABLE copy_t (a integer, b text);
+CREATE POLICY p1 ON copy_t USING (a % 2 = 0);
+ALTER TABLE copy_t ENABLE ROW LEVEL SECURITY;
+GRANT ALL ON copy_t TO regress_rls_bob, regress_rls_exempt_user;
+INSERT INTO copy_t (SELECT x, md5(x::text) FROM generate_series(0,10) x);
+-- Check COPY TO as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,cfcd208495d565ef66e7dff9f98764da
+1,c4ca4238a0b923820dcc509a6f75849b
+2,c81e728d9d4c2f636f067f89cc14862c
+3,eccbc87e4b5ce2fe28308fd9f2a7baf3
+4,a87ff679a2f3e71d9181a67b7542122c
+5,e4da3b7fbbce2345d7772b0674a318d5
+6,1679091c5a880faf6fb5e6087eb1b2dc
+7,8f14e45fceea167a5a36dedd4bea2543
+8,c9f0f895fb98ab9159f51fd0297e236d
+9,45c48cce2e2d7fbdea1afc51c7c6ad26
+10,d3d9446802a44259755d38e6d163e820
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ',';
+0,cfcd208495d565ef66e7dff9f98764da
+1,c4ca4238a0b923820dcc509a6f75849b
+2,c81e728d9d4c2f636f067f89cc14862c
+3,eccbc87e4b5ce2fe28308fd9f2a7baf3
+4,a87ff679a2f3e71d9181a67b7542122c
+5,e4da3b7fbbce2345d7772b0674a318d5
+6,1679091c5a880faf6fb5e6087eb1b2dc
+7,8f14e45fceea167a5a36dedd4bea2543
+8,c9f0f895fb98ab9159f51fd0297e236d
+9,45c48cce2e2d7fbdea1afc51c7c6ad26
+10,d3d9446802a44259755d38e6d163e820
+-- Check COPY TO as user with permissions.
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
+ERROR: query would be affected by row-level security policy for table "copy_t"
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+0,cfcd208495d565ef66e7dff9f98764da
+2,c81e728d9d4c2f636f067f89cc14862c
+4,a87ff679a2f3e71d9181a67b7542122c
+6,1679091c5a880faf6fb5e6087eb1b2dc
+8,c9f0f895fb98ab9159f51fd0297e236d
+10,d3d9446802a44259755d38e6d163e820
+-- Check COPY TO as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION regress_rls_exempt_user;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+0,cfcd208495d565ef66e7dff9f98764da
+1,c4ca4238a0b923820dcc509a6f75849b
+2,c81e728d9d4c2f636f067f89cc14862c
+3,eccbc87e4b5ce2fe28308fd9f2a7baf3
+4,a87ff679a2f3e71d9181a67b7542122c
+5,e4da3b7fbbce2345d7772b0674a318d5
+6,1679091c5a880faf6fb5e6087eb1b2dc
+7,8f14e45fceea167a5a36dedd4bea2543
+8,c9f0f895fb98ab9159f51fd0297e236d
+9,45c48cce2e2d7fbdea1afc51c7c6ad26
+10,d3d9446802a44259755d38e6d163e820
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --ok
+0,cfcd208495d565ef66e7dff9f98764da
+1,c4ca4238a0b923820dcc509a6f75849b
+2,c81e728d9d4c2f636f067f89cc14862c
+3,eccbc87e4b5ce2fe28308fd9f2a7baf3
+4,a87ff679a2f3e71d9181a67b7542122c
+5,e4da3b7fbbce2345d7772b0674a318d5
+6,1679091c5a880faf6fb5e6087eb1b2dc
+7,8f14e45fceea167a5a36dedd4bea2543
+8,c9f0f895fb98ab9159f51fd0297e236d
+9,45c48cce2e2d7fbdea1afc51c7c6ad26
+10,d3d9446802a44259755d38e6d163e820
+-- Check COPY TO as user without permissions. SET row_security TO OFF;
+SET SESSION AUTHORIZATION regress_rls_carol;
+SET row_security TO OFF;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
+ERROR: query would be affected by row-level security policy for table "copy_t"
+SET row_security TO ON;
+COPY (SELECT * FROM copy_t ORDER BY a ASC) TO STDOUT WITH DELIMITER ','; --fail - permission denied
+ERROR: permission denied for table copy_t
+-- Check COPY relation TO; keep it just one row to avoid reordering issues
+RESET SESSION AUTHORIZATION;
+SET row_security TO ON;
+CREATE TABLE copy_rel_to (a integer, b text);
+CREATE POLICY p1 ON copy_rel_to USING (a % 2 = 0);
+ALTER TABLE copy_rel_to ENABLE ROW LEVEL SECURITY;
+GRANT ALL ON copy_rel_to TO regress_rls_bob, regress_rls_exempt_user;
+INSERT INTO copy_rel_to VALUES (1, md5('1'));
+-- Check COPY TO as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
+1,c4ca4238a0b923820dcc509a6f75849b
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ',';
+1,c4ca4238a0b923820dcc509a6f75849b
+-- Check COPY TO as user with permissions.
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - would be affected by RLS
+ERROR: query would be affected by row-level security policy for table "copy_rel_to"
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+-- Check COPY TO as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION regress_rls_exempt_user;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+1,c4ca4238a0b923820dcc509a6f75849b
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --ok
+1,c4ca4238a0b923820dcc509a6f75849b
+-- Check COPY TO as user without permissions. SET row_security TO OFF;
+SET SESSION AUTHORIZATION regress_rls_carol;
+SET row_security TO OFF;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
+ERROR: permission denied for table copy_rel_to
+SET row_security TO ON;
+COPY copy_rel_to TO STDOUT WITH DELIMITER ','; --fail - permission denied
+ERROR: permission denied for table copy_rel_to
+-- Check COPY FROM as Superuser/owner.
+RESET SESSION AUTHORIZATION;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --ok
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --ok
+-- Check COPY FROM as user with permissions.
+SET SESSION AUTHORIZATION regress_rls_bob;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --fail - would be affected by RLS.
+ERROR: query would be affected by row-level security policy for table "copy_t"
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --fail - COPY FROM not supported by RLS.
+ERROR: COPY FROM not supported with row-level security
+HINT: Use INSERT statements instead.
+-- Check COPY FROM as user with permissions and BYPASSRLS
+SET SESSION AUTHORIZATION regress_rls_exempt_user;
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --ok
+-- Check COPY FROM as user without permissions.
+SET SESSION AUTHORIZATION regress_rls_carol;
+SET row_security TO OFF;
+COPY copy_t FROM STDIN; --fail - permission denied.
+ERROR: permission denied for table copy_t
+SET row_security TO ON;
+COPY copy_t FROM STDIN; --fail - permission denied.
+ERROR: permission denied for table copy_t
+RESET SESSION AUTHORIZATION;
+DROP TABLE copy_t;
+DROP TABLE copy_rel_to CASCADE;
+-- Check WHERE CURRENT OF
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE current_check (currentid int, payload text, rlsuser text);
+GRANT ALL ON current_check TO PUBLIC;
+INSERT INTO current_check VALUES
+ (1, 'abc', 'regress_rls_bob'),
+ (2, 'bcd', 'regress_rls_bob'),
+ (3, 'cde', 'regress_rls_bob'),
+ (4, 'def', 'regress_rls_bob');
+CREATE POLICY p1 ON current_check FOR SELECT USING (currentid % 2 = 0);
+CREATE POLICY p2 ON current_check FOR DELETE USING (currentid = 4 AND rlsuser = current_user);
+CREATE POLICY p3 ON current_check FOR UPDATE USING (currentid = 4) WITH CHECK (rlsuser = current_user);
+ALTER TABLE current_check ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+-- Can SELECT even rows
+SELECT * FROM current_check;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+ 4 | def | regress_rls_bob
+(2 rows)
+
+-- Cannot UPDATE row 2
+UPDATE current_check SET payload = payload || '_new' WHERE currentid = 2 RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+---------
+(0 rows)
+
+BEGIN;
+DECLARE current_check_cursor SCROLL CURSOR FOR SELECT * FROM current_check;
+-- Returns rows that can be seen according to SELECT policy, like plain SELECT
+-- above (even rows)
+FETCH ABSOLUTE 1 FROM current_check_cursor;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+(1 row)
+
+-- Still cannot UPDATE row 2 through cursor
+UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+---------
+(0 rows)
+
+-- Can update row 4 through cursor, which is the next visible row
+FETCH RELATIVE 1 FROM current_check_cursor;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 4 | def | regress_rls_bob
+(1 row)
+
+UPDATE current_check SET payload = payload || '_new' WHERE CURRENT OF current_check_cursor RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 4 | def_new | regress_rls_bob
+(1 row)
+
+SELECT * FROM current_check;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+ 4 | def_new | regress_rls_bob
+(2 rows)
+
+-- Plan should be a subquery TID scan
+EXPLAIN (COSTS OFF) UPDATE current_check SET payload = payload WHERE CURRENT OF current_check_cursor;
+ QUERY PLAN
+-------------------------------------------------------------
+ Update on current_check
+ -> Tid Scan on current_check
+ TID Cond: CURRENT OF current_check_cursor
+ Filter: ((currentid = 4) AND ((currentid % 2) = 0))
+(4 rows)
+
+-- Similarly can only delete row 4
+FETCH ABSOLUTE 1 FROM current_check_cursor;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+(1 row)
+
+DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+---------
+(0 rows)
+
+FETCH RELATIVE 1 FROM current_check_cursor;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 4 | def | regress_rls_bob
+(1 row)
+
+DELETE FROM current_check WHERE CURRENT OF current_check_cursor RETURNING *;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 4 | def_new | regress_rls_bob
+(1 row)
+
+SELECT * FROM current_check;
+ currentid | payload | rlsuser
+-----------+---------+-----------------
+ 2 | bcd | regress_rls_bob
+(1 row)
+
+COMMIT;
+--
+-- check pg_stats view filtering
+--
+SET row_security TO ON;
+SET SESSION AUTHORIZATION regress_rls_alice;
+ANALYZE current_check;
+-- Stats visible
+SELECT row_security_active('current_check');
+ row_security_active
+---------------------
+ f
+(1 row)
+
+SELECT attname, most_common_vals FROM pg_stats
+ WHERE tablename = 'current_check'
+ ORDER BY 1;
+ attname | most_common_vals
+-----------+-------------------
+ currentid |
+ payload |
+ rlsuser | {regress_rls_bob}
+(3 rows)
+
+SET SESSION AUTHORIZATION regress_rls_bob;
+-- Stats not visible
+SELECT row_security_active('current_check');
+ row_security_active
+---------------------
+ t
+(1 row)
+
+SELECT attname, most_common_vals FROM pg_stats
+ WHERE tablename = 'current_check'
+ ORDER BY 1;
+ attname | most_common_vals
+---------+------------------
+(0 rows)
+
+--
+-- Collation support
+--
+BEGIN;
+CREATE TABLE coll_t (c) AS VALUES ('bar'::text);
+CREATE POLICY coll_p ON coll_t USING (c < ('foo'::text COLLATE "C"));
+ALTER TABLE coll_t ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON coll_t TO regress_rls_alice;
+SELECT (string_to_array(polqual, ':'))[7] AS inputcollid FROM pg_policy WHERE polrelid = 'coll_t'::regclass;
+ inputcollid
+------------------
+ inputcollid 950
+(1 row)
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM coll_t;
+ c
+-----
+ bar
+(1 row)
+
+ROLLBACK;
+--
+-- Shared Object Dependencies
+--
+RESET SESSION AUTHORIZATION;
+BEGIN;
+CREATE ROLE regress_rls_eve;
+CREATE ROLE regress_rls_frank;
+CREATE TABLE tbl1 (c) AS VALUES ('bar'::text);
+GRANT SELECT ON TABLE tbl1 TO regress_rls_eve;
+CREATE POLICY P ON tbl1 TO regress_rls_eve, regress_rls_frank USING (true);
+SELECT refclassid::regclass, deptype
+ FROM pg_depend
+ WHERE classid = 'pg_policy'::regclass
+ AND refobjid = 'tbl1'::regclass;
+ refclassid | deptype
+------------+---------
+ pg_class | a
+(1 row)
+
+SELECT refclassid::regclass, deptype
+ FROM pg_shdepend
+ WHERE classid = 'pg_policy'::regclass
+ AND refobjid IN ('regress_rls_eve'::regrole, 'regress_rls_frank'::regrole);
+ refclassid | deptype
+------------+---------
+ pg_authid | r
+ pg_authid | r
+(2 rows)
+
+SAVEPOINT q;
+DROP ROLE regress_rls_eve; --fails due to dependency on POLICY p
+ERROR: role "regress_rls_eve" cannot be dropped because some objects depend on it
+DETAIL: privileges for table tbl1
+target of policy p on table tbl1
+ROLLBACK TO q;
+ALTER POLICY p ON tbl1 TO regress_rls_frank USING (true);
+SAVEPOINT q;
+DROP ROLE regress_rls_eve; --fails due to dependency on GRANT SELECT
+ERROR: role "regress_rls_eve" cannot be dropped because some objects depend on it
+DETAIL: privileges for table tbl1
+ROLLBACK TO q;
+REVOKE ALL ON TABLE tbl1 FROM regress_rls_eve;
+SAVEPOINT q;
+DROP ROLE regress_rls_eve; --succeeds
+ROLLBACK TO q;
+SAVEPOINT q;
+DROP ROLE regress_rls_frank; --fails due to dependency on POLICY p
+ERROR: role "regress_rls_frank" cannot be dropped because some objects depend on it
+DETAIL: target of policy p on table tbl1
+ROLLBACK TO q;
+DROP POLICY p ON tbl1;
+SAVEPOINT q;
+DROP ROLE regress_rls_frank; -- succeeds
+ROLLBACK TO q;
+ROLLBACK; -- cleanup
+--
+-- Converting table to view
+--
+BEGIN;
+CREATE TABLE t (c int);
+CREATE POLICY p ON t USING (c % 2 = 1);
+ALTER TABLE t ENABLE ROW LEVEL SECURITY;
+SAVEPOINT q;
+CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
+ SELECT * FROM generate_series(1,5) t0(c); -- fails due to row level security enabled
+ERROR: could not convert table "t" to a view because it has row security enabled
+ROLLBACK TO q;
+ALTER TABLE t DISABLE ROW LEVEL SECURITY;
+SAVEPOINT q;
+CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
+ SELECT * FROM generate_series(1,5) t0(c); -- fails due to policy p on t
+ERROR: could not convert table "t" to a view because it has row security policies
+ROLLBACK TO q;
+DROP POLICY p ON t;
+CREATE RULE "_RETURN" AS ON SELECT TO t DO INSTEAD
+ SELECT * FROM generate_series(1,5) t0(c); -- succeeds
+ROLLBACK;
+--
+-- Policy expression handling
+--
+BEGIN;
+CREATE TABLE t (c) AS VALUES ('bar'::text);
+CREATE POLICY p ON t USING (max(c)); -- fails: aggregate functions are not allowed in policy expressions
+ERROR: aggregate functions are not allowed in policy expressions
+ROLLBACK;
+--
+-- Non-target relations are only subject to SELECT policies
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE r1 (a int);
+CREATE TABLE r2 (a int);
+INSERT INTO r1 VALUES (10), (20);
+INSERT INTO r2 VALUES (10), (20);
+GRANT ALL ON r1, r2 TO regress_rls_bob;
+CREATE POLICY p1 ON r1 USING (true);
+ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
+CREATE POLICY p1 ON r2 FOR SELECT USING (true);
+CREATE POLICY p2 ON r2 FOR INSERT WITH CHECK (false);
+CREATE POLICY p3 ON r2 FOR UPDATE USING (false);
+CREATE POLICY p4 ON r2 FOR DELETE USING (false);
+ALTER TABLE r2 ENABLE ROW LEVEL SECURITY;
+SET SESSION AUTHORIZATION regress_rls_bob;
+SELECT * FROM r1;
+ a
+----
+ 10
+ 20
+(2 rows)
+
+SELECT * FROM r2;
+ a
+----
+ 10
+ 20
+(2 rows)
+
+-- r2 is read-only
+INSERT INTO r2 VALUES (2); -- Not allowed
+ERROR: new row violates row-level security policy for table "r2"
+UPDATE r2 SET a = 2 RETURNING *; -- Updates nothing
+ a
+---
+(0 rows)
+
+DELETE FROM r2 RETURNING *; -- Deletes nothing
+ a
+---
+(0 rows)
+
+-- r2 can be used as a non-target relation in DML
+INSERT INTO r1 SELECT a + 1 FROM r2 RETURNING *; -- OK
+ a
+----
+ 11
+ 21
+(2 rows)
+
+UPDATE r1 SET a = r2.a + 2 FROM r2 WHERE r1.a = r2.a RETURNING *; -- OK
+ a | a
+----+----
+ 12 | 10
+ 22 | 20
+(2 rows)
+
+DELETE FROM r1 USING r2 WHERE r1.a = r2.a + 2 RETURNING *; -- OK
+ a | a
+----+----
+ 12 | 10
+ 22 | 20
+(2 rows)
+
+SELECT * FROM r1;
+ a
+----
+ 11
+ 21
+(2 rows)
+
+SELECT * FROM r2;
+ a
+----
+ 10
+ 20
+(2 rows)
+
+SET SESSION AUTHORIZATION regress_rls_alice;
+DROP TABLE r1;
+DROP TABLE r2;
+--
+-- FORCE ROW LEVEL SECURITY applies RLS to owners too
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security = on;
+CREATE TABLE r1 (a int);
+INSERT INTO r1 VALUES (10), (20);
+CREATE POLICY p1 ON r1 USING (false);
+ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE r1 FORCE ROW LEVEL SECURITY;
+-- No error, but no rows
+TABLE r1;
+ a
+---
+(0 rows)
+
+-- RLS error
+INSERT INTO r1 VALUES (1);
+ERROR: new row violates row-level security policy for table "r1"
+-- No error (unable to see any rows to update)
+UPDATE r1 SET a = 1;
+TABLE r1;
+ a
+---
+(0 rows)
+
+-- No error (unable to see any rows to delete)
+DELETE FROM r1;
+TABLE r1;
+ a
+---
+(0 rows)
+
+SET row_security = off;
+-- these all fail, would be affected by RLS
+TABLE r1;
+ERROR: query would be affected by row-level security policy for table "r1"
+HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
+UPDATE r1 SET a = 1;
+ERROR: query would be affected by row-level security policy for table "r1"
+HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
+DELETE FROM r1;
+ERROR: query would be affected by row-level security policy for table "r1"
+HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
+DROP TABLE r1;
+--
+-- FORCE ROW LEVEL SECURITY does not break RI
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security = on;
+CREATE TABLE r1 (a int PRIMARY KEY);
+CREATE TABLE r2 (a int REFERENCES r1);
+INSERT INTO r1 VALUES (10), (20);
+INSERT INTO r2 VALUES (10), (20);
+-- Create policies on r2 which prevent the
+-- owner from seeing any rows, but RI should
+-- still see them.
+CREATE POLICY p1 ON r2 USING (false);
+ALTER TABLE r2 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE r2 FORCE ROW LEVEL SECURITY;
+-- Errors due to rows in r2
+DELETE FROM r1;
+ERROR: update or delete on table "r1" violates foreign key constraint "r2_a_fkey" on table "r2"
+DETAIL: Key (a)=(10) is still referenced from table "r2".
+-- Reset r2 to no-RLS
+DROP POLICY p1 ON r2;
+ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;
+ALTER TABLE r2 DISABLE ROW LEVEL SECURITY;
+-- clean out r2 for INSERT test below
+DELETE FROM r2;
+-- Change r1 to not allow rows to be seen
+CREATE POLICY p1 ON r1 USING (false);
+ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE r1 FORCE ROW LEVEL SECURITY;
+-- No rows seen
+TABLE r1;
+ a
+---
+(0 rows)
+
+-- No error, RI still sees that row exists in r1
+INSERT INTO r2 VALUES (10);
+DROP TABLE r2;
+DROP TABLE r1;
+-- Ensure cascaded DELETE works
+CREATE TABLE r1 (a int PRIMARY KEY);
+CREATE TABLE r2 (a int REFERENCES r1 ON DELETE CASCADE);
+INSERT INTO r1 VALUES (10), (20);
+INSERT INTO r2 VALUES (10), (20);
+-- Create policies on r2 which prevent the
+-- owner from seeing any rows, but RI should
+-- still see them.
+CREATE POLICY p1 ON r2 USING (false);
+ALTER TABLE r2 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE r2 FORCE ROW LEVEL SECURITY;
+-- Deletes all records from both
+DELETE FROM r1;
+-- Remove FORCE from r2
+ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;
+-- As owner, we now bypass RLS
+-- verify no rows in r2 now
+TABLE r2;
+ a
+---
+(0 rows)
+
+DROP TABLE r2;
+DROP TABLE r1;
+-- Ensure cascaded UPDATE works
+CREATE TABLE r1 (a int PRIMARY KEY);
+CREATE TABLE r2 (a int REFERENCES r1 ON UPDATE CASCADE);
+INSERT INTO r1 VALUES (10), (20);
+INSERT INTO r2 VALUES (10), (20);
+-- Create policies on r2 which prevent the
+-- owner from seeing any rows, but RI should
+-- still see them.
+CREATE POLICY p1 ON r2 USING (false);
+ALTER TABLE r2 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE r2 FORCE ROW LEVEL SECURITY;
+-- Updates records in both
+UPDATE r1 SET a = a+5;
+-- Remove FORCE from r2
+ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;
+-- As owner, we now bypass RLS
+-- verify records in r2 updated
+TABLE r2;
+ a
+----
+ 15
+ 25
+(2 rows)
+
+DROP TABLE r2;
+DROP TABLE r1;
+--
+-- Test INSERT+RETURNING applies SELECT policies as
+-- WithCheckOptions (meaning an error is thrown)
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security = on;
+CREATE TABLE r1 (a int);
+CREATE POLICY p1 ON r1 FOR SELECT USING (false);
+CREATE POLICY p2 ON r1 FOR INSERT WITH CHECK (true);
+ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE r1 FORCE ROW LEVEL SECURITY;
+-- Works fine
+INSERT INTO r1 VALUES (10), (20);
+-- No error, but no rows
+TABLE r1;
+ a
+---
+(0 rows)
+
+SET row_security = off;
+-- fail, would be affected by RLS
+TABLE r1;
+ERROR: query would be affected by row-level security policy for table "r1"
+HINT: To disable the policy for the table's owner, use ALTER TABLE NO FORCE ROW LEVEL SECURITY.
+SET row_security = on;
+-- Error
+INSERT INTO r1 VALUES (10), (20) RETURNING *;
+ERROR: new row violates row-level security policy for table "r1"
+DROP TABLE r1;
+--
+-- Test UPDATE+RETURNING applies SELECT policies as
+-- WithCheckOptions (meaning an error is thrown)
+--
+SET SESSION AUTHORIZATION regress_rls_alice;
+SET row_security = on;
+CREATE TABLE r1 (a int PRIMARY KEY);
+CREATE POLICY p1 ON r1 FOR SELECT USING (a < 20);
+CREATE POLICY p2 ON r1 FOR UPDATE USING (a < 20) WITH CHECK (true);
+CREATE POLICY p3 ON r1 FOR INSERT WITH CHECK (true);
+INSERT INTO r1 VALUES (10);
+ALTER TABLE r1 ENABLE ROW LEVEL SECURITY;
+ALTER TABLE r1 FORCE ROW LEVEL SECURITY;
+-- Works fine
+UPDATE r1 SET a = 30;
+-- Show updated rows
+ALTER TABLE r1 NO FORCE ROW LEVEL SECURITY;
+TABLE r1;
+ a
+----
+ 30
+(1 row)
+
+-- reset value in r1 for test with RETURNING
+UPDATE r1 SET a = 10;
+-- Verify row reset
+TABLE r1;
+ a
+----
+ 10
+(1 row)
+
+ALTER TABLE r1 FORCE ROW LEVEL SECURITY;
+-- Error
+UPDATE r1 SET a = 30 RETURNING *;
+ERROR: new row violates row-level security policy for table "r1"
+-- UPDATE path of INSERT ... ON CONFLICT DO UPDATE should also error out
+INSERT INTO r1 VALUES (10)
+ ON CONFLICT (a) DO UPDATE SET a = 30 RETURNING *;
+ERROR: new row violates row-level security policy for table "r1"
+-- Should still error out without RETURNING (use of arbiter always requires
+-- SELECT permissions)
+INSERT INTO r1 VALUES (10)
+ ON CONFLICT (a) DO UPDATE SET a = 30;
+ERROR: new row violates row-level security policy for table "r1"
+INSERT INTO r1 VALUES (10)
+ ON CONFLICT ON CONSTRAINT r1_pkey DO UPDATE SET a = 30;
+ERROR: new row violates row-level security policy for table "r1"
+DROP TABLE r1;
+-- Check dependency handling
+RESET SESSION AUTHORIZATION;
+CREATE TABLE dep1 (c1 int);
+CREATE TABLE dep2 (c1 int);
+CREATE POLICY dep_p1 ON dep1 TO regress_rls_bob USING (c1 > (select max(dep2.c1) from dep2));
+ALTER POLICY dep_p1 ON dep1 TO regress_rls_bob,regress_rls_carol;
+-- Should return one
+SELECT count(*) = 1 FROM pg_depend
+ WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')
+ AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');
+ ?column?
+----------
+ t
+(1 row)
+
+ALTER POLICY dep_p1 ON dep1 USING (true);
+-- Should return one
+SELECT count(*) = 1 FROM pg_shdepend
+ WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')
+ AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_bob');
+ ?column?
+----------
+ t
+(1 row)
+
+-- Should return one
+SELECT count(*) = 1 FROM pg_shdepend
+ WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')
+ AND refobjid = (SELECT oid FROM pg_authid WHERE rolname = 'regress_rls_carol');
+ ?column?
+----------
+ t
+(1 row)
+
+-- Should return zero
+SELECT count(*) = 0 FROM pg_depend
+ WHERE objid = (SELECT oid FROM pg_policy WHERE polname = 'dep_p1')
+ AND refobjid = (SELECT oid FROM pg_class WHERE relname = 'dep2');
+ ?column?
+----------
+ t
+(1 row)
+
+-- DROP OWNED BY testing
+RESET SESSION AUTHORIZATION;
+CREATE ROLE regress_rls_dob_role1;
+CREATE ROLE regress_rls_dob_role2;
+CREATE TABLE dob_t1 (c1 int);
+CREATE TABLE dob_t2 (c1 int) PARTITION BY RANGE (c1);
+CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1 USING (true);
+DROP OWNED BY regress_rls_dob_role1;
+DROP POLICY p1 ON dob_t1; -- should fail, already gone
+ERROR: policy "p1" for table "dob_t1" does not exist
+CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);
+DROP OWNED BY regress_rls_dob_role1;
+DROP POLICY p1 ON dob_t1; -- should succeed
+-- same cases with duplicate polroles entries
+CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1 USING (true);
+DROP OWNED BY regress_rls_dob_role1;
+DROP POLICY p1 ON dob_t1; -- should fail, already gone
+ERROR: policy "p1" for table "dob_t1" does not exist
+CREATE POLICY p1 ON dob_t1 TO regress_rls_dob_role1,regress_rls_dob_role1,regress_rls_dob_role2 USING (true);
+DROP OWNED BY regress_rls_dob_role1;
+DROP POLICY p1 ON dob_t1; -- should succeed
+-- partitioned target
+CREATE POLICY p1 ON dob_t2 TO regress_rls_dob_role1,regress_rls_dob_role2 USING (true);
+DROP OWNED BY regress_rls_dob_role1;
+DROP POLICY p1 ON dob_t2; -- should succeed
+DROP USER regress_rls_dob_role1;
+DROP USER regress_rls_dob_role2;
+-- Bug #15708: view + table with RLS should check policies as view owner
+CREATE TABLE ref_tbl (a int);
+INSERT INTO ref_tbl VALUES (1);
+CREATE TABLE rls_tbl (a int);
+INSERT INTO rls_tbl VALUES (10);
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY p1 ON rls_tbl USING (EXISTS (SELECT 1 FROM ref_tbl));
+GRANT SELECT ON ref_tbl TO regress_rls_bob;
+GRANT SELECT ON rls_tbl TO regress_rls_bob;
+CREATE VIEW rls_view AS SELECT * FROM rls_tbl;
+ALTER VIEW rls_view OWNER TO regress_rls_bob;
+GRANT SELECT ON rls_view TO regress_rls_alice;
+SET SESSION AUTHORIZATION regress_rls_alice;
+SELECT * FROM ref_tbl; -- Permission denied
+ERROR: permission denied for table ref_tbl
+SELECT * FROM rls_tbl; -- Permission denied
+ERROR: permission denied for table rls_tbl
+SELECT * FROM rls_view; -- OK
+ a
+----
+ 10
+(1 row)
+
+RESET SESSION AUTHORIZATION;
+DROP VIEW rls_view;
+DROP TABLE rls_tbl;
+DROP TABLE ref_tbl;
+-- Leaky operator test
+CREATE TABLE rls_tbl (a int);
+INSERT INTO rls_tbl SELECT x/10 FROM generate_series(1, 100) x;
+ANALYZE rls_tbl;
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+GRANT SELECT ON rls_tbl TO regress_rls_alice;
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE FUNCTION op_leak(int, int) RETURNS bool
+ AS 'BEGIN RAISE NOTICE ''op_leak => %, %'', $1, $2; RETURN $1 < $2; END'
+ LANGUAGE plpgsql;
+CREATE OPERATOR <<< (procedure = op_leak, leftarg = int, rightarg = int,
+ restrict = scalarltsel);
+SELECT * FROM rls_tbl WHERE a <<< 1000;
+ a
+---
+(0 rows)
+
+DROP OPERATOR <<< (int, int);
+DROP FUNCTION op_leak(int, int);
+RESET SESSION AUTHORIZATION;
+DROP TABLE rls_tbl;
+-- Bug #16006: whole-row Vars in a policy don't play nice with sub-selects
+SET SESSION AUTHORIZATION regress_rls_alice;
+CREATE TABLE rls_tbl (a int, b int, c int);
+CREATE POLICY p1 ON rls_tbl USING (rls_tbl >= ROW(1,1,1));
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_tbl FORCE ROW LEVEL SECURITY;
+INSERT INTO rls_tbl SELECT 10, 20, 30;
+EXPLAIN (VERBOSE, COSTS OFF)
+INSERT INTO rls_tbl
+ SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss;
+ QUERY PLAN
+--------------------------------------------------------------------
+ Insert on regress_rls_schema.rls_tbl
+ -> Subquery Scan on ss
+ Output: ss.b, ss.c, NULL::integer
+ -> Sort
+ Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a
+ Sort Key: rls_tbl_1.a
+ -> Seq Scan on regress_rls_schema.rls_tbl rls_tbl_1
+ Output: rls_tbl_1.b, rls_tbl_1.c, rls_tbl_1.a
+ Filter: (rls_tbl_1.* >= '(1,1,1)'::record)
+(9 rows)
+
+INSERT INTO rls_tbl
+ SELECT * FROM (SELECT b, c FROM rls_tbl ORDER BY a) ss;
+SELECT * FROM rls_tbl;
+ a | b | c
+----+----+----
+ 10 | 20 | 30
+ 20 | 30 |
+(2 rows)
+
+DROP TABLE rls_tbl;
+RESET SESSION AUTHORIZATION;
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA regress_rls_schema CASCADE;
+NOTICE: drop cascades to 29 other objects
+DETAIL: drop cascades to function f_leak(text)
+drop cascades to table uaccount
+drop cascades to table category
+drop cascades to table document
+drop cascades to table part_document
+drop cascades to table dependent
+drop cascades to table rec1
+drop cascades to table rec2
+drop cascades to view rec1v
+drop cascades to view rec2v
+drop cascades to table s1
+drop cascades to table s2
+drop cascades to view v2
+drop cascades to table b1
+drop cascades to view bv1
+drop cascades to table z1
+drop cascades to table z2
+drop cascades to table x1
+drop cascades to table y1
+drop cascades to table y2
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+drop cascades to table t4
+drop cascades to table current_check
+drop cascades to table dep1
+drop cascades to table dep2
+drop cascades to table dob_t1
+drop cascades to table dob_t2
+DROP USER regress_rls_alice;
+DROP USER regress_rls_bob;
+DROP USER regress_rls_carol;
+DROP USER regress_rls_dave;
+DROP USER regress_rls_exempt_user;
+DROP ROLE regress_rls_group1;
+DROP ROLE regress_rls_group2;
+-- Arrange to have a few policies left over, for testing
+-- pg_dump/pg_restore
+CREATE SCHEMA regress_rls_schema;
+CREATE TABLE rls_tbl (c1 int);
+ALTER TABLE rls_tbl ENABLE ROW LEVEL SECURITY;
+CREATE POLICY p1 ON rls_tbl USING (c1 > 5);
+CREATE POLICY p2 ON rls_tbl FOR SELECT USING (c1 <= 3);
+CREATE POLICY p3 ON rls_tbl FOR UPDATE USING (c1 <= 3) WITH CHECK (c1 > 5);
+CREATE POLICY p4 ON rls_tbl FOR DELETE USING (c1 <= 3);
+CREATE TABLE rls_tbl_force (c1 int);
+ALTER TABLE rls_tbl_force ENABLE ROW LEVEL SECURITY;
+ALTER TABLE rls_tbl_force FORCE ROW LEVEL SECURITY;
+CREATE POLICY p1 ON rls_tbl_force USING (c1 = 5) WITH CHECK (c1 < 5);
+CREATE POLICY p2 ON rls_tbl_force FOR SELECT USING (c1 = 8);
+CREATE POLICY p3 ON rls_tbl_force FOR UPDATE USING (c1 = 8) WITH CHECK (c1 >= 5);
+CREATE POLICY p4 ON rls_tbl_force FOR DELETE USING (c1 = 8);