mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-31 08:07:22 +02:00
Fast default trigger and expand_tuple fixes
Ensure that triggers get properly filled in tuples for the OLD value. Also fix the logic of detecting missing null values. The previous logic failed to detect a missing null column before the first missing column with a default. Fixing this has simplified the logic a bit. Regression tests are added to test changes. This should ensure better coverage of expand_tuple(). Original bug reports, and some code and test scripts from Tomas Vondra Backpatch to release 11.
This commit is contained in:
parent
bfdd02f88b
commit
9625ab7924
@ -823,44 +823,35 @@ expand_tuple(HeapTuple *targetHeapTuple,
|
|||||||
{
|
{
|
||||||
if (attrmiss[firstmissingnum].am_present)
|
if (attrmiss[firstmissingnum].am_present)
|
||||||
break;
|
break;
|
||||||
|
else
|
||||||
|
hasNulls = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there are no more missing values everything else must be NULL
|
* Now walk the missing attributes. If there is a missing value
|
||||||
|
* make space for it. Otherwise, it's going to be NULL.
|
||||||
*/
|
*/
|
||||||
if (firstmissingnum >= natts)
|
for (attnum = firstmissingnum;
|
||||||
|
attnum < natts;
|
||||||
|
attnum++)
|
||||||
{
|
{
|
||||||
hasNulls = true;
|
if (attrmiss[attnum].am_present)
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now walk the missing attributes. If there is a missing value
|
|
||||||
* make space for it. Otherwise, it's going to be NULL.
|
|
||||||
*/
|
|
||||||
for (attnum = firstmissingnum;
|
|
||||||
attnum < natts;
|
|
||||||
attnum++)
|
|
||||||
{
|
{
|
||||||
if (attrmiss[attnum].am_present)
|
Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
|
||||||
{
|
|
||||||
Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
|
|
||||||
|
|
||||||
targetDataLen = att_align_datum(targetDataLen,
|
targetDataLen = att_align_datum(targetDataLen,
|
||||||
att->attalign,
|
att->attalign,
|
||||||
att->attlen,
|
att->attlen,
|
||||||
attrmiss[attnum].am_value);
|
attrmiss[attnum].am_value);
|
||||||
|
|
||||||
targetDataLen = att_addlength_pointer(targetDataLen,
|
targetDataLen = att_addlength_pointer(targetDataLen,
|
||||||
att->attlen,
|
att->attlen,
|
||||||
attrmiss[attnum].am_value);
|
attrmiss[attnum].am_value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* no missing value, so it must be null */
|
/* no missing value, so it must be null */
|
||||||
hasNulls = true;
|
hasNulls = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} /* end if have missing values */
|
} /* end if have missing values */
|
||||||
|
@ -3396,7 +3396,10 @@ ltrmark:;
|
|||||||
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = heap_copytuple(&tuple);
|
if (HeapTupleHeaderGetNatts(tuple.t_data) < relation->rd_att->natts)
|
||||||
|
result = heap_expand_tuple(&tuple, relation->rd_att);
|
||||||
|
else
|
||||||
|
result = heap_copytuple(&tuple);
|
||||||
ReleaseBuffer(buffer);
|
ReleaseBuffer(buffer);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -539,8 +539,197 @@ FROM t1;
|
|||||||
1 | 0
|
1 | 0
|
||||||
(20 rows)
|
(20 rows)
|
||||||
|
|
||||||
DROP TABLE t1;
|
|
||||||
DROP TABLE T;
|
DROP TABLE T;
|
||||||
|
-- test that we account for missing columns without defaults correctly
|
||||||
|
-- in expand_tuple, and that rows are correctly expanded for triggers
|
||||||
|
CREATE FUNCTION test_trigger()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
|
||||||
|
begin
|
||||||
|
raise notice 'old tuple: %', to_json(OLD)::text;
|
||||||
|
if TG_OP = 'DELETE'
|
||||||
|
then
|
||||||
|
return OLD;
|
||||||
|
else
|
||||||
|
return NEW;
|
||||||
|
end if;
|
||||||
|
end;
|
||||||
|
|
||||||
|
$$;
|
||||||
|
-- 2 new columns, both have defaults
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,3);
|
||||||
|
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
|
||||||
|
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | 3 | 4 | 5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":4,"y":5}
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | 3 | 4 | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- 2 new columns, first has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,3);
|
||||||
|
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
|
||||||
|
ALTER TABLE t ADD COLUMN y int;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | 3 | 4 |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":4,"y":null}
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | 3 | 4 | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- 2 new columns, second has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,3);
|
||||||
|
ALTER TABLE t ADD COLUMN x int;
|
||||||
|
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | 3 | | 5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":null,"y":5}
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | 3 | | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- 2 new columns, neither has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,3);
|
||||||
|
ALTER TABLE t ADD COLUMN x int;
|
||||||
|
ALTER TABLE t ADD COLUMN y int;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | 3 | |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":null,"y":null}
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | 3 | | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- same as last 4 tests but here the last original column has a NULL value
|
||||||
|
-- 2 new columns, both have defaults
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
|
||||||
|
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
|
||||||
|
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | | 4 | 5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":4,"y":5}
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | | 4 | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- 2 new columns, first has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
|
||||||
|
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
|
||||||
|
ALTER TABLE t ADD COLUMN y int;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | | 4 |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":4,"y":null}
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | | 4 | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- 2 new columns, second has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
|
||||||
|
ALTER TABLE t ADD COLUMN x int;
|
||||||
|
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | | | 5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":null,"y":5}
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | | | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- 2 new columns, neither has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
|
||||||
|
ALTER TABLE t ADD COLUMN x int;
|
||||||
|
ALTER TABLE t ADD COLUMN y int;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | | |
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":null,"y":null}
|
||||||
|
SELECT * FROM t;
|
||||||
|
id | a | b | c | x | y
|
||||||
|
----+---+---+---+---+---
|
||||||
|
1 | 1 | 2 | | | 2
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
DROP TABLE t;
|
||||||
|
-- cleanup
|
||||||
|
DROP FUNCTION test_trigger();
|
||||||
|
DROP TABLE t1;
|
||||||
DROP FUNCTION set(name);
|
DROP FUNCTION set(name);
|
||||||
DROP FUNCTION comp();
|
DROP FUNCTION comp();
|
||||||
DROP TABLE m;
|
DROP TABLE m;
|
||||||
|
@ -360,8 +360,120 @@ SELECT a,
|
|||||||
AS z
|
AS z
|
||||||
FROM t1;
|
FROM t1;
|
||||||
|
|
||||||
DROP TABLE t1;
|
|
||||||
DROP TABLE T;
|
DROP TABLE T;
|
||||||
|
|
||||||
|
-- test that we account for missing columns without defaults correctly
|
||||||
|
-- in expand_tuple, and that rows are correctly expanded for triggers
|
||||||
|
|
||||||
|
CREATE FUNCTION test_trigger()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
|
||||||
|
begin
|
||||||
|
raise notice 'old tuple: %', to_json(OLD)::text;
|
||||||
|
if TG_OP = 'DELETE'
|
||||||
|
then
|
||||||
|
return OLD;
|
||||||
|
else
|
||||||
|
return NEW;
|
||||||
|
end if;
|
||||||
|
end;
|
||||||
|
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- 2 new columns, both have defaults
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,3);
|
||||||
|
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
|
||||||
|
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
SELECT * FROM t;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- 2 new columns, first has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,3);
|
||||||
|
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
|
||||||
|
ALTER TABLE t ADD COLUMN y int;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
SELECT * FROM t;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- 2 new columns, second has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,3);
|
||||||
|
ALTER TABLE t ADD COLUMN x int;
|
||||||
|
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
SELECT * FROM t;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- 2 new columns, neither has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,3);
|
||||||
|
ALTER TABLE t ADD COLUMN x int;
|
||||||
|
ALTER TABLE t ADD COLUMN y int;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
SELECT * FROM t;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- same as last 4 tests but here the last original column has a NULL value
|
||||||
|
-- 2 new columns, both have defaults
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
|
||||||
|
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
|
||||||
|
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
SELECT * FROM t;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- 2 new columns, first has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
|
||||||
|
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
|
||||||
|
ALTER TABLE t ADD COLUMN y int;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
SELECT * FROM t;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- 2 new columns, second has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
|
||||||
|
ALTER TABLE t ADD COLUMN x int;
|
||||||
|
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
SELECT * FROM t;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- 2 new columns, neither has default
|
||||||
|
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
|
||||||
|
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
|
||||||
|
ALTER TABLE t ADD COLUMN x int;
|
||||||
|
ALTER TABLE t ADD COLUMN y int;
|
||||||
|
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
|
||||||
|
SELECT * FROM t;
|
||||||
|
UPDATE t SET y = 2;
|
||||||
|
SELECT * FROM t;
|
||||||
|
DROP TABLE t;
|
||||||
|
|
||||||
|
-- cleanup
|
||||||
|
DROP FUNCTION test_trigger();
|
||||||
|
DROP TABLE t1;
|
||||||
DROP FUNCTION set(name);
|
DROP FUNCTION set(name);
|
||||||
DROP FUNCTION comp();
|
DROP FUNCTION comp();
|
||||||
DROP TABLE m;
|
DROP TABLE m;
|
||||||
|
Loading…
Reference in New Issue
Block a user