diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index ffcf25a6be7..93fa66ae57c 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1549,6 +1549,7 @@ transformForPortionOfClause(ParseState *pstate, List *funcArgs; Node *rangeTLEExpr; TargetEntry *tle; + RTEPermissionInfo *target_perminfo = pstate->p_target_nsitem->p_perminfo; /* * Whatever operator is used for intersect by temporal foreign keys, @@ -1598,14 +1599,9 @@ transformForPortionOfClause(ParseState *pstate, forPortionOf->range_name, false); result->rangeTargetList = lappend(result->rangeTargetList, tle); - /* - * The range column will change, but you don't need UPDATE permission - * on it, so we don't add to updatedCols here. XXX: If - * https://www.postgresql.org/message-id/CACJufxEtY1hdLcx%3DFhnqp-ERcV1PhbvELG5COy_CZjoEW76ZPQ%40mail.gmail.com - * is merged (only validate CHECK constraints if they depend on one of - * the columns being UPDATEd), we need to make sure that code knows - * that we are updating the application-time column. - */ + /* Mark the range column as requiring update permissions */ + target_perminfo->updatedCols = bms_add_member(target_perminfo->updatedCols, + range_attno - FirstLowInvalidHeapAttributeNumber); } else result->rangeTargetList = NIL; diff --git a/src/test/regress/expected/for_portion_of.out b/src/test/regress/expected/for_portion_of.out index 16b2f998dc0..a53ed6e2d64 100644 --- a/src/test/regress/expected/for_portion_of.out +++ b/src/test/regress/expected/for_portion_of.out @@ -1365,6 +1365,9 @@ $$; CREATE TRIGGER fpo_before_stmt BEFORE INSERT OR UPDATE OR DELETE ON for_portion_of_test FOR EACH STATEMENT EXECUTE PROCEDURE dump_trigger(false, false); +CREATE TRIGGER fpo_before_stmt1 + BEFORE UPDATE OF valid_at ON for_portion_of_test + FOR EACH STATEMENT EXECUTE PROCEDURE dump_trigger(false, false); CREATE TRIGGER fpo_after_insert_stmt AFTER INSERT ON for_portion_of_test FOR EACH STATEMENT EXECUTE PROCEDURE dump_trigger(false, false); @@ -1378,6 +1381,9 @@ CREATE TRIGGER fpo_after_delete_stmt CREATE TRIGGER fpo_before_row BEFORE INSERT OR UPDATE OR DELETE ON for_portion_of_test FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false); +CREATE TRIGGER fpo_before_row1 + BEFORE UPDATE OF valid_at ON for_portion_of_test + FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false); CREATE TRIGGER fpo_after_insert_row AFTER INSERT ON for_portion_of_test FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false); @@ -1394,9 +1400,15 @@ UPDATE for_portion_of_test NOTICE: fpo_before_stmt: BEFORE UPDATE STATEMENT: NOTICE: old: NOTICE: new: +NOTICE: fpo_before_stmt1: BEFORE UPDATE STATEMENT: +NOTICE: old: +NOTICE: new: NOTICE: fpo_before_row: BEFORE UPDATE ROW: NOTICE: old: [2019-01-01,2030-01-01) NOTICE: new: [2021-01-01,2022-01-01) +NOTICE: fpo_before_row1: BEFORE UPDATE ROW: +NOTICE: old: [2019-01-01,2030-01-01) +NOTICE: new: [2021-01-01,2022-01-01) NOTICE: fpo_before_stmt: BEFORE INSERT STATEMENT: NOTICE: old: NOTICE: new: @@ -2024,6 +2036,7 @@ SELECT * FROM for_portion_of_test2 ORDER BY id, valid_at; DROP TABLE for_portion_of_test2; DROP TYPE mydaterange; -- Test FOR PORTION OF against a partitioned table. +-- Include a GENERATED STORED column to test updatedCols column mapping. -- temporal_partitioned_1 has the same attnums as the root -- temporal_partitioned_3 has the different attnums from the root -- temporal_partitioned_5 has the different attnums too, but reversed @@ -2031,29 +2044,34 @@ CREATE TABLE temporal_partitioned ( id int4range, valid_at daterange, name text, + range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED, CONSTRAINT temporal_paritioned_uq UNIQUE (id, valid_at WITHOUT OVERLAPS) ) PARTITION BY LIST (id); CREATE TABLE temporal_partitioned_1 PARTITION OF temporal_partitioned FOR VALUES IN ('[1,2)', '[2,3)'); CREATE TABLE temporal_partitioned_3 PARTITION OF temporal_partitioned FOR VALUES IN ('[3,4)', '[4,5)'); CREATE TABLE temporal_partitioned_5 PARTITION OF temporal_partitioned FOR VALUES IN ('[5,6)', '[6,7)'); ALTER TABLE temporal_partitioned DETACH PARTITION temporal_partitioned_3; -ALTER TABLE temporal_partitioned_3 DROP COLUMN id, DROP COLUMN valid_at; +ALTER TABLE temporal_partitioned_3 DROP COLUMN id, DROP COLUMN valid_at CASCADE; +NOTICE: drop cascades to column range_len of table temporal_partitioned_3 ALTER TABLE temporal_partitioned_3 ADD COLUMN id int4range NOT NULL, ADD COLUMN valid_at daterange NOT NULL; +ALTER TABLE temporal_partitioned_3 ADD COLUMN range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED; ALTER TABLE temporal_partitioned ATTACH PARTITION temporal_partitioned_3 FOR VALUES IN ('[3,4)', '[4,5)'); ALTER TABLE temporal_partitioned DETACH PARTITION temporal_partitioned_5; -ALTER TABLE temporal_partitioned_5 DROP COLUMN id, DROP COLUMN valid_at; +ALTER TABLE temporal_partitioned_5 DROP COLUMN id, DROP COLUMN valid_at CASCADE; +NOTICE: drop cascades to column range_len of table temporal_partitioned_5 ALTER TABLE temporal_partitioned_5 ADD COLUMN valid_at daterange NOT NULL, ADD COLUMN id int4range NOT NULL; +ALTER TABLE temporal_partitioned_5 ADD COLUMN range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED; ALTER TABLE temporal_partitioned ATTACH PARTITION temporal_partitioned_5 FOR VALUES IN ('[5,6)', '[6,7)'); INSERT INTO temporal_partitioned (id, valid_at, name) VALUES ('[1,2)', daterange('2000-01-01', '2010-01-01'), 'one'), ('[3,4)', daterange('2000-01-01', '2010-01-01'), 'three'), ('[5,6)', daterange('2000-01-01', '2010-01-01'), 'five'); SELECT * FROM temporal_partitioned; - id | valid_at | name --------+-------------------------+------- - [1,2) | [2000-01-01,2010-01-01) | one - [3,4) | [2000-01-01,2010-01-01) | three - [5,6) | [2000-01-01,2010-01-01) | five + id | valid_at | name | range_len +-------+-------------------------+-------+----------- + [1,2) | [2000-01-01,2010-01-01) | one | 3653 + [3,4) | [2000-01-01,2010-01-01) | three | 3653 + [5,6) | [2000-01-01,2010-01-01) | five | 3653 (3 rows) -- Update without moving within partition 1 @@ -2084,54 +2102,54 @@ UPDATE temporal_partitioned FOR PORTION OF valid_at FROM '2000-06-01' TO '2000-0 id = '[3,4)' WHERE id = '[5,6)'; -- Update all partitions at once (each with leftovers) -SELECT * FROM temporal_partitioned ORDER BY id, valid_at; - id | valid_at | name --------+-------------------------+--------- - [1,2) | [2000-01-01,2000-03-01) | one - [1,2) | [2000-03-01,2000-04-01) | one^1 - [1,2) | [2000-04-01,2000-06-01) | one - [1,2) | [2000-07-01,2010-01-01) | one - [2,3) | [2000-06-01,2000-07-01) | three^2 - [3,4) | [2000-01-01,2000-03-01) | three - [3,4) | [2000-03-01,2000-04-01) | three^1 - [3,4) | [2000-04-01,2000-06-01) | three - [3,4) | [2000-06-01,2000-07-01) | five^2 - [3,4) | [2000-07-01,2010-01-01) | three - [4,5) | [2000-06-01,2000-07-01) | one^2 - [5,6) | [2000-01-01,2000-03-01) | five - [5,6) | [2000-03-01,2000-04-01) | five^1 - [5,6) | [2000-04-01,2000-06-01) | five - [5,6) | [2000-07-01,2010-01-01) | five +SELECT *, upper(valid_at) - lower(valid_at) FROM temporal_partitioned ORDER BY id, valid_at; + id | valid_at | name | range_len | ?column? +-------+-------------------------+---------+-----------+---------- + [1,2) | [2000-01-01,2000-03-01) | one | 60 | 60 + [1,2) | [2000-03-01,2000-04-01) | one^1 | 31 | 31 + [1,2) | [2000-04-01,2000-06-01) | one | 61 | 61 + [1,2) | [2000-07-01,2010-01-01) | one | 3471 | 3471 + [2,3) | [2000-06-01,2000-07-01) | three^2 | 30 | 30 + [3,4) | [2000-01-01,2000-03-01) | three | 60 | 60 + [3,4) | [2000-03-01,2000-04-01) | three^1 | 31 | 31 + [3,4) | [2000-04-01,2000-06-01) | three | 61 | 61 + [3,4) | [2000-06-01,2000-07-01) | five^2 | 30 | 30 + [3,4) | [2000-07-01,2010-01-01) | three | 3471 | 3471 + [4,5) | [2000-06-01,2000-07-01) | one^2 | 30 | 30 + [5,6) | [2000-01-01,2000-03-01) | five | 60 | 60 + [5,6) | [2000-03-01,2000-04-01) | five^1 | 31 | 31 + [5,6) | [2000-04-01,2000-06-01) | five | 61 | 61 + [5,6) | [2000-07-01,2010-01-01) | five | 3471 | 3471 (15 rows) SELECT * FROM temporal_partitioned_1 ORDER BY id, valid_at; - id | valid_at | name --------+-------------------------+--------- - [1,2) | [2000-01-01,2000-03-01) | one - [1,2) | [2000-03-01,2000-04-01) | one^1 - [1,2) | [2000-04-01,2000-06-01) | one - [1,2) | [2000-07-01,2010-01-01) | one - [2,3) | [2000-06-01,2000-07-01) | three^2 + id | valid_at | name | range_len +-------+-------------------------+---------+----------- + [1,2) | [2000-01-01,2000-03-01) | one | 60 + [1,2) | [2000-03-01,2000-04-01) | one^1 | 31 + [1,2) | [2000-04-01,2000-06-01) | one | 61 + [1,2) | [2000-07-01,2010-01-01) | one | 3471 + [2,3) | [2000-06-01,2000-07-01) | three^2 | 30 (5 rows) SELECT * FROM temporal_partitioned_3 ORDER BY id, valid_at; - name | id | valid_at ----------+-------+------------------------- - three | [3,4) | [2000-01-01,2000-03-01) - three^1 | [3,4) | [2000-03-01,2000-04-01) - three | [3,4) | [2000-04-01,2000-06-01) - five^2 | [3,4) | [2000-06-01,2000-07-01) - three | [3,4) | [2000-07-01,2010-01-01) - one^2 | [4,5) | [2000-06-01,2000-07-01) + name | id | valid_at | range_len +---------+-------+-------------------------+----------- + three | [3,4) | [2000-01-01,2000-03-01) | 60 + three^1 | [3,4) | [2000-03-01,2000-04-01) | 31 + three | [3,4) | [2000-04-01,2000-06-01) | 61 + five^2 | [3,4) | [2000-06-01,2000-07-01) | 30 + three | [3,4) | [2000-07-01,2010-01-01) | 3471 + one^2 | [4,5) | [2000-06-01,2000-07-01) | 30 (6 rows) SELECT * FROM temporal_partitioned_5 ORDER BY id, valid_at; - name | valid_at | id ---------+-------------------------+------- - five | [2000-01-01,2000-03-01) | [5,6) - five^1 | [2000-03-01,2000-04-01) | [5,6) - five | [2000-04-01,2000-06-01) | [5,6) - five | [2000-07-01,2010-01-01) | [5,6) + name | valid_at | id | range_len +--------+-------------------------+-------+----------- + five | [2000-01-01,2000-03-01) | [5,6) | 60 + five^1 | [2000-03-01,2000-04-01) | [5,6) | 31 + five | [2000-04-01,2000-06-01) | [5,6) | 61 + five | [2000-07-01,2010-01-01) | [5,6) | 3471 (4 rows) DROP TABLE temporal_partitioned; @@ -2190,4 +2208,84 @@ SELECT * FROM fpo_rule ORDER BY f1; (2 rows) DROP TABLE fpo_rule; +-- UPDATE FOR PORTION OF with generated stored columns +-- The generated column depends on the range column, so it must be +-- recomputed when FOR PORTION OF narrows the range. +CREATE TABLE fpo_generated ( + id int, + valid_at int4range, + range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED, + range_lenv int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) +); +INSERT INTO fpo_generated (id, valid_at) VALUES (1, '[10,100)'); +SELECT * FROM fpo_generated ORDER BY valid_at; + id | valid_at | range_len | range_lenv +----+----------+-----------+------------ + 1 | [10,100) | 90 | 90 +(1 row) + +-- After the FOR PORTION OF (FPO) update, all three resulting rows +-- (leftover-before, updated, and leftover-after) must contain the correct +-- values for range_len and range_lenv. +UPDATE fpo_generated + FOR PORTION OF valid_at FROM 30 TO 70 + SET id = 2; +SELECT * FROM fpo_generated ORDER BY valid_at; + id | valid_at | range_len | range_lenv +----+----------+-----------+------------ + 1 | [10,30) | 20 | 20 + 2 | [30,70) | 40 | 40 + 1 | [70,100) | 30 | 30 +(3 rows) + +-- Also test with a generated column that references both a SET column +-- and the range column. +DROP TABLE fpo_generated; +CREATE TABLE fpo_generated ( + id int, + valid_at int4range, + id_plus_len int GENERATED ALWAYS AS (id + upper(valid_at) - lower(valid_at)) STORED, + id_plus_lenv int GENERATED ALWAYS AS (id + upper(valid_at) - lower(valid_at)) +); +INSERT INTO fpo_generated (id, valid_at) VALUES (1, '[10,100)'); +SELECT * FROM fpo_generated ORDER BY valid_at; + id | valid_at | id_plus_len | id_plus_lenv +----+----------+-------------+-------------- + 1 | [10,100) | 91 | 91 +(1 row) + +UPDATE fpo_generated + FOR PORTION OF valid_at FROM 30 TO 70 + SET id = 2; +SELECT * FROM fpo_generated ORDER BY valid_at; + id | valid_at | id_plus_len | id_plus_lenv +----+----------+-------------+-------------- + 1 | [10,30) | 21 | 21 + 2 | [30,70) | 42 | 42 + 1 | [70,100) | 31 | 31 +(3 rows) + +DROP TABLE fpo_generated; +-- Test that UPDATE OF colname triggers fire if colname is valid_at: +CREATE TABLE fpo_update_of_trigger ( + id int, + valid_at int4range +); +INSERT INTO fpo_update_of_trigger (id, valid_at) VALUES (1, '[10,100)'); +CREATE TRIGGER fpo_before_row1 + BEFORE UPDATE OF valid_at ON fpo_update_of_trigger + FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false); +CREATE TRIGGER fpo_before_row2 + BEFORE UPDATE OF valid_at ON fpo_update_of_trigger + FOR EACH STATEMENT EXECUTE PROCEDURE dump_trigger(false, false); +UPDATE fpo_update_of_trigger + FOR PORTION OF valid_at FROM 30 TO 70 + SET id = 2; +NOTICE: fpo_before_row2: BEFORE UPDATE STATEMENT: +NOTICE: old: +NOTICE: new: +NOTICE: fpo_before_row1: BEFORE UPDATE ROW: +NOTICE: old: [10,100) +NOTICE: new: [30,70) +DROP TABLE fpo_update_of_trigger; RESET datestyle; diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 0de13612818..f6cc1a1029c 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -1152,16 +1152,26 @@ CREATE TABLE t1 ( valid_at tsrange, CONSTRAINT t1pk PRIMARY KEY (c1, valid_at WITHOUT OVERLAPS) ); --- UPDATE requires select permission on the valid_at column (but not update): +-- UPDATE requires select and update permission on the valid_at column: GRANT SELECT (c1) ON t1 TO regress_priv_user2; GRANT UPDATE (c1) ON t1 TO regress_priv_user2; GRANT SELECT (c1, valid_at) ON t1 TO regress_priv_user3; GRANT UPDATE (c1) ON t1 TO regress_priv_user3; +GRANT SELECT (c1) ON t1 TO regress_priv_user4; +GRANT UPDATE (c1, valid_at) ON t1 TO regress_priv_user4; +GRANT SELECT (c1, valid_at) ON t1 TO regress_priv_user5; +GRANT UPDATE (c1, valid_at) ON t1 TO regress_priv_user5; SET SESSION AUTHORIZATION regress_priv_user2; UPDATE t1 FOR PORTION OF valid_at FROM '2000-01-01' TO '2001-01-01' SET c1 = '[2,3)'; ERROR: permission denied for table t1 SET SESSION AUTHORIZATION regress_priv_user3; UPDATE t1 FOR PORTION OF valid_at FROM '2000-01-01' TO '2001-01-01' SET c1 = '[2,3)'; +ERROR: permission denied for table t1 +SET SESSION AUTHORIZATION regress_priv_user4; +UPDATE t1 FOR PORTION OF valid_at FROM '2000-01-01' TO '2001-01-01' SET c1 = '[2,3)'; +ERROR: permission denied for table t1 +SET SESSION AUTHORIZATION regress_priv_user5; +UPDATE t1 FOR PORTION OF valid_at FROM '2000-01-01' TO '2001-01-01' SET c1 = '[2,3)'; SET SESSION AUTHORIZATION regress_priv_user1; -- DELETE requires select permission on the valid_at column: GRANT DELETE ON t1 TO regress_priv_user2; diff --git a/src/test/regress/sql/for_portion_of.sql b/src/test/regress/sql/for_portion_of.sql index 63642b1851e..56c66d91a5d 100644 --- a/src/test/regress/sql/for_portion_of.sql +++ b/src/test/regress/sql/for_portion_of.sql @@ -913,6 +913,10 @@ CREATE TRIGGER fpo_before_stmt BEFORE INSERT OR UPDATE OR DELETE ON for_portion_of_test FOR EACH STATEMENT EXECUTE PROCEDURE dump_trigger(false, false); +CREATE TRIGGER fpo_before_stmt1 + BEFORE UPDATE OF valid_at ON for_portion_of_test + FOR EACH STATEMENT EXECUTE PROCEDURE dump_trigger(false, false); + CREATE TRIGGER fpo_after_insert_stmt AFTER INSERT ON for_portion_of_test FOR EACH STATEMENT EXECUTE PROCEDURE dump_trigger(false, false); @@ -931,6 +935,10 @@ CREATE TRIGGER fpo_before_row BEFORE INSERT OR UPDATE OR DELETE ON for_portion_of_test FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false); +CREATE TRIGGER fpo_before_row1 + BEFORE UPDATE OF valid_at ON for_portion_of_test + FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false); + CREATE TRIGGER fpo_after_insert_row AFTER INSERT ON for_portion_of_test FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false); @@ -1330,6 +1338,7 @@ DROP TABLE for_portion_of_test2; DROP TYPE mydaterange; -- Test FOR PORTION OF against a partitioned table. +-- Include a GENERATED STORED column to test updatedCols column mapping. -- temporal_partitioned_1 has the same attnums as the root -- temporal_partitioned_3 has the different attnums from the root -- temporal_partitioned_5 has the different attnums too, but reversed @@ -1338,6 +1347,7 @@ CREATE TABLE temporal_partitioned ( id int4range, valid_at daterange, name text, + range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED, CONSTRAINT temporal_paritioned_uq UNIQUE (id, valid_at WITHOUT OVERLAPS) ) PARTITION BY LIST (id); CREATE TABLE temporal_partitioned_1 PARTITION OF temporal_partitioned FOR VALUES IN ('[1,2)', '[2,3)'); @@ -1345,13 +1355,15 @@ CREATE TABLE temporal_partitioned_3 PARTITION OF temporal_partitioned FOR VALUES CREATE TABLE temporal_partitioned_5 PARTITION OF temporal_partitioned FOR VALUES IN ('[5,6)', '[6,7)'); ALTER TABLE temporal_partitioned DETACH PARTITION temporal_partitioned_3; -ALTER TABLE temporal_partitioned_3 DROP COLUMN id, DROP COLUMN valid_at; +ALTER TABLE temporal_partitioned_3 DROP COLUMN id, DROP COLUMN valid_at CASCADE; ALTER TABLE temporal_partitioned_3 ADD COLUMN id int4range NOT NULL, ADD COLUMN valid_at daterange NOT NULL; +ALTER TABLE temporal_partitioned_3 ADD COLUMN range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED; ALTER TABLE temporal_partitioned ATTACH PARTITION temporal_partitioned_3 FOR VALUES IN ('[3,4)', '[4,5)'); ALTER TABLE temporal_partitioned DETACH PARTITION temporal_partitioned_5; -ALTER TABLE temporal_partitioned_5 DROP COLUMN id, DROP COLUMN valid_at; +ALTER TABLE temporal_partitioned_5 DROP COLUMN id, DROP COLUMN valid_at CASCADE; ALTER TABLE temporal_partitioned_5 ADD COLUMN valid_at daterange NOT NULL, ADD COLUMN id int4range NOT NULL; +ALTER TABLE temporal_partitioned_5 ADD COLUMN range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED; ALTER TABLE temporal_partitioned ATTACH PARTITION temporal_partitioned_5 FOR VALUES IN ('[5,6)', '[6,7)'); INSERT INTO temporal_partitioned (id, valid_at, name) VALUES @@ -1396,7 +1408,7 @@ UPDATE temporal_partitioned FOR PORTION OF valid_at FROM '2000-06-01' TO '2000-0 -- Update all partitions at once (each with leftovers) -SELECT * FROM temporal_partitioned ORDER BY id, valid_at; +SELECT *, upper(valid_at) - lower(valid_at) FROM temporal_partitioned ORDER BY id, valid_at; SELECT * FROM temporal_partitioned_1 ORDER BY id, valid_at; SELECT * FROM temporal_partitioned_3 ORDER BY id, valid_at; SELECT * FROM temporal_partitioned_5 ORDER BY id, valid_at; @@ -1436,4 +1448,62 @@ SELECT * FROM fpo_rule ORDER BY f1; DROP TABLE fpo_rule; +-- UPDATE FOR PORTION OF with generated stored columns +-- The generated column depends on the range column, so it must be +-- recomputed when FOR PORTION OF narrows the range. +CREATE TABLE fpo_generated ( + id int, + valid_at int4range, + range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED, + range_lenv int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) +); +INSERT INTO fpo_generated (id, valid_at) VALUES (1, '[10,100)'); + +SELECT * FROM fpo_generated ORDER BY valid_at; + +-- After the FOR PORTION OF (FPO) update, all three resulting rows +-- (leftover-before, updated, and leftover-after) must contain the correct +-- values for range_len and range_lenv. +UPDATE fpo_generated + FOR PORTION OF valid_at FROM 30 TO 70 + SET id = 2; + +SELECT * FROM fpo_generated ORDER BY valid_at; + +-- Also test with a generated column that references both a SET column +-- and the range column. +DROP TABLE fpo_generated; +CREATE TABLE fpo_generated ( + id int, + valid_at int4range, + id_plus_len int GENERATED ALWAYS AS (id + upper(valid_at) - lower(valid_at)) STORED, + id_plus_lenv int GENERATED ALWAYS AS (id + upper(valid_at) - lower(valid_at)) +); + +INSERT INTO fpo_generated (id, valid_at) VALUES (1, '[10,100)'); +SELECT * FROM fpo_generated ORDER BY valid_at; + +UPDATE fpo_generated + FOR PORTION OF valid_at FROM 30 TO 70 + SET id = 2; +SELECT * FROM fpo_generated ORDER BY valid_at; +DROP TABLE fpo_generated; + +-- Test that UPDATE OF colname triggers fire if colname is valid_at: +CREATE TABLE fpo_update_of_trigger ( + id int, + valid_at int4range +); +INSERT INTO fpo_update_of_trigger (id, valid_at) VALUES (1, '[10,100)'); +CREATE TRIGGER fpo_before_row1 + BEFORE UPDATE OF valid_at ON fpo_update_of_trigger + FOR EACH ROW EXECUTE PROCEDURE dump_trigger(false, false); +CREATE TRIGGER fpo_before_row2 + BEFORE UPDATE OF valid_at ON fpo_update_of_trigger + FOR EACH STATEMENT EXECUTE PROCEDURE dump_trigger(false, false); +UPDATE fpo_update_of_trigger + FOR PORTION OF valid_at FROM 30 TO 70 + SET id = 2; +DROP TABLE fpo_update_of_trigger; + RESET datestyle; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index 95a46854b37..6cd9bb840ff 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -790,15 +790,23 @@ CREATE TABLE t1 ( valid_at tsrange, CONSTRAINT t1pk PRIMARY KEY (c1, valid_at WITHOUT OVERLAPS) ); --- UPDATE requires select permission on the valid_at column (but not update): +-- UPDATE requires select and update permission on the valid_at column: GRANT SELECT (c1) ON t1 TO regress_priv_user2; GRANT UPDATE (c1) ON t1 TO regress_priv_user2; GRANT SELECT (c1, valid_at) ON t1 TO regress_priv_user3; GRANT UPDATE (c1) ON t1 TO regress_priv_user3; +GRANT SELECT (c1) ON t1 TO regress_priv_user4; +GRANT UPDATE (c1, valid_at) ON t1 TO regress_priv_user4; +GRANT SELECT (c1, valid_at) ON t1 TO regress_priv_user5; +GRANT UPDATE (c1, valid_at) ON t1 TO regress_priv_user5; SET SESSION AUTHORIZATION regress_priv_user2; UPDATE t1 FOR PORTION OF valid_at FROM '2000-01-01' TO '2001-01-01' SET c1 = '[2,3)'; SET SESSION AUTHORIZATION regress_priv_user3; UPDATE t1 FOR PORTION OF valid_at FROM '2000-01-01' TO '2001-01-01' SET c1 = '[2,3)'; +SET SESSION AUTHORIZATION regress_priv_user4; +UPDATE t1 FOR PORTION OF valid_at FROM '2000-01-01' TO '2001-01-01' SET c1 = '[2,3)'; +SET SESSION AUTHORIZATION regress_priv_user5; +UPDATE t1 FOR PORTION OF valid_at FROM '2000-01-01' TO '2001-01-01' SET c1 = '[2,3)'; SET SESSION AUTHORIZATION regress_priv_user1; -- DELETE requires select permission on the valid_at column: GRANT DELETE ON t1 TO regress_priv_user2;