summaryrefslogtreecommitdiffstats
path: root/src/test/regress/expected/updatable_views.out
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/regress/expected/updatable_views.out')
-rw-r--r--src/test/regress/expected/updatable_views.out3071
1 files changed, 3071 insertions, 0 deletions
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index 0000000..c3a68ee
--- /dev/null
+++ b/src/test/regress/expected/updatable_views.out
@@ -0,0 +1,3071 @@
+--
+-- UPDATABLE VIEWS
+--
+-- avoid bit-exact output here because operations may not be bit-exact.
+SET extra_float_digits = 0;
+-- 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 uv_seq;
+CREATE VIEW ro_view19 AS SELECT * FROM uv_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 | last_value | NO
+ ro_view19 | log_cnt | 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
+(39 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 last_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.
+-- A view with a conditional INSTEAD rule but no unconditional INSTEAD rules
+-- or INSTEAD OF triggers should be non-updatable and generate useful error
+-- messages with appropriate detail
+CREATE RULE rw_view16_ins_rule AS ON INSERT TO rw_view16
+ WHERE NEW.a > 0 DO INSTEAD INSERT INTO base_tbl VALUES (NEW.a, NEW.b);
+CREATE RULE rw_view16_upd_rule AS ON UPDATE TO rw_view16
+ WHERE OLD.a > 0 DO INSTEAD UPDATE base_tbl SET b=NEW.b WHERE a=OLD.a;
+CREATE RULE rw_view16_del_rule AS ON DELETE TO rw_view16
+ WHERE OLD.a > 0 DO INSTEAD DELETE FROM base_tbl WHERE a=OLD.a;
+INSERT INTO rw_view16 (a, b) VALUES (3, 'Row 3'); -- should fail
+ERROR: cannot insert into view "rw_view16"
+DETAIL: Views with conditional DO INSTEAD rules 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.
+UPDATE rw_view16 SET b='ROW 2' WHERE a=2; -- should fail
+ERROR: cannot update view "rw_view16"
+DETAIL: Views with conditional DO INSTEAD rules are not automatically updatable.
+HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
+DELETE FROM rw_view16 WHERE a=2; -- should fail
+ERROR: cannot delete from view "rw_view16"
+DETAIL: Views with conditional DO INSTEAD rules 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.
+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_view4
+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_view14
+drop cascades to view rw_view15
+drop cascades to view rw_view16
+drop cascades to view ro_view20
+DROP VIEW ro_view10, ro_view12, ro_view18;
+DROP SEQUENCE uv_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 regress_view_user1;
+CREATE USER regress_view_user2;
+SET SESSION AUTHORIZATION regress_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 regress_view_user2;
+GRANT SELECT ON rw_view1 TO regress_view_user2;
+GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2;
+GRANT UPDATE (bb,cc) ON rw_view1 TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_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 table base_tbl
+INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
+ERROR: permission denied for view rw_view1
+INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE base_tbl SET a=a, c=c; -- ok
+UPDATE base_tbl SET b=b; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view1 SET bb=bb, cc=cc; -- ok
+UPDATE rw_view1 SET aa=aa; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
+UPDATE rw_view2 SET bb=bb; -- not allowed
+ERROR: permission denied for table base_tbl
+DELETE FROM base_tbl; -- not allowed
+ERROR: permission denied for table base_tbl
+DELETE FROM rw_view1; -- not allowed
+ERROR: permission denied for view rw_view1
+DELETE FROM rw_view2; -- not allowed
+ERROR: permission denied for table base_tbl
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_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 view 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 view 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 regress_view_user1;
+REVOKE INSERT, DELETE ON base_tbl FROM regress_view_user2;
+GRANT INSERT, DELETE ON rw_view1 TO regress_view_user2;
+RESET SESSION AUTHORIZATION;
+SET SESSION AUTHORIZATION regress_view_user2;
+INSERT INTO base_tbl VALUES (5, 'Row 5', 5.0); -- not allowed
+ERROR: permission denied for table 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 table base_tbl
+DELETE FROM base_tbl WHERE a=3; -- not allowed
+ERROR: permission denied for table base_tbl
+DELETE FROM rw_view1 WHERE aa=3; -- ok
+DELETE FROM rw_view2 WHERE aa=4; -- not allowed
+ERROR: permission denied for table 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
+-- nested-view permissions
+CREATE TABLE base_tbl(a int, b text, c float);
+INSERT INTO base_tbl VALUES (1, 'Row 1', 1.0);
+SET SESSION AUTHORIZATION regress_view_user1;
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
+SELECT * FROM rw_view1; -- not allowed
+ERROR: permission denied for table base_tbl
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+ERROR: permission denied for table base_tbl
+SET SESSION AUTHORIZATION regress_view_user2;
+CREATE VIEW rw_view2 AS SELECT * FROM rw_view1;
+SELECT * FROM rw_view2; -- not allowed
+ERROR: permission denied for view rw_view1
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for view rw_view1
+RESET SESSION AUTHORIZATION;
+GRANT SELECT ON base_tbl TO regress_view_user1;
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+(1 row)
+
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+ERROR: permission denied for table base_tbl
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2; -- not allowed
+ERROR: permission denied for view rw_view1
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for view rw_view1
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT SELECT ON rw_view1 TO regress_view_user2;
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+(1 row)
+
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for view rw_view1
+RESET SESSION AUTHORIZATION;
+GRANT UPDATE ON base_tbl TO regress_view_user1;
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+(1 row)
+
+SELECT * FROM rw_view1 FOR UPDATE;
+ a | b | c
+---+-------+---
+ 1 | Row 1 | 1
+(1 row)
+
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1;
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+ a | b | c
+---+-----+---
+ 1 | foo | 1
+(1 row)
+
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for view rw_view1
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for view rw_view1
+SET SESSION AUTHORIZATION regress_view_user1;
+GRANT UPDATE ON rw_view1 TO regress_view_user2;
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+ a | b | c
+---+-----+---
+ 1 | foo | 1
+(1 row)
+
+SELECT * FROM rw_view2 FOR UPDATE;
+ a | b | c
+---+-----+---
+ 1 | foo | 1
+(1 row)
+
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1;
+RESET SESSION AUTHORIZATION;
+REVOKE UPDATE ON base_tbl FROM regress_view_user1;
+SET SESSION AUTHORIZATION regress_view_user1;
+SELECT * FROM rw_view1;
+ a | b | c
+---+-----+---
+ 1 | bar | 1
+(1 row)
+
+SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
+ERROR: permission denied for table base_tbl
+SET SESSION AUTHORIZATION regress_view_user2;
+SELECT * FROM rw_view2;
+ a | b | c
+---+-----+---
+ 1 | bar | 1
+(1 row)
+
+SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
+ERROR: permission denied for table base_tbl
+UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
+ERROR: permission denied for table base_tbl
+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 regress_view_user1;
+DROP USER regress_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
+-- view on table with GENERATED columns
+CREATE TABLE base_tbl (id int, idplus1 int GENERATED ALWAYS AS (id + 1) STORED);
+CREATE VIEW rw_view1 AS SELECT * FROM base_tbl;
+INSERT INTO base_tbl (id) VALUES (1);
+INSERT INTO rw_view1 (id) VALUES (2);
+INSERT INTO base_tbl (id, idplus1) VALUES (3, DEFAULT);
+INSERT INTO rw_view1 (id, idplus1) VALUES (4, DEFAULT);
+INSERT INTO base_tbl (id, idplus1) VALUES (5, 6); -- error
+ERROR: cannot insert into column "idplus1"
+DETAIL: Column "idplus1" is a generated column.
+INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error
+ERROR: cannot insert into column "idplus1"
+DETAIL: Column "idplus1" is a generated column.
+SELECT * FROM base_tbl;
+ id | idplus1
+----+---------
+ 1 | 2
+ 2 | 3
+ 3 | 4
+ 4 | 5
+(4 rows)
+
+UPDATE base_tbl SET id = 2000 WHERE id = 2;
+UPDATE rw_view1 SET id = 3000 WHERE id = 3;
+SELECT * FROM base_tbl;
+ id | idplus1
+------+---------
+ 1 | 2
+ 4 | 5
+ 2000 | 2001
+ 3000 | 3001
+(4 rows)
+
+DROP TABLE base_tbl CASCADE;
+NOTICE: drop cascades to view rw_view1
+-- 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)
+
+CREATE TABLE other_tbl_parent (id int);
+CREATE TABLE other_tbl_child () INHERITS (other_tbl_parent);
+INSERT INTO other_tbl_parent VALUES (7),(200);
+INSERT INTO other_tbl_child VALUES (8),(100);
+EXPLAIN (costs off)
+UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Update on base_tbl_parent
+ Update on base_tbl_parent
+ Update on base_tbl_child base_tbl_parent_1
+ -> Hash Join
+ Hash Cond: (other_tbl_parent.id = base_tbl_parent.a)
+ -> Append
+ -> Seq Scan on other_tbl_parent other_tbl_parent_1
+ -> Seq Scan on other_tbl_child other_tbl_parent_2
+ -> Hash
+ -> Seq Scan on base_tbl_parent
+ -> Merge Join
+ Merge Cond: (base_tbl_parent_1.a = other_tbl_parent.id)
+ -> Sort
+ Sort Key: base_tbl_parent_1.a
+ -> Seq Scan on base_tbl_child base_tbl_parent_1
+ -> Sort
+ Sort Key: other_tbl_parent.id
+ -> Append
+ -> Seq Scan on other_tbl_parent other_tbl_parent_1
+ -> Seq Scan on other_tbl_child other_tbl_parent_2
+(20 rows)
+
+UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
+SELECT * FROM ONLY base_tbl_parent ORDER BY a;
+ a
+------
+ -200
+ -100
+ -40
+ -30
+ -20
+ -10
+ 1100
+ 1200
+(8 rows)
+
+SELECT * FROM base_tbl_child ORDER BY a;
+ a
+------
+ 3
+ 4
+ 10
+ 20
+ 1007
+ 1008
+(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
+DROP TABLE other_tbl_parent CASCADE;
+NOTICE: drop cascades to table other_tbl_child
+-- 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 | Collation | Nullable | Default | 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 | Collation | Nullable | Default | 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 | Collation | Nullable | Default | 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 enum option "check_option": here
+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 | Collation | Nullable | Default | 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 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
+ -> Seq Scan on base_tbl
+ Filter: ((visibility = 'public'::text) AND snoop(person))
+(3 rows)
+
+EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
+ QUERY PLAN
+-------------------------------------------------------------------------
+ Delete on base_tbl
+ -> Seq Scan on base_tbl
+ Filter: ((visibility = 'public'::text) AND (NOT snoop(person)))
+(3 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
+ -> Seq Scan on base_tbl
+ Filter: ((visibility = 'public'::text) AND snoop(person) AND snoop(person))
+(3 rows)
+
+EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
+ QUERY PLAN
+-------------------------------------------------------------------------------------------
+ Delete on base_tbl
+ -> Seq Scan on base_tbl
+ Filter: ((visibility = 'public'::text) AND snoop(person) AND (NOT snoop(person)))
+(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
+-- 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)
+ -> Index Scan using base_tbl_pkey on base_tbl
+ Index Cond: (id = 1)
+ Filter: ((NOT deleted) AND snoop(data))
+(7 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 < 7 AND a != 6;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------------------------
+ Update on public.t1
+ Update on public.t1
+ Update on public.t11 t1_1
+ Update on public.t12 t1_2
+ Update on public.t111 t1_3
+ -> Index Scan using t1_a_idx on public.t1
+ Output: 100, t1.b, t1.c, t1.ctid
+ Index Cond: ((t1.a > 5) AND (t1.a < 7))
+ Filter: ((t1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on public.t12 t12_1
+ Filter: (t12_1.a = t1.a)
+ -> Seq Scan on public.t111 t12_2
+ Filter: (t12_2.a = t1.a)
+ SubPlan 2
+ -> Append
+ -> Seq Scan on public.t12 t12_4
+ Output: t12_4.a
+ -> Seq Scan on public.t111 t12_5
+ Output: t12_5.a
+ -> Index Scan using t11_a_idx on public.t11 t1_1
+ Output: 100, t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
+ Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
+ Filter: ((t1_1.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+ -> Index Scan using t12_a_idx on public.t12 t1_2
+ Output: 100, t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
+ Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
+ Filter: ((t1_2.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+ -> Index Scan using t111_a_idx on public.t111 t1_3
+ Output: 100, t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
+ Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
+ Filter: ((t1_3.a <> 6) AND (alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+(33 rows)
+
+UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
+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
+ Update on public.t1
+ Update on public.t11 t1_1
+ Update on public.t12 t1_2
+ Update on public.t111 t1_3
+ -> Index Scan using t1_a_idx on public.t1
+ Output: (t1.a + 1), t1.b, t1.c, t1.ctid
+ Index Cond: ((t1.a > 5) AND (t1.a = 8))
+ Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1.a) AND leakproof(t1.a))
+ SubPlan 1
+ -> Append
+ -> Seq Scan on public.t12 t12_1
+ Filter: (t12_1.a = t1.a)
+ -> Seq Scan on public.t111 t12_2
+ Filter: (t12_2.a = t1.a)
+ SubPlan 2
+ -> Append
+ -> Seq Scan on public.t12 t12_4
+ Output: t12_4.a
+ -> Seq Scan on public.t111 t12_5
+ Output: t12_5.a
+ -> Index Scan using t11_a_idx on public.t11 t1_1
+ Output: (t1_1.a + 1), t1_1.b, t1_1.c, t1_1.d, t1_1.ctid
+ Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
+ Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_1.a) AND leakproof(t1_1.a))
+ -> Index Scan using t12_a_idx on public.t12 t1_2
+ Output: (t1_2.a + 1), t1_2.b, t1_2.c, t1_2.e, t1_2.ctid
+ Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
+ Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_2.a) AND leakproof(t1_2.a))
+ -> Index Scan using t111_a_idx on public.t111 t1_3
+ Output: (t1_3.a + 1), t1_3.b, t1_3.c, t1_3.d, t1_3.e, t1_3.ctid
+ Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
+ Filter: ((alternatives: SubPlan 1 or hashed SubPlan 2) AND snoop(t1_3.a) AND leakproof(t1_3.a))
+(33 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;
+--
+-- Test CREATE OR REPLACE VIEW turning a non-updatable view into an
+-- auto-updatable view and adding check options in a single step
+--
+CREATE TABLE t1 (a int, b text);
+CREATE VIEW v1 AS SELECT null::int AS a;
+CREATE OR REPLACE VIEW v1 AS SELECT * FROM t1 WHERE a > 0 WITH CHECK OPTION;
+INSERT INTO v1 VALUES (1, 'ok'); -- ok
+INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail
+ERROR: new row violates check option for view "v1"
+DETAIL: Failing row contains (-1, invalid).
+DROP VIEW v1;
+DROP TABLE t1;
+-- check that an auto-updatable view on a partitioned table works correctly
+create table uv_pt (a int, b int, v varchar) partition by range (a, b);
+create table uv_pt1 (b int not null, v varchar, a int not null) partition by range (b);
+create table uv_pt11 (like uv_pt1);
+alter table uv_pt11 drop a;
+alter table uv_pt11 add a int;
+alter table uv_pt11 drop a;
+alter table uv_pt11 add a int not null;
+alter table uv_pt1 attach partition uv_pt11 for values from (2) to (5);
+alter table uv_pt attach partition uv_pt1 for values from (1, 2) to (1, 10);
+create view uv_ptv as select * from uv_pt;
+select events & 4 != 0 AS upd,
+ events & 8 != 0 AS ins,
+ events & 16 != 0 AS del
+ from pg_catalog.pg_relation_is_updatable('uv_pt'::regclass, false) t(events);
+ upd | ins | del
+-----+-----+-----
+ t | t | t
+(1 row)
+
+select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 1::smallint, false);
+ pg_column_is_updatable
+------------------------
+ t
+(1 row)
+
+select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 2::smallint, false);
+ pg_column_is_updatable
+------------------------
+ t
+(1 row)
+
+select table_name, is_updatable, is_insertable_into
+ from information_schema.views where table_name = 'uv_ptv';
+ table_name | is_updatable | is_insertable_into
+------------+--------------+--------------------
+ uv_ptv | YES | YES
+(1 row)
+
+select table_name, column_name, is_updatable
+ from information_schema.columns where table_name = 'uv_ptv' order by column_name;
+ table_name | column_name | is_updatable
+------------+-------------+--------------
+ uv_ptv | a | YES
+ uv_ptv | b | YES
+ uv_ptv | v | YES
+(3 rows)
+
+insert into uv_ptv values (1, 2);
+select tableoid::regclass, * from uv_pt;
+ tableoid | a | b | v
+----------+---+---+---
+ uv_pt11 | 1 | 2 |
+(1 row)
+
+create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
+insert into uv_ptv_wco values (1, 2);
+ERROR: new row violates check option for view "uv_ptv_wco"
+DETAIL: Failing row contains (1, 2, null).
+drop view uv_ptv, uv_ptv_wco;
+drop table uv_pt, uv_pt1, uv_pt11;
+-- check that wholerow vars appearing in WITH CHECK OPTION constraint expressions
+-- work fine with partitioned tables
+create table wcowrtest (a int) partition by list (a);
+create table wcowrtest1 partition of wcowrtest for values in (1);
+create view wcowrtest_v as select * from wcowrtest where wcowrtest = '(2)'::wcowrtest with check option;
+insert into wcowrtest_v values (1);
+ERROR: new row violates check option for view "wcowrtest_v"
+DETAIL: Failing row contains (1).
+alter table wcowrtest add b text;
+create table wcowrtest2 (b text, c int, a int);
+alter table wcowrtest2 drop c;
+alter table wcowrtest attach partition wcowrtest2 for values in (2);
+create table sometable (a int, b text);
+insert into sometable values (1, 'a'), (2, 'b');
+create view wcowrtest_v2 as
+ select *
+ from wcowrtest r
+ where r in (select s from sometable s where r.a = s.a)
+with check option;
+-- WITH CHECK qual will be processed with wcowrtest2's
+-- rowtype after tuple-routing
+insert into wcowrtest_v2 values (2, 'no such row in sometable');
+ERROR: new row violates check option for view "wcowrtest_v2"
+DETAIL: Failing row contains (2, no such row in sometable).
+drop view wcowrtest_v, wcowrtest_v2;
+drop table wcowrtest, sometable;
+-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's
+-- columns are named and ordered differently than the underlying table's.
+create table uv_iocu_tab (a text unique, b float);
+insert into uv_iocu_tab values ('xyxyxy', 0);
+create view uv_iocu_view as
+ select b, b+1 as c, a, '2.0'::text as two from uv_iocu_tab;
+insert into uv_iocu_view (a, b) values ('xyxyxy', 1)
+ on conflict (a) do update set b = uv_iocu_view.b;
+select * from uv_iocu_tab;
+ a | b
+--------+---
+ xyxyxy | 0
+(1 row)
+
+insert into uv_iocu_view (a, b) values ('xyxyxy', 1)
+ on conflict (a) do update set b = excluded.b;
+select * from uv_iocu_tab;
+ a | b
+--------+---
+ xyxyxy | 1
+(1 row)
+
+-- OK to access view columns that are not present in underlying base
+-- relation in the ON CONFLICT portion of the query
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = cast(excluded.two as float);
+select * from uv_iocu_tab;
+ a | b
+--------+---
+ xyxyxy | 2
+(1 row)
+
+explain (costs off)
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = excluded.b where excluded.c > 0;
+ QUERY PLAN
+-----------------------------------------------------------------------------------
+ Insert on uv_iocu_tab
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: uv_iocu_tab_a_key
+ Conflict Filter: ((excluded.b + '1'::double precision) > '0'::double precision)
+ -> Result
+(5 rows)
+
+insert into uv_iocu_view (a, b) values ('xyxyxy', 3)
+ on conflict (a) do update set b = excluded.b where excluded.c > 0;
+select * from uv_iocu_tab;
+ a | b
+--------+---
+ xyxyxy | 3
+(1 row)
+
+drop view uv_iocu_view;
+drop table uv_iocu_tab;
+-- Test whole-row references to the view
+create table uv_iocu_tab (a int unique, b text);
+create view uv_iocu_view as
+ select b as bb, a as aa, uv_iocu_tab::text as cc from uv_iocu_tab;
+insert into uv_iocu_view (aa,bb) values (1,'x');
+explain (costs off)
+insert into uv_iocu_view (aa,bb) values (1,'y')
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*
+ where excluded.aa > 0
+ and excluded.bb != ''
+ and excluded.cc is not null;
+ QUERY PLAN
+---------------------------------------------------------------------------------------------------------
+ Insert on uv_iocu_tab
+ Conflict Resolution: UPDATE
+ Conflict Arbiter Indexes: uv_iocu_tab_a_key
+ Conflict Filter: ((excluded.a > 0) AND (excluded.b <> ''::text) AND ((excluded.*)::text IS NOT NULL))
+ -> Result
+(5 rows)
+
+insert into uv_iocu_view (aa,bb) values (1,'y')
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*
+ where excluded.aa > 0
+ and excluded.bb != ''
+ and excluded.cc is not null;
+select * from uv_iocu_view;
+ bb | aa | cc
+-------------------------+----+---------------------------------
+ Rejected: (y,1,"(1,y)") | 1 | (1,"Rejected: (y,1,""(1,y)"")")
+(1 row)
+
+-- Test omitting a column of the base relation
+delete from uv_iocu_view;
+insert into uv_iocu_view (aa,bb) values (1,'x');
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+ bb | aa | cc
+-----------------------+----+-------------------------------
+ Rejected: (,1,"(1,)") | 1 | (1,"Rejected: (,1,""(1,)"")")
+(1 row)
+
+alter table uv_iocu_tab alter column b set default 'table default';
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+ bb | aa | cc
+-------------------------------------------------------+----+---------------------------------------------------------------------
+ Rejected: ("table default",1,"(1,""table default"")") | 1 | (1,"Rejected: (""table default"",1,""(1,""""table default"""")"")")
+(1 row)
+
+alter view uv_iocu_view alter column bb set default 'view default';
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set bb = 'Rejected: '||excluded.*;
+select * from uv_iocu_view;
+ bb | aa | cc
+-----------------------------------------------------+----+-------------------------------------------------------------------
+ Rejected: ("view default",1,"(1,""view default"")") | 1 | (1,"Rejected: (""view default"",1,""(1,""""view default"""")"")")
+(1 row)
+
+-- Should fail to update non-updatable columns
+insert into uv_iocu_view (aa) values (1)
+ on conflict (aa) do update set cc = 'XXX';
+ERROR: cannot insert into column "cc" of view "uv_iocu_view"
+DETAIL: View columns that are not columns of their base relation are not updatable.
+drop view uv_iocu_view;
+drop table uv_iocu_tab;
+-- ON CONFLICT DO UPDATE permissions checks
+create user regress_view_user1;
+create user regress_view_user2;
+set session authorization regress_view_user1;
+create table base_tbl(a int unique, b text, c float);
+insert into base_tbl values (1,'xxx',1.0);
+create view rw_view1 as select b as bb, c as cc, a as aa from base_tbl;
+grant select (aa,bb) on rw_view1 to regress_view_user2;
+grant insert on rw_view1 to regress_view_user2;
+grant update (bb) on rw_view1 to regress_view_user2;
+set session authorization regress_view_user2;
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = excluded.cc; -- Not allowed
+ERROR: permission denied for view rw_view1
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = rw_view1.cc; -- Not allowed
+ERROR: permission denied for view rw_view1
+insert into rw_view1 values ('yyy',2.0,1)
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+insert into rw_view1 values ('zzz',2.0,1)
+ on conflict (aa) do update set bb = rw_view1.bb||'xxx'; -- OK
+insert into rw_view1 values ('zzz',2.0,1)
+ on conflict (aa) do update set cc = 3.0; -- Not allowed
+ERROR: permission denied for view rw_view1
+reset session authorization;
+select * from base_tbl;
+ a | b | c
+---+--------+---
+ 1 | yyyxxx | 1
+(1 row)
+
+set session authorization regress_view_user1;
+grant select (a,b) on base_tbl to regress_view_user2;
+grant insert (a,b) on base_tbl to regress_view_user2;
+grant update (a,b) on base_tbl to regress_view_user2;
+set session authorization regress_view_user2;
+create view rw_view2 as select b as bb, c as cc, a as aa from base_tbl;
+insert into rw_view2 (aa,bb) values (1,'xxx')
+ on conflict (aa) do update set bb = excluded.bb; -- Not allowed
+ERROR: permission denied for table base_tbl
+create view rw_view3 as select b as bb, a as aa from base_tbl;
+insert into rw_view3 (aa,bb) values (1,'xxx')
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+reset session authorization;
+select * from base_tbl;
+ a | b | c
+---+-----+---
+ 1 | xxx | 1
+(1 row)
+
+set session authorization regress_view_user2;
+create view rw_view4 as select aa, bb, cc FROM rw_view1;
+insert into rw_view4 (aa,bb) values (1,'yyy')
+ on conflict (aa) do update set bb = excluded.bb; -- Not allowed
+ERROR: permission denied for view rw_view1
+create view rw_view5 as select aa, bb FROM rw_view1;
+insert into rw_view5 (aa,bb) values (1,'yyy')
+ on conflict (aa) do update set bb = excluded.bb; -- OK
+reset session authorization;
+select * from base_tbl;
+ a | b | c
+---+-----+---
+ 1 | yyy | 1
+(1 row)
+
+drop view rw_view5;
+drop view rw_view4;
+drop view rw_view3;
+drop view rw_view2;
+drop view rw_view1;
+drop table base_tbl;
+drop user regress_view_user1;
+drop user regress_view_user2;
+-- Test single- and multi-row inserts with table and view defaults.
+-- Table defaults should be used, unless overridden by view defaults.
+create table base_tab_def (a int, b text default 'Table default',
+ c text default 'Table default', d text, e text);
+create view base_tab_def_view as select * from base_tab_def;
+alter view base_tab_def_view alter b set default 'View default';
+alter view base_tab_def_view alter d set default 'View default';
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+ a | b | c | d | e
+----+---------------+---------------+--------------+---
+ 1 | Table default | Table default | |
+ 2 | Table default | Table default | |
+ 3 | Table default | Table default | |
+ 4 | Table default | Table default | |
+ 5 | Table default | Table default | |
+ 6 | Table default | Table default | |
+ 11 | View default | Table default | View default |
+ 12 | View default | Table default | View default |
+ 13 | View default | Table default | View default |
+ 14 | View default | Table default | View default |
+ 15 | View default | Table default | View default |
+ 16 | View default | Table default | View default |
+ 17 | View default | Table default | View default |
+ | View default | Table default | View default |
+(14 rows)
+
+-- Adding an INSTEAD OF trigger should cause NULLs to be inserted instead of
+-- table defaults, where there are no view defaults.
+create function base_tab_def_view_instrig_func() returns trigger
+as
+$$
+begin
+ insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+ return new;
+end;
+$$
+language plpgsql;
+create trigger base_tab_def_view_instrig instead of insert on base_tab_def_view
+ for each row execute function base_tab_def_view_instrig_func();
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+ a | b | c | d | e
+----+---------------+---------------+--------------+---
+ 1 | Table default | Table default | |
+ 2 | Table default | Table default | |
+ 3 | Table default | Table default | |
+ 4 | Table default | Table default | |
+ 5 | Table default | Table default | |
+ 6 | Table default | Table default | |
+ 11 | View default | | View default |
+ 12 | View default | | View default |
+ 13 | View default | | View default |
+ 14 | View default | | View default |
+ 15 | View default | | View default |
+ 16 | View default | | View default |
+ 17 | View default | | View default |
+ | View default | | View default |
+(14 rows)
+
+-- Using an unconditional DO INSTEAD rule should also cause NULLs to be
+-- inserted where there are no view defaults.
+drop trigger base_tab_def_view_instrig on base_tab_def_view;
+drop function base_tab_def_view_instrig_func;
+create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view
+ do instead insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a;
+ a | b | c | d | e
+----+---------------+---------------+--------------+---
+ 1 | Table default | Table default | |
+ 2 | Table default | Table default | |
+ 3 | Table default | Table default | |
+ 4 | Table default | Table default | |
+ 5 | Table default | Table default | |
+ 6 | Table default | Table default | |
+ 11 | View default | | View default |
+ 12 | View default | | View default |
+ 13 | View default | | View default |
+ 14 | View default | | View default |
+ 15 | View default | | View default |
+ 16 | View default | | View default |
+ 17 | View default | | View default |
+ | View default | | View default |
+(14 rows)
+
+-- A DO ALSO rule should cause each row to be inserted twice. The first
+-- insert should behave the same as an auto-updatable view (using table
+-- defaults, unless overridden by view defaults). The second insert should
+-- behave the same as a rule-updatable view (inserting NULLs where there are
+-- no view defaults).
+drop rule base_tab_def_view_ins_rule on base_tab_def_view;
+create rule base_tab_def_view_ins_rule as on insert to base_tab_def_view
+ do also insert into base_tab_def values (new.a, new.b, new.c, new.d, new.e);
+truncate base_tab_def;
+insert into base_tab_def values (1);
+insert into base_tab_def values (2), (3);
+insert into base_tab_def values (4, default, default, default, default);
+insert into base_tab_def values (5, default, default, default, default),
+ (6, default, default, default, default);
+insert into base_tab_def_view values (11);
+insert into base_tab_def_view values (12), (13);
+insert into base_tab_def_view values (14, default, default, default, default);
+insert into base_tab_def_view values (15, default, default, default, default),
+ (16, default, default, default, default);
+insert into base_tab_def_view values (17), (default);
+select * from base_tab_def order by a, c NULLS LAST;
+ a | b | c | d | e
+----+---------------+---------------+--------------+---
+ 1 | Table default | Table default | |
+ 2 | Table default | Table default | |
+ 3 | Table default | Table default | |
+ 4 | Table default | Table default | |
+ 5 | Table default | Table default | |
+ 6 | Table default | Table default | |
+ 11 | View default | Table default | View default |
+ 11 | View default | | View default |
+ 12 | View default | Table default | View default |
+ 12 | View default | | View default |
+ 13 | View default | Table default | View default |
+ 13 | View default | | View default |
+ 14 | View default | Table default | View default |
+ 14 | View default | | View default |
+ 15 | View default | Table default | View default |
+ 15 | View default | | View default |
+ 16 | View default | Table default | View default |
+ 16 | View default | | View default |
+ 17 | View default | Table default | View default |
+ 17 | View default | | View default |
+ | View default | Table default | View default |
+ | View default | | View default |
+(22 rows)
+
+drop view base_tab_def_view;
+drop table base_tab_def;
+-- Test defaults with array assignments
+create table base_tab (a serial, b int[], c text, d text default 'Table default');
+create view base_tab_view as select c, a, b from base_tab;
+alter view base_tab_view alter column c set default 'View default';
+insert into base_tab_view (b[1], b[2], c, b[5], b[4], a, b[3])
+values (1, 2, default, 5, 4, default, 3), (10, 11, 'C value', 14, 13, 100, 12);
+select * from base_tab order by a;
+ a | b | c | d
+-----+------------------+--------------+---------------
+ 1 | {1,2,3,4,5} | View default | Table default
+ 100 | {10,11,12,13,14} | C value | Table default
+(2 rows)
+
+drop view base_tab_view;
+drop table base_tab;