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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2013-10-18 16:35:36 +02:00
|
|
|
-- Read-only views
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM ro_view1;
|
|
|
|
DELETE FROM ro_view2;
|
|
|
|
DELETE FROM ro_view3;
|
|
|
|
DELETE FROM ro_view4;
|
|
|
|
DELETE FROM ro_view5;
|
|
|
|
DELETE FROM ro_view6;
|
|
|
|
UPDATE ro_view7 SET a=a+1;
|
|
|
|
UPDATE ro_view8 SET a=a+1;
|
|
|
|
UPDATE ro_view9 SET a=a+1;
|
|
|
|
UPDATE ro_view10 SET a=a+1;
|
|
|
|
UPDATE ro_view11 SET a=a+1;
|
|
|
|
UPDATE ro_view12 SET a=a+1;
|
|
|
|
INSERT INTO ro_view13 VALUES (3, 'Row 3');
|
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;
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
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
|
|
|
|
INSERT INTO rw_view14 (a, b) VALUES (3, 'Row 3'); -- should be OK
|
|
|
|
UPDATE rw_view14 SET ctid=null WHERE a=3; -- should fail
|
|
|
|
UPDATE rw_view14 SET b='ROW 3' WHERE a=3; -- should be OK
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
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;
|
2013-10-18 16:35:36 +02:00
|
|
|
-- Partially updatable view
|
|
|
|
INSERT INTO rw_view15 VALUES (3, 'ROW 3'); -- should fail
|
|
|
|
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;
|
|
|
|
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO NOTHING; -- succeeds
|
|
|
|
SELECT * FROM rw_view15;
|
|
|
|
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set a = excluded.a; -- succeeds
|
|
|
|
SELECT * FROM rw_view15;
|
|
|
|
INSERT INTO rw_view15 (a) VALUES (3) ON CONFLICT (a) DO UPDATE set upper = 'blarg'; -- fails
|
|
|
|
SELECT * FROM rw_view15;
|
|
|
|
SELECT * FROM rw_view15;
|
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
|
|
|
|
UPDATE rw_view15 SET upper='ROW 3' WHERE a=3; -- should fail
|
|
|
|
UPDATE rw_view15 SET upper=DEFAULT WHERE a=3; -- should fail
|
|
|
|
UPDATE rw_view15 SET a=4 WHERE a=3; -- should be OK
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
DELETE FROM rw_view15 WHERE a=4; -- should be OK
|
|
|
|
-- Partially updatable view
|
|
|
|
INSERT INTO rw_view16 VALUES (3, 'Row 3', 3); -- should fail
|
|
|
|
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
|
|
|
|
UPDATE rw_view16 SET aa=-3 WHERE a=3; -- should be OK
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
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');
|
2014-04-13 03:04:58 +02:00
|
|
|
DELETE FROM ro_view18;
|
2016-12-20 18:00:00 +01:00
|
|
|
UPDATE ro_view19 SET last_value=1000;
|
2014-04-13 03:04:58 +02:00
|
|
|
UPDATE ro_view20 SET b=upper(b);
|
2012-12-09 00:25:48 +01:00
|
|
|
|
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
|
|
|
|
UPDATE rw_view16 SET b='ROW 2' WHERE a=2; -- should fail
|
|
|
|
DELETE FROM rw_view16 WHERE a=2; -- should fail
|
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
|
2020-01-14 10:52:21 +01:00
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
DROP TABLE base_tbl CASCADE;
|
2014-04-13 03:04:58 +02:00
|
|
|
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;
|
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';
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name = 'rw_view1';
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name = 'rw_view1'
|
|
|
|
ORDER BY ordinal_position;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
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.*;
|
2024-02-29 16:56:59 +01:00
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
|
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.*;
|
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
EXPLAIN (costs off) UPDATE rw_view1 SET a=6 WHERE a=5;
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE a=5;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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';
|
|
|
|
|
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;
|
|
|
|
|
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);
|
|
|
|
|
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';
|
|
|
|
|
|
|
|
-- Check behavior with DEFAULTs (bug #17633)
|
|
|
|
|
|
|
|
INSERT INTO rw_view1 VALUES (9, DEFAULT), (10, DEFAULT);
|
|
|
|
SELECT a, b FROM base_tbl_hist;
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
DROP TABLE base_tbl CASCADE;
|
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';
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name = 'rw_view2';
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name = 'rw_view2'
|
|
|
|
ORDER BY ordinal_position;
|
|
|
|
|
|
|
|
INSERT INTO rw_view2 VALUES (3, 'Row 3');
|
|
|
|
INSERT INTO rw_view2 (aaa) VALUES (4);
|
|
|
|
SELECT * FROM rw_view2;
|
|
|
|
UPDATE rw_view2 SET bbb='Row 4' WHERE aaa=4;
|
|
|
|
DELETE FROM rw_view2 WHERE aaa=2;
|
|
|
|
SELECT * FROM rw_view2;
|
|
|
|
|
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.*;
|
2024-02-29 16:56:59 +01:00
|
|
|
SELECT * FROM rw_view2 ORDER BY aaa;
|
|
|
|
|
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.*;
|
|
|
|
SELECT * FROM rw_view2 ORDER BY aaa;
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET aaa=5 WHERE aaa=4;
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE aaa=4;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
|
|
|
|
UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
|
|
|
|
SELECT * FROM rw_view2;
|
|
|
|
DELETE FROM rw_view2 WHERE a=3 RETURNING *;
|
|
|
|
SELECT * FROM rw_view2;
|
|
|
|
|
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
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE 'rw_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
INSERT INTO rw_view2 VALUES (3, 'Row 3') RETURNING *;
|
|
|
|
UPDATE rw_view2 SET b='Row three' WHERE a=3 RETURNING *;
|
|
|
|
SELECT * FROM rw_view2;
|
|
|
|
DELETE FROM rw_view2 WHERE a=3 RETURNING *;
|
|
|
|
SELECT * FROM rw_view2;
|
|
|
|
|
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.*;
|
2024-02-29 16:56:59 +01:00
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
|
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.*;
|
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET a=3 WHERE a=2;
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE a=2;
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
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;
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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;
|
2012-12-09 00:25:48 +01:00
|
|
|
|
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
|
|
|
|
SELECT * FROM rw_view1; -- ok
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
|
|
|
|
|
|
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
|
|
|
|
INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
|
|
|
|
INSERT INTO rw_view2 VALUES ('Row 3', 3.0, 3); -- not allowed
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
UPDATE rw_view1 SET bb=bb, cc=cc; -- ok
|
|
|
|
UPDATE rw_view1 SET aa=aa; -- not allowed
|
|
|
|
UPDATE rw_view2 SET aa=aa, cc=cc; -- ok
|
|
|
|
UPDATE rw_view2 SET bb=bb; -- not allowed
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
|
2012-12-09 00:25:48 +01:00
|
|
|
DELETE FROM base_tbl; -- not allowed
|
|
|
|
DELETE FROM rw_view1; -- not allowed
|
|
|
|
DELETE FROM rw_view2; -- not allowed
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
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
|
|
|
|
INSERT INTO rw_view1 VALUES ('Row 5', 5.0, 5); -- ok
|
|
|
|
INSERT INTO rw_view2 VALUES ('Row 6', 6.0, 6); -- not allowed
|
|
|
|
DELETE FROM base_tbl WHERE a=3; -- not allowed
|
|
|
|
DELETE FROM rw_view1 WHERE aa=3; -- ok
|
|
|
|
DELETE FROM rw_view2 WHERE aa=4; -- not allowed
|
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
|
2012-12-09 00:25:48 +01:00
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
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
|
|
|
-- 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
|
|
|
|
SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
|
|
|
|
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
|
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
|
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
|
|
|
|
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
|
|
|
|
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
|
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
|
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;
|
|
|
|
SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
|
|
|
|
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
|
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
|
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
|
|
|
|
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
|
|
|
|
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
|
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
|
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;
|
|
|
|
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
|
|
|
|
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
|
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
|
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;
|
|
|
|
SELECT * FROM rw_view1 FOR UPDATE;
|
|
|
|
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;
|
|
|
|
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
|
|
|
|
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
|
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
|
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;
|
|
|
|
SELECT * FROM rw_view2 FOR UPDATE;
|
|
|
|
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;
|
|
|
|
SELECT * FROM rw_view1 FOR UPDATE; -- not allowed
|
|
|
|
UPDATE rw_view1 SET b = 'foo' WHERE a = 1; -- not allowed
|
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
|
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;
|
|
|
|
SELECT * FROM rw_view2 FOR UPDATE; -- not allowed
|
|
|
|
UPDATE rw_view2 SET b = 'bar' WHERE a = 1; -- not allowed
|
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
|
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;
|
|
|
|
|
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
|
|
|
|
SELECT * FROM rw_view1; -- not allowed
|
|
|
|
INSERT INTO base_tbl VALUES (3, 'Row 3', 3.0); -- not allowed
|
|
|
|
INSERT INTO rw_view1 VALUES ('Row 3', 3.0, 3); -- not allowed
|
|
|
|
UPDATE base_tbl SET a=a; -- not allowed
|
|
|
|
UPDATE rw_view1 SET bb=bb, cc=cc; -- not allowed
|
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
|
2022-03-22 11:28:10 +01:00
|
|
|
DELETE FROM base_tbl; -- not allowed
|
|
|
|
DELETE FROM rw_view1; -- not allowed
|
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
|
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
|
|
|
|
SELECT * FROM rw_view1; -- ok
|
|
|
|
UPDATE base_tbl SET a=a, c=c; -- ok
|
|
|
|
UPDATE base_tbl SET b=b; -- not allowed
|
|
|
|
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
|
|
|
|
UPDATE rw_view1 SET bb=bb; -- not allowed
|
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
|
|
|
|
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
|
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
|
|
|
|
DELETE FROM base_tbl WHERE a=1; -- ok
|
|
|
|
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
|
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
|
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
|
|
|
|
DELETE FROM rw_view1 WHERE aa=2; -- not allowed
|
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
|
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
|
|
|
|
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
UPDATE rw_view1 SET aa=aa; -- not allowed
|
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
|
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
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
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
|
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
|
|
|
|
UPDATE rw_view1 SET aa=aa, bb=bb; -- ok
|
|
|
|
UPDATE rw_view1 SET cc=cc; -- not allowed
|
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
|
2022-03-22 11:28:10 +01:00
|
|
|
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
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
|
2022-03-22 11:28:10 +01:00
|
|
|
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
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
|
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
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
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
|
2022-03-22 11:28:10 +01:00
|
|
|
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- not allowed
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
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
|
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
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- not allowed
|
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
|
|
|
|
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
|
|
|
|
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
|
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
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
UPDATE rw_view1 SET aa=aa; -- not allowed
|
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
|
2022-03-22 11:28:10 +01:00
|
|
|
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user2;
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
UPDATE rw_view2 SET ccc=ccc; -- not allowed
|
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
|
|
|
|
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
|
|
|
|
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
|
2022-03-22 11:28:10 +01:00
|
|
|
|
|
|
|
SET SESSION AUTHORIZATION regress_view_user3;
|
|
|
|
SELECT * FROM rw_view2; -- ok
|
|
|
|
UPDATE rw_view2 SET aaa=aaa; -- not allowed
|
|
|
|
UPDATE rw_view2 SET bbb=bbb; -- not allowed
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
INSERT INTO rw_view1 VALUES (7,-8);
|
|
|
|
SELECT * FROM rw_view1;
|
|
|
|
|
|
|
|
EXPLAIN (verbose, costs off) UPDATE rw_view1 SET b = b + 1 RETURNING *;
|
|
|
|
UPDATE rw_view1 SET b = b + 1 RETURNING *;
|
|
|
|
SELECT * FROM rw_view1;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
2013-07-03 18:26:19 +02:00
|
|
|
|
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
|
|
|
|
INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail
|
|
|
|
INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK
|
|
|
|
UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail
|
|
|
|
UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK
|
|
|
|
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
|
|
|
|
INSERT INTO rw_view2(s, c, base_a) VALUES (null, null, 1.1); -- should fail
|
|
|
|
INSERT INTO rw_view2(base_a) VALUES (1.1) RETURNING t; -- OK
|
|
|
|
UPDATE rw_view2 SET s = s WHERE base_a = 1.1; -- should fail
|
|
|
|
UPDATE rw_view2 SET t = t WHERE base_a = 1.1; -- should fail
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
INSERT INTO rw_view3(s) VALUES (null); -- should fail
|
|
|
|
UPDATE rw_view3 SET s = s; -- should fail
|
|
|
|
DELETE FROM rw_view3 WHERE s = sin(0.1); -- should be OK
|
|
|
|
SELECT * FROM base_tbl ORDER BY a;
|
|
|
|
|
|
|
|
SELECT table_name, is_insertable_into
|
|
|
|
FROM information_schema.tables
|
|
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
|
|
ORDER BY table_name;
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
|
|
ORDER BY table_name;
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name LIKE E'r_\\_view%'
|
|
|
|
ORDER BY table_name, ordinal_position;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
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
|
|
|
|
INSERT INTO rw_view1 (id, idplus1) VALUES (6, 7); -- error
|
|
|
|
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
|
|
|
|
UPDATE base_tbl SET id = 2000 WHERE id = 2;
|
|
|
|
UPDATE rw_view1 SET id = 3000 WHERE id = 3;
|
|
|
|
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
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;
|
|
|
|
SELECT * FROM ONLY rw_view1 ORDER BY a;
|
|
|
|
SELECT * FROM rw_view2 ORDER BY a;
|
|
|
|
|
|
|
|
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;
|
|
|
|
SELECT * FROM base_tbl_child ORDER BY a;
|
|
|
|
|
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;
|
|
|
|
SELECT * FROM base_tbl_child ORDER BY a;
|
|
|
|
|
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;
|
|
|
|
UPDATE rw_view1 SET a = a + 1000 FROM other_tbl_parent WHERE a = id;
|
|
|
|
|
|
|
|
SELECT * FROM ONLY base_tbl_parent ORDER BY a;
|
|
|
|
SELECT * FROM base_tbl_child ORDER BY a;
|
|
|
|
|
2013-07-03 18:26:19 +02:00
|
|
|
DROP TABLE base_tbl_parent, base_tbl_child CASCADE;
|
2019-03-31 21:49:06 +02:00
|
|
|
DROP TABLE other_tbl_parent CASCADE;
|
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
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view1';
|
|
|
|
|
|
|
|
INSERT INTO rw_view1 VALUES(3,4); -- ok
|
|
|
|
INSERT INTO rw_view1 VALUES(4,3); -- should fail
|
|
|
|
INSERT INTO rw_view1 VALUES(5,null); -- should fail
|
|
|
|
UPDATE rw_view1 SET b = 5 WHERE a = 3; -- ok
|
|
|
|
UPDATE rw_view1 SET b = -5 WHERE a = 3; -- should fail
|
|
|
|
INSERT INTO rw_view1(a) VALUES (9); -- ok
|
|
|
|
INSERT INTO rw_view1(a) VALUES (10); -- should fail
|
2024-02-29 16:56:59 +01:00
|
|
|
SELECT * FROM base_tbl ORDER BY a, b;
|
|
|
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
SELECT * FROM base_tbl ORDER BY a, b;
|
2013-07-18 23:10:16 +02:00
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
|
|
|
|
|
|
|
INSERT INTO rw_view2 VALUES (-5); -- should fail
|
|
|
|
INSERT INTO rw_view2 VALUES (5); -- ok
|
|
|
|
INSERT INTO rw_view2 VALUES (15); -- should fail
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
|
|
|
|
UPDATE rw_view2 SET a = a - 10; -- should fail
|
|
|
|
UPDATE rw_view2 SET a = a + 10; -- should fail
|
|
|
|
|
|
|
|
CREATE OR REPLACE VIEW rw_view2 AS SELECT * FROM rw_view1 WHERE a < 10
|
|
|
|
WITH LOCAL CHECK OPTION;
|
|
|
|
\d+ rw_view2
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
|
|
|
|
|
|
|
INSERT INTO rw_view2 VALUES (-10); -- ok, but not in view
|
|
|
|
INSERT INTO rw_view2 VALUES (20); -- should fail
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
|
|
|
|
ALTER VIEW rw_view1 SET (check_option=here); -- invalid
|
|
|
|
ALTER VIEW rw_view1 SET (check_option=local);
|
|
|
|
|
|
|
|
INSERT INTO rw_view2 VALUES (-20); -- should fail
|
|
|
|
INSERT INTO rw_view2 VALUES (30); -- should fail
|
|
|
|
|
|
|
|
ALTER VIEW rw_view2 RESET (check_option);
|
|
|
|
\d+ rw_view2
|
|
|
|
SELECT * FROM information_schema.views WHERE table_name = 'rw_view2';
|
|
|
|
INSERT INTO rw_view2 VALUES (30); -- ok, but not in view
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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;
|
2013-07-18 23:10:16 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
INSERT INTO rw_view3 VALUES (3); -- ok
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
DEALLOCATE PREPARE ins;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
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
|
|
|
|
|
|
|
|
UPDATE rw_view1 SET a = a + 5; -- ok
|
|
|
|
UPDATE rw_view1 SET a = a + 5; -- should fail
|
|
|
|
|
|
|
|
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (5);
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view1 SET a = a + 5;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl, ref_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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
|
|
|
|
UPDATE rw_view1 SET a = 20, b = 30; -- should fail
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
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
|
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
|
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
|
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
|
2013-07-18 23:10:16 +02:00
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
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;
|
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);
|
|
|
|
UPDATE rw_view1 SET person=person WHERE snoop(person);
|
|
|
|
DELETE FROM rw_view1 WHERE NOT snoop(person);
|
|
|
|
|
|
|
|
ALTER VIEW rw_view1 SET (security_barrier = true);
|
|
|
|
|
|
|
|
SELECT table_name, is_insertable_into
|
|
|
|
FROM information_schema.tables
|
|
|
|
WHERE table_name = 'rw_view1';
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name = 'rw_view1';
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name = 'rw_view1'
|
|
|
|
ORDER BY ordinal_position;
|
|
|
|
|
|
|
|
SELECT * FROM rw_view1 WHERE snoop(person);
|
|
|
|
UPDATE rw_view1 SET person=person WHERE snoop(person);
|
|
|
|
DELETE FROM rw_view1 WHERE NOT snoop(person);
|
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;
|
2014-04-13 03:04:58 +02:00
|
|
|
|
|
|
|
EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person);
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person);
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
|
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;
|
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';
|
|
|
|
|
|
|
|
SELECT table_name, is_updatable, is_insertable_into
|
|
|
|
FROM information_schema.views
|
|
|
|
WHERE table_name = 'rw_view2';
|
|
|
|
|
|
|
|
SELECT table_name, column_name, is_updatable
|
|
|
|
FROM information_schema.columns
|
|
|
|
WHERE table_name = 'rw_view2'
|
|
|
|
ORDER BY ordinal_position;
|
|
|
|
|
|
|
|
SELECT * FROM rw_view2 WHERE snoop(person);
|
|
|
|
UPDATE rw_view2 SET person=person WHERE snoop(person);
|
|
|
|
DELETE FROM rw_view2 WHERE NOT snoop(person);
|
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;
|
2014-04-13 03:04:58 +02:00
|
|
|
|
|
|
|
EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person);
|
|
|
|
EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person);
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
|
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;
|
2014-04-13 03:04:58 +02:00
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
|
|
|
|
DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
|
|
|
|
|
|
|
|
EXPLAIN (costs off) INSERT INTO rw_view1 VALUES (2, 'New row 2');
|
|
|
|
INSERT INTO rw_view1 VALUES (2, 'New row 2');
|
|
|
|
|
|
|
|
SELECT * FROM base_tbl;
|
|
|
|
|
|
|
|
DROP TABLE base_tbl CASCADE;
|
|
|
|
|
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);
|
|
|
|
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
|
|
|
|
SELECT * FROM v1 WHERE a=8;
|
|
|
|
|
|
|
|
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;
|
|
|
|
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
|
|
|
|
SELECT * FROM t1 WHERE a=100; -- Nothing should have been changed to 100
|
|
|
|
|
|
|
|
EXPLAIN (VERBOSE, COSTS OFF)
|
|
|
|
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
|
|
|
|
UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
|
|
|
|
|
|
|
|
SELECT * FROM v1 WHERE b=8;
|
|
|
|
|
|
|
|
DELETE FROM v1 WHERE snoop(a) AND leakproof(a); -- should not delete everything, just where a>5
|
|
|
|
|
|
|
|
TABLE t1; -- verify all a<=5 are intact
|
|
|
|
|
|
|
|
DROP TABLE t1, t11, t12, t111 CASCADE;
|
|
|
|
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;
|
|
|
|
SELECT * FROM vx1;
|
|
|
|
|
|
|
|
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;
|
|
|
|
SELECT * FROM vx1;
|
|
|
|
|
|
|
|
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;
|
|
|
|
SELECT * FROM vx1;
|
|
|
|
|
|
|
|
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
|
|
|
|
INSERT INTO v2 VALUES (3, 'three', 30); -- not allowed
|
|
|
|
|
|
|
|
UPDATE v2 SET b = 'ONE' WHERE a = 1; -- ok
|
|
|
|
UPDATE v2 SET a = -1 WHERE a = 1; -- not allowed
|
|
|
|
UPDATE v2 SET c = 30 WHERE a = 1; -- not allowed
|
|
|
|
|
|
|
|
DELETE FROM v2 WHERE a = 2; -- ok
|
|
|
|
SELECT * FROM v2;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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);
|
|
|
|
select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 1::smallint, false);
|
|
|
|
select pg_catalog.pg_column_is_updatable('uv_pt'::regclass, 2::smallint, false);
|
2017-06-13 18:30:36 +02:00
|
|
|
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
|
|
|
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;
|
|
|
|
insert into uv_ptv values (1, 2);
|
|
|
|
select tableoid::regclass, * from uv_pt;
|
|
|
|
create view uv_ptv_wco as select * from uv_pt where a = 0 with check option;
|
|
|
|
insert into uv_ptv_wco values (1, 2);
|
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);
|
|
|
|
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
|
|
|
|
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;
|
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);
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
|
|
|
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;
|
|
|
|
insert into uv_iocu_view (a, b) values ('xyxyxy', 1)
|
|
|
|
on conflict (a) do update set b = excluded.b;
|
|
|
|
select * from uv_iocu_tab;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
-- Should fail to update non-updatable columns
|
|
|
|
insert into uv_iocu_view (aa) values (1)
|
|
|
|
on conflict (aa) do update set cc = 'XXX';
|
|
|
|
|
|
|
|
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
|
|
|
|
insert into rw_view1 values ('yyy',2.0,1)
|
|
|
|
on conflict (aa) do update set bb = rw_view1.cc; -- Not allowed
|
|
|
|
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
|
|
|
|
reset session authorization;
|
|
|
|
select * from base_tbl;
|
|
|
|
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
|
|
|
|
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
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
-- 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;
|
|
|
|
|
|
|
|
-- 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 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;
|
|
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
drop view base_tab_view;
|
|
|
|
drop table base_tab;
|