From: Peter Eisentraut Date: Tue, 21 Jul 2026 06:33:25 +0000 (+0200) Subject: Test what BEFORE UPDATE triggers do to FOR PORTION OF X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9170c8b71693cf76a4b9a9fbcd16b1fb20b3357c;p=thirdparty%2Fpostgresql.git Test what BEFORE UPDATE triggers do to FOR PORTION OF If a BEFORE trigger changes NEW.valid_at, what is the interaction with FOR PORTION OF? This commit gives a test to capture our current behavior: The trigger's change replaces the value we computed automatically, but it does not change the bounds of the temporal leftovers. This matches the behavior of MariaDB. On the other hand, DB2 rejects changing the start/end columns of a PERIOD. Since we don't have PERIODs, we can't reject the change at trigger definition time as DB2 does, but we could reject it at run time by comparing the values before and after running triggers. Author: Paul A. Jungwirth Discussion: https://www.postgresql.org/message-id/CA%2BrenyV3Cr9BvWsPeb1t8b%3DPk24apuzyGbubAEs_YsgLUTfXpg%40mail.gmail.com --- diff --git a/src/test/regress/expected/for_portion_of.out b/src/test/regress/expected/for_portion_of.out index 271282c2d3b..0e217f104ef 100644 --- a/src/test/regress/expected/for_portion_of.out +++ b/src/test/regress/expected/for_portion_of.out @@ -1843,6 +1843,53 @@ SELECT * FROM for_portion_of_test ORDER BY valid_at; DROP FUNCTION fpo_append_name_suffix CASCADE; NOTICE: drop cascades to trigger fpo_before_insert_row on table for_portion_of_test DROP TABLE for_portion_of_test; +-- A BEFORE UPDATE trigger that changes the application-time column is allowed, +-- even if the results are senseless. +-- Note this is likely to cause a primary key violation. +CREATE TABLE for_portion_of_test ( + id int4range, + valid_at daterange, + name text +); +CREATE FUNCTION trg_fpo_change_valid_at() +RETURNS TRIGGER LANGUAGE plpgsql AS +$$ +BEGIN + NEW.valid_at = daterange('2018-01-01', '2019-01-01'); + RETURN NEW; +END; +$$; +CREATE TRIGGER fpo_before_update_row + BEFORE UPDATE ON for_portion_of_test + FOR EACH ROW EXECUTE PROCEDURE trg_fpo_change_valid_at(); +INSERT INTO for_portion_of_test VALUES ('[1,2)', '[2010-01-01,2020-01-01)', 'foo'); +UPDATE for_portion_of_test + FOR PORTION OF valid_at FROM '2018-05-01' TO '2018-06-01' + SET name = CONCAT(name, '!') + WHERE id = '[1,2)'; +SELECT * FROM for_portion_of_test ORDER BY id, valid_at; + id | valid_at | name +-------+-------------------------+------ + [1,2) | [2010-01-01,2018-05-01) | foo + [1,2) | [2018-01-01,2019-01-01) | foo! + [1,2) | [2018-06-01,2020-01-01) | foo +(3 rows) + +-- A primary key should reject anything invalid: +TRUNCATE for_portion_of_test; +ALTER TABLE for_portion_of_test + ADD CONSTRAINT for_portion_of_test_key + PRIMARY KEY (id, valid_at WITHOUT OVERLAPS); +INSERT INTO for_portion_of_test VALUES ('[1,2)', '[2010-01-01,2020-01-01)', 'foo'); +UPDATE for_portion_of_test + FOR PORTION OF valid_at FROM '2018-05-01' TO '2018-06-01' + SET name = CONCAT(name, '!') + WHERE id = '[1,2)'; +ERROR: conflicting key value violates exclusion constraint "for_portion_of_test_key" +DETAIL: Key (id, valid_at)=([1,2), [2010-01-01,2018-05-01)) conflicts with existing key (id, valid_at)=([1,2), [2018-01-01,2019-01-01)). +DROP TRIGGER fpo_before_update_row ON for_portion_of_test; +DROP FUNCTION trg_fpo_change_valid_at(); +DROP TABLE for_portion_of_test; -- Test with multiranges CREATE TABLE for_portion_of_test2 ( id int4range NOT NULL, diff --git a/src/test/regress/sql/for_portion_of.sql b/src/test/regress/sql/for_portion_of.sql index f48644347d1..a8d29a76b22 100644 --- a/src/test/regress/sql/for_portion_of.sql +++ b/src/test/regress/sql/for_portion_of.sql @@ -1215,6 +1215,53 @@ SELECT * FROM for_portion_of_test ORDER BY valid_at; DROP FUNCTION fpo_append_name_suffix CASCADE; DROP TABLE for_portion_of_test; +-- A BEFORE UPDATE trigger that changes the application-time column is allowed, +-- even if the results are senseless. +-- Note this is likely to cause a primary key violation. + +CREATE TABLE for_portion_of_test ( + id int4range, + valid_at daterange, + name text +); + +CREATE FUNCTION trg_fpo_change_valid_at() +RETURNS TRIGGER LANGUAGE plpgsql AS +$$ +BEGIN + NEW.valid_at = daterange('2018-01-01', '2019-01-01'); + RETURN NEW; +END; +$$; + +CREATE TRIGGER fpo_before_update_row + BEFORE UPDATE ON for_portion_of_test + FOR EACH ROW EXECUTE PROCEDURE trg_fpo_change_valid_at(); + +INSERT INTO for_portion_of_test VALUES ('[1,2)', '[2010-01-01,2020-01-01)', 'foo'); + +UPDATE for_portion_of_test + FOR PORTION OF valid_at FROM '2018-05-01' TO '2018-06-01' + SET name = CONCAT(name, '!') + WHERE id = '[1,2)'; + +SELECT * FROM for_portion_of_test ORDER BY id, valid_at; + +-- A primary key should reject anything invalid: +TRUNCATE for_portion_of_test; +ALTER TABLE for_portion_of_test + ADD CONSTRAINT for_portion_of_test_key + PRIMARY KEY (id, valid_at WITHOUT OVERLAPS); +INSERT INTO for_portion_of_test VALUES ('[1,2)', '[2010-01-01,2020-01-01)', 'foo'); +UPDATE for_portion_of_test + FOR PORTION OF valid_at FROM '2018-05-01' TO '2018-06-01' + SET name = CONCAT(name, '!') + WHERE id = '[1,2)'; + +DROP TRIGGER fpo_before_update_row ON for_portion_of_test; +DROP FUNCTION trg_fpo_change_valid_at(); +DROP TABLE for_portion_of_test; + -- Test with multiranges CREATE TABLE for_portion_of_test2 (