mirror of
https://github.com/postgres/postgres.git
synced 2026-06-09 00:32:10 -04:00
Fix cross-leftover pollution in FOR PORTION OF insert triggers
When we insert temporal leftovers after an UPDATE FOR PORTION OF, we must make a new copy of the tuple before each insert. Otherwise, if an insert trigger assigns to attributes of NEW, the second leftover sees those changes. Author: Sergei Patiakin <sergei.patiakin@enterprisedb.com> Reviewed-by: Paul A Jungwirth <pj@illuminatedcomputing.com> Discussion: https://www.postgresql.org/message-id/flat/CANE55rCqcse_pwXBMWhbj3_7XROb8Dks6%3DOLFmKy3bO3zDsCsg%40mail.gmail.com
This commit is contained in:
parent
90354030b8
commit
993a7aa0e4
3 changed files with 88 additions and 0 deletions
|
|
@ -1601,6 +1601,18 @@ ExecForPortionOfLeftovers(ModifyTableContext *context,
|
|||
|
||||
didInit = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Re-copy the original row into leftoverSlot because ExecInsert
|
||||
* might pass leftoverSlot to BEFORE ROW INSERT triggers, which can
|
||||
* modify the slot contents.
|
||||
*/
|
||||
if (map != NULL)
|
||||
execute_attr_map_slot(map->attrMap, oldtupleSlot, leftoverSlot);
|
||||
else
|
||||
ExecForceStoreHeapTuple(oldtuple, leftoverSlot, false);
|
||||
}
|
||||
|
||||
leftoverSlot->tts_values[forPortionOf->rangeVar->varattno - 1] = leftover;
|
||||
leftoverSlot->tts_isnull[forPortionOf->rangeVar->varattno - 1] = false;
|
||||
|
|
|
|||
|
|
@ -1793,6 +1793,44 @@ SELECT * FROM for_portion_of_test WHERE id = '[4,5)' ORDER BY id, valid_at;
|
|||
(3 rows)
|
||||
|
||||
DROP TRIGGER fpo_after_delete_row ON for_portion_of_test;
|
||||
-- Test that a tuple-modifying BEFORE INSERT ROW trigger acts
|
||||
-- consistently on both temporal leftovers.
|
||||
-- When FOR PORTION OF splits a row into two leftovers, both triggers
|
||||
-- should get the original row's values.
|
||||
DROP TABLE for_portion_of_test;
|
||||
CREATE TABLE for_portion_of_test (
|
||||
id int,
|
||||
valid_at daterange,
|
||||
name text
|
||||
);
|
||||
CREATE FUNCTION fpo_append_name_suffix()
|
||||
RETURNS TRIGGER LANGUAGE plpgsql AS
|
||||
$$
|
||||
BEGIN
|
||||
NEW.name := NEW.name || '+insert';
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
CREATE TRIGGER fpo_before_insert_row
|
||||
BEFORE INSERT ON for_portion_of_test
|
||||
FOR EACH ROW EXECUTE PROCEDURE fpo_append_name_suffix();
|
||||
INSERT INTO for_portion_of_test VALUES (1, '[2020-01-01,2020-12-31)', 'foo');
|
||||
UPDATE for_portion_of_test
|
||||
FOR PORTION OF valid_at FROM '2020-04-01' TO '2020-08-01'
|
||||
SET name = 'bar'
|
||||
WHERE id = 1;
|
||||
-- Both leftovers should have the same name: 'foo+insert+insert'.
|
||||
SELECT * FROM for_portion_of_test ORDER BY valid_at;
|
||||
id | valid_at | name
|
||||
----+-------------------------+-------------------
|
||||
1 | [2020-01-01,2020-04-01) | foo+insert+insert
|
||||
1 | [2020-04-01,2020-08-01) | bar
|
||||
1 | [2020-08-01,2020-12-31) | foo+insert+insert
|
||||
(3 rows)
|
||||
|
||||
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;
|
||||
-- Test with multiranges
|
||||
CREATE TABLE for_portion_of_test2 (
|
||||
id int4range NOT NULL,
|
||||
|
|
|
|||
|
|
@ -1169,6 +1169,44 @@ SELECT * FROM for_portion_of_test WHERE id = '[4,5)' ORDER BY id, valid_at;
|
|||
|
||||
DROP TRIGGER fpo_after_delete_row ON for_portion_of_test;
|
||||
|
||||
-- Test that a tuple-modifying BEFORE INSERT ROW trigger acts
|
||||
-- consistently on both temporal leftovers.
|
||||
-- When FOR PORTION OF splits a row into two leftovers, both triggers
|
||||
-- should get the original row's values.
|
||||
|
||||
DROP TABLE for_portion_of_test;
|
||||
CREATE TABLE for_portion_of_test (
|
||||
id int,
|
||||
valid_at daterange,
|
||||
name text
|
||||
);
|
||||
|
||||
CREATE FUNCTION fpo_append_name_suffix()
|
||||
RETURNS TRIGGER LANGUAGE plpgsql AS
|
||||
$$
|
||||
BEGIN
|
||||
NEW.name := NEW.name || '+insert';
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER fpo_before_insert_row
|
||||
BEFORE INSERT ON for_portion_of_test
|
||||
FOR EACH ROW EXECUTE PROCEDURE fpo_append_name_suffix();
|
||||
|
||||
INSERT INTO for_portion_of_test VALUES (1, '[2020-01-01,2020-12-31)', 'foo');
|
||||
|
||||
UPDATE for_portion_of_test
|
||||
FOR PORTION OF valid_at FROM '2020-04-01' TO '2020-08-01'
|
||||
SET name = 'bar'
|
||||
WHERE id = 1;
|
||||
|
||||
-- Both leftovers should have the same name: 'foo+insert+insert'.
|
||||
SELECT * FROM for_portion_of_test ORDER BY valid_at;
|
||||
|
||||
DROP FUNCTION fpo_append_name_suffix CASCADE;
|
||||
DROP TABLE for_portion_of_test;
|
||||
|
||||
-- Test with multiranges
|
||||
|
||||
CREATE TABLE for_portion_of_test2 (
|
||||
|
|
|
|||
Loading…
Reference in a new issue