2012-12-09 00:25:48 +01:00
|
|
|
--
|
|
|
|
-- UPDATABLE VIEWS
|
|
|
|
--
|
Change floating-point output format for improved performance.
Previously, floating-point output was done by rounding to a specific
decimal precision; by default, to 6 or 15 decimal digits (losing
information) or as requested using extra_float_digits. Drivers that
wanted exact float values, and applications like pg_dump that must
preserve values exactly, set extra_float_digits=3 (or sometimes 2 for
historical reasons, though this isn't enough for float4).
Unfortunately, decimal rounded output is slow enough to become a
noticable bottleneck when dealing with large result sets or COPY of
large tables when many floating-point values are involved.
Floating-point output can be done much faster when the output is not
rounded to a specific decimal length, but rather is chosen as the
shortest decimal representation that is closer to the original float
value than to any other value representable in the same precision. The
recently published Ryu algorithm by Ulf Adams is both relatively
simple and remarkably fast.
Accordingly, change float4out/float8out to output shortest decimal
representations if extra_float_digits is greater than 0, and make that
the new default. Applications that need rounded output can set
extra_float_digits back to 0 or below, and take the resulting
performance hit.
We make one concession to portability for systems with buggy
floating-point input: we do not output decimal values that fall
exactly halfway between adjacent representable binary values (which
would rely on the reader doing round-to-nearest-even correctly). This
is known to be a problem at least for VS2013 on Windows.
Our version of the Ryu code originates from
https://github.com/ulfjack/ryu/ at commit c9c3fb1979, but with the
following (significant) modifications:
- Output format is changed to use fixed-point notation for small
exponents, as printf would, and also to use lowercase 'e', a
minimum of 2 exponent digits, and a mandatory sign on the exponent,
to keep the formatting as close as possible to previous output.
- The output of exact midpoint values is disabled as noted above.
- The integer fast-path code is changed somewhat (since we have
fixed-point output and the upstream did not).
- Our project style has been largely applied to the code with the
exception of C99 declaration-after-statement, which has been
retained as an exception to our present policy.
- Most of upstream's debugging and conditionals are removed, and we
use our own configure tests to determine things like uint128
availability.
Changing the float output format obviously affects a number of
regression tests. This patch uses an explicit setting of
extra_float_digits=0 for test output that is not expected to be
exactly reproducible (e.g. due to numerical instability or differing
algorithms for transcendental functions).
Conversions from floats to numeric are unchanged by this patch. These
may appear in index expressions and it is not yet clear whether any
change should be made, so that can be left for another day.
This patch assumes that the only supported floating point format is
now IEEE format, and the documentation is updated to reflect that.
Code by me, adapting the work of Ulf Adams and other contributors.
References:
https://dl.acm.org/citation.cfm?id=3192369
Reviewed-by: Tom Lane, Andres Freund, Donald Dong
Discussion: https://postgr.es/m/87r2el1bx6.fsf@news-spur.riddles.org.uk
2019-02-13 16:20:33 +01:00
|
|
|
-- avoid bit-exact output here because operations may not be bit-exact.
|
|
|
|
SET extra_float_digits = 0;
|
2013-10-18 16:35:36 +02:00
|
|
|
-- check that non-updatable views and columns are rejected with useful error
|
|
|
|
-- messages
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
2013-10-18 16:35:36 +02:00
|
|
|
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
|
2012-12-09 00:25:48 +01:00
|
|
|
CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
|
2014-04-13 03:04:58 +02:00
|
|
|
CREATE VIEW ro_view18 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
CREATE SEQUENCE uv_seq;
|
|
|
|
CREATE VIEW ro_view19 AS SELECT * FROM uv_seq; -- View based on a sequence
|
2014-04-13 03:04:58 +02:00
|
|
|
CREATE VIEW ro_view20 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
|
2012-12-09 00:25:48 +01:00
|
|
|
SELECT table_name, is_insertable_into
|
|
|
|
FROM information_schema.tables
|
2013-10-18 16:35:36 +02:00
|
|
|
WHERE table_name LIKE E'r_\\_view%'
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
2013-10-18 16:35:36 +02:00
|
|
|
rw_view14 | YES
|
|
|
|
rw_view15 | YES
|
|
|
|
rw_view16 | YES
|
2014-04-13 03:04:58 +02:00
|
|
|
(20 rows)
|
2012-12-09 00:25:48 +01:00
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
2013-10-18 16:35:36 +02:00
|
|
|
WHERE table_name LIKE E'r_\\_view%'
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
2013-10-18 16:35:36 +02:00
|
|
|
rw_view14 | YES | YES
|
|
|
|
rw_view15 | YES | YES
|
|
|
|
rw_view16 | YES | YES
|
2014-04-13 03:04:58 +02:00
|
|
|
(20 rows)
|
2012-12-09 00:25:48 +01:00
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
2013-10-18 16:35:36 +02:00
|
|
|
WHERE table_name LIKE E'r_\\_view%'
|
2012-12-09 00:25:48 +01:00
|
|
|
ORDER BY table_name, ordinal_position;
|
2016-12-20 18:00:00 +01:00
|
|
|
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)
|
2013-10-18 16:35:36 +02:00
|
|
|
|
|
|
|
-- Read-only views
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM ro_view1;
|
|
|
|
ERROR: cannot delete from view "ro_view1"
|
|
|
|
DETAIL: Views containing DISTINCT are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM ro_view2;
|
|
|
|
ERROR: cannot delete from view "ro_view2"
|
|
|
|
DETAIL: Views containing GROUP BY are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM ro_view3;
|
|
|
|
ERROR: cannot delete from view "ro_view3"
|
|
|
|
DETAIL: Views containing HAVING are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM ro_view4;
|
|
|
|
ERROR: cannot delete from view "ro_view4"
|
2014-08-29 06:01:34 +02:00
|
|
|
DETAIL: Views that return aggregate functions are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM ro_view5;
|
|
|
|
ERROR: cannot delete from view "ro_view5"
|
2014-08-29 06:01:34 +02:00
|
|
|
DETAIL: Views that return window functions are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM ro_view6;
|
|
|
|
ERROR: cannot delete from view "ro_view6"
|
2013-04-14 17:12:30 +02:00
|
|
|
DETAIL: Views containing UNION, INTERSECT, or EXCEPT are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
UPDATE ro_view7 SET a=a+1;
|
|
|
|
ERROR: cannot update view "ro_view7"
|
|
|
|
DETAIL: Views containing WITH are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
UPDATE ro_view8 SET a=a+1;
|
|
|
|
ERROR: cannot update view "ro_view8"
|
|
|
|
DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
UPDATE ro_view9 SET a=a+1;
|
|
|
|
ERROR: cannot update view "ro_view9"
|
|
|
|
DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
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.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
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.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
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.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
2012-12-09 00:25:48 +01:00
|
|
|
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.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO ro_view13 AS t USING (VALUES (1, 'Row 1')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN DELETE;
|
|
|
|
ERROR: cannot delete from view "ro_view13"
|
|
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
|
|
|
HINT: To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger.
|
|
|
|
MERGE INTO ro_view13 AS t USING (VALUES (2, 'Row 2')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = v.b;
|
|
|
|
ERROR: cannot update view "ro_view13"
|
|
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
|
|
|
HINT: To enable updating the view using MERGE, provide an INSTEAD OF UPDATE trigger.
|
|
|
|
MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b);
|
|
|
|
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 using MERGE, provide an INSTEAD OF INSERT trigger.
|
|
|
|
MERGE INTO ro_view13 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN DO NOTHING
|
|
|
|
WHEN NOT MATCHED THEN DO NOTHING; -- should be OK to do nothing
|
2013-10-18 16:35:36 +02:00
|
|
|
-- 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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view14 AS t
|
|
|
|
USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = v.b -- should be OK, except...
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (null, v.a, v.b); -- should fail
|
|
|
|
ERROR: cannot merge into column "ctid" of view "rw_view14"
|
|
|
|
DETAIL: View columns that refer to system columns are not updatable.
|
|
|
|
MERGE INTO rw_view14 AS t
|
|
|
|
USING (VALUES (2, 'Merged row 2'), (3, 'Merged row 3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = v.b -- should be OK
|
|
|
|
WHEN NOT MATCHED THEN INSERT (a,b) VALUES (v.a, v.b); -- should be OK
|
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
a | b
|
|
|
|
----+--------------
|
|
|
|
-2 | Row -2
|
|
|
|
-1 | Row -1
|
|
|
|
0 | Row 0
|
|
|
|
1 | Row 1
|
|
|
|
2 | Merged row 2
|
|
|
|
3 | Merged row 3
|
|
|
|
(6 rows)
|
|
|
|
|
|
|
|
MERGE INTO rw_view14 AS t
|
|
|
|
USING (VALUES (2, 'Row 2'), (3, 'Row 3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED AND t.a = 2 THEN UPDATE SET b = v.b -- should be OK
|
|
|
|
WHEN MATCHED AND t.a = 3 THEN DELETE; -- should be OK
|
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
a | b
|
|
|
|
----+--------
|
|
|
|
-2 | Row -2
|
|
|
|
-1 | Row -1
|
|
|
|
0 | Row 0
|
|
|
|
1 | Row 1
|
|
|
|
2 | Row 2
|
|
|
|
(5 rows)
|
|
|
|
|
2013-10-18 16:35:36 +02:00
|
|
|
-- 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
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
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)
|
|
|
|
|
2013-10-18 16:35:36 +02:00
|
|
|
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
|
2012-12-09 00:25:48 +01:00
|
|
|
INSERT INTO ro_view17 VALUES (3, 'ROW 3');
|
|
|
|
ERROR: cannot insert into view "ro_view1"
|
|
|
|
DETAIL: Views containing DISTINCT are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
|
2014-04-13 03:04:58 +02:00
|
|
|
DELETE FROM ro_view18;
|
|
|
|
ERROR: cannot delete from view "ro_view18"
|
2012-12-09 00:25:48 +01:00
|
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
|
2016-12-20 18:00:00 +01:00
|
|
|
UPDATE ro_view19 SET last_value=1000;
|
2014-04-13 03:04:58 +02:00
|
|
|
ERROR: cannot update view "ro_view19"
|
2012-12-09 00:25:48 +01:00
|
|
|
DETAIL: Views that do not select from a single table or view are not automatically updatable.
|
2013-08-15 05:00:34 +02:00
|
|
|
HINT: To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
|
2014-04-13 03:04:58 +02:00
|
|
|
UPDATE ro_view20 SET b=upper(b);
|
|
|
|
ERROR: cannot update view "ro_view20"
|
2013-10-18 16:35:36 +02:00
|
|
|
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.
|
2020-01-14 10:52:21 +01:00
|
|
|
-- 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.
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view16 AS t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
|
|
|
|
ERROR: cannot execute MERGE on relation "rw_view16"
|
|
|
|
DETAIL: MERGE is not supported for relations with rules.
|
2012-12-09 00:25:48 +01:00
|
|
|
DROP TABLE base_tbl CASCADE;
|
2014-04-13 03:04:58 +02:00
|
|
|
NOTICE: drop cascades to 16 other objects
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
Sort the dependent objects before recursing in findDependentObjects().
Historically, the notices output by DROP CASCADE tended to come out
in uncertain order, and in some cases you might get different claims
about which object depends on which other one. This is because we
just traversed the dependency tree in the order in which pg_depend
entries are seen, and nbtree has never promised anything about the
order of equal-keyed index entries. We've put up with that for years,
hacking regression tests when necessary to prevent them from emitting
unstable output. However, it's a problem for pending work that will
change nbtree's behavior for equal keys, as that causes unexpected
changes in the regression test results.
Hence, adjust findDependentObjects to sort the results of each
indexscan before processing them. The sort is on descending OID of
the dependent objects, hence more or less reverse creation order.
While this rule could still result in bogus regression test failures
if an OID wraparound occurred mid-test, that seems unlikely to happen
in any plausible development or packaging-test scenario.
This is enough to ensure output stability for ordinary DROP CASCADE
commands, but not for DROP OWNED BY, because that has a different
code path with the same problem. We might later choose to sort in
the DROP OWNED BY code as well, but this patch doesn't do so.
I've also not done anything about reverting the existing hacks to
suppress unstable DROP CASCADE output in specific regression tests.
It might be worth undoing those, but it seems like a distinct question.
The first indexscan loop in findDependentObjects is not touched,
meaning there is a hazard of unstable error reports from that too.
However, said hazard is not the fault of that code: it was designed
on the assumption that there could be at most one "owning" object
to complain about, and that assumption does not seem unreasonable.
The recent patch that added the possibility of multiple
DEPENDENCY_INTERNAL_AUTO links broke that assumption, but we should
fix that situation not band-aid around it. That's a matter for
another patch, though.
Discussion: https://postgr.es/m/12244.1547854440@sss.pgh.pa.us
2019-01-21 19:48:07 +01:00
|
|
|
drop cascades to view ro_view4
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
Sort the dependent objects before recursing in findDependentObjects().
Historically, the notices output by DROP CASCADE tended to come out
in uncertain order, and in some cases you might get different claims
about which object depends on which other one. This is because we
just traversed the dependency tree in the order in which pg_depend
entries are seen, and nbtree has never promised anything about the
order of equal-keyed index entries. We've put up with that for years,
hacking regression tests when necessary to prevent them from emitting
unstable output. However, it's a problem for pending work that will
change nbtree's behavior for equal keys, as that causes unexpected
changes in the regression test results.
Hence, adjust findDependentObjects to sort the results of each
indexscan before processing them. The sort is on descending OID of
the dependent objects, hence more or less reverse creation order.
While this rule could still result in bogus regression test failures
if an OID wraparound occurred mid-test, that seems unlikely to happen
in any plausible development or packaging-test scenario.
This is enough to ensure output stability for ordinary DROP CASCADE
commands, but not for DROP OWNED BY, because that has a different
code path with the same problem. We might later choose to sort in
the DROP OWNED BY code as well, but this patch doesn't do so.
I've also not done anything about reverting the existing hacks to
suppress unstable DROP CASCADE output in specific regression tests.
It might be worth undoing those, but it seems like a distinct question.
The first indexscan loop in findDependentObjects is not touched,
meaning there is a hazard of unstable error reports from that too.
However, said hazard is not the fault of that code: it was designed
on the assumption that there could be at most one "owning" object
to complain about, and that assumption does not seem unreasonable.
The recent patch that added the possibility of multiple
DEPENDENCY_INTERNAL_AUTO links broke that assumption, but we should
fix that situation not band-aid around it. That's a matter for
another patch, though.
Discussion: https://postgr.es/m/12244.1547854440@sss.pgh.pa.us
2019-01-21 19:48:07 +01:00
|
|
|
drop cascades to view rw_view14
|
2013-10-18 16:35:36 +02:00
|
|
|
drop cascades to view rw_view15
|
|
|
|
drop cascades to view rw_view16
|
2014-04-13 03:04:58 +02:00
|
|
|
drop cascades to view ro_view20
|
|
|
|
DROP VIEW ro_view10, ro_view12, ro_view18;
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
DROP SEQUENCE uv_seq CASCADE;
|
2014-04-13 03:04:58 +02:00
|
|
|
NOTICE: drop cascades to view ro_view19
|
2012-12-09 00:25:48 +01:00
|
|
|
-- 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)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t
|
|
|
|
USING (VALUES (0, 'ROW 0'), (1, 'ROW 1'),
|
|
|
|
(2, 'ROW 2'), (3, 'ROW 3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED AND t.a <= 1 THEN UPDATE SET b = v.b
|
|
|
|
WHEN MATCHED THEN DELETE
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a)
|
|
|
|
RETURNING merge_action(), v.*, t.*;
|
|
|
|
merge_action | a | b | a | b
|
|
|
|
--------------+---+-------+---+-------------
|
|
|
|
UPDATE | 1 | ROW 1 | 1 | ROW 1
|
|
|
|
DELETE | 3 | ROW 3 | 3 | Row 3
|
|
|
|
INSERT | 2 | ROW 2 | 2 | Unspecified
|
|
|
|
(3 rows)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
a | b
|
|
|
|
----+-------------
|
|
|
|
-2 | Row -2
|
|
|
|
-1 | Row -1
|
|
|
|
0 | Row 0
|
|
|
|
1 | ROW 1
|
|
|
|
2 | Unspecified
|
|
|
|
5 | Unspecified
|
|
|
|
(6 rows)
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
MERGE INTO rw_view1 t
|
|
|
|
USING (VALUES (0, 'R0'), (1, 'R1'),
|
|
|
|
(2, 'R2'), (3, 'R3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED AND t.a <= 1 THEN UPDATE SET b = v.b
|
|
|
|
WHEN MATCHED THEN DELETE
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN DELETE
|
|
|
|
WHEN NOT MATCHED AND a > 0 THEN INSERT (a) VALUES (v.a)
|
|
|
|
RETURNING merge_action(), v.*, t.*;
|
|
|
|
merge_action | a | b | a | b
|
|
|
|
--------------+---+----+---+-------------
|
|
|
|
UPDATE | 1 | R1 | 1 | R1
|
|
|
|
DELETE | | | 5 | Unspecified
|
|
|
|
DELETE | 2 | R2 | 2 | Unspecified
|
|
|
|
INSERT | 3 | R3 | 3 | Unspecified
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
a | b
|
|
|
|
----+-------------
|
|
|
|
-2 | Row -2
|
|
|
|
-1 | Row -1
|
|
|
|
0 | Row 0
|
|
|
|
1 | R1
|
|
|
|
3 | Unspecified
|
|
|
|
(5 rows)
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
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)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
EXPLAIN (costs off)
|
|
|
|
MERGE INTO rw_view1 t USING (VALUES (5, 'X')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN DELETE;
|
|
|
|
QUERY PLAN
|
|
|
|
--------------------------------------------------
|
|
|
|
Merge on base_tbl
|
|
|
|
-> Index Scan using base_tbl_pkey on base_tbl
|
|
|
|
Index Cond: ((a > 0) AND (a = 5))
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
EXPLAIN (costs off)
|
|
|
|
MERGE INTO rw_view1 t
|
|
|
|
USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'Updated';
|
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------
|
|
|
|
Merge on base_tbl
|
|
|
|
-> Hash Join
|
|
|
|
Hash Cond: (base_tbl.a = generate_series.generate_series)
|
|
|
|
-> Bitmap Heap Scan on base_tbl
|
|
|
|
Recheck Cond: (a > 0)
|
|
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
|
|
Index Cond: (a > 0)
|
|
|
|
-> Hash
|
|
|
|
-> Function Scan on generate_series
|
|
|
|
(9 rows)
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
EXPLAIN (costs off)
|
|
|
|
MERGE INTO rw_view1 t
|
|
|
|
USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN DELETE;
|
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------
|
|
|
|
Merge on base_tbl
|
|
|
|
-> Hash Left Join
|
|
|
|
Hash Cond: (base_tbl.a = generate_series.generate_series)
|
|
|
|
-> Bitmap Heap Scan on base_tbl
|
|
|
|
Recheck Cond: (a > 0)
|
|
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
|
|
Index Cond: (a > 0)
|
|
|
|
-> Hash
|
|
|
|
-> Function Scan on generate_series
|
|
|
|
(9 rows)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
EXPLAIN (costs off)
|
|
|
|
MERGE INTO rw_view1 t
|
|
|
|
USING (SELECT * FROM generate_series(1,5)) AS s(a) ON t.a = s.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT (a) VALUES (s.a);
|
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------
|
|
|
|
Merge on base_tbl
|
|
|
|
-> Hash Right Join
|
|
|
|
Hash Cond: (base_tbl.a = generate_series.generate_series)
|
|
|
|
-> Bitmap Heap Scan on base_tbl
|
|
|
|
Recheck Cond: (a > 0)
|
|
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
|
|
Index Cond: (a > 0)
|
|
|
|
-> Hash
|
|
|
|
-> Function Scan on generate_series
|
|
|
|
(9 rows)
|
|
|
|
|
2022-10-12 00:24:14 +02:00
|
|
|
-- it's still updatable if we add a DO ALSO rule
|
|
|
|
CREATE TABLE base_tbl_hist(ts timestamptz default now(), a int, b text);
|
|
|
|
CREATE RULE base_tbl_log AS ON INSERT TO rw_view1 DO ALSO
|
|
|
|
INSERT INTO base_tbl_hist(a,b) VALUES(new.a, new.b);
|
|
|
|
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)
|
|
|
|
|
|
|
|
-- Check behavior with DEFAULTs (bug #17633)
|
|
|
|
INSERT INTO rw_view1 VALUES (9, DEFAULT), (10, DEFAULT);
|
|
|
|
SELECT a, b FROM base_tbl_hist;
|
|
|
|
a | b
|
|
|
|
----+---
|
|
|
|
9 |
|
|
|
|
10 |
|
|
|
|
(2 rows)
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
NOTICE: drop cascades to view rw_view1
|
2022-10-12 00:24:14 +02:00
|
|
|
DROP TABLE base_tbl_hist;
|
2012-12-09 00:25:48 +01:00
|
|
|
-- 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)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (VALUES (3, 'R3'), (4, 'R4'), (5, 'R5')) AS v(a,b) ON aaa = v.a
|
|
|
|
WHEN MATCHED AND aaa = 3 THEN DELETE
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = v.b
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a)
|
|
|
|
RETURNING merge_action(), v.*, t.*;
|
|
|
|
merge_action | a | b | aaa | bbb
|
|
|
|
--------------+---+----+-----+-------------
|
|
|
|
DELETE | 3 | R3 | 3 | Row 3
|
|
|
|
UPDATE | 4 | R4 | 4 | R4
|
|
|
|
INSERT | 5 | R5 | 5 | Unspecified
|
|
|
|
(3 rows)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
SELECT * FROM rw_view2 ORDER BY aaa;
|
|
|
|
aaa | bbb
|
|
|
|
-----+-------------
|
|
|
|
1 | Row 1
|
|
|
|
4 | R4
|
|
|
|
5 | Unspecified
|
|
|
|
(3 rows)
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (VALUES (4, 'r4'), (5, 'r5'), (6, 'r6')) AS v(a,b) ON aaa = v.a
|
|
|
|
WHEN MATCHED AND aaa = 4 THEN DELETE
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = v.b
|
|
|
|
WHEN NOT MATCHED THEN INSERT (aaa) VALUES (v.a)
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET bbb = 'Not matched by source'
|
|
|
|
RETURNING merge_action(), v.*, t.*;
|
|
|
|
merge_action | a | b | aaa | bbb
|
|
|
|
--------------+---+----+-----+-----------------------
|
|
|
|
UPDATE | | | 1 | Not matched by source
|
|
|
|
DELETE | 4 | r4 | 4 | R4
|
|
|
|
UPDATE | 5 | r5 | 5 | r5
|
|
|
|
INSERT | 6 | r6 | 6 | Unspecified
|
|
|
|
(4 rows)
|
|
|
|
|
|
|
|
SELECT * FROM rw_view2 ORDER BY aaa;
|
|
|
|
aaa | bbb
|
|
|
|
-----+-----------------------
|
|
|
|
1 | Not matched by source
|
|
|
|
5 | r5
|
|
|
|
6 | Unspecified
|
|
|
|
(3 rows)
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
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)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (3, 'Row 3')) AS v(a,b) ON t.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.b); -- should fail
|
|
|
|
ERROR: cannot execute MERGE on relation "rw_view1"
|
|
|
|
DETAIL: MERGE is not supported for relations with rules.
|
2012-12-09 00:25:48 +01:00
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
|
2013-03-14 20:10:41 +01:00
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------------------------------
|
2012-12-09 00:25:48 +01:00
|
|
|
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))
|
2013-03-14 20:10:41 +01:00
|
|
|
-> 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)
|
2012-12-09 00:25:48 +01:00
|
|
|
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
|
2013-03-14 20:10:41 +01:00
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------------------------------
|
2012-12-09 00:25:48 +01:00
|
|
|
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))
|
2013-03-14 20:10:41 +01:00
|
|
|
-> 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)
|
2012-12-09 00:25:48 +01:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED AND t.a <= 1 THEN DELETE
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b
|
Add RETURNING support to MERGE.
This allows a RETURNING clause to be appended to a MERGE query, to
return values based on each row inserted, updated, or deleted. As with
plain INSERT, UPDATE, and DELETE commands, the returned values are
based on the new contents of the target table for INSERT and UPDATE
actions, and on its old contents for DELETE actions. Values from the
source relation may also be returned.
As with INSERT/UPDATE/DELETE, the output of MERGE ... RETURNING may be
used as the source relation for other operations such as WITH queries
and COPY commands.
Additionally, a special function merge_action() is provided, which
returns 'INSERT', 'UPDATE', or 'DELETE', depending on the action
executed for each row. The merge_action() function can be used
anywhere in the RETURNING list, including in arbitrary expressions and
subqueries, but it is an error to use it anywhere outside of a MERGE
query's RETURNING list.
Dean Rasheed, reviewed by Isaac Morland, Vik Fearing, Alvaro Herrera,
Gurjeet Singh, Jian He, Jeff Davis, Merlin Moncure, Peter Eisentraut,
and Wolfgang Walther.
Discussion: http://postgr.es/m/CAEZATCWePEGQR5LBn-vD6SfeLZafzEm2Qy_L_Oky2=qw2w3Pzg@mail.gmail.com
2024-03-17 14:58:59 +01:00
|
|
|
WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b)
|
|
|
|
RETURNING merge_action(), s.*, t.*;
|
|
|
|
merge_action | a | b | a | b
|
|
|
|
--------------+---+----+---+-------
|
|
|
|
DELETE | 1 | R1 | 1 | Row 1
|
|
|
|
UPDATE | 2 | R2 | 2 | R2
|
|
|
|
INSERT | 3 | R3 | 3 | R3
|
|
|
|
(3 rows)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
a | b
|
|
|
|
----+--------
|
|
|
|
-2 | Row -2
|
|
|
|
-1 | Row -1
|
|
|
|
0 | Row 0
|
|
|
|
2 | R2
|
|
|
|
3 | R3
|
|
|
|
(5 rows)
|
|
|
|
|
2024-03-30 11:00:26 +01:00
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'r'||x FROM generate_series(0,2) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b
|
|
|
|
WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b)
|
|
|
|
WHEN NOT MATCHED BY SOURCE THEN UPDATE SET b = 'Not matched by source'
|
|
|
|
RETURNING merge_action(), s.*, t.*;
|
|
|
|
merge_action | a | b | a | b
|
|
|
|
--------------+---+----+---+-----------------------
|
|
|
|
UPDATE | 2 | r2 | 2 | r2
|
|
|
|
UPDATE | | | 3 | Not matched by source
|
|
|
|
INSERT | 1 | r1 | 1 | r1
|
|
|
|
(3 rows)
|
|
|
|
|
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
a | b
|
|
|
|
----+-----------------------
|
|
|
|
-2 | Row -2
|
|
|
|
-1 | Row -1
|
|
|
|
0 | Row 0
|
|
|
|
1 | r1
|
|
|
|
2 | r2
|
|
|
|
3 | Not matched by source
|
|
|
|
(6 rows)
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
|
2013-03-14 20:10:41 +01:00
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------------------------
|
2012-12-09 00:25:48 +01:00
|
|
|
Update on rw_view1 rw_view1_1
|
|
|
|
-> Subquery Scan on rw_view1
|
|
|
|
Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
|
2013-03-14 20:10:41 +01:00
|
|
|
-> Bitmap Heap Scan on base_tbl
|
|
|
|
Recheck Cond: (a > 0)
|
|
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
|
|
Index Cond: (a > 0)
|
|
|
|
(7 rows)
|
2012-12-09 00:25:48 +01:00
|
|
|
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
|
2013-03-14 20:10:41 +01:00
|
|
|
QUERY PLAN
|
|
|
|
----------------------------------------------------------
|
2012-12-09 00:25:48 +01:00
|
|
|
Delete on rw_view1 rw_view1_1
|
|
|
|
-> Subquery Scan on rw_view1
|
|
|
|
Filter: ((rw_view1.a < 10) AND (rw_view1.a = 2))
|
2013-03-14 20:10:41 +01:00
|
|
|
-> Bitmap Heap Scan on base_tbl
|
|
|
|
Recheck Cond: (a > 0)
|
|
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
|
|
Index Cond: (a > 0)
|
|
|
|
(7 rows)
|
2012-12-09 00:25:48 +01:00
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
EXPLAIN (costs off)
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED AND t.a <= 1 THEN DELETE
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b
|
|
|
|
WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b);
|
|
|
|
QUERY PLAN
|
|
|
|
------------------------------------------------------------
|
|
|
|
Merge on rw_view1 rw_view1_1
|
|
|
|
-> Hash Right Join
|
|
|
|
Hash Cond: (rw_view1.a = x.x)
|
|
|
|
-> Subquery Scan on rw_view1
|
|
|
|
Filter: (rw_view1.a < 10)
|
|
|
|
-> Bitmap Heap Scan on base_tbl
|
|
|
|
Recheck Cond: (a > 0)
|
|
|
|
-> Bitmap Index Scan on base_tbl_pkey
|
|
|
|
Index Cond: (a > 0)
|
|
|
|
-> Hash
|
|
|
|
-> Function Scan on generate_series x
|
|
|
|
(11 rows)
|
|
|
|
|
|
|
|
-- MERGE with incomplete set of INSTEAD OF triggers
|
|
|
|
DROP TRIGGER rw_view1_del_trig ON rw_view1;
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED AND t.a <= 1 THEN DELETE
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b
|
|
|
|
WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
|
|
|
|
ERROR: cannot delete from view "rw_view1"
|
|
|
|
DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
|
|
|
|
HINT: To enable deleting from the view using MERGE, provide an INSTEAD OF DELETE trigger.
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b
|
|
|
|
WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- ok
|
|
|
|
DROP TRIGGER rw_view1_ins_trig ON rw_view1;
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b
|
|
|
|
WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
|
|
|
|
ERROR: cannot insert into view "rw_view1"
|
|
|
|
DETAIL: Views containing LIMIT or OFFSET are not automatically updatable.
|
|
|
|
HINT: To enable inserting into the view using MERGE, provide an INSTEAD OF INSERT trigger.
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
|
|
|
|
-- MERGE with INSTEAD OF triggers on auto-updatable view
|
|
|
|
CREATE TRIGGER rw_view2_upd_trig INSTEAD OF UPDATE ON rw_view2
|
|
|
|
FOR EACH ROW EXECUTE PROCEDURE rw_view1_trig_fn();
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b
|
|
|
|
WHEN NOT MATCHED AND s.a > 0 THEN INSERT VALUES (s.a, s.b); -- should fail
|
|
|
|
ERROR: cannot merge into view "rw_view2"
|
|
|
|
DETAIL: MERGE is not supported for views with INSTEAD OF triggers for some actions, but not others.
|
|
|
|
HINT: To enable merging into the view, either provide a full set of INSTEAD OF triggers or drop the existing INSTEAD OF triggers.
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (SELECT x, 'R'||x FROM generate_series(0,3) x) AS s(a,b) ON t.a = s.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = s.b; -- ok
|
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
a | b
|
|
|
|
----+--------
|
|
|
|
-2 | Row -2
|
|
|
|
-1 | Row -1
|
|
|
|
0 | Row 0
|
|
|
|
1 | R1
|
|
|
|
2 | R2
|
|
|
|
3 | R3
|
|
|
|
(6 rows)
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
2016-07-18 00:42:31 +02:00
|
|
|
CREATE USER regress_view_user1;
|
|
|
|
CREATE USER regress_view_user2;
|
2022-03-22 11:28:10 +01:00
|
|
|
CREATE USER regress_view_user3;
|
2016-07-18 00:42:31 +02:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user1;
|
2012-12-09 00:25:48 +01:00
|
|
|
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);
|
2016-07-18 00:42:31 +02:00
|
|
|
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;
|
2012-12-09 00:25:48 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
2016-07-18 00:42:31 +02:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for view rw_view1
|
2012-12-09 00:25:48 +01:00
|
|
|
INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t
|
|
|
|
USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (VALUES ('Row 3', 3.0, 3)) AS v(b,c,a) ON t.aa = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
UPDATE base_tbl SET a=a, c=c; -- ok
|
|
|
|
UPDATE base_tbl SET b=b; -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
UPDATE rw_view1 SET bb=bb, cc=cc; -- ok
|
|
|
|
UPDATE rw_view1 SET aa=aa; -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for view rw_view1
|
2012-12-09 00:25:48 +01:00
|
|
|
UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
|
|
|
|
UPDATE rw_view2 SET bb=bb; -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bb = bb, cc = cc; -- ok
|
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aa = aa, cc = cc; -- ok
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM base_tbl; -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM rw_view1; -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for view rw_view1
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM rw_view2; -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
2016-07-18 00:42:31 +02:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user1;
|
|
|
|
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
|
2012-12-09 00:25:48 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
2016-07-18 00:42:31 +02:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
2012-12-09 00:25:48 +01:00
|
|
|
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- ok
|
|
|
|
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for view rw_view1
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for view rw_view1
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM rw_view2 WHERE aa=2; -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
|
2012-12-09 00:25:48 +01:00
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
a | b | c
|
|
|
|
---+-------+---
|
|
|
|
3 | Row 3 | 3
|
|
|
|
4 | Row 4 | 4
|
|
|
|
(2 rows)
|
|
|
|
|
|
|
|
RESET SESSION AUTHORIZATION;
|
2016-07-18 00:42:31 +02:00
|
|
|
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;
|
2012-12-09 00:25:48 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
2016-07-18 00:42:31 +02:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
2012-12-09 00:25:48 +01:00
|
|
|
INSERT INTO base_tbl VALUES (5, 'Row 5', 5.0); -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
INSERT INTO rw_view1 VALUES ('Row 5', 5.0, 5); -- ok
|
|
|
|
INSERT INTO rw_view2 VALUES ('Row 6', 6.0, 6); -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM base_tbl WHERE a=3; -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM rw_view1 WHERE aa=3; -- ok
|
|
|
|
DELETE FROM rw_view2 WHERE aa=4; -- not allowed
|
2017-12-02 15:26:34 +01:00
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- ok
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED AND bb = 'xxx' THEN DELETE; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
a | b | c
|
|
|
|
---+-------+---
|
|
|
|
4 | Row 4 | 4
|
|
|
|
5 | Row 5 | 5
|
|
|
|
(2 rows)
|
|
|
|
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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;
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'foo';
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'bar'; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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;
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'fud';
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
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
|
|
|
|
---+-----+---
|
2024-02-29 16:56:59 +01:00
|
|
|
1 | fud | 1
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
(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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM rw_view2;
|
|
|
|
a | b | c
|
|
|
|
---+-----+---
|
2024-02-29 16:56:59 +01:00
|
|
|
1 | fud | 1
|
Fix enforcement of SELECT FOR UPDATE permissions with nested views.
SELECT FOR UPDATE on a view should require UPDATE (as well as SELECT)
permissions on the view, and then the view's owner needs those same
permissions against the relations it references, and so on all the way
down to base tables. But ApplyRetrieveRule did things in the wrong order,
resulting in failure to mark intermediate view levels as needing UPDATE
permission. Thus for example, if user A creates a table T and an updatable
view V1 on T, then grants only SELECT permissions on V1 to user B, B could
create a second view V2 on V1 and then would be allowed to perform SELECT
FOR UPDATE via V2 (since V1 wouldn't be checked for UPDATE permissions).
To fix, just switch the order of expanding sub-views and marking referenced
objects as needing UPDATE permission. I think additional simplifications
are now possible, but that's distinct from the bug fix proper.
This is certainly a security issue, but the consequences are pretty minor
(just the ability to lock rows that shouldn't be lockable). Against that
we have a small risk of breaking applications that are working as-desired,
since nested views have behaved this way since such cases worked at all.
On balance I'm inclined not to back-patch.
Per report from Alexander Lakhin.
Discussion: https://postgr.es/m/24db7b8f-3de5-e25f-7ab9-d8848351d42c@gmail.com
2018-04-14 21:38:09 +02:00
|
|
|
(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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET b = 'foo'; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
2022-03-22 11:28:10 +01:00
|
|
|
-- security invoker view permissions
|
|
|
|
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;
|
|
|
|
ALTER VIEW rw_view1 SET (security_invoker = true);
|
|
|
|
INSERT INTO rw_view1 VALUES ('Row 2', 2.0, 2);
|
|
|
|
GRANT SELECT ON rw_view1 TO regress_view_user2;
|
|
|
|
GRANT UPDATE (bb,cc) ON rw_view1 TO regress_view_user2;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM base_tbl; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
SELECT * FROM rw_view1; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
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
|
|
|
|
UPDATE base_tbl SET a=a; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN DELETE; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user1;
|
|
|
|
GRANT SELECT ON base_tbl TO regress_view_user2;
|
|
|
|
GRANT UPDATE (a,c) ON base_tbl TO regress_view_user2;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
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)
|
|
|
|
|
|
|
|
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 cc=cc; -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET cc = cc; -- ok
|
2022-03-22 11:28:10 +01:00
|
|
|
UPDATE rw_view1 SET aa=aa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view1 SET bb=bb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bb = bb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user1;
|
|
|
|
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
|
|
|
|
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
|
|
|
|
DELETE FROM base_tbl WHERE a=1; -- ok
|
|
|
|
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN DELETE; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2022-03-22 11:28:10 +01:00
|
|
|
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;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN DELETE; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user1;
|
|
|
|
GRANT INSERT, DELETE ON base_tbl TO regress_view_user2;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
INSERT INTO rw_view1 VALUES ('Row 4', 4.0, 4); -- ok
|
|
|
|
DELETE FROM rw_view1 WHERE aa=2; -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (3)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN DELETE; -- ok
|
2022-03-22 11:28:10 +01:00
|
|
|
SELECT * FROM base_tbl; -- ok
|
|
|
|
a | b | c
|
|
|
|
---+-------+---
|
|
|
|
4 | Row 4 | 4
|
2024-02-29 16:56:59 +01:00
|
|
|
(1 row)
|
2022-03-22 11:28:10 +01:00
|
|
|
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
NOTICE: drop cascades to view rw_view1
|
|
|
|
-- ordinary view on top of security invoker 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 b AS bb, c AS cc, a AS aa FROM base_tbl;
|
|
|
|
ALTER VIEW rw_view1 SET (security_invoker = true);
|
|
|
|
SELECT * FROM rw_view1; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view1 SET aa=aa; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aa = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.b, v.c, v.a); -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
CREATE VIEW rw_view2 AS SELECT cc AS ccc, aa AS aaa, bb AS bbb FROM rw_view1;
|
|
|
|
GRANT SELECT, UPDATE ON rw_view2 TO regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (2, 'Row 2', 2.0)) AS v(a,b,c) ON t.aaa = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.c, v.a, v.b); -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2022-03-22 11:28:10 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
GRANT SELECT ON base_tbl TO regress_view_user1;
|
|
|
|
GRANT UPDATE (a, b) ON base_tbl TO regress_view_user1;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user1;
|
|
|
|
SELECT * FROM rw_view1; -- ok
|
|
|
|
bb | cc | aa
|
|
|
|
-------+----+----
|
|
|
|
Row 1 | 1 | 1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
UPDATE rw_view1 SET aa=aa, bb=bb; -- ok
|
|
|
|
UPDATE rw_view1 SET cc=cc; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aa = aa, bb = bb; -- ok
|
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET cc = cc; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user1;
|
|
|
|
GRANT SELECT ON rw_view1 TO regress_view_user2;
|
|
|
|
GRANT UPDATE (bb, cc) ON rw_view1 TO regress_view_user2;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
GRANT SELECT ON base_tbl TO regress_view_user2;
|
|
|
|
GRANT UPDATE (a, c) ON base_tbl TO regress_view_user2;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
|
|
ccc | aaa | bbb
|
|
|
|
-----+-----+-------
|
|
|
|
1 | 1 | Row 1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
GRANT SELECT ON base_tbl TO regress_view_user3;
|
|
|
|
GRANT UPDATE (a, c) ON base_tbl TO regress_view_user3;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
|
|
ccc | aaa | bbb
|
|
|
|
-----+-----+-------
|
|
|
|
1 | 1 | Row 1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
|
2022-03-22 11:28:10 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user1;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user1;
|
|
|
|
SELECT * FROM rw_view1; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view1 SET aa=aa; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aa = aa; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
|
|
ccc | aaa | bbb
|
|
|
|
-----+-----+-------
|
|
|
|
1 | 1 | Row 1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
|
|
ccc | aaa | bbb
|
|
|
|
-----+-----+-------
|
|
|
|
1 | 1 | Row 1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
|
2022-03-22 11:28:10 +01:00
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
REVOKE SELECT, UPDATE ON base_tbl FROM regress_view_user2;
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET ccc = ccc; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
2022-03-22 11:28:10 +01:00
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
|
|
ccc | aaa | bbb
|
|
|
|
-----+-----+-------
|
|
|
|
1 | 1 | Row 1
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET aaa = aaa; -- not allowed
|
|
|
|
ERROR: permission denied for view rw_view1
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET bbb = bbb; -- not allowed
|
|
|
|
ERROR: permission denied for table base_tbl
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (1)) AS v(a) ON t.aaa = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET ccc = ccc; -- ok
|
2022-03-22 11:28:10 +01:00
|
|
|
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
|
2016-07-18 00:42:31 +02:00
|
|
|
DROP USER regress_view_user1;
|
|
|
|
DROP USER regress_view_user2;
|
2022-03-22 11:28:10 +01:00
|
|
|
DROP USER regress_view_user3;
|
2012-12-09 00:25:48 +01:00
|
|
|
-- 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);
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (6)) AS v(a) ON t.aa = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT (aa) VALUES (v.a);
|
2012-12-09 00:25:48 +01:00
|
|
|
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
|
2024-02-29 16:56:59 +01:00
|
|
|
6 | View default | 6
|
|
|
|
(6 rows)
|
2012-12-09 00:25:48 +01:00
|
|
|
|
|
|
|
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 *;
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------
|
2012-12-09 00:25:48 +01:00
|
|
|
Update on public.base_tbl
|
|
|
|
Output: base_tbl.a, base_tbl.b
|
|
|
|
-> Seq Scan on public.base_tbl
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
Output: (base_tbl.b + 1), base_tbl.ctid
|
2012-12-09 00:25:48 +01:00
|
|
|
(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
|
2013-10-18 16:35:36 +02:00
|
|
|
-- 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
|
Calculate extraUpdatedCols in query rewriter, not parser.
It's unsafe to do this at parse time because addition of generated
columns to a table would not invalidate stored rules containing
UPDATEs on the table ... but there might now be dependent generated
columns that were not there when the rule was made. This also fixes
an oversight that rewriteTargetView failed to update extraUpdatedCols
when transforming an UPDATE on an updatable view. (Since the new
calculation is downstream of that, rewriteTargetView doesn't actually
need to do anything; but before, there was a demonstrable bug there.)
In v13 and HEAD, this leads to easily-visible bugs because (since
commit c6679e4fc) we won't recalculate generated columns that aren't
listed in extraUpdatedCols. In v12 this bitmap is mostly just used
for trigger-firing decisions, so you'd only notice a problem if a
trigger cared whether a generated column had been updated.
I'd complained about this back in May, but then forgot about it
until bug #16671 from Michael Paul Killian revived the issue.
Back-patch to v12 where this field was introduced. If existing
stored rules contain any extraUpdatedCols values, they'll be
ignored because the rewriter will overwrite them, so the bug will
be fixed even for existing rules. (But note that if someone were
to update to 13.1 or 12.5, store some rules with UPDATEs on tables
having generated columns, and then downgrade to a prior minor version,
they might observe issues similar to what this patch fixes. That
seems unlikely enough to not be worth going to a lot of effort to fix.)
Discussion: https://postgr.es/m/10206.1588964727@sss.pgh.pa.us
Discussion: https://postgr.es/m/16671-2fa55851859fb166@postgresql.org
2020-10-28 18:47:02 +01:00
|
|
|
-- 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
|
2020-11-23 17:15:03 +01:00
|
|
|
ERROR: cannot insert a non-DEFAULT value into column "idplus1"
|
Calculate extraUpdatedCols in query rewriter, not parser.
It's unsafe to do this at parse time because addition of generated
columns to a table would not invalidate stored rules containing
UPDATEs on the table ... but there might now be dependent generated
columns that were not there when the rule was made. This also fixes
an oversight that rewriteTargetView failed to update extraUpdatedCols
when transforming an UPDATE on an updatable view. (Since the new
calculation is downstream of that, rewriteTargetView doesn't actually
need to do anything; but before, there was a demonstrable bug there.)
In v13 and HEAD, this leads to easily-visible bugs because (since
commit c6679e4fc) we won't recalculate generated columns that aren't
listed in extraUpdatedCols. In v12 this bitmap is mostly just used
for trigger-firing decisions, so you'd only notice a problem if a
trigger cared whether a generated column had been updated.
I'd complained about this back in May, but then forgot about it
until bug #16671 from Michael Paul Killian revived the issue.
Back-patch to v12 where this field was introduced. If existing
stored rules contain any extraUpdatedCols values, they'll be
ignored because the rewriter will overwrite them, so the bug will
be fixed even for existing rules. (But note that if someone were
to update to 13.1 or 12.5, store some rules with UPDATEs on tables
having generated columns, and then downgrade to a prior minor version,
they might observe issues similar to what this patch fixes. That
seems unlikely enough to not be worth going to a lot of effort to fix.)
Discussion: https://postgr.es/m/10206.1588964727@sss.pgh.pa.us
Discussion: https://postgr.es/m/16671-2fa55851859fb166@postgresql.org
2020-10-28 18:47:02 +01:00
|
|
|
DETAIL: Column "idplus1" is a generated column.
|
|
|
|
INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error
|
2020-11-23 17:15:03 +01:00
|
|
|
ERROR: cannot insert a non-DEFAULT value into column "idplus1"
|
Calculate extraUpdatedCols in query rewriter, not parser.
It's unsafe to do this at parse time because addition of generated
columns to a table would not invalidate stored rules containing
UPDATEs on the table ... but there might now be dependent generated
columns that were not there when the rule was made. This also fixes
an oversight that rewriteTargetView failed to update extraUpdatedCols
when transforming an UPDATE on an updatable view. (Since the new
calculation is downstream of that, rewriteTargetView doesn't actually
need to do anything; but before, there was a demonstrable bug there.)
In v13 and HEAD, this leads to easily-visible bugs because (since
commit c6679e4fc) we won't recalculate generated columns that aren't
listed in extraUpdatedCols. In v12 this bitmap is mostly just used
for trigger-firing decisions, so you'd only notice a problem if a
trigger cared whether a generated column had been updated.
I'd complained about this back in May, but then forgot about it
until bug #16671 from Michael Paul Killian revived the issue.
Back-patch to v12 where this field was introduced. If existing
stored rules contain any extraUpdatedCols values, they'll be
ignored because the rewriter will overwrite them, so the bug will
be fixed even for existing rules. (But note that if someone were
to update to 13.1 or 12.5, store some rules with UPDATEs on tables
having generated columns, and then downgrade to a prior minor version,
they might observe issues similar to what this patch fixes. That
seems unlikely enough to not be worth going to a lot of effort to fix.)
Discussion: https://postgr.es/m/10206.1588964727@sss.pgh.pa.us
Discussion: https://postgr.es/m/16671-2fa55851859fb166@postgresql.org
2020-10-28 18:47:02 +01:00
|
|
|
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
|
2013-07-03 18:26:19 +02:00
|
|
|
-- 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)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (-200), (10)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -199 and 11
|
|
|
|
MERGE INTO ONLY rw_view1 t USING (VALUES (-100), (20)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -99 and 21
|
|
|
|
MERGE INTO rw_view2 t USING (VALUES (-40), (3)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -39 only
|
|
|
|
MERGE INTO ONLY rw_view2 t USING (VALUES (-30), (4)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET a = t.a+1; -- Should produce -29 only
|
|
|
|
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
|
|
|
|
a
|
|
|
|
------
|
|
|
|
-199
|
|
|
|
-99
|
|
|
|
-39
|
|
|
|
-29
|
|
|
|
-20
|
|
|
|
-10
|
|
|
|
100
|
|
|
|
200
|
|
|
|
(8 rows)
|
|
|
|
|
|
|
|
SELECT * FROM base_tbl_child ORDER BY a;
|
|
|
|
a
|
|
|
|
----
|
|
|
|
3
|
|
|
|
4
|
|
|
|
7
|
|
|
|
8
|
|
|
|
11
|
|
|
|
21
|
|
|
|
(6 rows)
|
|
|
|
|
2019-03-31 21:49:06 +02:00
|
|
|
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;
|
Further adjust EXPLAIN's choices of table alias names.
This patch causes EXPLAIN to always assign a separate table alias to the
parent RTE of an append relation (inheritance set); before, such RTEs
were ignored if not actually scanned by the plan. Since the child RTEs
now always have that same alias to start with (cf. commit 55a1954da),
the net effect is that the parent RTE usually gets the alias used or
implied by the query text, and the children all get that alias with "_N"
appended. (The exception to "usually" is if there are duplicate aliases
in different subtrees of the original query; then some of those original
RTEs will also have "_N" appended.)
This results in more uniform output for partitioned-table plans than
we had before: the partitioned table itself gets the original alias,
and all child tables have aliases with "_N", rather than the previous
behavior where one of the children would get an alias without "_N".
The reason for giving the parent RTE an alias, even if it isn't scanned
by the plan, is that we now use the parent's alias to qualify Vars that
refer to an appendrel output column and appear above the Append or
MergeAppend that computes the appendrel. But below the append, Vars
refer to some one of the child relations, and are displayed that way.
This seems clearer than the old behavior where a Var that could carry
values from any child relation was displayed as if it referred to only
one of them.
While at it, change ruleutils.c so that the code paths used by EXPLAIN
deal in Plan trees not PlanState trees. This effectively reverts a
decision made in commit 1cc29fe7c, which seemed like a good idea at
the time to make ruleutils.c consistent with explain.c. However,
it's problematic because we'd really like to allow executor startup
pruning to remove all the children of an append node when possible,
leaving no child PlanState to resolve Vars against. (That's not done
here, but will be in the next patch.) This requires different handling
of subplans and initplans than before, but is otherwise a pretty
straightforward change.
Discussion: https://postgr.es/m/001001d4f44b$2a2cca50$7e865ef0$@lab.ntt.co.jp
2019-12-11 23:05:18 +01:00
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------------
|
2019-03-31 21:49:06 +02:00
|
|
|
Update on base_tbl_parent
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
Update on base_tbl_parent base_tbl_parent_1
|
|
|
|
Update on base_tbl_child base_tbl_parent_2
|
2019-03-31 21:49:06 +02:00
|
|
|
-> Merge Join
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
Merge Cond: (base_tbl_parent.a = other_tbl_parent.id)
|
2019-03-31 21:49:06 +02:00
|
|
|
-> Sort
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
Sort Key: base_tbl_parent.a
|
|
|
|
-> Append
|
|
|
|
-> Seq Scan on base_tbl_parent base_tbl_parent_1
|
|
|
|
-> Seq Scan on base_tbl_child base_tbl_parent_2
|
2019-03-31 21:49:06 +02:00
|
|
|
-> Sort
|
|
|
|
Sort Key: other_tbl_parent.id
|
|
|
|
-> Append
|
Further adjust EXPLAIN's choices of table alias names.
This patch causes EXPLAIN to always assign a separate table alias to the
parent RTE of an append relation (inheritance set); before, such RTEs
were ignored if not actually scanned by the plan. Since the child RTEs
now always have that same alias to start with (cf. commit 55a1954da),
the net effect is that the parent RTE usually gets the alias used or
implied by the query text, and the children all get that alias with "_N"
appended. (The exception to "usually" is if there are duplicate aliases
in different subtrees of the original query; then some of those original
RTEs will also have "_N" appended.)
This results in more uniform output for partitioned-table plans than
we had before: the partitioned table itself gets the original alias,
and all child tables have aliases with "_N", rather than the previous
behavior where one of the children would get an alias without "_N".
The reason for giving the parent RTE an alias, even if it isn't scanned
by the plan, is that we now use the parent's alias to qualify Vars that
refer to an appendrel output column and appear above the Append or
MergeAppend that computes the appendrel. But below the append, Vars
refer to some one of the child relations, and are displayed that way.
This seems clearer than the old behavior where a Var that could carry
values from any child relation was displayed as if it referred to only
one of them.
While at it, change ruleutils.c so that the code paths used by EXPLAIN
deal in Plan trees not PlanState trees. This effectively reverts a
decision made in commit 1cc29fe7c, which seemed like a good idea at
the time to make ruleutils.c consistent with explain.c. However,
it's problematic because we'd really like to allow executor startup
pruning to remove all the children of an append node when possible,
leaving no child PlanState to resolve Vars against. (That's not done
here, but will be in the next patch.) This requires different handling
of subplans and initplans than before, but is otherwise a pretty
straightforward change.
Discussion: https://postgr.es/m/001001d4f44b$2a2cca50$7e865ef0$@lab.ntt.co.jp
2019-12-11 23:05:18 +01:00
|
|
|
-> Seq Scan on other_tbl_parent other_tbl_parent_1
|
|
|
|
-> Seq Scan on other_tbl_child other_tbl_parent_2
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
(15 rows)
|
2019-03-31 21:49:06 +02:00
|
|
|
|
|
|
|
UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
|
|
|
|
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
|
|
|
|
a
|
|
|
|
------
|
2024-02-29 16:56:59 +01:00
|
|
|
-199
|
|
|
|
-99
|
|
|
|
-39
|
|
|
|
-29
|
2019-03-31 21:49:06 +02:00
|
|
|
-20
|
|
|
|
-10
|
|
|
|
1100
|
|
|
|
1200
|
|
|
|
(8 rows)
|
|
|
|
|
|
|
|
SELECT * FROM base_tbl_child ORDER BY a;
|
|
|
|
a
|
|
|
|
------
|
|
|
|
3
|
|
|
|
4
|
2024-02-29 16:56:59 +01:00
|
|
|
11
|
|
|
|
21
|
2019-03-31 21:49:06 +02:00
|
|
|
1007
|
|
|
|
1008
|
|
|
|
(6 rows)
|
|
|
|
|
2013-07-03 18:26:19 +02:00
|
|
|
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
|
2019-03-31 21:49:06 +02:00
|
|
|
DROP TABLE other_tbl_parent CASCADE;
|
|
|
|
NOTICE: drop cascades to table other_tbl_child
|
2013-07-18 23:10:16 +02:00
|
|
|
-- 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
|
2016-11-03 17:00:00 +01:00
|
|
|
View "public.rw_view1"
|
|
|
|
Column | Type | Collation | Nullable | Default | Storage | Description
|
|
|
|
--------+---------+-----------+----------+---------+---------+-------------
|
|
|
|
a | integer | | | | plain |
|
|
|
|
b | integer | | | | plain |
|
2013-07-18 23:10:16 +02:00
|
|
|
View definition:
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT a,
|
|
|
|
b
|
2013-07-18 23:10:16 +02:00
|
|
|
FROM base_tbl
|
2023-01-18 19:23:57 +01:00
|
|
|
WHERE a < b;
|
2013-07-18 23:10:16 +02:00
|
|
|
Options: check_option=local
|
|
|
|
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
|
2023-01-18 19:23:57 +01:00
|
|
|
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 a, +| LOCAL | YES | YES | NO | NO | NO
|
|
|
|
| | | b +| | | | | |
|
|
|
|
| | | FROM base_tbl+| | | | | |
|
|
|
|
| | | WHERE (a < b); | | | | | |
|
2013-07-18 23:10:16 +02:00
|
|
|
(1 row)
|
|
|
|
|
|
|
|
INSERT INTO rw_view1 VALUES(3,4); -- ok
|
|
|
|
INSERT INTO rw_view1 VALUES(4,3); -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (4, 3).
|
|
|
|
INSERT INTO rw_view1 VALUES(5,null); -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
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
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (3, -5).
|
|
|
|
INSERT INTO rw_view1(a) VALUES (9); -- ok
|
|
|
|
INSERT INTO rw_view1(a) VALUES (10); -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (10, 10).
|
2024-02-29 16:56:59 +01:00
|
|
|
SELECT * FROM base_tbl ORDER BY a, b;
|
2013-07-18 23:10:16 +02:00
|
|
|
a | b
|
|
|
|
---+----
|
2024-02-29 16:56:59 +01:00
|
|
|
1 | -1
|
2013-07-18 23:10:16 +02:00
|
|
|
1 | 2
|
|
|
|
2 | 3
|
|
|
|
3 | 5
|
|
|
|
9 | 10
|
|
|
|
(5 rows)
|
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t USING (VALUES (10)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a + 1); -- ok
|
|
|
|
MERGE INTO rw_view1 t USING (VALUES (11)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a, v.a - 1); -- should fail
|
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
|
|
|
DETAIL: Failing row contains (11, 10).
|
|
|
|
MERGE INTO rw_view1 t USING (VALUES (1)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET a = t.a - 1; -- ok
|
|
|
|
MERGE INTO rw_view1 t USING (VALUES (2)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET a = t.a + 1; -- should fail
|
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
|
|
|
DETAIL: Failing row contains (3, 3).
|
|
|
|
SELECT * FROM base_tbl ORDER BY a, b;
|
|
|
|
a | b
|
|
|
|
----+----
|
|
|
|
0 | 2
|
|
|
|
1 | -1
|
|
|
|
2 | 3
|
|
|
|
3 | 5
|
|
|
|
9 | 10
|
|
|
|
10 | 11
|
|
|
|
(6 rows)
|
|
|
|
|
2013-07-18 23:10:16 +02:00
|
|
|
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
|
2016-11-03 17:00:00 +01:00
|
|
|
View "public.rw_view2"
|
|
|
|
Column | Type | Collation | Nullable | Default | Storage | Description
|
|
|
|
--------+---------+-----------+----------+---------+---------+-------------
|
|
|
|
a | integer | | | | plain |
|
2013-07-18 23:10:16 +02:00
|
|
|
View definition:
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT a
|
2013-07-18 23:10:16 +02:00
|
|
|
FROM rw_view1
|
2023-01-18 19:23:57 +01:00
|
|
|
WHERE a < 10;
|
2013-07-18 23:10:16 +02:00
|
|
|
Options: check_option=cascaded
|
|
|
|
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
2023-01-18 19:23:57 +01:00
|
|
|
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 a +| CASCADED | YES | YES | NO | NO | NO
|
|
|
|
| | | FROM rw_view1 +| | | | | |
|
|
|
|
| | | WHERE (a < 10); | | | | | |
|
2013-07-18 23:10:16 +02:00
|
|
|
(1 row)
|
|
|
|
|
|
|
|
INSERT INTO rw_view2 VALUES (-5); -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (-5).
|
|
|
|
INSERT INTO rw_view2 VALUES (5); -- ok
|
|
|
|
INSERT INTO rw_view2 VALUES (15); -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (15).
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
a
|
|
|
|
---
|
|
|
|
5
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
UPDATE rw_view2 SET a = a - 10; -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (-5).
|
|
|
|
UPDATE rw_view2 SET a = a + 10; -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
2013-07-18 23:10:16 +02:00
|
|
|
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
|
2016-11-03 17:00:00 +01:00
|
|
|
View "public.rw_view2"
|
|
|
|
Column | Type | Collation | Nullable | Default | Storage | Description
|
|
|
|
--------+---------+-----------+----------+---------+---------+-------------
|
|
|
|
a | integer | | | | plain |
|
2013-07-18 23:10:16 +02:00
|
|
|
View definition:
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT a
|
2013-07-18 23:10:16 +02:00
|
|
|
FROM rw_view1
|
2023-01-18 19:23:57 +01:00
|
|
|
WHERE a < 10;
|
2013-07-18 23:10:16 +02:00
|
|
|
Options: check_option=local
|
|
|
|
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
2023-01-18 19:23:57 +01:00
|
|
|
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 a +| LOCAL | YES | YES | NO | NO | NO
|
|
|
|
| | | FROM rw_view1 +| | | | | |
|
|
|
|
| | | WHERE (a < 10); | | | | | |
|
2013-07-18 23:10:16 +02:00
|
|
|
(1 row)
|
|
|
|
|
|
|
|
INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
|
|
|
|
INSERT INTO rw_view2 VALUES (20); -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (20).
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
a
|
|
|
|
-----
|
|
|
|
5
|
|
|
|
-10
|
|
|
|
(2 rows)
|
|
|
|
|
|
|
|
ALTER VIEW rw_view1 SET (check_option=here); -- invalid
|
2019-09-25 20:56:52 +02:00
|
|
|
ERROR: invalid value for enum option "check_option": here
|
2014-08-29 06:01:34 +02:00
|
|
|
DETAIL: Valid values are "local" and "cascaded".
|
2013-07-18 23:10:16 +02:00
|
|
|
ALTER VIEW rw_view1 SET (check_option=local);
|
|
|
|
INSERT INTO rw_view2 VALUES (-20); -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (-20).
|
|
|
|
INSERT INTO rw_view2 VALUES (30); -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (30).
|
|
|
|
ALTER VIEW rw_view2 RESET (check_option);
|
|
|
|
\d+ rw_view2
|
2016-11-03 17:00:00 +01:00
|
|
|
View "public.rw_view2"
|
|
|
|
Column | Type | Collation | Nullable | Default | Storage | Description
|
|
|
|
--------+---------+-----------+----------+---------+---------+-------------
|
|
|
|
a | integer | | | | plain |
|
2013-07-18 23:10:16 +02:00
|
|
|
View definition:
|
2023-01-18 19:23:57 +01:00
|
|
|
SELECT a
|
2013-07-18 23:10:16 +02:00
|
|
|
FROM rw_view1
|
2023-01-18 19:23:57 +01:00
|
|
|
WHERE a < 10;
|
2013-07-18 23:10:16 +02:00
|
|
|
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
2023-01-18 19:23:57 +01:00
|
|
|
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 a +| NONE | YES | YES | NO | NO | NO
|
|
|
|
| | | FROM rw_view1 +| | | | | |
|
|
|
|
| | | WHERE (a < 10); | | | | | |
|
2013-07-18 23:10:16 +02:00
|
|
|
(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;
|
2013-10-18 16:35:36 +02:00
|
|
|
SELECT * FROM information_schema.views WHERE table_name LIKE E'rw\\_view_' ORDER BY table_name;
|
2023-01-18 19:23:57 +01:00
|
|
|
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 a +| CASCADED | YES | YES | NO | NO | NO
|
|
|
|
| | | FROM base_tbl; | | | | | |
|
|
|
|
regression | public | rw_view2 | SELECT a +| NONE | YES | YES | NO | NO | NO
|
|
|
|
| | | FROM rw_view1 +| | | | | |
|
|
|
|
| | | WHERE (a > 0); | | | | | |
|
|
|
|
regression | public | rw_view3 | SELECT a +| CASCADED | YES | YES | NO | NO | NO
|
|
|
|
| | | FROM rw_view2; | | | | | |
|
2013-07-18 23:10:16 +02:00
|
|
|
(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
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
2013-07-18 23:10:16 +02:00
|
|
|
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
|
2014-09-23 02:12:51 +02:00
|
|
|
-- 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
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2014-09-23 02:12:51 +02:00
|
|
|
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
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2014-09-23 02:12:51 +02:00
|
|
|
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
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2014-09-23 02:12:51 +02:00
|
|
|
DETAIL: Failing row contains (10, {4,5}).
|
|
|
|
DEALLOCATE PREPARE ins;
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
NOTICE: drop cascades to view rw_view1
|
2013-07-18 23:10:16 +02:00
|
|
|
-- 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
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (15).
|
|
|
|
UPDATE rw_view1 SET a = a + 5; -- ok
|
|
|
|
UPDATE rw_view1 SET a = a + 5; -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (15).
|
|
|
|
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
|
2019-01-15 20:59:32 +01:00
|
|
|
QUERY PLAN
|
|
|
|
---------------------------------------------------------
|
2013-07-18 23:10:16 +02:00
|
|
|
Insert on base_tbl b
|
|
|
|
-> Result
|
2019-01-15 20:59:32 +01:00
|
|
|
SubPlan 1
|
|
|
|
-> Index Only Scan using ref_tbl_pkey on ref_tbl r
|
|
|
|
Index Cond: (a = b.a)
|
Move resolution of AlternativeSubPlan choices to the planner.
When commit bd3daddaf introduced AlternativeSubPlans, I had some
ambitions towards allowing the choice of subplan to change during
execution. That has not happened, or even been thought about, in the
ensuing twelve years; so it seems like a failed experiment. So let's
rip that out and resolve the choice of subplan at the end of planning
(in setrefs.c) rather than during executor startup. This has a number
of positive benefits:
* Removal of a few hundred lines of executor code, since
AlternativeSubPlans need no longer be supported there.
* Removal of executor-startup overhead (particularly, initialization
of subplans that won't be used).
* Removal of incidental costs of having a larger plan tree, such as
tree-scanning and copying costs in the plancache; not to mention
setrefs.c's own costs of processing the discarded subplans.
* EXPLAIN no longer has to print a weird (and undocumented)
representation of an AlternativeSubPlan choice; it sees only the
subplan actually used. This should mean less confusion for users.
* Since setrefs.c knows which subexpression of a plan node it's
working on at any instant, it's possible to adjust the estimated
number of executions of the subplan based on that. For example,
we should usually estimate more executions of a qual expression
than a targetlist expression. The implementation used here is
pretty simplistic, because we don't want to expend a lot of cycles
on the issue; but it's better than ignoring the point entirely,
as the executor had to.
That last point might possibly result in shifting the choice
between hashed and non-hashed EXISTS subplans in a few cases,
but in general this patch isn't meant to change planner choices.
Since we're doing the resolution so late, it's really impossible
to change any plan choices outside the AlternativeSubPlan itself.
Patch by me; thanks to David Rowley for review.
Discussion: https://postgr.es/m/1992952.1592785225@sss.pgh.pa.us
2020-09-27 18:51:28 +02:00
|
|
|
(5 rows)
|
2013-07-18 23:10:16 +02:00
|
|
|
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
|
2019-01-15 20:59:32 +01:00
|
|
|
QUERY PLAN
|
|
|
|
-----------------------------------------------------------
|
2013-07-18 23:10:16 +02:00
|
|
|
Update on base_tbl b
|
2017-05-01 20:53:42 +02:00
|
|
|
-> Hash Join
|
2013-07-18 23:10:16 +02:00
|
|
|
Hash Cond: (b.a = r.a)
|
|
|
|
-> Seq Scan on base_tbl b
|
|
|
|
-> Hash
|
|
|
|
-> Seq Scan on ref_tbl r
|
2019-01-15 20:59:32 +01:00
|
|
|
SubPlan 1
|
|
|
|
-> Index Only Scan using ref_tbl_pkey on ref_tbl r_1
|
|
|
|
Index Cond: (a = b.a)
|
Move resolution of AlternativeSubPlan choices to the planner.
When commit bd3daddaf introduced AlternativeSubPlans, I had some
ambitions towards allowing the choice of subplan to change during
execution. That has not happened, or even been thought about, in the
ensuing twelve years; so it seems like a failed experiment. So let's
rip that out and resolve the choice of subplan at the end of planning
(in setrefs.c) rather than during executor startup. This has a number
of positive benefits:
* Removal of a few hundred lines of executor code, since
AlternativeSubPlans need no longer be supported there.
* Removal of executor-startup overhead (particularly, initialization
of subplans that won't be used).
* Removal of incidental costs of having a larger plan tree, such as
tree-scanning and copying costs in the plancache; not to mention
setrefs.c's own costs of processing the discarded subplans.
* EXPLAIN no longer has to print a weird (and undocumented)
representation of an AlternativeSubPlan choice; it sees only the
subplan actually used. This should mean less confusion for users.
* Since setrefs.c knows which subexpression of a plan node it's
working on at any instant, it's possible to adjust the estimated
number of executions of the subplan based on that. For example,
we should usually estimate more executions of a qual expression
than a targetlist expression. The implementation used here is
pretty simplistic, because we don't want to expend a lot of cycles
on the issue; but it's better than ignoring the point entirely,
as the executor had to.
That last point might possibly result in shifting the choice
between hashed and non-hashed EXISTS subplans in a few cases,
but in general this patch isn't meant to change planner choices.
Since we're doing the resolution so late, it's really impossible
to change any plan choices outside the AlternativeSubPlan itself.
Patch by me; thanks to David Rowley for review.
Discussion: https://postgr.es/m/1992952.1592785225@sss.pgh.pa.us
2020-09-27 18:51:28 +02:00
|
|
|
(9 rows)
|
2013-07-18 23:10:16 +02:00
|
|
|
|
|
|
|
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
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (15, 10).
|
|
|
|
UPDATE rw_view1 SET a = 20, b = 30; -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view1"
|
2013-07-18 23:10:16 +02:00
|
|
|
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
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (-5).
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (-5)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- should fail
|
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
|
|
|
DETAIL: Failing row contains (-5).
|
2013-07-18 23:10:16 +02:00
|
|
|
INSERT INTO rw_view2 VALUES (5); -- ok
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok
|
2013-07-18 23:10:16 +02:00
|
|
|
INSERT INTO rw_view2 VALUES (50); -- ok, but not in view
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (60)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN NOT MATCHED THEN INSERT VALUES (v.a); -- ok, but not in view
|
2013-07-18 23:10:16 +02:00
|
|
|
UPDATE rw_view2 SET a = a - 10; -- should fail
|
2015-10-29 01:23:53 +01:00
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
2013-07-18 23:10:16 +02:00
|
|
|
DETAIL: Failing row contains (-5).
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t USING (VALUES (6)) AS v(a) ON t.a = v.a
|
|
|
|
WHEN MATCHED THEN UPDATE SET a = t.a - 10; -- should fail
|
|
|
|
ERROR: new row violates check option for view "rw_view2"
|
|
|
|
DETAIL: Failing row contains (-4).
|
2013-07-18 23:10:16 +02:00
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
a | b
|
|
|
|
----+----
|
|
|
|
5 | 10
|
2024-02-29 16:56:59 +01:00
|
|
|
6 | 10
|
2013-07-18 23:10:16 +02:00
|
|
|
50 | 10
|
2024-02-29 16:56:59 +01:00
|
|
|
60 | 10
|
|
|
|
(4 rows)
|
2013-07-18 23:10:16 +02:00
|
|
|
|
|
|
|
-- 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
|
|
|
|
-----+----
|
2024-02-29 16:56:59 +01:00
|
|
|
6 | 10
|
2013-07-18 23:10:16 +02:00
|
|
|
50 | 10
|
2024-02-29 16:56:59 +01:00
|
|
|
60 | 10
|
2013-07-18 23:10:16 +02:00
|
|
|
100 | 10
|
|
|
|
200 | 10
|
2024-02-29 16:56:59 +01:00
|
|
|
(5 rows)
|
2013-07-18 23:10:16 +02:00
|
|
|
|
|
|
|
-- 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
|
|
|
|
-----+----
|
2024-02-29 16:56:59 +01:00
|
|
|
6 | 10
|
2013-07-18 23:10:16 +02:00
|
|
|
50 | 10
|
2024-02-29 16:56:59 +01:00
|
|
|
60 | 10
|
2013-07-18 23:10:16 +02:00
|
|
|
100 | 10
|
|
|
|
200 | 10
|
|
|
|
-10 | 10
|
|
|
|
20 | 10
|
|
|
|
30 | 10
|
|
|
|
-5 | 10
|
2024-02-29 16:56:59 +01:00
|
|
|
(9 rows)
|
2013-07-18 23:10:16 +02:00
|
|
|
|
|
|
|
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
|
2014-04-13 03:04:58 +02:00
|
|
|
-- 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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view1 t
|
|
|
|
USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
|
|
|
|
WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
|
|
|
|
NOTICE: snooped value: Tom
|
|
|
|
NOTICE: snooped value: Harry
|
2014-04-13 03:04:58 +02:00
|
|
|
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);
|
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
2017-01-18 18:58:20 +01:00
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------
|
|
|
|
Update on base_tbl
|
|
|
|
-> Seq Scan on base_tbl
|
|
|
|
Filter: ((visibility = 'public'::text) AND snoop(person))
|
|
|
|
(3 rows)
|
2014-04-13 03:04:58 +02:00
|
|
|
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
|
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
2017-01-18 18:58:20 +01:00
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------------
|
|
|
|
Delete on base_tbl
|
|
|
|
-> Seq Scan on base_tbl
|
|
|
|
Filter: ((visibility = 'public'::text) AND (NOT snoop(person)))
|
|
|
|
(3 rows)
|
2014-04-13 03:04:58 +02:00
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
EXPLAIN (costs off)
|
|
|
|
MERGE INTO rw_view1 t
|
|
|
|
USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
|
|
|
|
WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
|
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------
|
|
|
|
Merge on base_tbl
|
|
|
|
-> Nested Loop
|
|
|
|
Join Filter: (base_tbl.person = "*VALUES*".column1)
|
|
|
|
-> Seq Scan on base_tbl
|
|
|
|
Filter: (visibility = 'public'::text)
|
|
|
|
-> Materialize
|
|
|
|
-> Values Scan on "*VALUES*"
|
|
|
|
(7 rows)
|
|
|
|
|
2014-04-13 03:04:58 +02:00
|
|
|
-- 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
|
2024-02-29 16:56:59 +01:00
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
|
|
|
|
WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
|
|
|
|
NOTICE: snooped value: Tom
|
|
|
|
NOTICE: snooped value: Tom
|
|
|
|
NOTICE: snooped value: Harry
|
|
|
|
NOTICE: snooped value: Harry
|
2014-04-13 03:04:58 +02:00
|
|
|
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);
|
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
2017-01-18 18:58:20 +01:00
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------------------------
|
|
|
|
Update on base_tbl
|
|
|
|
-> Seq Scan on base_tbl
|
|
|
|
Filter: ((visibility = 'public'::text) AND snoop(person) AND snoop(person))
|
|
|
|
(3 rows)
|
2014-04-13 03:04:58 +02:00
|
|
|
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
|
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
2017-01-18 18:58:20 +01:00
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------------------------------
|
|
|
|
Delete on base_tbl
|
|
|
|
-> Seq Scan on base_tbl
|
|
|
|
Filter: ((visibility = 'public'::text) AND snoop(person) AND (NOT snoop(person)))
|
|
|
|
(3 rows)
|
2014-04-13 03:04:58 +02:00
|
|
|
|
2024-02-29 16:56:59 +01:00
|
|
|
EXPLAIN (costs off)
|
|
|
|
MERGE INTO rw_view2 t
|
|
|
|
USING (VALUES ('Tom'), ('Dick'), ('Harry')) AS v(person) ON t.person = v.person
|
|
|
|
WHEN MATCHED AND snoop(t.person) THEN UPDATE SET person = v.person;
|
|
|
|
QUERY PLAN
|
|
|
|
-------------------------------------------------------------------------
|
|
|
|
Merge on base_tbl
|
|
|
|
-> Nested Loop
|
|
|
|
Join Filter: (base_tbl.person = "*VALUES*".column1)
|
|
|
|
-> Seq Scan on base_tbl
|
|
|
|
Filter: ((visibility = 'public'::text) AND snoop(person))
|
|
|
|
-> Values Scan on "*VALUES*"
|
|
|
|
(6 rows)
|
|
|
|
|
2014-04-13 03:04:58 +02:00
|
|
|
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);
|
2024-01-09 09:01:22 +01:00
|
|
|
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)
|
2014-04-13 03:04:58 +02:00
|
|
|
|
|
|
|
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
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
InitPlan 1
|
2014-04-13 03:04:58 +02:00
|
|
|
-> Index Only Scan using base_tbl_pkey on base_tbl t
|
|
|
|
Index Cond: (id = 2)
|
|
|
|
-> Result
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
One-Time Filter: ((InitPlan 1).col1 IS NOT TRUE)
|
2014-04-13 03:04:58 +02:00
|
|
|
|
|
|
|
Update on base_tbl
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
InitPlan 1
|
2014-04-13 03:04:58 +02:00
|
|
|
-> Index Only Scan using base_tbl_pkey on base_tbl t
|
|
|
|
Index Cond: (id = 2)
|
|
|
|
-> Result
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
One-Time Filter: (InitPlan 1).col1
|
2014-04-13 03:04:58 +02:00
|
|
|
-> 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
|
2015-04-24 18:00:49 +02:00
|
|
|
-- security barrier view based on inheritance set
|
2014-04-13 03:04:58 +02:00
|
|
|
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);
|
2014-04-13 06:41:33 +02:00
|
|
|
ANALYZE t1;
|
2014-04-13 03:04:58 +02:00
|
|
|
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);
|
2014-04-13 06:41:33 +02:00
|
|
|
ANALYZE t11;
|
2014-04-13 03:04:58 +02:00
|
|
|
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);
|
2014-04-13 06:41:33 +02:00
|
|
|
ANALYZE t12;
|
2014-04-13 03:04:58 +02:00
|
|
|
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);
|
2014-04-13 06:41:33 +02:00
|
|
|
ANALYZE t111;
|
2014-04-13 03:04:58 +02:00
|
|
|
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)
|
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
2017-01-18 18:58:20 +01:00
|
|
|
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
QUERY PLAN
|
|
|
|
-----------------------------------------------------------------------------------------------------------
|
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
2017-01-18 18:58:20 +01:00
|
|
|
Update on public.t1
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
Update on public.t1 t1_1
|
|
|
|
Update on public.t11 t1_2
|
|
|
|
Update on public.t12 t1_3
|
|
|
|
Update on public.t111 t1_4
|
|
|
|
-> Result
|
|
|
|
Output: 100, t1.tableoid, t1.ctid
|
|
|
|
-> Append
|
|
|
|
-> Index Scan using t1_a_idx on public.t1 t1_1
|
|
|
|
Output: t1_1.tableoid, t1_1.ctid
|
|
|
|
Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Filter: ((t1_1.a <> 6) AND EXISTS(SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
SubPlan 1
|
|
|
|
-> Append
|
|
|
|
-> Seq Scan on public.t12 t12_1
|
|
|
|
Filter: (t12_1.a = t1_1.a)
|
|
|
|
-> Seq Scan on public.t111 t12_2
|
|
|
|
Filter: (t12_2.a = t1_1.a)
|
|
|
|
-> Index Scan using t11_a_idx on public.t11 t1_2
|
|
|
|
Output: t1_2.tableoid, t1_2.ctid
|
|
|
|
Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Filter: ((t1_2.a <> 6) AND EXISTS(SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
-> Index Scan using t12_a_idx on public.t12 t1_3
|
|
|
|
Output: t1_3.tableoid, t1_3.ctid
|
|
|
|
Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Filter: ((t1_3.a <> 6) AND EXISTS(SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
-> Index Scan using t111_a_idx on public.t111 t1_4
|
|
|
|
Output: t1_4.tableoid, t1_4.ctid
|
|
|
|
Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Filter: ((t1_4.a <> 6) AND EXISTS(SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
(30 rows)
|
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
2017-01-18 18:58:20 +01:00
|
|
|
|
|
|
|
UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
|
2014-04-13 03:04:58 +02:00
|
|
|
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;
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
QUERY PLAN
|
|
|
|
-----------------------------------------------------------------------------------------
|
Improve RLS planning by marking individual quals with security levels.
In an RLS query, we must ensure that security filter quals are evaluated
before ordinary query quals, in case the latter contain "leaky" functions
that could expose the contents of sensitive rows. The original
implementation of RLS planning ensured this by pushing the scan of a
secured table into a sub-query that it marked as a security-barrier view.
Unfortunately this results in very inefficient plans in many cases, because
the sub-query cannot be flattened and gets planned independently of the
rest of the query.
To fix, drop the use of sub-queries to enforce RLS qual order, and instead
mark each qual (RestrictInfo) with a security_level field establishing its
priority for evaluation. Quals must be evaluated in security_level order,
except that "leakproof" quals can be allowed to go ahead of quals of lower
security_level, if it's helpful to do so. This has to be enforced within
the ordering of any one list of quals to be evaluated at a table scan node,
and we also have to ensure that quals are not chosen for early evaluation
(i.e., use as an index qual or TID scan qual) if they're not allowed to go
ahead of other quals at the scan node.
This is sufficient to fix the problem for RLS quals, since we only support
RLS policies on simple tables and thus RLS quals will always exist at the
table scan level only. Eventually these qual ordering rules should be
enforced for join quals as well, which would permit improving planning for
explicit security-barrier views; but that's a task for another patch.
Note that FDWs would need to be aware of these rules --- and not, for
example, send an insecure qual for remote execution --- but since we do
not yet allow RLS policies on foreign tables, the case doesn't arise.
This will need to be addressed before we can allow such policies.
Patch by me, reviewed by Stephen Frost and Dean Rasheed.
Discussion: https://postgr.es/m/8185.1477432701@sss.pgh.pa.us
2017-01-18 18:58:20 +01:00
|
|
|
Update on public.t1
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
Update on public.t1 t1_1
|
|
|
|
Update on public.t11 t1_2
|
|
|
|
Update on public.t12 t1_3
|
|
|
|
Update on public.t111 t1_4
|
|
|
|
-> Result
|
|
|
|
Output: (t1.a + 1), t1.tableoid, t1.ctid
|
|
|
|
-> Append
|
|
|
|
-> Index Scan using t1_a_idx on public.t1 t1_1
|
|
|
|
Output: t1_1.a, t1_1.tableoid, t1_1.ctid
|
|
|
|
Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Filter: (EXISTS(SubPlan 1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
SubPlan 1
|
|
|
|
-> Append
|
|
|
|
-> Seq Scan on public.t12 t12_1
|
|
|
|
Filter: (t12_1.a = t1_1.a)
|
|
|
|
-> Seq Scan on public.t111 t12_2
|
|
|
|
Filter: (t12_2.a = t1_1.a)
|
|
|
|
-> Index Scan using t11_a_idx on public.t11 t1_2
|
|
|
|
Output: t1_2.a, t1_2.tableoid, t1_2.ctid
|
|
|
|
Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Filter: (EXISTS(SubPlan 1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
-> Index Scan using t12_a_idx on public.t12 t1_3
|
|
|
|
Output: t1_3.a, t1_3.tableoid, t1_3.ctid
|
|
|
|
Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Filter: (EXISTS(SubPlan 1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
-> Index Scan using t111_a_idx on public.t111 t1_4
|
|
|
|
Output: t1_4.a, t1_4.tableoid, t1_4.ctid
|
|
|
|
Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
Filter: (EXISTS(SubPlan 1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
|
Rework planning and execution of UPDATE and DELETE.
This patch makes two closely related sets of changes:
1. For UPDATE, the subplan of the ModifyTable node now only delivers
the new values of the changed columns (i.e., the expressions computed
in the query's SET clause) plus row identity information such as CTID.
ModifyTable must re-fetch the original tuple to merge in the old
values of any unchanged columns. The core advantage of this is that
the changed columns are uniform across all tables of an inherited or
partitioned target relation, whereas the other columns might not be.
A secondary advantage, when the UPDATE involves joins, is that less
data needs to pass through the plan tree. The disadvantage of course
is an extra fetch of each tuple to be updated. However, that seems to
be very nearly free in context; even worst-case tests don't show it to
add more than a couple percent to the total query cost. At some point
it might be interesting to combine the re-fetch with the tuple access
that ModifyTable must do anyway to mark the old tuple dead; but that
would require a good deal of refactoring and it seems it wouldn't buy
all that much, so this patch doesn't attempt it.
2. For inherited UPDATE/DELETE, instead of generating a separate
subplan for each target relation, we now generate a single subplan
that is just exactly like a SELECT's plan, then stick ModifyTable
on top of that. To let ModifyTable know which target relation a
given incoming row refers to, a tableoid junk column is added to
the row identity information. This gets rid of the horrid hack
that was inheritance_planner(), eliminating O(N^2) planning cost
and memory consumption in cases where there were many unprunable
target relations.
Point 2 of course requires point 1, so that there is a uniform
definition of the non-junk columns to be returned by the subplan.
We can't insist on uniform definition of the row identity junk
columns however, if we want to keep the ability to have both
plain and foreign tables in a partitioning hierarchy. Since
it wouldn't scale very far to have every child table have its
own row identity column, this patch includes provisions to merge
similar row identity columns into one column of the subplan result.
In particular, we can merge the whole-row Vars typically used as
row identity by FDWs into one column by pretending they are type
RECORD. (It's still okay for the actual composite Datums to be
labeled with the table's rowtype OID, though.)
There is more that can be done to file down residual inefficiencies
in this patch, but it seems to be committable now.
FDW authors should note several API changes:
* The argument list for AddForeignUpdateTargets() has changed, and so
has the method it must use for adding junk columns to the query. Call
add_row_identity_var() instead of manipulating the parse tree directly.
You might want to reconsider exactly what you're adding, too.
* PlanDirectModify() must now work a little harder to find the
ForeignScan plan node; if the foreign table is part of a partitioning
hierarchy then the ForeignScan might not be the direct child of
ModifyTable. See postgres_fdw for sample code.
* To check whether a relation is a target relation, it's no
longer sufficient to compare its relid to root->parse->resultRelation.
Instead, check it against all_result_relids or leaf_result_relids,
as appropriate.
Amit Langote and Tom Lane
Discussion: https://postgr.es/m/CA+HiwqHpHdqdDn48yCEhynnniahH78rwcrv1rEX65-fsZGBOLQ@mail.gmail.com
2021-03-31 17:52:34 +02:00
|
|
|
(30 rows)
|
2014-04-13 03:04:58 +02:00
|
|
|
|
|
|
|
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
|
2014-04-13 06:41:33 +02:00
|
|
|
9 | 8 | t11 | t11d
|
|
|
|
9 | 8 | t12 | t11d
|
|
|
|
9 | 8 | t111 | t11d
|
2014-04-13 03:04:58 +02:00
|
|
|
(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
|
2014-04-13 06:41:33 +02:00
|
|
|
NOTICE: snooped value: 10
|
2014-04-13 03:04:58 +02:00
|
|
|
NOTICE: snooped value: 9
|
|
|
|
NOTICE: snooped value: 6
|
|
|
|
NOTICE: snooped value: 7
|
|
|
|
NOTICE: snooped value: 9
|
2014-04-13 06:41:33 +02:00
|
|
|
NOTICE: snooped value: 10
|
2014-04-13 03:04:58 +02:00
|
|
|
NOTICE: snooped value: 9
|
|
|
|
NOTICE: snooped value: 6
|
|
|
|
NOTICE: snooped value: 7
|
2014-04-13 06:41:33 +02:00
|
|
|
NOTICE: snooped value: 9
|
|
|
|
NOTICE: snooped value: 10
|
|
|
|
NOTICE: snooped value: 9
|
2014-04-13 03:04:58 +02:00
|
|
|
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);
|
2015-12-21 16:34:14 +01:00
|
|
|
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;
|
2016-02-29 13:28:06 +01:00
|
|
|
--
|
|
|
|
-- 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;
|
2016-12-21 17:58:18 +01:00
|
|
|
--
|
2023-06-13 21:58:37 +02:00
|
|
|
-- Test sub-select in nested security barrier views, per bug #17972
|
|
|
|
--
|
|
|
|
CREATE TABLE t1 (a int);
|
|
|
|
CREATE VIEW v1 WITH (security_barrier = true) AS
|
|
|
|
SELECT * FROM t1;
|
|
|
|
CREATE RULE v1_upd_rule AS ON UPDATE TO v1 DO INSTEAD
|
|
|
|
UPDATE t1 SET a = NEW.a WHERE a = OLD.a;
|
|
|
|
CREATE VIEW v2 WITH (security_barrier = true) AS
|
|
|
|
SELECT * FROM v1 WHERE EXISTS (SELECT 1);
|
|
|
|
EXPLAIN (COSTS OFF) UPDATE v2 SET a = 1;
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
QUERY PLAN
|
|
|
|
--------------------------------------------------------------
|
2023-06-13 21:58:37 +02:00
|
|
|
Update on t1
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
InitPlan 1
|
2023-06-13 21:58:37 +02:00
|
|
|
-> Result
|
|
|
|
-> Merge Join
|
|
|
|
Merge Cond: (t1.a = v1.a)
|
|
|
|
-> Sort
|
|
|
|
Sort Key: t1.a
|
|
|
|
-> Seq Scan on t1
|
|
|
|
-> Sort
|
|
|
|
Sort Key: v1.a
|
|
|
|
-> Subquery Scan on v1
|
|
|
|
-> Result
|
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
|
|
|
One-Time Filter: (InitPlan 1).col1
|
2023-06-13 21:58:37 +02:00
|
|
|
-> Seq Scan on t1 t1_1
|
|
|
|
(14 rows)
|
|
|
|
|
|
|
|
DROP VIEW v2;
|
|
|
|
DROP VIEW v1;
|
|
|
|
DROP TABLE t1;
|
|
|
|
--
|
2016-12-21 17:58:18 +01:00
|
|
|
-- 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;
|
2017-01-24 21:46:50 +01:00
|
|
|
-- check that an auto-updatable view on a partitioned table works correctly
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
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;
|
2017-06-13 18:30:36 +02:00
|
|
|
select events & 4 != 0 AS upd,
|
|
|
|
events & 8 != 0 AS ins,
|
|
|
|
events & 16 != 0 AS del
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
from pg_catalog.pg_relation_is_updatable('uv_pt'::regclass, false) t(events);
|
2017-06-13 18:30:36 +02:00
|
|
|
upd | ins | del
|
|
|
|
-----+-----+-----
|
|
|
|
t | t | t
|
|
|
|
(1 row)
|
|
|
|
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 1::smallint, false);
|
2017-06-13 18:30:36 +02:00
|
|
|
pg_column_is_updatable
|
|
|
|
------------------------
|
|
|
|
t
|
|
|
|
(1 row)
|
|
|
|
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 2::smallint, false);
|
2017-06-13 18:30:36 +02:00
|
|
|
pg_column_is_updatable
|
|
|
|
------------------------
|
|
|
|
t
|
|
|
|
(1 row)
|
|
|
|
|
|
|
|
select table_name, is_updatable, is_insertable_into
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
from information_schema.views where table_name = 'uv_ptv';
|
2017-06-13 18:30:36 +02:00
|
|
|
table_name | is_updatable | is_insertable_into
|
|
|
|
------------+--------------+--------------------
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
uv_ptv | YES | YES
|
2017-06-13 18:30:36 +02:00
|
|
|
(1 row)
|
|
|
|
|
|
|
|
select table_name, column_name, is_updatable
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
from information_schema.columns where table_name = 'uv_ptv' order by column_name;
|
2017-06-13 18:30:36 +02:00
|
|
|
table_name | column_name | is_updatable
|
|
|
|
------------+-------------+--------------
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
uv_ptv | a | YES
|
|
|
|
uv_ptv | b | YES
|
|
|
|
uv_ptv | v | YES
|
2017-07-25 00:08:08 +02:00
|
|
|
(3 rows)
|
2017-06-13 18:30:36 +02:00
|
|
|
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
insert into uv_ptv values (1, 2);
|
|
|
|
select tableoid::regclass, * from uv_pt;
|
2017-07-25 00:08:08 +02:00
|
|
|
tableoid | a | b | v
|
|
|
|
----------+---+---+---
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
uv_pt11 | 1 | 2 |
|
2017-01-24 21:46:50 +01:00
|
|
|
(1 row)
|
|
|
|
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
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"
|
2017-07-25 00:08:08 +02:00
|
|
|
DETAIL: Failing row contains (1, 2, null).
|
2024-02-29 16:56:59 +01:00
|
|
|
merge into uv_ptv t
|
|
|
|
using (values (1,2), (1,4)) as v(a,b) on t.a = v.a -- fail: matches 2 src rows
|
|
|
|
when matched then update set b = t.b + 1
|
|
|
|
when not matched then insert values (v.a, v.b + 1);
|
|
|
|
ERROR: MERGE command cannot affect row a second time
|
|
|
|
HINT: Ensure that not more than one source row matches any one target row.
|
|
|
|
merge into uv_ptv t
|
|
|
|
using (values (1,2), (1,4)) as v(a,b) on t.a = v.a and t.b = v.b
|
|
|
|
when matched then update set b = t.b + 1
|
|
|
|
when not matched then insert values (v.a, v.b + 1); -- fail: no partition for b=5
|
|
|
|
ERROR: no partition of relation "uv_pt1" found for row
|
|
|
|
DETAIL: Partition key of the failing row contains (b) = (5).
|
|
|
|
merge into uv_ptv t
|
|
|
|
using (values (1,2), (1,3)) as v(a,b) on t.a = v.a and t.b = v.b
|
|
|
|
when matched then update set b = t.b + 1
|
|
|
|
when not matched then insert values (v.a, v.b + 1); -- ok
|
|
|
|
select tableoid::regclass, * from uv_pt order by a, b;
|
|
|
|
tableoid | a | b | v
|
|
|
|
----------+---+---+---
|
|
|
|
uv_pt11 | 1 | 3 |
|
|
|
|
uv_pt11 | 1 | 4 |
|
|
|
|
(2 rows)
|
|
|
|
|
Clean up duplicate table and function names in regression tests.
Many of the objects we create during the regression tests are put in the
public schema, so that using the same names in different regression tests
creates a hazard of test failures if any two such scripts run concurrently.
This patch cleans up a bunch of latent hazards of that sort, as well as two
live hazards.
The current situation in this regard is far worse than it was a year or two
back, because practically all of the partitioning-related test cases have
reused table names with enthusiasm. I despaired of cleaning up that mess
within the five most-affected tests (create_table, alter_table, insert,
update, inherit); fortunately those don't run concurrently.
Other than partitioning problems, most of the issues boil down to using
names like "foo", "bar", "tmp", etc, without thought for the fact that
other test scripts might use similar names concurrently. I've made an
effort to make all such names more specific.
One of the live hazards was that commit 7421f4b8 caused with.sql to
create a table named "test", conflicting with a similarly-named table
in alter_table.sql; this was exposed in the buildfarm recently.
The other one was that join.sql and transactions.sql both create tables
named "foo" and "bar"; but join.sql's uses of those names date back
only to December or so.
Since commit 7421f4b8 was back-patched to v10, back-patch a minimal
fix for that problem. The rest of this is just future-proofing.
Discussion: https://postgr.es/m/4627.1521070268@sss.pgh.pa.us
2018-03-15 22:08:51 +01:00
|
|
|
drop view uv_ptv, uv_ptv_wco;
|
|
|
|
drop table uv_pt, uv_pt1, uv_pt11;
|
2017-08-03 17:21:29 +02:00
|
|
|
-- 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;
|
Fix INSERT ON CONFLICT UPDATE through a view that isn't just SELECT *.
When expanding an updatable view that is an INSERT's target, the rewriter
failed to rewrite Vars in the ON CONFLICT UPDATE clause. This accidentally
worked if the view was just "SELECT * FROM ...", as the transformation
would be a no-op in that case. With more complicated view targetlists,
this omission would often lead to "attribute ... has the wrong type" errors
or even crashes, as reported by Mario De Frutos Dieguez.
Fix by adding code to rewriteTargetView to fix up the data structure
correctly. The easiest way to update the exclRelTlist list is to rebuild
it from scratch looking at the new target relation, so factor the code
for that out of transformOnConflictClause to make it sharable.
In passing, avoid duplicate permissions checks against the EXCLUDED
pseudo-relation, and prevent useless view expansion of that relation's
dummy RTE. The latter is only known to happen (after this patch) in cases
where the query would fail later due to not having any INSTEAD OF triggers
for the view. But by exactly that token, it would create an unintended
and very poorly tested state of the query data structure, so it seems like
a good idea to prevent it from happening at all.
This has been broken since ON CONFLICT was introduced, so back-patch
to 9.5.
Dean Rasheed, based on an earlier patch by Amit Langote;
comment-kibitzing and back-patching by me
Discussion: https://postgr.es/m/CAFYwGJ0xfzy8jaK80hVN2eUWr6huce0RU8AgU04MGD00igqkTg@mail.gmail.com
2018-08-05 01:38:58 +02:00
|
|
|
-- 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;
|
2019-02-20 09:30:21 +01:00
|
|
|
-- 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);
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
insert into base_tab_def_view values (17), (default);
|
2019-02-20 09:30:21 +01:00
|
|
|
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 |
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
17 | View default | Table default | View default |
|
|
|
|
| View default | Table default | View default |
|
|
|
|
(14 rows)
|
2019-02-20 09:30:21 +01:00
|
|
|
|
|
|
|
-- 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);
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
insert into base_tab_def_view values (17), (default);
|
2019-02-20 09:30:21 +01:00
|
|
|
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 |
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
17 | View default | | View default |
|
|
|
|
| View default | | View default |
|
|
|
|
(14 rows)
|
2019-02-20 09:30:21 +01:00
|
|
|
|
|
|
|
-- 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);
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
insert into base_tab_def_view values (17), (default);
|
2019-02-20 09:30:21 +01:00
|
|
|
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 |
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
17 | View default | | View default |
|
|
|
|
| View default | | View default |
|
|
|
|
(14 rows)
|
2019-02-20 09:30:21 +01:00
|
|
|
|
|
|
|
-- 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);
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
insert into base_tab_def_view values (17), (default);
|
2019-02-20 09:30:21 +01:00
|
|
|
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 |
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
17 | View default | Table default | View default |
|
|
|
|
17 | View default | | View default |
|
|
|
|
| View default | Table default | View default |
|
|
|
|
| View default | | View default |
|
|
|
|
(22 rows)
|
2019-02-20 09:30:21 +01:00
|
|
|
|
Fix multi-row DEFAULT handling for INSERT ... SELECT rules.
Given an updatable view with a DO ALSO INSERT ... SELECT rule, a
multi-row INSERT ... VALUES query on the view fails if the VALUES list
contains any DEFAULTs that are not replaced by view defaults. This
manifests as an "unrecognized node type" error, or an Assert failure,
in an assert-enabled build.
The reason is that when RewriteQuery() attempts to replace the
remaining DEFAULT items with NULLs in any product queries, using
rewriteValuesRTEToNulls(), it assumes that the VALUES RTE is located
at the same rangetable index in each product query. However, if the
product query is an INSERT ... SELECT, then the VALUES RTE is actually
in the SELECT part of that query (at the same index), rather than the
top-level product query itself.
Fix, by descending to the SELECT in such cases. Note that we can't
simply use getInsertSelectQuery() for this, since that expects to be
given a raw rule action with OLD and NEW placeholder entries, so we
duplicate its logic instead.
While at it, beef up the checks in getInsertSelectQuery() by checking
that the jointree->fromlist node is indeed a RangeTblRef, and that the
RTE it points to has rtekind == RTE_SUBQUERY.
Per bug #17803, from Alexander Lakhin. Back-patch to all supported
branches.
Dean Rasheed, reviewed by Tom Lane.
Discussion: https://postgr.es/m/17803-53c63ed4ecb4eac6%40postgresql.org
2023-02-23 11:53:01 +01:00
|
|
|
-- Test a DO ALSO INSERT ... SELECT rule
|
|
|
|
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 (a, b, e) select new.a, new.b, 'xxx';
|
|
|
|
truncate base_tab_def;
|
|
|
|
insert into base_tab_def_view values (1, default, default, default, default);
|
|
|
|
insert into base_tab_def_view values (2, default, default, default, default),
|
|
|
|
(3, default, default, default, default);
|
|
|
|
select * from base_tab_def order by a, e nulls first;
|
|
|
|
a | b | c | d | e
|
|
|
|
---+--------------+---------------+--------------+-----
|
|
|
|
1 | View default | Table default | View default |
|
|
|
|
1 | View default | Table default | | xxx
|
|
|
|
2 | View default | Table default | View default |
|
|
|
|
2 | View default | Table default | | xxx
|
|
|
|
3 | View default | Table default | View default |
|
|
|
|
3 | View default | Table default | | xxx
|
|
|
|
(6 rows)
|
|
|
|
|
2019-02-20 09:30:21 +01:00
|
|
|
drop view base_tab_def_view;
|
|
|
|
drop table base_tab_def;
|
Further fixing for multi-row VALUES lists for updatable views.
Previously, rewriteTargetListIU() generated a list of attribute
numbers from the targetlist, which were passed to rewriteValuesRTE(),
which expected them to contain the same number of entries as there are
columns in the VALUES RTE, and to be in the same order. That was fine
when the target relation was a table, but for an updatable view it
could be broken in at least three different ways ---
rewriteTargetListIU() could insert additional targetlist entries for
view columns with defaults, the view columns could be in a different
order from the columns of the underlying base relation, and targetlist
entries could be merged together when assigning to elements of an
array or composite type. As a result, when recursing to the base
relation, the list of attribute numbers generated from the rewritten
targetlist could no longer be relied upon to match the columns of the
VALUES RTE. We got away with that prior to 41531e42d3 because it used
to always be the case that rewriteValuesRTE() did nothing for the
underlying base relation, since all DEFAULTS had already been replaced
when it was initially invoked for the view, but that was incorrect
because it failed to apply defaults from the base relation.
Fix this by examining the targetlist entries more carefully and
picking out just those that are simple Vars referencing the VALUES
RTE. That's sufficient for the purposes of rewriteValuesRTE(), which
is only responsible for dealing with DEFAULT items in the VALUES
RTE. Any DEFAULT item in the VALUES RTE that doesn't have a matching
simple-Var-assignment in the targetlist is an error which we complain
about, but in theory that ought to be impossible.
Additionally, move this code into rewriteValuesRTE() to give a clearer
separation of concerns between the 2 functions. There is no need for
rewriteTargetListIU() to know about the details of the VALUES RTE.
While at it, fix the comment for rewriteValuesRTE() which claimed that
it doesn't support array element and field assignments --- that hasn't
been true since a3c7a993d5 (9.6 and later).
Back-patch to all supported versions, with minor differences for the
pre-9.6 branches, which don't support array element and field
assignments to the same column in multi-row VALUES lists.
Reviewed by Amit Langote.
Discussion: https://postgr.es/m/15623-5d67a46788ec8b7f@postgresql.org
2019-03-03 11:51:13 +01:00
|
|
|
-- 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;
|