postgresql/src/test/regress/expected/updatable_views.out

2462 lines
87 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
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT DO NOTHING; -- succeeds
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO NOTHING; -- succeeds
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set a = excluded.a; -- succeeds
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set upper = 'blarg'; -- fails
ERROR: cannot insert into column "upper" of view "rw_view15"
DETAIL: View columns that are not columns of their base relation are not updatable.
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
SELECT * FROM rw_view15;
a | upper
----+-------------
-2 | ROW -2
-1 | ROW -1
0 | ROW 0
1 | ROW 1
2 | ROW 2
3 | UNSPECIFIED
(6 rows)
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 check option for view "rw_view1"
DETAIL: Failing row contains (4, 3).
INSERT INTO rw_view1 VALUES(5,null); -- should fail
ERROR: new row violates check option for view "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 check option for view "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 check option for view "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 check option for view "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 check option for view "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 check option for view "rw_view1"
DETAIL: Failing row contains (-5).
UPDATE rw_view2 SET a = a + 10; -- should fail
ERROR: new row violates check option for view "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 check option for view "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 check option for view "rw_view1"
DETAIL: Failing row contains (-20).
INSERT INTO rw_view2 VALUES (30); -- should fail
ERROR: new row violates check option for view "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 check option for view "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 check option for view "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 check option for view "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 check option for view "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 check option for view "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 check option for view "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 check option for view "rw_view1"
DETAIL: Failing row contains (15, 10).
UPDATE rw_view1 SET a = 20, b = 30; -- should fail
ERROR: new row violates check option for view "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 check option for view "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 check option for view "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 inheritance 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);
CREATE TABLE tx1 (a integer);
CREATE TABLE tx2 (b integer);
CREATE TABLE tx3 (c integer);
CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
INSERT INTO vx1 values (1);
SELECT * FROM tx1;
a
---
1
(1 row)
SELECT * FROM vx1;
a
---
(0 rows)
DROP VIEW vx1;
DROP TABLE tx1;
DROP TABLE tx2;
DROP TABLE tx3;
CREATE TABLE tx1 (a integer);
CREATE TABLE tx2 (b integer);
CREATE TABLE tx3 (c integer);
CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
INSERT INTO vx1 VALUES (1);
INSERT INTO vx1 VALUES (1);
SELECT * FROM tx1;
a
---
1
1
(2 rows)
SELECT * FROM vx1;
a
---
(0 rows)
DROP VIEW vx1;
DROP TABLE tx1;
DROP TABLE tx2;
DROP TABLE tx3;
CREATE TABLE tx1 (a integer, b integer);
CREATE TABLE tx2 (b integer, c integer);
CREATE TABLE tx3 (c integer, d integer);
ALTER TABLE tx1 DROP COLUMN b;
ALTER TABLE tx2 DROP COLUMN c;
ALTER TABLE tx3 DROP COLUMN d;
CREATE VIEW vx1 AS SELECT a FROM tx1 WHERE EXISTS(SELECT 1 FROM tx2 JOIN tx3 ON b=c);
INSERT INTO vx1 VALUES (1);
INSERT INTO vx1 VALUES (1);
SELECT * FROM tx1;
a
---
1
1
(2 rows)
SELECT * FROM vx1;
a
---
(0 rows)
DROP VIEW vx1;
DROP TABLE tx1;
DROP TABLE tx2;
DROP TABLE tx3;
--
-- Test handling of vars from correlated subqueries in quals from outer
-- security barrier views, per bug #13988
--
CREATE TABLE t1 (a int, b text, c int);
INSERT INTO t1 VALUES (1, 'one', 10);
CREATE TABLE t2 (cc int);
INSERT INTO t2 VALUES (10), (20);
CREATE VIEW v1 WITH (security_barrier = true) AS
SELECT * FROM t1 WHERE (a > 0)
WITH CHECK OPTION;
CREATE VIEW v2 WITH (security_barrier = true) AS
SELECT * FROM v1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.cc = v1.c)
WITH CHECK OPTION;
INSERT INTO v2 VALUES (2, 'two', 20); -- ok
INSERT INTO v2 VALUES (-2, 'minus two', 20); -- not allowed
ERROR: new row violates check option for view "v1"
DETAIL: Failing row contains (-2, minus two, 20).
INSERT INTO v2 VALUES (3, 'three', 30); -- not allowed
ERROR: new row violates check option for view "v2"
DETAIL: Failing row contains (3, three, 30).
UPDATE v2 SET b = 'ONE' WHERE a = 1; -- ok
UPDATE v2 SET a = -1 WHERE a = 1; -- not allowed
ERROR: new row violates check option for view "v1"
DETAIL: Failing row contains (-1, ONE, 10).
UPDATE v2 SET c = 30 WHERE a = 1; -- not allowed
ERROR: new row violates check option for view "v2"
DETAIL: Failing row contains (1, ONE, 30).
DELETE FROM v2 WHERE a = 2; -- ok
SELECT * FROM v2;
a | b | c
---+-----+----
1 | ONE | 10
(1 row)
DROP VIEW v2;
DROP VIEW v1;
DROP TABLE t2;
DROP TABLE t1;