mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-10-01 12:21:18 +02:00
cb1ca4d800
Foreign tables can now be inheritance children, or parents. Much of the system was already ready for this, but we had to fix a few things of course, mostly in the area of planner and executor handling of row locks. As side effects of this, allow foreign tables to have NOT VALID CHECK constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to disallow these things would've required bizarre and inconsistent special cases in inheritance behavior. Since foreign tables don't enforce CHECK constraints anyway, a NOT VALID one is a complete no-op, but that doesn't mean we shouldn't allow it. And it's possible that some FDWs might have use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops for most. An additional change in support of this is that when a ModifyTable node has multiple target tables, they will all now be explicitly identified in EXPLAIN output, for example: Update on pt1 (cost=0.00..321.05 rows=3541 width=46) Update on pt1 Foreign Update on ft1 Foreign Update on ft2 Update on child3 -> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46) -> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46) -> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46) -> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46) This was done mainly to provide an unambiguous place to attach "Remote SQL" fields, but it is useful for inherited updates even when no foreign tables are involved. Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro Horiguchi, some additional hacking by me
2295 lines
83 KiB
Plaintext
2295 lines
83 KiB
Plaintext
--
|
|
-- UPDATABLE VIEWS
|
|
--
|
|
-- check that non-updatable views and columns are rejected with useful error
|
|
-- messages
|
|
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
|
|
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
|
|
CREATE VIEW ro_view1 AS SELECT DISTINCT a, b FROM base_tbl; -- DISTINCT not supported
|
|
CREATE VIEW ro_view2 AS SELECT a, b FROM base_tbl GROUP BY a, b; -- GROUP BY not supported
|
|
CREATE VIEW ro_view3 AS SELECT 1 FROM base_tbl HAVING max(a) > 0; -- HAVING not supported
|
|
CREATE VIEW ro_view4 AS SELECT count(*) FROM base_tbl; -- Aggregate functions not supported
|
|
CREATE VIEW ro_view5 AS SELECT a, rank() OVER() FROM base_tbl; -- Window functions not supported
|
|
CREATE VIEW ro_view6 AS SELECT a, b FROM base_tbl UNION SELECT -a, b FROM base_tbl; -- Set ops not supported
|
|
CREATE VIEW ro_view7 AS WITH t AS (SELECT a, b FROM base_tbl) SELECT * FROM t; -- WITH not supported
|
|
CREATE VIEW ro_view8 AS SELECT a, b FROM base_tbl ORDER BY a OFFSET 1; -- OFFSET not supported
|
|
CREATE VIEW ro_view9 AS SELECT a, b FROM base_tbl ORDER BY a LIMIT 1; -- LIMIT not supported
|
|
CREATE VIEW ro_view10 AS SELECT 1 AS a; -- No base relations
|
|
CREATE VIEW ro_view11 AS SELECT b1.a, b2.b FROM base_tbl b1, base_tbl b2; -- Multiple base relations
|
|
CREATE VIEW ro_view12 AS SELECT * FROM generate_series(1, 10) AS g(a); -- SRF in rangetable
|
|
CREATE VIEW ro_view13 AS SELECT a, b FROM (SELECT * FROM base_tbl) AS t; -- Subselect in rangetable
|
|
CREATE VIEW rw_view14 AS SELECT ctid, a, b FROM base_tbl; -- System columns may be part of an updatable view
|
|
CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
|
|
CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
|
|
CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
|
|
CREATE VIEW ro_view18 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
|
|
CREATE SEQUENCE seq;
|
|
CREATE VIEW ro_view19 AS SELECT * FROM seq; -- View based on a sequence
|
|
CREATE VIEW ro_view20 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
ro_view1 | NO
|
|
ro_view10 | NO
|
|
ro_view11 | NO
|
|
ro_view12 | NO
|
|
ro_view13 | NO
|
|
ro_view17 | NO
|
|
ro_view18 | NO
|
|
ro_view19 | NO
|
|
ro_view2 | NO
|
|
ro_view20 | NO
|
|
ro_view3 | NO
|
|
ro_view4 | NO
|
|
ro_view5 | NO
|
|
ro_view6 | NO
|
|
ro_view7 | NO
|
|
ro_view8 | NO
|
|
ro_view9 | NO
|
|
rw_view14 | YES
|
|
rw_view15 | YES
|
|
rw_view16 | YES
|
|
(20 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
ro_view1 | NO | NO
|
|
ro_view10 | NO | NO
|
|
ro_view11 | NO | NO
|
|
ro_view12 | NO | NO
|
|
ro_view13 | NO | NO
|
|
ro_view17 | NO | NO
|
|
ro_view18 | NO | NO
|
|
ro_view19 | NO | NO
|
|
ro_view2 | NO | NO
|
|
ro_view20 | NO | NO
|
|
ro_view3 | NO | NO
|
|
ro_view4 | NO | NO
|
|
ro_view5 | NO | NO
|
|
ro_view6 | NO | NO
|
|
ro_view7 | NO | NO
|
|
ro_view8 | NO | NO
|
|
ro_view9 | NO | NO
|
|
rw_view14 | YES | YES
|
|
rw_view15 | YES | YES
|
|
rw_view16 | YES | YES
|
|
(20 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+---------------+--------------
|
|
ro_view1 | a | NO
|
|
ro_view1 | b | NO
|
|
ro_view10 | a | NO
|
|
ro_view11 | a | NO
|
|
ro_view11 | b | NO
|
|
ro_view12 | a | NO
|
|
ro_view13 | a | NO
|
|
ro_view13 | b | NO
|
|
ro_view17 | a | NO
|
|
ro_view17 | b | NO
|
|
ro_view18 | a | NO
|
|
ro_view19 | sequence_name | NO
|
|
ro_view19 | last_value | NO
|
|
ro_view19 | start_value | NO
|
|
ro_view19 | increment_by | NO
|
|
ro_view19 | max_value | NO
|
|
ro_view19 | min_value | NO
|
|
ro_view19 | cache_value | NO
|
|
ro_view19 | log_cnt | NO
|
|
ro_view19 | is_cycled | NO
|
|
ro_view19 | is_called | NO
|
|
ro_view2 | a | NO
|
|
ro_view2 | b | NO
|
|
ro_view20 | a | NO
|
|
ro_view20 | b | NO
|
|
ro_view20 | g | NO
|
|
ro_view3 | ?column? | NO
|
|
ro_view4 | count | NO
|
|
ro_view5 | a | NO
|
|
ro_view5 | rank | NO
|
|
ro_view6 | a | NO
|
|
ro_view6 | b | NO
|
|
ro_view7 | a | NO
|
|
ro_view7 | b | NO
|
|
ro_view8 | a | NO
|
|
ro_view8 | b | NO
|
|
ro_view9 | a | NO
|
|
ro_view9 | b | NO
|
|
rw_view14 | ctid | NO
|
|
rw_view14 | a | YES
|
|
rw_view14 | b | YES
|
|
rw_view15 | a | YES
|
|
rw_view15 | upper | NO
|
|
rw_view16 | a | YES
|
|
rw_view16 | b | YES
|
|
rw_view16 | aa | YES
|
|
(46 rows)
|
|
|
|
-- Read-only views
|
|
DELETE FROM ro_view1;
|
|
ERROR: cannot delete from view "ro_view1"
|
|
DETAIL: Views containing DISTINCT are not automatically updatable.
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
|
DELETE FROM ro_view2;
|
|
ERROR: cannot delete from view "ro_view2"
|
|
DETAIL: Views containing GROUP BY are not automatically updatable.
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
|
DELETE FROM ro_view3;
|
|
ERROR: cannot delete from view "ro_view3"
|
|
DETAIL: Views containing HAVING are not automatically updatable.
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
|
DELETE FROM ro_view4;
|
|
ERROR: cannot delete from view "ro_view4"
|
|
DETAIL: Views that return aggregate functions are not automatically updatable.
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
|
DELETE FROM ro_view5;
|
|
ERROR: cannot delete from view "ro_view5"
|
|
DETAIL: Views that return window functions are not automatically updatable.
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
|
DELETE FROM ro_view6;
|
|
ERROR: cannot delete from view "ro_view6"
|
|
DETAIL: Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
|
UPDATE ro_view7 SET a=a+1;
|
|
ERROR: cannot update view "ro_view7"
|
|
DETAIL: Views containing WITH are not automatically updatable.
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
|
UPDATE ro_view8 SET a=a+1;
|
|
ERROR: cannot update view "ro_view8"
|
|
DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
|
UPDATE ro_view9 SET a=a+1;
|
|
ERROR: cannot update view "ro_view9"
|
|
DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
|
UPDATE ro_view10 SET a=a+1;
|
|
ERROR: cannot update view "ro_view10"
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
|
UPDATE ro_view11 SET a=a+1;
|
|
ERROR: cannot update view "ro_view11"
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
|
UPDATE ro_view12 SET a=a+1;
|
|
ERROR: cannot update view "ro_view12"
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
|
INSERT INTO ro_view13 VALUES (3, 'Row 3');
|
|
ERROR: cannot insert into view "ro_view13"
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
|
HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
|
|
-- Partially updatable view
|
|
INSERT INTO rw_view14 VALUES (null, 3, 'Row 3'); -- should fail
|
|
ERROR: cannot insert into column "ctid" of view "rw_view14"
|
|
DETAIL: View columns that refer to system columns are not updatable.
|
|
INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
|
|
UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
|
|
ERROR: cannot update column "ctid" of view "rw_view14"
|
|
DETAIL: View columns that refer to system columns are not updatable.
|
|
UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
----+--------
|
|
-2 | Row -2
|
|
-1 | Row -1
|
|
0 | Row 0
|
|
1 | Row 1
|
|
2 | Row 2
|
|
3 | ROW 3
|
|
(6 rows)
|
|
|
|
DELETE FROM rw_view14 WHERE a=3; -- should be OK
|
|
-- Partially updatable view
|
|
INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
|
|
ERROR: cannot insert into column "upper" of view "rw_view15"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
INSERT INTO rw_view15 (a) VALUES (3); -- should be OK
|
|
ALTER VIEW rw_view15 ALTER COLUMN upper SET DEFAULT 'NOT SET';
|
|
INSERT INTO rw_view15 (a) VALUES (4); -- should fail
|
|
ERROR: cannot insert into column "upper" of view "rw_view15"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
|
|
ERROR: cannot update column "upper" of view "rw_view15"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
|
|
ERROR: cannot update column "upper" of view "rw_view15"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
----+-------------
|
|
-2 | Row -2
|
|
-1 | Row -1
|
|
0 | Row 0
|
|
1 | Row 1
|
|
2 | Row 2
|
|
4 | Unspecified
|
|
(6 rows)
|
|
|
|
DELETE FROM rw_view15 WHERE a=4; -- should be OK
|
|
-- Partially updatable view
|
|
INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
|
|
ERROR: multiple assignments to same column "a"
|
|
INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should be OK
|
|
UPDATE rw_view16 SET a=3, aa=-3 WHERE a=3; -- should fail
|
|
ERROR: multiple assignments to same column "a"
|
|
UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
----+--------
|
|
-2 | Row -2
|
|
-1 | Row -1
|
|
0 | Row 0
|
|
1 | Row 1
|
|
2 | Row 2
|
|
-3 | Row 3
|
|
(6 rows)
|
|
|
|
DELETE FROM rw_view16 WHERE a=-3; -- should be OK
|
|
-- Read-only views
|
|
INSERT INTO ro_view17 VALUES (3, 'ROW 3');
|
|
ERROR: cannot insert into view "ro_view1"
|
|
DETAIL: Views containing DISTINCT are not automatically updatable.
|
|
HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
|
|
DELETE FROM ro_view18;
|
|
ERROR: cannot delete from view "ro_view18"
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
|
UPDATE ro_view19 SET max_value=1000;
|
|
ERROR: cannot update view "ro_view19"
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
|
UPDATE ro_view20 SET b=upper(b);
|
|
ERROR: cannot update view "ro_view20"
|
|
DETAIL: Views that return set-returning functions are not automatically updatable.
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 16 other objects
|
|
DETAIL: drop cascades to view ro_view1
|
|
drop cascades to view ro_view17
|
|
drop cascades to view ro_view2
|
|
drop cascades to view ro_view3
|
|
drop cascades to view ro_view5
|
|
drop cascades to view ro_view6
|
|
drop cascades to view ro_view7
|
|
drop cascades to view ro_view8
|
|
drop cascades to view ro_view9
|
|
drop cascades to view ro_view11
|
|
drop cascades to view ro_view13
|
|
drop cascades to view rw_view15
|
|
drop cascades to view rw_view16
|
|
drop cascades to view ro_view20
|
|
drop cascades to view ro_view4
|
|
drop cascades to view rw_view14
|
|
DROP VIEW ro_view10, ro_view12, ro_view18;
|
|
DROP SEQUENCE seq CASCADE;
|
|
NOTICE: drop cascades to view ro_view19
|
|
-- simple updatable view
|
|
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
|
|
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0;
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name = 'rw_view1';
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | YES
|
|
(1 row)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name = 'rw_view1';
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view1 | YES | YES
|
|
(1 row)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'rw_view1'
|
|
ORDER BY ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | YES
|
|
rw_view1 | b | YES
|
|
(2 rows)
|
|
|
|
INSERT INTO rw_view1 VALUES (3, 'Row 3');
|
|
INSERT INTO rw_view1 (a) VALUES (4);
|
|
UPDATE rw_view1 SET a=5 WHERE a=4;
|
|
DELETE FROM rw_view1 WHERE b='Row 2';
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
----+-------------
|
|
-2 | Row -2
|
|
-1 | Row -1
|
|
0 | Row 0
|
|
1 | Row 1
|
|
3 | Row 3
|
|
5 | Unspecified
|
|
(6 rows)
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
|
|
QUERY PLAN
|
|
--------------------------------------------------
|
|
Update on base_tbl
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
Index Cond: ((a > 0) AND (a = 5))
|
|
(3 rows)
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
|
|
QUERY PLAN
|
|
--------------------------------------------------
|
|
Delete on base_tbl
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
Index Cond: ((a > 0) AND (a = 5))
|
|
(3 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
-- view on top of view
|
|
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
|
|
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
|
|
CREATE VIEW rw_view1 AS SELECT b AS bb, a AS aa FROM base_tbl WHERE a>0;
|
|
CREATE VIEW rw_view2 AS SELECT aa AS aaa, bb AS bbb FROM rw_view1 WHERE aa<10;
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name = 'rw_view2';
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view2 | YES
|
|
(1 row)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name = 'rw_view2';
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view2 | YES | YES
|
|
(1 row)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'rw_view2'
|
|
ORDER BY ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view2 | aaa | YES
|
|
rw_view2 | bbb | YES
|
|
(2 rows)
|
|
|
|
INSERT INTO rw_view2 VALUES (3, 'Row 3');
|
|
INSERT INTO rw_view2 (aaa) VALUES (4);
|
|
SELECT * FROM rw_view2;
|
|
aaa | bbb
|
|
-----+-------------
|
|
1 | Row 1
|
|
2 | Row 2
|
|
3 | Row 3
|
|
4 | Unspecified
|
|
(4 rows)
|
|
|
|
UPDATE rw_view2 SET bbb='Row 4' WHERE aaa=4;
|
|
DELETE FROM rw_view2 WHERE aaa=2;
|
|
SELECT * FROM rw_view2;
|
|
aaa | bbb
|
|
-----+-------
|
|
1 | Row 1
|
|
3 | Row 3
|
|
4 | Row 4
|
|
(3 rows)
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
|
|
QUERY PLAN
|
|
--------------------------------------------------------
|
|
Update on base_tbl
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
Index Cond: ((a < 10) AND (a > 0) AND (a = 4))
|
|
(3 rows)
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE aaa=4;
|
|
QUERY PLAN
|
|
--------------------------------------------------------
|
|
Delete on base_tbl
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
Index Cond: ((a < 10) AND (a > 0) AND (a = 4))
|
|
(3 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
-- view on top of view with rules
|
|
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
|
|
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0 OFFSET 0; -- not updatable without rules/triggers
|
|
CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a<10;
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | NO
|
|
rw_view2 | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view1 | NO | NO
|
|
rw_view2 | NO | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | NO
|
|
rw_view1 | b | NO
|
|
rw_view2 | a | NO
|
|
rw_view2 | b | NO
|
|
(4 rows)
|
|
|
|
CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
|
|
DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, NEW.b) RETURNING *;
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | YES
|
|
rw_view2 | YES
|
|
(2 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view1 | NO | YES
|
|
rw_view2 | NO | YES
|
|
(2 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | NO
|
|
rw_view1 | b | NO
|
|
rw_view2 | a | NO
|
|
rw_view2 | b | NO
|
|
(4 rows)
|
|
|
|
CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
|
|
DO INSTEAD UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a RETURNING NEW.*;
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | YES
|
|
rw_view2 | YES
|
|
(2 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view1 | NO | YES
|
|
rw_view2 | NO | YES
|
|
(2 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | NO
|
|
rw_view1 | b | NO
|
|
rw_view2 | a | NO
|
|
rw_view2 | b | NO
|
|
(4 rows)
|
|
|
|
CREATE RULE rw_view1_del_rule AS ON DELETE TO rw_view1
|
|
DO INSTEAD DELETE FROM base_tbl WHERE a=OLD.a RETURNING OLD.*;
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | YES
|
|
rw_view2 | YES
|
|
(2 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view1 | YES | YES
|
|
rw_view2 | YES | YES
|
|
(2 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | YES
|
|
rw_view1 | b | YES
|
|
rw_view2 | a | YES
|
|
rw_view2 | b | YES
|
|
(4 rows)
|
|
|
|
INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
|
|
a | b
|
|
---+-------
|
|
3 | Row 3
|
|
(1 row)
|
|
|
|
UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
|
|
a | b
|
|
---+-----------
|
|
3 | Row three
|
|
(1 row)
|
|
|
|
SELECT * FROM rw_view2;
|
|
a | b
|
|
---+-----------
|
|
1 | Row 1
|
|
2 | Row 2
|
|
3 | Row three
|
|
(3 rows)
|
|
|
|
DELETE FROM rw_view2 WHERE a=3 RETURNING *;
|
|
a | b
|
|
---+-----------
|
|
3 | Row three
|
|
(1 row)
|
|
|
|
SELECT * FROM rw_view2;
|
|
a | b
|
|
---+-------
|
|
1 | Row 1
|
|
2 | Row 2
|
|
(2 rows)
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
|
|
QUERY PLAN
|
|
----------------------------------------------------------------
|
|
Update on base_tbl
|
|
-> Nested Loop
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
Index Cond: (a = 2)
|
|
-> Subquery Scan on rw_view1
|
|
Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
|
|
-> Bitmap Heap Scan on base_tbl base_tbl_1
|
|
Recheck Cond: (a > 0)
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
Index Cond: (a > 0)
|
|
(10 rows)
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
|
|
QUERY PLAN
|
|
----------------------------------------------------------------
|
|
Delete on base_tbl
|
|
-> Nested Loop
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
Index Cond: (a = 2)
|
|
-> Subquery Scan on rw_view1
|
|
Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
|
|
-> Bitmap Heap Scan on base_tbl base_tbl_1
|
|
Recheck Cond: (a > 0)
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
Index Cond: (a > 0)
|
|
(10 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
-- view on top of view with triggers
|
|
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
|
|
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a>0 OFFSET 0; -- not updatable without rules/triggers
|
|
CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a<10;
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | NO
|
|
rw_view2 | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into,
|
|
is_trigger_updatable, is_trigger_deletable,
|
|
is_trigger_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
rw_view1 | NO | NO | NO | NO | NO
|
|
rw_view2 | NO | NO | NO | NO | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | NO
|
|
rw_view1 | b | NO
|
|
rw_view2 | a | NO
|
|
rw_view2 | b | NO
|
|
(4 rows)
|
|
|
|
CREATE FUNCTION rw_view1_trig_fn()
|
|
RETURNS trigger AS
|
|
$$
|
|
BEGIN
|
|
IF TG_OP = 'INSERT' THEN
|
|
INSERT INTO base_tbl VALUES (NEW.a, NEW.b);
|
|
RETURN NEW;
|
|
ELSIF TG_OP = 'UPDATE' THEN
|
|
UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a;
|
|
RETURN NEW;
|
|
ELSIF TG_OP = 'DELETE' THEN
|
|
DELETE FROM base_tbl WHERE a=OLD.a;
|
|
RETURN OLD;
|
|
END IF;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql;
|
|
CREATE TRIGGER rw_view1_ins_trig INSTEAD OF INSERT ON rw_view1
|
|
FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | NO
|
|
rw_view2 | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into,
|
|
is_trigger_updatable, is_trigger_deletable,
|
|
is_trigger_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
rw_view1 | NO | NO | NO | NO | YES
|
|
rw_view2 | NO | NO | NO | NO | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | NO
|
|
rw_view1 | b | NO
|
|
rw_view2 | a | NO
|
|
rw_view2 | b | NO
|
|
(4 rows)
|
|
|
|
CREATE TRIGGER rw_view1_upd_trig INSTEAD OF UPDATE ON rw_view1
|
|
FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | NO
|
|
rw_view2 | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into,
|
|
is_trigger_updatable, is_trigger_deletable,
|
|
is_trigger_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
rw_view1 | NO | NO | YES | NO | YES
|
|
rw_view2 | NO | NO | NO | NO | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | NO
|
|
rw_view1 | b | NO
|
|
rw_view2 | a | NO
|
|
rw_view2 | b | NO
|
|
(4 rows)
|
|
|
|
CREATE TRIGGER rw_view1_del_trig INSTEAD OF DELETE ON rw_view1
|
|
FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | NO
|
|
rw_view2 | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into,
|
|
is_trigger_updatable, is_trigger_deletable,
|
|
is_trigger_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
rw_view1 | NO | NO | YES | YES | YES
|
|
rw_view2 | NO | NO | NO | NO | NO
|
|
(2 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE 'rw_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | a | NO
|
|
rw_view1 | b | NO
|
|
rw_view2 | a | NO
|
|
rw_view2 | b | NO
|
|
(4 rows)
|
|
|
|
INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
|
|
a | b
|
|
---+-------
|
|
3 | Row 3
|
|
(1 row)
|
|
|
|
UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
|
|
a | b
|
|
---+-----------
|
|
3 | Row three
|
|
(1 row)
|
|
|
|
SELECT * FROM rw_view2;
|
|
a | b
|
|
---+-----------
|
|
1 | Row 1
|
|
2 | Row 2
|
|
3 | Row three
|
|
(3 rows)
|
|
|
|
DELETE FROM rw_view2 WHERE a=3 RETURNING *;
|
|
a | b
|
|
---+-----------
|
|
3 | Row three
|
|
(1 row)
|
|
|
|
SELECT * FROM rw_view2;
|
|
a | b
|
|
---+-------
|
|
1 | Row 1
|
|
2 | Row 2
|
|
(2 rows)
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
|
|
QUERY PLAN
|
|
----------------------------------------------------------
|
|
Update on rw_view1 rw_view1_1
|
|
-> Subquery Scan on rw_view1
|
|
Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
|
|
-> Bitmap Heap Scan on base_tbl
|
|
Recheck Cond: (a > 0)
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
Index Cond: (a > 0)
|
|
(7 rows)
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
|
|
QUERY PLAN
|
|
----------------------------------------------------------
|
|
Delete on rw_view1 rw_view1_1
|
|
-> Subquery Scan on rw_view1
|
|
Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
|
|
-> Bitmap Heap Scan on base_tbl
|
|
Recheck Cond: (a > 0)
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
Index Cond: (a > 0)
|
|
(7 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
DROP FUNCTION rw_view1_trig_fn();
|
|
-- update using whole row from view
|
|
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
|
|
INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
|
|
CREATE VIEW rw_view1 AS SELECT b AS bb, a AS aa FROM base_tbl;
|
|
CREATE FUNCTION rw_view1_aa(x rw_view1)
|
|
RETURNS int AS $$ SELECT x.aa $$ LANGUAGE sql;
|
|
UPDATE rw_view1 v SET bb='Updated row 2' WHERE rw_view1_aa(v)=2
|
|
RETURNING rw_view1_aa(v), v.bb;
|
|
rw_view1_aa | bb
|
|
-------------+---------------
|
|
2 | Updated row 2
|
|
(1 row)
|
|
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
----+---------------
|
|
-2 | Row -2
|
|
-1 | Row -1
|
|
0 | Row 0
|
|
1 | Row 1
|
|
2 | Updated row 2
|
|
(5 rows)
|
|
|
|
EXPLAIN (costs off)
|
|
UPDATE rw_view1 v SET bb='Updated row 2' WHERE rw_view1_aa(v)=2
|
|
RETURNING rw_view1_aa(v), v.bb;
|
|
QUERY PLAN
|
|
--------------------------------------------------
|
|
Update on base_tbl
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
Index Cond: (a = 2)
|
|
(3 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to function rw_view1_aa(rw_view1)
|
|
-- permissions checks
|
|
CREATE USER view_user1;
|
|
CREATE USER view_user2;
|
|
SET SESSION AUTHORIZATION view_user1;
|
|
CREATE TABLE base_tbl(a int, b text, c float);
|
|
INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
|
|
CREATE VIEW rw_view1 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
|
|
INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2);
|
|
GRANT SELECT ON base_tbl TO view_user2;
|
|
GRANT SELECT ON rw_view1 TO view_user2;
|
|
GRANT UPDATE (a,c) ON base_tbl TO view_user2;
|
|
GRANT UPDATE (bb,cc) ON rw_view1 TO view_user2;
|
|
RESET SESSION AUTHORIZATION;
|
|
SET SESSION AUTHORIZATION view_user2;
|
|
CREATE VIEW rw_view2 AS SELECT b AS bb, c AS cc, a AS aa FROM base_tbl;
|
|
SELECT * FROM base_tbl; -- ok
|
|
a | b | c
|
|
---+-------+---
|
|
1 | Row 1 | 1
|
|
2 | Row 2 | 2
|
|
(2 rows)
|
|
|
|
SELECT * FROM rw_view1; -- ok
|
|
bb | cc | aa
|
|
-------+----+----
|
|
Row 1 | 1 | 1
|
|
Row 2 | 2 | 2
|
|
(2 rows)
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
bb | cc | aa
|
|
-------+----+----
|
|
Row 1 | 1 | 1
|
|
Row 2 | 2 | 2
|
|
(2 rows)
|
|
|
|
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
|
|
ERROR: permission denied for relation rw_view1
|
|
INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
UPDATE base_tbl SET a=a, c=c; -- ok
|
|
UPDATE base_tbl SET b=b; -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
UPDATE rw_view1 SET bb=bb, cc=cc; -- ok
|
|
UPDATE rw_view1 SET aa=aa; -- not allowed
|
|
ERROR: permission denied for relation rw_view1
|
|
UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
|
|
UPDATE rw_view2 SET bb=bb; -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
DELETE FROM base_tbl; -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
DELETE FROM rw_view1; -- not allowed
|
|
ERROR: permission denied for relation rw_view1
|
|
DELETE FROM rw_view2; -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
RESET SESSION AUTHORIZATION;
|
|
SET SESSION AUTHORIZATION view_user1;
|
|
GRANT INSERT, DELETE ON base_tbl TO view_user2;
|
|
RESET SESSION AUTHORIZATION;
|
|
SET SESSION AUTHORIZATION view_user2;
|
|
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok
|
|
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
|
|
ERROR: permission denied for relation rw_view1
|
|
INSERT INTO rw_view2 VALUES ('Row 4', 4.0, 4); -- ok
|
|
DELETE FROM base_tbl WHERE a=1; -- ok
|
|
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
|
|
ERROR: permission denied for relation rw_view1
|
|
DELETE FROM rw_view2 WHERE aa=2; -- ok
|
|
SELECT * FROM base_tbl;
|
|
a | b | c
|
|
---+-------+---
|
|
3 | Row 3 | 3
|
|
4 | Row 4 | 4
|
|
(2 rows)
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
SET SESSION AUTHORIZATION view_user1;
|
|
REVOKE INSERT, DELETE ON base_tbl FROM view_user2;
|
|
GRANT INSERT, DELETE ON rw_view1 TO view_user2;
|
|
RESET SESSION AUTHORIZATION;
|
|
SET SESSION AUTHORIZATION view_user2;
|
|
INSERT INTO base_tbl VALUES (5, 'Row 5', 5.0); -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
INSERT INTO rw_view1 VALUES ('Row 5', 5.0, 5); -- ok
|
|
INSERT INTO rw_view2 VALUES ('Row 6', 6.0, 6); -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
DELETE FROM base_tbl WHERE a=3; -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
DELETE FROM rw_view1 WHERE aa=3; -- ok
|
|
DELETE FROM rw_view2 WHERE aa=4; -- not allowed
|
|
ERROR: permission denied for relation base_tbl
|
|
SELECT * FROM base_tbl;
|
|
a | b | c
|
|
---+-------+---
|
|
4 | Row 4 | 4
|
|
5 | Row 5 | 5
|
|
(2 rows)
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
DROP USER view_user1;
|
|
DROP USER view_user2;
|
|
-- column defaults
|
|
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified', c serial);
|
|
INSERT INTO base_tbl VALUES (1, 'Row 1');
|
|
INSERT INTO base_tbl VALUES (2, 'Row 2');
|
|
INSERT INTO base_tbl VALUES (3);
|
|
CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl;
|
|
ALTER VIEW rw_view1 ALTER COLUMN bb SET DEFAULT 'View default';
|
|
INSERT INTO rw_view1 VALUES (4, 'Row 4');
|
|
INSERT INTO rw_view1 (aa) VALUES (5);
|
|
SELECT * FROM base_tbl;
|
|
a | b | c
|
|
---+--------------+---
|
|
1 | Row 1 | 1
|
|
2 | Row 2 | 2
|
|
3 | Unspecified | 3
|
|
4 | Row 4 | 4
|
|
5 | View default | 5
|
|
(5 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
-- Table having triggers
|
|
CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
|
|
INSERT INTO base_tbl VALUES (1, 'Row 1');
|
|
INSERT INTO base_tbl VALUES (2, 'Row 2');
|
|
CREATE FUNCTION rw_view1_trig_fn()
|
|
RETURNS trigger AS
|
|
$$
|
|
BEGIN
|
|
IF TG_OP = 'INSERT' THEN
|
|
UPDATE base_tbl SET b=NEW.b WHERE a=1;
|
|
RETURN NULL;
|
|
END IF;
|
|
RETURN NULL;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql;
|
|
CREATE TRIGGER rw_view1_ins_trig AFTER INSERT ON base_tbl
|
|
FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
|
|
CREATE VIEW rw_view1 AS SELECT a AS aa, b AS bb FROM base_tbl;
|
|
INSERT INTO rw_view1 VALUES (3, 'Row 3');
|
|
select * from base_tbl;
|
|
a | b
|
|
---+-------
|
|
2 | Row 2
|
|
3 | Row 3
|
|
1 | Row 3
|
|
(3 rows)
|
|
|
|
DROP VIEW rw_view1;
|
|
DROP TRIGGER rw_view1_ins_trig on base_tbl;
|
|
DROP FUNCTION rw_view1_trig_fn();
|
|
DROP TABLE base_tbl;
|
|
-- view with ORDER BY
|
|
CREATE TABLE base_tbl (a int, b int);
|
|
INSERT INTO base_tbl VALUES (1,2), (4,5), (3,-3);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl ORDER BY a+b;
|
|
SELECT * FROM rw_view1;
|
|
a | b
|
|
---+----
|
|
3 | -3
|
|
1 | 2
|
|
4 | 5
|
|
(3 rows)
|
|
|
|
INSERT INTO rw_view1 VALUES (7,-8);
|
|
SELECT * FROM rw_view1;
|
|
a | b
|
|
---+----
|
|
7 | -8
|
|
3 | -3
|
|
1 | 2
|
|
4 | 5
|
|
(4 rows)
|
|
|
|
EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *;
|
|
QUERY PLAN
|
|
-------------------------------------------------------------
|
|
Update on public.base_tbl
|
|
Output: base_tbl.a, base_tbl.b
|
|
-> Seq Scan on public.base_tbl
|
|
Output: base_tbl.a, (base_tbl.b + 1), base_tbl.ctid
|
|
(4 rows)
|
|
|
|
UPDATE rw_view1 SET b = b + 1 RETURNING *;
|
|
a | b
|
|
---+----
|
|
1 | 3
|
|
4 | 6
|
|
3 | -2
|
|
7 | -7
|
|
(4 rows)
|
|
|
|
SELECT * FROM rw_view1;
|
|
a | b
|
|
---+----
|
|
7 | -7
|
|
3 | -2
|
|
1 | 3
|
|
4 | 6
|
|
(4 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
-- multiple array-column updates
|
|
CREATE TABLE base_tbl (a int, arr int[]);
|
|
INSERT INTO base_tbl VALUES (1,ARRAY[2]), (3,ARRAY[4]);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
|
|
UPDATE rw_view1 SET arr[1] = 42, arr[2] = 77 WHERE a = 3;
|
|
SELECT * FROM rw_view1;
|
|
a | arr
|
|
---+---------
|
|
1 | {2}
|
|
3 | {42,77}
|
|
(2 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
-- views with updatable and non-updatable columns
|
|
CREATE TABLE base_tbl(a float);
|
|
INSERT INTO base_tbl SELECT i/10.0 FROM generate_series(1,10) g(i);
|
|
CREATE VIEW rw_view1 AS
|
|
SELECT ctid, sin(a) s, a, cos(a) c
|
|
FROM base_tbl
|
|
WHERE a != 0
|
|
ORDER BY abs(a);
|
|
INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail
|
|
ERROR: cannot insert into column "ctid" of view "rw_view1"
|
|
DETAIL: View columns that refer to system columns are not updatable.
|
|
INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
|
|
ERROR: cannot insert into column "s" of view "rw_view1"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
|
|
a | s | c
|
|
-----+-------------------+-------------------
|
|
1.1 | 0.891207360061435 | 0.453596121425577
|
|
(1 row)
|
|
|
|
UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
|
|
ERROR: cannot update column "s" of view "rw_view1"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
|
|
s
|
|
-------------------
|
|
0.867423225594017
|
|
(1 row)
|
|
|
|
DELETE FROM rw_view1 WHERE a = 1.05; -- OK
|
|
CREATE VIEW rw_view2 AS
|
|
SELECT s, c, s/c t, a base_a, ctid
|
|
FROM rw_view1;
|
|
INSERT INTO rw_view2 VALUES (null, null, null, 1.1, null); -- should fail
|
|
ERROR: cannot insert into column "t" of view "rw_view2"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
|
|
ERROR: cannot insert into column "s" of view "rw_view1"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
|
|
t
|
|
------------------
|
|
1.96475965724865
|
|
(1 row)
|
|
|
|
UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
|
|
ERROR: cannot update column "s" of view "rw_view1"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
|
|
ERROR: cannot update column "t" of view "rw_view2"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
UPDATE rw_view2 SET base_a = 1.05 WHERE base_a = 1.1; -- OK
|
|
DELETE FROM rw_view2 WHERE base_a = 1.05 RETURNING base_a, s, c, t; -- OK
|
|
base_a | s | c | t
|
|
--------+-------------------+-------------------+------------------
|
|
1.05 | 0.867423225594017 | 0.497571047891727 | 1.74331530998317
|
|
(1 row)
|
|
|
|
CREATE VIEW rw_view3 AS
|
|
SELECT s, c, s/c t, ctid
|
|
FROM rw_view1;
|
|
INSERT INTO rw_view3 VALUES (null, null, null, null); -- should fail
|
|
ERROR: cannot insert into column "t" of view "rw_view3"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
INSERT INTO rw_view3(s) VALUES (null); -- should fail
|
|
ERROR: cannot insert into column "s" of view "rw_view1"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
UPDATE rw_view3 SET s = s; -- should fail
|
|
ERROR: cannot update column "s" of view "rw_view1"
|
|
DETAIL: View columns that are not columns of their base relation are not updatable.
|
|
DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
a
|
|
-----
|
|
0.2
|
|
0.3
|
|
0.4
|
|
0.5
|
|
0.6
|
|
0.7
|
|
0.8
|
|
0.9
|
|
1
|
|
(9 rows)
|
|
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | YES
|
|
rw_view2 | YES
|
|
rw_view3 | NO
|
|
(3 rows)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
ORDER BY table_name;
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view1 | YES | YES
|
|
rw_view2 | YES | YES
|
|
rw_view3 | NO | NO
|
|
(3 rows)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
ORDER BY table_name, ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | ctid | NO
|
|
rw_view1 | s | NO
|
|
rw_view1 | a | YES
|
|
rw_view1 | c | NO
|
|
rw_view2 | s | NO
|
|
rw_view2 | c | NO
|
|
rw_view2 | t | NO
|
|
rw_view2 | base_a | YES
|
|
rw_view2 | ctid | NO
|
|
rw_view3 | s | NO
|
|
rw_view3 | c | NO
|
|
rw_view3 | t | NO
|
|
rw_view3 | ctid | NO
|
|
(13 rows)
|
|
|
|
SELECT events & 4 != 0 AS upd,
|
|
events & 8 != 0 AS ins,
|
|
events & 16 != 0 AS del
|
|
FROM pg_catalog.pg_relation_is_updatable('rw_view3'::regclass, false) t(events);
|
|
upd | ins | del
|
|
-----+-----+-----
|
|
f | f | t
|
|
(1 row)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 3 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
drop cascades to view rw_view3
|
|
-- inheritance tests
|
|
CREATE TABLE base_tbl_parent (a int);
|
|
CREATE TABLE base_tbl_child (CHECK (a > 0)) INHERITS (base_tbl_parent);
|
|
INSERT INTO base_tbl_parent SELECT * FROM generate_series(-8, -1);
|
|
INSERT INTO base_tbl_child SELECT * FROM generate_series(1, 8);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl_parent;
|
|
CREATE VIEW rw_view2 AS SELECT * FROM ONLY base_tbl_parent;
|
|
SELECT * FROM rw_view1 ORDER BY a;
|
|
a
|
|
----
|
|
-8
|
|
-7
|
|
-6
|
|
-5
|
|
-4
|
|
-3
|
|
-2
|
|
-1
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
(16 rows)
|
|
|
|
SELECT * FROM ONLY rw_view1 ORDER BY a;
|
|
a
|
|
----
|
|
-8
|
|
-7
|
|
-6
|
|
-5
|
|
-4
|
|
-3
|
|
-2
|
|
-1
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
(16 rows)
|
|
|
|
SELECT * FROM rw_view2 ORDER BY a;
|
|
a
|
|
----
|
|
-8
|
|
-7
|
|
-6
|
|
-5
|
|
-4
|
|
-3
|
|
-2
|
|
-1
|
|
(8 rows)
|
|
|
|
INSERT INTO rw_view1 VALUES (-100), (100);
|
|
INSERT INTO rw_view2 VALUES (-200), (200);
|
|
UPDATE rw_view1 SET a = a*10 WHERE a IN (-1, 1); -- Should produce -10 and 10
|
|
UPDATE ONLY rw_view1 SET a = a*10 WHERE a IN (-2, 2); -- Should produce -20 and 20
|
|
UPDATE rw_view2 SET a = a*10 WHERE a IN (-3, 3); -- Should produce -30 only
|
|
UPDATE ONLY rw_view2 SET a = a*10 WHERE a IN (-4, 4); -- Should produce -40 only
|
|
DELETE FROM rw_view1 WHERE a IN (-5, 5); -- Should delete -5 and 5
|
|
DELETE FROM ONLY rw_view1 WHERE a IN (-6, 6); -- Should delete -6 and 6
|
|
DELETE FROM rw_view2 WHERE a IN (-7, 7); -- Should delete -7 only
|
|
DELETE FROM ONLY rw_view2 WHERE a IN (-8, 8); -- Should delete -8 only
|
|
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
|
|
a
|
|
------
|
|
-200
|
|
-100
|
|
-40
|
|
-30
|
|
-20
|
|
-10
|
|
100
|
|
200
|
|
(8 rows)
|
|
|
|
SELECT * FROM base_tbl_child ORDER BY a;
|
|
a
|
|
----
|
|
3
|
|
4
|
|
7
|
|
8
|
|
10
|
|
20
|
|
(6 rows)
|
|
|
|
DROP TABLE base_tbl_parent, base_tbl_child CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
-- simple WITH CHECK OPTION
|
|
CREATE TABLE base_tbl (a int, b int DEFAULT 10);
|
|
INSERT INTO base_tbl VALUES (1,2), (2,3), (1,-1);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b
|
|
WITH LOCAL CHECK OPTION;
|
|
\d+ rw_view1
|
|
View "public.rw_view1"
|
|
Column | Type | Modifiers | Storage | Description
|
|
--------+---------+-----------+---------+-------------
|
|
a | integer | | plain |
|
|
b | integer | | plain |
|
|
View definition:
|
|
SELECT base_tbl.a,
|
|
base_tbl.b
|
|
FROM base_tbl
|
|
WHERE base_tbl.a < base_tbl.b;
|
|
Options: check_option=local
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
|
|
table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
---------------+--------------+------------+------------------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
regression | public | rw_view1 | SELECT base_tbl.a, +| LOCAL | YES | YES | NO | NO | NO
|
|
| | | base_tbl.b +| | | | | |
|
|
| | | FROM base_tbl +| | | | | |
|
|
| | | WHERE (base_tbl.a < base_tbl.b); | | | | | |
|
|
(1 row)
|
|
|
|
INSERT INTO rw_view1 VALUES(3,4); -- ok
|
|
INSERT INTO rw_view1 VALUES(4,3); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (4, 3).
|
|
INSERT INTO rw_view1 VALUES(5,null); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (5, null).
|
|
UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
|
|
UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (3, -5).
|
|
INSERT INTO rw_view1(a) VALUES (9); -- ok
|
|
INSERT INTO rw_view1(a) VALUES (10); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (10, 10).
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
---+----
|
|
1 | 2
|
|
2 | 3
|
|
1 | -1
|
|
3 | 5
|
|
9 | 10
|
|
(5 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
-- WITH LOCAL/CASCADED CHECK OPTION
|
|
CREATE TABLE base_tbl (a int);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a > 0;
|
|
CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
|
|
WITH CHECK OPTION; -- implicitly cascaded
|
|
\d+ rw_view2
|
|
View "public.rw_view2"
|
|
Column | Type | Modifiers | Storage | Description
|
|
--------+---------+-----------+---------+-------------
|
|
a | integer | | plain |
|
|
View definition:
|
|
SELECT rw_view1.a
|
|
FROM rw_view1
|
|
WHERE rw_view1.a < 10;
|
|
Options: check_option=cascaded
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
|
table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
regression | public | rw_view2 | SELECT rw_view1.a +| CASCADED | YES | YES | NO | NO | NO
|
|
| | | FROM rw_view1 +| | | | | |
|
|
| | | WHERE (rw_view1.a < 10); | | | | | |
|
|
(1 row)
|
|
|
|
INSERT INTO rw_view2 VALUES (-5); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (-5).
|
|
INSERT INTO rw_view2 VALUES (5); -- ok
|
|
INSERT INTO rw_view2 VALUES (15); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view2"
|
|
DETAIL: Failing row contains (15).
|
|
SELECT * FROM base_tbl;
|
|
a
|
|
---
|
|
5
|
|
(1 row)
|
|
|
|
UPDATE rw_view2 SET a = a - 10; -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (-5).
|
|
UPDATE rw_view2 SET a = a + 10; -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view2"
|
|
DETAIL: Failing row contains (15).
|
|
CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
|
|
WITH LOCAL CHECK OPTION;
|
|
\d+ rw_view2
|
|
View "public.rw_view2"
|
|
Column | Type | Modifiers | Storage | Description
|
|
--------+---------+-----------+---------+-------------
|
|
a | integer | | plain |
|
|
View definition:
|
|
SELECT rw_view1.a
|
|
FROM rw_view1
|
|
WHERE rw_view1.a < 10;
|
|
Options: check_option=local
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
|
table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
regression | public | rw_view2 | SELECT rw_view1.a +| LOCAL | YES | YES | NO | NO | NO
|
|
| | | FROM rw_view1 +| | | | | |
|
|
| | | WHERE (rw_view1.a < 10); | | | | | |
|
|
(1 row)
|
|
|
|
INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
|
|
INSERT INTO rw_view2 VALUES (20); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view2"
|
|
DETAIL: Failing row contains (20).
|
|
SELECT * FROM base_tbl;
|
|
a
|
|
-----
|
|
5
|
|
-10
|
|
(2 rows)
|
|
|
|
ALTER VIEW rw_view1 SET (check_option=here); -- invalid
|
|
ERROR: invalid value for "check_option" option
|
|
DETAIL: Valid values are "local" and "cascaded".
|
|
ALTER VIEW rw_view1 SET (check_option=local);
|
|
INSERT INTO rw_view2 VALUES (-20); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (-20).
|
|
INSERT INTO rw_view2 VALUES (30); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view2"
|
|
DETAIL: Failing row contains (30).
|
|
ALTER VIEW rw_view2 RESET (check_option);
|
|
\d+ rw_view2
|
|
View "public.rw_view2"
|
|
Column | Type | Modifiers | Storage | Description
|
|
--------+---------+-----------+---------+-------------
|
|
a | integer | | plain |
|
|
View definition:
|
|
SELECT rw_view1.a
|
|
FROM rw_view1
|
|
WHERE rw_view1.a < 10;
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
|
table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
---------------+--------------+------------+----------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
regression | public | rw_view2 | SELECT rw_view1.a +| NONE | YES | YES | NO | NO | NO
|
|
| | | FROM rw_view1 +| | | | | |
|
|
| | | WHERE (rw_view1.a < 10); | | | | | |
|
|
(1 row)
|
|
|
|
INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
|
|
SELECT * FROM base_tbl;
|
|
a
|
|
-----
|
|
5
|
|
-10
|
|
30
|
|
(3 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
-- WITH CHECK OPTION with no local view qual
|
|
CREATE TABLE base_tbl (a int);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WITH CHECK OPTION;
|
|
CREATE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a > 0;
|
|
CREATE VIEW rw_view3 AS SELECT * FROM rw_view2 WITH CHECK OPTION;
|
|
SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
|
|
table_catalog | table_schema | table_name | view_definition | check_option | is_updatable | is_insertable_into | is_trigger_updatable | is_trigger_deletable | is_trigger_insertable_into
|
|
---------------+--------------+------------+---------------------------+--------------+--------------+--------------------+----------------------+----------------------+----------------------------
|
|
regression | public | rw_view1 | SELECT base_tbl.a +| CASCADED | YES | YES | NO | NO | NO
|
|
| | | FROM base_tbl; | | | | | |
|
|
regression | public | rw_view2 | SELECT rw_view1.a +| NONE | YES | YES | NO | NO | NO
|
|
| | | FROM rw_view1 +| | | | | |
|
|
| | | WHERE (rw_view1.a > 0); | | | | | |
|
|
regression | public | rw_view3 | SELECT rw_view2.a +| CASCADED | YES | YES | NO | NO | NO
|
|
| | | FROM rw_view2; | | | | | |
|
|
(3 rows)
|
|
|
|
INSERT INTO rw_view1 VALUES (-1); -- ok
|
|
INSERT INTO rw_view1 VALUES (1); -- ok
|
|
INSERT INTO rw_view2 VALUES (-2); -- ok, but not in view
|
|
INSERT INTO rw_view2 VALUES (2); -- ok
|
|
INSERT INTO rw_view3 VALUES (-3); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view2"
|
|
DETAIL: Failing row contains (-3).
|
|
INSERT INTO rw_view3 VALUES (3); -- ok
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 3 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
drop cascades to view rw_view3
|
|
-- WITH CHECK OPTION with scalar array ops
|
|
CREATE TABLE base_tbl (a int, b int[]);
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a = ANY (b)
|
|
WITH CHECK OPTION;
|
|
INSERT INTO rw_view1 VALUES (1, ARRAY[1,2,3]); -- ok
|
|
INSERT INTO rw_view1 VALUES (10, ARRAY[4,5]); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (10, {4,5}).
|
|
UPDATE rw_view1 SET b[2] = -b[2] WHERE a = 1; -- ok
|
|
UPDATE rw_view1 SET b[1] = -b[1] WHERE a = 1; -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (1, {-1,-2,3}).
|
|
PREPARE ins(int, int[]) AS INSERT INTO rw_view1 VALUES($1, $2);
|
|
EXECUTE ins(2, ARRAY[1,2,3]); -- ok
|
|
EXECUTE ins(10, ARRAY[4,5]); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (10, {4,5}).
|
|
DEALLOCATE PREPARE ins;
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
-- WITH CHECK OPTION with subquery
|
|
CREATE TABLE base_tbl (a int);
|
|
CREATE TABLE ref_tbl (a int PRIMARY KEY);
|
|
INSERT INTO ref_tbl SELECT * FROM generate_series(1,10);
|
|
CREATE VIEW rw_view1 AS
|
|
SELECT * FROM base_tbl b
|
|
WHERE EXISTS(SELECT 1 FROM ref_tbl r WHERE r.a = b.a)
|
|
WITH CHECK OPTION;
|
|
INSERT INTO rw_view1 VALUES (5); -- ok
|
|
INSERT INTO rw_view1 VALUES (15); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (15).
|
|
UPDATE rw_view1 SET a = a + 5; -- ok
|
|
UPDATE rw_view1 SET a = a + 5; -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (15).
|
|
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
|
|
QUERY PLAN
|
|
---------------------------------------------------------------
|
|
Insert on base_tbl b
|
|
-> Result
|
|
SubPlan 1
|
|
-> Index Only Scan using ref_tbl_pkey on ref_tbl r
|
|
Index Cond: (a = b.a)
|
|
SubPlan 2
|
|
-> Seq Scan on ref_tbl r_1
|
|
(7 rows)
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
|
|
QUERY PLAN
|
|
-----------------------------------------------------------------
|
|
Update on base_tbl b
|
|
-> Hash Semi Join
|
|
Hash Cond: (b.a = r.a)
|
|
-> Seq Scan on base_tbl b
|
|
-> Hash
|
|
-> Seq Scan on ref_tbl r
|
|
SubPlan 1
|
|
-> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
|
|
Index Cond: (a = b.a)
|
|
SubPlan 2
|
|
-> Seq Scan on ref_tbl r_2
|
|
(11 rows)
|
|
|
|
DROP TABLE base_tbl, ref_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
-- WITH CHECK OPTION with BEFORE trigger on base table
|
|
CREATE TABLE base_tbl (a int, b int);
|
|
CREATE FUNCTION base_tbl_trig_fn()
|
|
RETURNS trigger AS
|
|
$$
|
|
BEGIN
|
|
NEW.b := 10;
|
|
RETURN NEW;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql;
|
|
CREATE TRIGGER base_tbl_trig BEFORE INSERT OR UPDATE ON base_tbl
|
|
FOR EACH ROW EXECUTE PROCEDURE base_tbl_trig_fn();
|
|
CREATE VIEW rw_view1 AS SELECT * FROM base_tbl WHERE a < b WITH CHECK OPTION;
|
|
INSERT INTO rw_view1 VALUES (5,0); -- ok
|
|
INSERT INTO rw_view1 VALUES (15, 20); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (15, 10).
|
|
UPDATE rw_view1 SET a = 20, b = 30; -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view1"
|
|
DETAIL: Failing row contains (20, 10).
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
DROP FUNCTION base_tbl_trig_fn();
|
|
-- WITH LOCAL CHECK OPTION with INSTEAD OF trigger on base view
|
|
CREATE TABLE base_tbl (a int, b int);
|
|
CREATE VIEW rw_view1 AS SELECT a FROM base_tbl WHERE a < b;
|
|
CREATE FUNCTION rw_view1_trig_fn()
|
|
RETURNS trigger AS
|
|
$$
|
|
BEGIN
|
|
IF TG_OP = 'INSERT' THEN
|
|
INSERT INTO base_tbl VALUES (NEW.a, 10);
|
|
RETURN NEW;
|
|
ELSIF TG_OP = 'UPDATE' THEN
|
|
UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
|
|
RETURN NEW;
|
|
ELSIF TG_OP = 'DELETE' THEN
|
|
DELETE FROM base_tbl WHERE a=OLD.a;
|
|
RETURN OLD;
|
|
END IF;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql;
|
|
CREATE TRIGGER rw_view1_trig
|
|
INSTEAD OF INSERT OR UPDATE OR DELETE ON rw_view1
|
|
FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
|
|
CREATE VIEW rw_view2 AS
|
|
SELECT * FROM rw_view1 WHERE a > 0 WITH LOCAL CHECK OPTION;
|
|
INSERT INTO rw_view2 VALUES (-5); -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view2"
|
|
DETAIL: Failing row contains (-5).
|
|
INSERT INTO rw_view2 VALUES (5); -- ok
|
|
INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
|
|
UPDATE rw_view2 SET a = a - 10; -- should fail
|
|
ERROR: new row violates WITH CHECK OPTION for "rw_view2"
|
|
DETAIL: Failing row contains (-5).
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
----+----
|
|
5 | 10
|
|
50 | 10
|
|
(2 rows)
|
|
|
|
-- Check option won't cascade down to base view with INSTEAD OF triggers
|
|
ALTER VIEW rw_view2 SET (check_option=cascaded);
|
|
INSERT INTO rw_view2 VALUES (100); -- ok, but not in view (doesn't fail rw_view1's check)
|
|
UPDATE rw_view2 SET a = 200 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
-----+----
|
|
50 | 10
|
|
100 | 10
|
|
200 | 10
|
|
(3 rows)
|
|
|
|
-- Neither local nor cascaded check options work with INSTEAD rules
|
|
DROP TRIGGER rw_view1_trig ON rw_view1;
|
|
CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
|
|
DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, 10);
|
|
CREATE RULE rw_view1_upd_rule AS ON UPDATE TO rw_view1
|
|
DO INSTEAD UPDATE base_tbl SET a=NEW.a WHERE a=OLD.a;
|
|
INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view (doesn't fail rw_view2's check)
|
|
INSERT INTO rw_view2 VALUES (5); -- ok
|
|
INSERT INTO rw_view2 VALUES (20); -- ok, but not in view (doesn't fail rw_view1's check)
|
|
UPDATE rw_view2 SET a = 30 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view1's check)
|
|
INSERT INTO rw_view2 VALUES (5); -- ok
|
|
UPDATE rw_view2 SET a = -5 WHERE a = 5; -- ok, but not in view (doesn't fail rw_view2's check)
|
|
SELECT * FROM base_tbl;
|
|
a | b
|
|
-----+----
|
|
50 | 10
|
|
100 | 10
|
|
200 | 10
|
|
-10 | 10
|
|
20 | 10
|
|
30 | 10
|
|
-5 | 10
|
|
(7 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
DROP FUNCTION rw_view1_trig_fn();
|
|
CREATE TABLE base_tbl (a int);
|
|
CREATE VIEW rw_view1 AS SELECT a,10 AS b FROM base_tbl;
|
|
CREATE RULE rw_view1_ins_rule AS ON INSERT TO rw_view1
|
|
DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a);
|
|
CREATE VIEW rw_view2 AS
|
|
SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION;
|
|
INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check)
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
-- security barrier view
|
|
CREATE TABLE base_tbl (person text, visibility text);
|
|
INSERT INTO base_tbl VALUES ('Tom', 'public'),
|
|
('Dick', 'private'),
|
|
('Harry', 'public');
|
|
CREATE VIEW rw_view1 AS
|
|
SELECT person FROM base_tbl WHERE visibility = 'public';
|
|
CREATE FUNCTION snoop(anyelement)
|
|
RETURNS boolean AS
|
|
$$
|
|
BEGIN
|
|
RAISE NOTICE 'snooped value: %', $1;
|
|
RETURN true;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql COST 0.000001;
|
|
CREATE OR REPLACE FUNCTION leakproof(anyelement)
|
|
RETURNS boolean AS
|
|
$$
|
|
BEGIN
|
|
RETURN true;
|
|
END;
|
|
$$
|
|
LANGUAGE plpgsql STRICT IMMUTABLE LEAKPROOF;
|
|
SELECT * FROM rw_view1 WHERE snoop(person);
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Dick
|
|
NOTICE: snooped value: Harry
|
|
person
|
|
--------
|
|
Tom
|
|
Harry
|
|
(2 rows)
|
|
|
|
UPDATE rw_view1 SET person=person WHERE snoop(person);
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Dick
|
|
NOTICE: snooped value: Harry
|
|
DELETE FROM rw_view1 WHERE NOT snoop(person);
|
|
NOTICE: snooped value: Dick
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Harry
|
|
ALTER VIEW rw_view1 SET (security_barrier = true);
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name = 'rw_view1';
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view1 | YES
|
|
(1 row)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name = 'rw_view1';
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view1 | YES | YES
|
|
(1 row)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'rw_view1'
|
|
ORDER BY ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view1 | person | YES
|
|
(1 row)
|
|
|
|
SELECT * FROM rw_view1 WHERE snoop(person);
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Harry
|
|
person
|
|
--------
|
|
Tom
|
|
Harry
|
|
(2 rows)
|
|
|
|
UPDATE rw_view1 SET person=person WHERE snoop(person);
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Harry
|
|
DELETE FROM rw_view1 WHERE NOT snoop(person);
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Harry
|
|
EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person);
|
|
QUERY PLAN
|
|
-----------------------------------------------
|
|
Subquery Scan on rw_view1
|
|
Filter: snoop(rw_view1.person)
|
|
-> Seq Scan on base_tbl
|
|
Filter: (visibility = 'public'::text)
|
|
(4 rows)
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person);
|
|
QUERY PLAN
|
|
-----------------------------------------------------------
|
|
Update on base_tbl base_tbl_1
|
|
-> Subquery Scan on base_tbl
|
|
Filter: snoop(base_tbl.person)
|
|
-> LockRows
|
|
-> Seq Scan on base_tbl base_tbl_2
|
|
Filter: (visibility = 'public'::text)
|
|
(6 rows)
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
|
|
QUERY PLAN
|
|
-----------------------------------------------------------
|
|
Delete on base_tbl base_tbl_1
|
|
-> Subquery Scan on base_tbl
|
|
Filter: (NOT snoop(base_tbl.person))
|
|
-> LockRows
|
|
-> Seq Scan on base_tbl base_tbl_2
|
|
Filter: (visibility = 'public'::text)
|
|
(6 rows)
|
|
|
|
-- security barrier view on top of security barrier view
|
|
CREATE VIEW rw_view2 WITH (security_barrier = true) AS
|
|
SELECT * FROM rw_view1 WHERE snoop(person);
|
|
SELECT table_name, is_insertable_into
|
|
FROM information_schema.tables
|
|
WHERE table_name = 'rw_view2';
|
|
table_name | is_insertable_into
|
|
------------+--------------------
|
|
rw_view2 | YES
|
|
(1 row)
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
FROM information_schema.views
|
|
WHERE table_name = 'rw_view2';
|
|
table_name | is_updatable | is_insertable_into
|
|
------------+--------------+--------------------
|
|
rw_view2 | YES | YES
|
|
(1 row)
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'rw_view2'
|
|
ORDER BY ordinal_position;
|
|
table_name | column_name | is_updatable
|
|
------------+-------------+--------------
|
|
rw_view2 | person | YES
|
|
(1 row)
|
|
|
|
SELECT * FROM rw_view2 WHERE snoop(person);
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Harry
|
|
NOTICE: snooped value: Harry
|
|
person
|
|
--------
|
|
Tom
|
|
Harry
|
|
(2 rows)
|
|
|
|
UPDATE rw_view2 SET person=person WHERE snoop(person);
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Harry
|
|
NOTICE: snooped value: Harry
|
|
DELETE FROM rw_view2 WHERE NOT snoop(person);
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Tom
|
|
NOTICE: snooped value: Harry
|
|
NOTICE: snooped value: Harry
|
|
EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person);
|
|
QUERY PLAN
|
|
-----------------------------------------------------
|
|
Subquery Scan on rw_view2
|
|
Filter: snoop(rw_view2.person)
|
|
-> Subquery Scan on rw_view1
|
|
Filter: snoop(rw_view1.person)
|
|
-> Seq Scan on base_tbl
|
|
Filter: (visibility = 'public'::text)
|
|
(6 rows)
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person);
|
|
QUERY PLAN
|
|
-----------------------------------------------------------------
|
|
Update on base_tbl base_tbl_1
|
|
-> Subquery Scan on base_tbl
|
|
Filter: snoop(base_tbl.person)
|
|
-> Subquery Scan on base_tbl_2
|
|
Filter: snoop(base_tbl_2.person)
|
|
-> LockRows
|
|
-> Seq Scan on base_tbl base_tbl_3
|
|
Filter: (visibility = 'public'::text)
|
|
(8 rows)
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
|
|
QUERY PLAN
|
|
-----------------------------------------------------------------
|
|
Delete on base_tbl base_tbl_1
|
|
-> Subquery Scan on base_tbl
|
|
Filter: (NOT snoop(base_tbl.person))
|
|
-> Subquery Scan on base_tbl_2
|
|
Filter: snoop(base_tbl_2.person)
|
|
-> LockRows
|
|
-> Seq Scan on base_tbl base_tbl_3
|
|
Filter: (visibility = 'public'::text)
|
|
(8 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to 2 other objects
|
|
DETAIL: drop cascades to view rw_view1
|
|
drop cascades to view rw_view2
|
|
-- security barrier view on top of table with rules
|
|
CREATE TABLE base_tbl(id int PRIMARY KEY, data text, deleted boolean);
|
|
INSERT INTO base_tbl VALUES (1, 'Row 1', false), (2, 'Row 2', true);
|
|
CREATE RULE base_tbl_ins_rule AS ON INSERT TO base_tbl
|
|
WHERE EXISTS (SELECT 1 FROM base_tbl t WHERE t.id = new.id)
|
|
DO INSTEAD
|
|
UPDATE base_tbl SET data = new.data, deleted = false WHERE id = new.id;
|
|
CREATE RULE base_tbl_del_rule AS ON DELETE TO base_tbl
|
|
DO INSTEAD
|
|
UPDATE base_tbl SET deleted = true WHERE id = old.id;
|
|
CREATE VIEW rw_view1 WITH (security_barrier=true) AS
|
|
SELECT id, data FROM base_tbl WHERE NOT deleted;
|
|
SELECT * FROM rw_view1;
|
|
id | data
|
|
----+-------
|
|
1 | Row 1
|
|
(1 row)
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
|
|
QUERY PLAN
|
|
-------------------------------------------------------------------------
|
|
Update on base_tbl base_tbl_1
|
|
-> Nested Loop
|
|
-> Index Scan using base_tbl_pkey on base_tbl base_tbl_1
|
|
Index Cond: (id = 1)
|
|
-> Subquery Scan on base_tbl
|
|
Filter: snoop(base_tbl.data)
|
|
-> Index Scan using base_tbl_pkey on base_tbl base_tbl_2
|
|
Index Cond: (id = 1)
|
|
Filter: (NOT deleted)
|
|
(9 rows)
|
|
|
|
DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
|
|
NOTICE: snooped value: Row 1
|
|
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (2, 'New row 2');
|
|
QUERY PLAN
|
|
-----------------------------------------------------------
|
|
Insert on base_tbl
|
|
InitPlan 1 (returns $0)
|
|
-> Index Only Scan using base_tbl_pkey on base_tbl t
|
|
Index Cond: (id = 2)
|
|
-> Result
|
|
One-Time Filter: ($0 IS NOT TRUE)
|
|
|
|
Update on base_tbl
|
|
InitPlan 1 (returns $0)
|
|
-> Index Only Scan using base_tbl_pkey on base_tbl t
|
|
Index Cond: (id = 2)
|
|
-> Result
|
|
One-Time Filter: $0
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
Index Cond: (id = 2)
|
|
(15 rows)
|
|
|
|
INSERT INTO rw_view1 VALUES (2, 'New row 2');
|
|
SELECT * FROM base_tbl;
|
|
id | data | deleted
|
|
----+-----------+---------
|
|
1 | Row 1 | t
|
|
2 | New row 2 | f
|
|
(2 rows)
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
NOTICE: drop cascades to view rw_view1
|
|
-- security barrier view based on inheiritance set
|
|
CREATE TABLE t1 (a int, b float, c text);
|
|
CREATE INDEX t1_a_idx ON t1(a);
|
|
INSERT INTO t1
|
|
SELECT i,i,'t1' FROM generate_series(1,10) g(i);
|
|
ANALYZE t1;
|
|
CREATE TABLE t11 (d text) INHERITS (t1);
|
|
CREATE INDEX t11_a_idx ON t11(a);
|
|
INSERT INTO t11
|
|
SELECT i,i,'t11','t11d' FROM generate_series(1,10) g(i);
|
|
ANALYZE t11;
|
|
CREATE TABLE t12 (e int[]) INHERITS (t1);
|
|
CREATE INDEX t12_a_idx ON t12(a);
|
|
INSERT INTO t12
|
|
SELECT i,i,'t12','{1,2}'::int[] FROM generate_series(1,10) g(i);
|
|
ANALYZE t12;
|
|
CREATE TABLE t111 () INHERITS (t11, t12);
|
|
NOTICE: merging multiple inherited definitions of column "a"
|
|
NOTICE: merging multiple inherited definitions of column "b"
|
|
NOTICE: merging multiple inherited definitions of column "c"
|
|
CREATE INDEX t111_a_idx ON t111(a);
|
|
INSERT INTO t111
|
|
SELECT i,i,'t111','t111d','{1,1,1}'::int[] FROM generate_series(1,10) g(i);
|
|
ANALYZE t111;
|
|
CREATE VIEW v1 WITH (security_barrier=true) AS
|
|
SELECT *, (SELECT d FROM t11 WHERE t11.a = t1.a LIMIT 1) AS d
|
|
FROM t1
|
|
WHERE a > 5 AND EXISTS(SELECT 1 FROM t12 WHERE t12.a = t1.a);
|
|
SELECT * FROM v1 WHERE a=3; -- should not see anything
|
|
a | b | c | d
|
|
---+---+---+---
|
|
(0 rows)
|
|
|
|
SELECT * FROM v1 WHERE a=8;
|
|
a | b | c | d
|
|
---+---+------+------
|
|
8 | 8 | t1 | t11d
|
|
8 | 8 | t11 | t11d
|
|
8 | 8 | t12 | t11d
|
|
8 | 8 | t111 | t11d
|
|
(4 rows)
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3;
|
|
QUERY PLAN
|
|
------------------------------------------------------------------------------------------------------------------------------------
|
|
Update on public.t1 t1_4
|
|
Update on public.t1 t1_4
|
|
Update on public.t11 t1
|
|
Update on public.t12 t1
|
|
Update on public.t111 t1
|
|
-> Subquery Scan on t1
|
|
Output: 100, t1.b, t1.c, t1.ctid
|
|
Filter: snoop(t1.a)
|
|
-> LockRows
|
|
Output: t1_5.ctid, t1_5.a, t1_5.b, t1_5.c, t1_5.ctid, t12.ctid, t12.tableoid
|
|
-> Nested Loop Semi Join
|
|
Output: t1_5.ctid, t1_5.a, t1_5.b, t1_5.c, t1_5.ctid, t12.ctid, t12.tableoid
|
|
-> Seq Scan on public.t1 t1_5
|
|
Output: t1_5.ctid, t1_5.a, t1_5.b, t1_5.c
|
|
Filter: ((t1_5.a > 5) AND (t1_5.a = 3) AND leakproof(t1_5.a))
|
|
-> Append
|
|
-> Seq Scan on public.t12
|
|
Output: t12.ctid, t12.tableoid, t12.a
|
|
Filter: (t12.a = 3)
|
|
-> Seq Scan on public.t111
|
|
Output: t111.ctid, t111.tableoid, t111.a
|
|
Filter: (t111.a = 3)
|
|
-> Subquery Scan on t1_1
|
|
Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
|
|
Filter: snoop(t1_1.a)
|
|
-> LockRows
|
|
Output: t11.ctid, t11.a, t11.b, t11.c, t11.d, t11.ctid, t12_1.ctid, t12_1.tableoid
|
|
-> Nested Loop Semi Join
|
|
Output: t11.ctid, t11.a, t11.b, t11.c, t11.d, t11.ctid, t12_1.ctid, t12_1.tableoid
|
|
-> Seq Scan on public.t11
|
|
Output: t11.ctid, t11.a, t11.b, t11.c, t11.d
|
|
Filter: ((t11.a > 5) AND (t11.a = 3) AND leakproof(t11.a))
|
|
-> Append
|
|
-> Seq Scan on public.t12 t12_1
|
|
Output: t12_1.ctid, t12_1.tableoid, t12_1.a
|
|
Filter: (t12_1.a = 3)
|
|
-> Seq Scan on public.t111 t111_1
|
|
Output: t111_1.ctid, t111_1.tableoid, t111_1.a
|
|
Filter: (t111_1.a = 3)
|
|
-> Subquery Scan on t1_2
|
|
Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
|
|
Filter: snoop(t1_2.a)
|
|
-> LockRows
|
|
Output: t12_2.ctid, t12_2.a, t12_2.b, t12_2.c, t12_2.e, t12_2.ctid, t12_3.ctid, t12_3.tableoid
|
|
-> Nested Loop Semi Join
|
|
Output: t12_2.ctid, t12_2.a, t12_2.b, t12_2.c, t12_2.e, t12_2.ctid, t12_3.ctid, t12_3.tableoid
|
|
-> Seq Scan on public.t12 t12_2
|
|
Output: t12_2.ctid, t12_2.a, t12_2.b, t12_2.c, t12_2.e
|
|
Filter: ((t12_2.a > 5) AND (t12_2.a = 3) AND leakproof(t12_2.a))
|
|
-> Append
|
|
-> Seq Scan on public.t12 t12_3
|
|
Output: t12_3.ctid, t12_3.tableoid, t12_3.a
|
|
Filter: (t12_3.a = 3)
|
|
-> Seq Scan on public.t111 t111_2
|
|
Output: t111_2.ctid, t111_2.tableoid, t111_2.a
|
|
Filter: (t111_2.a = 3)
|
|
-> Subquery Scan on t1_3
|
|
Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
|
|
Filter: snoop(t1_3.a)
|
|
-> LockRows
|
|
Output: t111_3.ctid, t111_3.a, t111_3.b, t111_3.c, t111_3.d, t111_3.e, t111_3.ctid, t12_4.ctid, t12_4.tableoid
|
|
-> Nested Loop Semi Join
|
|
Output: t111_3.ctid, t111_3.a, t111_3.b, t111_3.c, t111_3.d, t111_3.e, t111_3.ctid, t12_4.ctid, t12_4.tableoid
|
|
-> Seq Scan on public.t111 t111_3
|
|
Output: t111_3.ctid, t111_3.a, t111_3.b, t111_3.c, t111_3.d, t111_3.e
|
|
Filter: ((t111_3.a > 5) AND (t111_3.a = 3) AND leakproof(t111_3.a))
|
|
-> Append
|
|
-> Seq Scan on public.t12 t12_4
|
|
Output: t12_4.ctid, t12_4.tableoid, t12_4.a
|
|
Filter: (t12_4.a = 3)
|
|
-> Seq Scan on public.t111 t111_4
|
|
Output: t111_4.ctid, t111_4.tableoid, t111_4.a
|
|
Filter: (t111_4.a = 3)
|
|
(73 rows)
|
|
|
|
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a = 3;
|
|
SELECT * FROM v1 WHERE a=100; -- Nothing should have been changed to 100
|
|
a | b | c | d
|
|
---+---+---+---
|
|
(0 rows)
|
|
|
|
SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
|
|
a | b | c
|
|
---+---+---
|
|
(0 rows)
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
|
|
QUERY PLAN
|
|
------------------------------------------------------------------------------------------------------------------------------------
|
|
Update on public.t1 t1_4
|
|
Update on public.t1 t1_4
|
|
Update on public.t11 t1
|
|
Update on public.t12 t1
|
|
Update on public.t111 t1
|
|
-> Subquery Scan on t1
|
|
Output: (t1.a + 1), t1.b, t1.c, t1.ctid
|
|
Filter: snoop(t1.a)
|
|
-> LockRows
|
|
Output: t1_5.a, t1_5.ctid, t1_5.b, t1_5.c, t1_5.ctid, t12.ctid, t12.tableoid
|
|
-> Nested Loop Semi Join
|
|
Output: t1_5.a, t1_5.ctid, t1_5.b, t1_5.c, t1_5.ctid, t12.ctid, t12.tableoid
|
|
-> Seq Scan on public.t1 t1_5
|
|
Output: t1_5.a, t1_5.ctid, t1_5.b, t1_5.c
|
|
Filter: ((t1_5.a > 5) AND (t1_5.a = 8) AND leakproof(t1_5.a))
|
|
-> Append
|
|
-> Seq Scan on public.t12
|
|
Output: t12.ctid, t12.tableoid, t12.a
|
|
Filter: (t12.a = 8)
|
|
-> Seq Scan on public.t111
|
|
Output: t111.ctid, t111.tableoid, t111.a
|
|
Filter: (t111.a = 8)
|
|
-> Subquery Scan on t1_1
|
|
Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
|
|
Filter: snoop(t1_1.a)
|
|
-> LockRows
|
|
Output: t11.a, t11.ctid, t11.b, t11.c, t11.d, t11.ctid, t12_1.ctid, t12_1.tableoid
|
|
-> Nested Loop Semi Join
|
|
Output: t11.a, t11.ctid, t11.b, t11.c, t11.d, t11.ctid, t12_1.ctid, t12_1.tableoid
|
|
-> Seq Scan on public.t11
|
|
Output: t11.a, t11.ctid, t11.b, t11.c, t11.d
|
|
Filter: ((t11.a > 5) AND (t11.a = 8) AND leakproof(t11.a))
|
|
-> Append
|
|
-> Seq Scan on public.t12 t12_1
|
|
Output: t12_1.ctid, t12_1.tableoid, t12_1.a
|
|
Filter: (t12_1.a = 8)
|
|
-> Seq Scan on public.t111 t111_1
|
|
Output: t111_1.ctid, t111_1.tableoid, t111_1.a
|
|
Filter: (t111_1.a = 8)
|
|
-> Subquery Scan on t1_2
|
|
Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
|
|
Filter: snoop(t1_2.a)
|
|
-> LockRows
|
|
Output: t12_2.a, t12_2.ctid, t12_2.b, t12_2.c, t12_2.e, t12_2.ctid, t12_3.ctid, t12_3.tableoid
|
|
-> Nested Loop Semi Join
|
|
Output: t12_2.a, t12_2.ctid, t12_2.b, t12_2.c, t12_2.e, t12_2.ctid, t12_3.ctid, t12_3.tableoid
|
|
-> Seq Scan on public.t12 t12_2
|
|
Output: t12_2.a, t12_2.ctid, t12_2.b, t12_2.c, t12_2.e
|
|
Filter: ((t12_2.a > 5) AND (t12_2.a = 8) AND leakproof(t12_2.a))
|
|
-> Append
|
|
-> Seq Scan on public.t12 t12_3
|
|
Output: t12_3.ctid, t12_3.tableoid, t12_3.a
|
|
Filter: (t12_3.a = 8)
|
|
-> Seq Scan on public.t111 t111_2
|
|
Output: t111_2.ctid, t111_2.tableoid, t111_2.a
|
|
Filter: (t111_2.a = 8)
|
|
-> Subquery Scan on t1_3
|
|
Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
|
|
Filter: snoop(t1_3.a)
|
|
-> LockRows
|
|
Output: t111_3.a, t111_3.ctid, t111_3.b, t111_3.c, t111_3.d, t111_3.e, t111_3.ctid, t12_4.ctid, t12_4.tableoid
|
|
-> Nested Loop Semi Join
|
|
Output: t111_3.a, t111_3.ctid, t111_3.b, t111_3.c, t111_3.d, t111_3.e, t111_3.ctid, t12_4.ctid, t12_4.tableoid
|
|
-> Seq Scan on public.t111 t111_3
|
|
Output: t111_3.a, t111_3.ctid, t111_3.b, t111_3.c, t111_3.d, t111_3.e
|
|
Filter: ((t111_3.a > 5) AND (t111_3.a = 8) AND leakproof(t111_3.a))
|
|
-> Append
|
|
-> Seq Scan on public.t12 t12_4
|
|
Output: t12_4.ctid, t12_4.tableoid, t12_4.a
|
|
Filter: (t12_4.a = 8)
|
|
-> Seq Scan on public.t111 t111_4
|
|
Output: t111_4.ctid, t111_4.tableoid, t111_4.a
|
|
Filter: (t111_4.a = 8)
|
|
(73 rows)
|
|
|
|
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
|
|
NOTICE: snooped value: 8
|
|
NOTICE: snooped value: 8
|
|
NOTICE: snooped value: 8
|
|
NOTICE: snooped value: 8
|
|
SELECT * FROM v1 WHERE b=8;
|
|
a | b | c | d
|
|
---+---+------+------
|
|
9 | 8 | t1 | t11d
|
|
9 | 8 | t11 | t11d
|
|
9 | 8 | t12 | t11d
|
|
9 | 8 | t111 | t11d
|
|
(4 rows)
|
|
|
|
DELETE FROM v1 WHERE snoop(a) AND leakproof(a); -- should not delete everything, just where a>5
|
|
NOTICE: snooped value: 6
|
|
NOTICE: snooped value: 7
|
|
NOTICE: snooped value: 9
|
|
NOTICE: snooped value: 10
|
|
NOTICE: snooped value: 9
|
|
NOTICE: snooped value: 6
|
|
NOTICE: snooped value: 7
|
|
NOTICE: snooped value: 9
|
|
NOTICE: snooped value: 10
|
|
NOTICE: snooped value: 9
|
|
NOTICE: snooped value: 6
|
|
NOTICE: snooped value: 7
|
|
NOTICE: snooped value: 9
|
|
NOTICE: snooped value: 10
|
|
NOTICE: snooped value: 9
|
|
NOTICE: snooped value: 6
|
|
NOTICE: snooped value: 7
|
|
NOTICE: snooped value: 9
|
|
NOTICE: snooped value: 10
|
|
NOTICE: snooped value: 9
|
|
TABLE t1; -- verify all a<=5 are intact
|
|
a | b | c
|
|
---+---+------
|
|
1 | 1 | t1
|
|
2 | 2 | t1
|
|
3 | 3 | t1
|
|
4 | 4 | t1
|
|
5 | 5 | t1
|
|
1 | 1 | t11
|
|
2 | 2 | t11
|
|
3 | 3 | t11
|
|
4 | 4 | t11
|
|
5 | 5 | t11
|
|
1 | 1 | t12
|
|
2 | 2 | t12
|
|
3 | 3 | t12
|
|
4 | 4 | t12
|
|
5 | 5 | t12
|
|
1 | 1 | t111
|
|
2 | 2 | t111
|
|
3 | 3 | t111
|
|
4 | 4 | t111
|
|
5 | 5 | t111
|
|
(20 rows)
|
|
|
|
DROP TABLE t1, t11, t12, t111 CASCADE;
|
|
NOTICE: drop cascades to view v1
|
|
DROP FUNCTION snoop(anyelement);
|
|
DROP FUNCTION leakproof(anyelement);
|