Skip to content

Commit 7636e5c

Browse files
committed
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.
1 parent 60e612b commit 7636e5c

File tree

4 files changed

+328
-33
lines changed

4 files changed

+328
-33
lines changed

src/backend/access/common/heaptuple.c

+21-30
Original file line numberDiff line numberDiff line change
@@ -823,44 +823,35 @@ expand_tuple(HeapTuple *targetHeapTuple,
823823
{
824824
if (attrmiss[firstmissingnum].am_present)
825825
break;
826+
else
827+
hasNulls = true;
826828
}
827829

828830
/*
829-
* If there are no more missing values everything else must be NULL
831+
* Now walk the missing attributes. If there is a missing value
832+
* make space for it. Otherwise, it's going to be NULL.
830833
*/
831-
if (firstmissingnum >= natts)
832-
{
833-
hasNulls = true;
834-
}
835-
else
834+
for (attnum = firstmissingnum;
835+
attnum < natts;
836+
attnum++)
836837
{
837-
838-
/*
839-
* Now walk the missing attributes. If there is a missing value
840-
* make space for it. Otherwise, it's going to be NULL.
841-
*/
842-
for (attnum = firstmissingnum;
843-
attnum < natts;
844-
attnum++)
838+
if (attrmiss[attnum].am_present)
845839
{
846-
if (attrmiss[attnum].am_present)
847-
{
848-
Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
840+
Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
849841

850-
targetDataLen = att_align_datum(targetDataLen,
851-
att->attalign,
852-
att->attlen,
853-
attrmiss[attnum].am_value);
842+
targetDataLen = att_align_datum(targetDataLen,
843+
att->attalign,
844+
att->attlen,
845+
attrmiss[attnum].am_value);
854846

855-
targetDataLen = att_addlength_pointer(targetDataLen,
856-
att->attlen,
857-
attrmiss[attnum].am_value);
858-
}
859-
else
860-
{
861-
/* no missing value, so it must be null */
862-
hasNulls = true;
863-
}
847+
targetDataLen = att_addlength_pointer(targetDataLen,
848+
att->attlen,
849+
attrmiss[attnum].am_value);
850+
}
851+
else
852+
{
853+
/* no missing value, so it must be null */
854+
hasNulls = true;
864855
}
865856
}
866857
} /* end if have missing values */

src/backend/commands/trigger.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -3396,7 +3396,10 @@ ltrmark:;
33963396
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
33973397
}
33983398

3399-
result = heap_copytuple(&tuple);
3399+
if (HeapTupleHeaderGetNatts(tuple.t_data) < relation->rd_att->natts)
3400+
result = heap_expand_tuple(&tuple, relation->rd_att);
3401+
else
3402+
result = heap_copytuple(&tuple);
34003403
ReleaseBuffer(buffer);
34013404

34023405
return result;

src/test/regress/expected/fast_default.out

+190-1
Original file line numberDiff line numberDiff line change
@@ -539,8 +539,197 @@ FROM t1;
539539
1 | 0
540540
(20 rows)
541541

542-
DROP TABLE t1;
543542
DROP TABLE T;
543+
-- test that we account for missing columns without defaults correctly
544+
-- in expand_tuple, and that rows are correctly expanded for triggers
545+
CREATE FUNCTION test_trigger()
546+
RETURNS trigger
547+
LANGUAGE plpgsql
548+
AS $$
549+
550+
begin
551+
raise notice 'old tuple: %', to_json(OLD)::text;
552+
if TG_OP = 'DELETE'
553+
then
554+
return OLD;
555+
else
556+
return NEW;
557+
end if;
558+
end;
559+
560+
$$;
561+
-- 2 new columns, both have defaults
562+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
563+
INSERT INTO t (a,b,c) VALUES (1,2,3);
564+
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
565+
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
566+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
567+
SELECT * FROM t;
568+
id | a | b | c | x | y
569+
----+---+---+---+---+---
570+
1 | 1 | 2 | 3 | 4 | 5
571+
(1 row)
572+
573+
UPDATE t SET y = 2;
574+
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":4,"y":5}
575+
SELECT * FROM t;
576+
id | a | b | c | x | y
577+
----+---+---+---+---+---
578+
1 | 1 | 2 | 3 | 4 | 2
579+
(1 row)
580+
581+
DROP TABLE t;
582+
-- 2 new columns, first has default
583+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
584+
INSERT INTO t (a,b,c) VALUES (1,2,3);
585+
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
586+
ALTER TABLE t ADD COLUMN y int;
587+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
588+
SELECT * FROM t;
589+
id | a | b | c | x | y
590+
----+---+---+---+---+---
591+
1 | 1 | 2 | 3 | 4 |
592+
(1 row)
593+
594+
UPDATE t SET y = 2;
595+
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":4,"y":null}
596+
SELECT * FROM t;
597+
id | a | b | c | x | y
598+
----+---+---+---+---+---
599+
1 | 1 | 2 | 3 | 4 | 2
600+
(1 row)
601+
602+
DROP TABLE t;
603+
-- 2 new columns, second has default
604+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
605+
INSERT INTO t (a,b,c) VALUES (1,2,3);
606+
ALTER TABLE t ADD COLUMN x int;
607+
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
608+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
609+
SELECT * FROM t;
610+
id | a | b | c | x | y
611+
----+---+---+---+---+---
612+
1 | 1 | 2 | 3 | | 5
613+
(1 row)
614+
615+
UPDATE t SET y = 2;
616+
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":null,"y":5}
617+
SELECT * FROM t;
618+
id | a | b | c | x | y
619+
----+---+---+---+---+---
620+
1 | 1 | 2 | 3 | | 2
621+
(1 row)
622+
623+
DROP TABLE t;
624+
-- 2 new columns, neither has default
625+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
626+
INSERT INTO t (a,b,c) VALUES (1,2,3);
627+
ALTER TABLE t ADD COLUMN x int;
628+
ALTER TABLE t ADD COLUMN y int;
629+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
630+
SELECT * FROM t;
631+
id | a | b | c | x | y
632+
----+---+---+---+---+---
633+
1 | 1 | 2 | 3 | |
634+
(1 row)
635+
636+
UPDATE t SET y = 2;
637+
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":3,"x":null,"y":null}
638+
SELECT * FROM t;
639+
id | a | b | c | x | y
640+
----+---+---+---+---+---
641+
1 | 1 | 2 | 3 | | 2
642+
(1 row)
643+
644+
DROP TABLE t;
645+
-- same as last 4 tests but here the last original column has a NULL value
646+
-- 2 new columns, both have defaults
647+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
648+
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
649+
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
650+
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
651+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
652+
SELECT * FROM t;
653+
id | a | b | c | x | y
654+
----+---+---+---+---+---
655+
1 | 1 | 2 | | 4 | 5
656+
(1 row)
657+
658+
UPDATE t SET y = 2;
659+
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":4,"y":5}
660+
SELECT * FROM t;
661+
id | a | b | c | x | y
662+
----+---+---+---+---+---
663+
1 | 1 | 2 | | 4 | 2
664+
(1 row)
665+
666+
DROP TABLE t;
667+
-- 2 new columns, first has default
668+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
669+
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
670+
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
671+
ALTER TABLE t ADD COLUMN y int;
672+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
673+
SELECT * FROM t;
674+
id | a | b | c | x | y
675+
----+---+---+---+---+---
676+
1 | 1 | 2 | | 4 |
677+
(1 row)
678+
679+
UPDATE t SET y = 2;
680+
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":4,"y":null}
681+
SELECT * FROM t;
682+
id | a | b | c | x | y
683+
----+---+---+---+---+---
684+
1 | 1 | 2 | | 4 | 2
685+
(1 row)
686+
687+
DROP TABLE t;
688+
-- 2 new columns, second has default
689+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
690+
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
691+
ALTER TABLE t ADD COLUMN x int;
692+
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
693+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
694+
SELECT * FROM t;
695+
id | a | b | c | x | y
696+
----+---+---+---+---+---
697+
1 | 1 | 2 | | | 5
698+
(1 row)
699+
700+
UPDATE t SET y = 2;
701+
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":null,"y":5}
702+
SELECT * FROM t;
703+
id | a | b | c | x | y
704+
----+---+---+---+---+---
705+
1 | 1 | 2 | | | 2
706+
(1 row)
707+
708+
DROP TABLE t;
709+
-- 2 new columns, neither has default
710+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
711+
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
712+
ALTER TABLE t ADD COLUMN x int;
713+
ALTER TABLE t ADD COLUMN y int;
714+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
715+
SELECT * FROM t;
716+
id | a | b | c | x | y
717+
----+---+---+---+---+---
718+
1 | 1 | 2 | | |
719+
(1 row)
720+
721+
UPDATE t SET y = 2;
722+
NOTICE: old tuple: {"id":1,"a":1,"b":2,"c":null,"x":null,"y":null}
723+
SELECT * FROM t;
724+
id | a | b | c | x | y
725+
----+---+---+---+---+---
726+
1 | 1 | 2 | | | 2
727+
(1 row)
728+
729+
DROP TABLE t;
730+
-- cleanup
731+
DROP FUNCTION test_trigger();
732+
DROP TABLE t1;
544733
DROP FUNCTION set(name);
545734
DROP FUNCTION comp();
546735
DROP TABLE m;

src/test/regress/sql/fast_default.sql

+113-1
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,120 @@ SELECT a,
360360
AS z
361361
FROM t1;
362362

363-
DROP TABLE t1;
364363
DROP TABLE T;
364+
365+
-- test that we account for missing columns without defaults correctly
366+
-- in expand_tuple, and that rows are correctly expanded for triggers
367+
368+
CREATE FUNCTION test_trigger()
369+
RETURNS trigger
370+
LANGUAGE plpgsql
371+
AS $$
372+
373+
begin
374+
raise notice 'old tuple: %', to_json(OLD)::text;
375+
if TG_OP = 'DELETE'
376+
then
377+
return OLD;
378+
else
379+
return NEW;
380+
end if;
381+
end;
382+
383+
$$;
384+
385+
-- 2 new columns, both have defaults
386+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
387+
INSERT INTO t (a,b,c) VALUES (1,2,3);
388+
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
389+
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
390+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
391+
SELECT * FROM t;
392+
UPDATE t SET y = 2;
393+
SELECT * FROM t;
394+
DROP TABLE t;
395+
396+
-- 2 new columns, first has default
397+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
398+
INSERT INTO t (a,b,c) VALUES (1,2,3);
399+
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
400+
ALTER TABLE t ADD COLUMN y int;
401+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
402+
SELECT * FROM t;
403+
UPDATE t SET y = 2;
404+
SELECT * FROM t;
405+
DROP TABLE t;
406+
407+
-- 2 new columns, second has default
408+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
409+
INSERT INTO t (a,b,c) VALUES (1,2,3);
410+
ALTER TABLE t ADD COLUMN x int;
411+
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
412+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
413+
SELECT * FROM t;
414+
UPDATE t SET y = 2;
415+
SELECT * FROM t;
416+
DROP TABLE t;
417+
418+
-- 2 new columns, neither has default
419+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
420+
INSERT INTO t (a,b,c) VALUES (1,2,3);
421+
ALTER TABLE t ADD COLUMN x int;
422+
ALTER TABLE t ADD COLUMN y int;
423+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
424+
SELECT * FROM t;
425+
UPDATE t SET y = 2;
426+
SELECT * FROM t;
427+
DROP TABLE t;
428+
429+
-- same as last 4 tests but here the last original column has a NULL value
430+
-- 2 new columns, both have defaults
431+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
432+
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
433+
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
434+
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
435+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
436+
SELECT * FROM t;
437+
UPDATE t SET y = 2;
438+
SELECT * FROM t;
439+
DROP TABLE t;
440+
441+
-- 2 new columns, first has default
442+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
443+
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
444+
ALTER TABLE t ADD COLUMN x int NOT NULL DEFAULT 4;
445+
ALTER TABLE t ADD COLUMN y int;
446+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
447+
SELECT * FROM t;
448+
UPDATE t SET y = 2;
449+
SELECT * FROM t;
450+
DROP TABLE t;
451+
452+
-- 2 new columns, second has default
453+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
454+
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
455+
ALTER TABLE t ADD COLUMN x int;
456+
ALTER TABLE t ADD COLUMN y int NOT NULL DEFAULT 5;
457+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
458+
SELECT * FROM t;
459+
UPDATE t SET y = 2;
460+
SELECT * FROM t;
461+
DROP TABLE t;
462+
463+
-- 2 new columns, neither has default
464+
CREATE TABLE t (id serial PRIMARY KEY, a int, b int, c int);
465+
INSERT INTO t (a,b,c) VALUES (1,2,NULL);
466+
ALTER TABLE t ADD COLUMN x int;
467+
ALTER TABLE t ADD COLUMN y int;
468+
CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
469+
SELECT * FROM t;
470+
UPDATE t SET y = 2;
471+
SELECT * FROM t;
472+
DROP TABLE t;
473+
474+
-- cleanup
475+
DROP FUNCTION test_trigger();
476+
DROP TABLE t1;
365477
DROP FUNCTION set(name);
366478
DROP FUNCTION comp();
367479
DROP TABLE m;

0 commit comments

Comments
 (0)