@@ -885,3 +885,287 @@ table two_int8s_tab;
885885 (42,42)
886886(1 row)
887887
888+ -- Tests for bug #19382: server crash when ALTER TYPE is used mid-transaction
889+ -- in PL/pgSQL. Record variables populated before ALTER TYPE must not be
890+ -- returned, as the stored data no longer matches the current type definition.
891+ -- Case 1: Direct composite type change (INT -> TEXT)
892+ create type bug19382_foo as (a int, b int);
893+ create function bug19382_test_direct() returns record as $$
894+ declare r bug19382_foo := row(123, power(2, 30));
895+ begin
896+ alter type bug19382_foo alter attribute b type text;
897+ return r;
898+ end;
899+ $$ language plpgsql;
900+ select bug19382_test_direct();
901+ ERROR: cannot return record variable "r" after composite type "bug19382_foo" was altered
902+ CONTEXT: PL/pgSQL function bug19382_test_direct() line 5 at RETURN
903+ drop function bug19382_test_direct();
904+ drop type bug19382_foo cascade;
905+ -- Case 2: Nested composite type change
906+ create type bug19382_inner as (x int, y int);
907+ create type bug19382_outer as (a int, b bug19382_inner);
908+ create function bug19382_test_nested() returns record as $$
909+ declare r bug19382_outer;
910+ begin
911+ r := row(1, row(10, power(2, 30)::int4)::bug19382_inner)::bug19382_outer;
912+ alter type bug19382_inner alter attribute y type text;
913+ return r;
914+ end;
915+ $$ language plpgsql;
916+ select bug19382_test_nested();
917+ ERROR: cannot return record variable "r" after composite type "bug19382_inner" was altered
918+ CONTEXT: PL/pgSQL function bug19382_test_nested() line 6 at RETURN
919+ drop function bug19382_test_nested();
920+ drop type bug19382_outer cascade;
921+ drop type bug19382_inner cascade;
922+ -- Case 3: OUT parameter
923+ create type bug19382_foo1 as (a int, b int);
924+ create function bug19382_test_out(out r1 bug19382_foo1) as $$
925+ begin
926+ r1 := row(1, 2);
927+ alter type bug19382_foo1 alter attribute b type text;
928+ return;
929+ end;
930+ $$ language plpgsql;
931+ select bug19382_test_out();
932+ ERROR: cannot return record variable "r1" after composite type "bug19382_foo1" was altered
933+ CONTEXT: PL/pgSQL function bug19382_test_out() line 5 at RETURN
934+ drop function bug19382_test_out();
935+ drop type bug19382_foo1 cascade;
936+ -- Case 4: No ALTER TYPE (baseline — must not error)
937+ create type bug19382_foo2 as (a int, b int);
938+ create function bug19382_test_baseline() returns bug19382_foo2 as $$
939+ declare r bug19382_foo2 := row(1, 2);
940+ begin
941+ return r;
942+ end;
943+ $$ language plpgsql;
944+ select bug19382_test_baseline();
945+ bug19382_test_baseline
946+ ------------------------
947+ (1,2)
948+ (1 row)
949+
950+ drop function bug19382_test_baseline();
951+ drop type bug19382_foo2;
952+ -- Case 5: Field-by-field assignment (dot notation)
953+ create type bug19382_foo3 as (a int, b int);
954+ create function bug19382_test_field_assign() returns record as $$
955+ declare r bug19382_foo3;
956+ begin
957+ r.a := 123;
958+ r.b := power(2, 30)::int4;
959+ alter type bug19382_foo3 alter attribute b type text;
960+ return r;
961+ end;
962+ $$ language plpgsql;
963+ select bug19382_test_field_assign();
964+ ERROR: cannot return record variable "r" after composite type "bug19382_foo3" was altered
965+ CONTEXT: PL/pgSQL function bug19382_test_field_assign() line 7 at RETURN
966+ drop function bug19382_test_field_assign();
967+ drop type bug19382_foo3 cascade;
968+ -- Case 6: SELECT INTO individual fields
969+ create type bug19382_foo4 as (a int, b int);
970+ create table bug19382_tbl (a int, b int);
971+ insert into bug19382_tbl values (123, power(2, 30)::int4);
972+ create function bug19382_test_select_into_field() returns record as $$
973+ declare r bug19382_foo4;
974+ begin
975+ select a, b into r.a, r.b from bug19382_tbl;
976+ alter type bug19382_foo4 alter attribute b type text;
977+ return r;
978+ end;
979+ $$ language plpgsql;
980+ select bug19382_test_select_into_field();
981+ ERROR: cannot return record variable "r" after composite type "bug19382_foo4" was altered
982+ CONTEXT: PL/pgSQL function bug19382_test_select_into_field() line 6 at RETURN
983+ drop function bug19382_test_select_into_field();
984+ drop table bug19382_tbl;
985+ drop type bug19382_foo4 cascade;
986+ -- Case 7: RAISE NOTICE with record variable (exec_eval_datum path)
987+ create type bug19382_foo5 as (a int, b int);
988+ create function bug19382_test_eval_datum() returns void as $$
989+ declare r bug19382_foo5;
990+ begin
991+ r.b := power(2, 30)::int4;
992+ alter type bug19382_foo5 alter attribute b type text;
993+ raise notice 'r = %', r;
994+ end;
995+ $$ language plpgsql;
996+ select bug19382_test_eval_datum();
997+ ERROR: cannot return record variable "r" after composite type "bug19382_foo5" was altered
998+ CONTEXT: PL/pgSQL function bug19382_test_eval_datum() line 6 at RAISE
999+ drop function bug19382_test_eval_datum();
1000+ drop type bug19382_foo5 cascade;
1001+ -- Case 8: Variable-to-variable copy after inner-type alter.
1002+ -- Reading r1 as an rvalue must detect the stale nested type.
1003+ create type bug19382_inner4 as (x int, y int);
1004+ create type bug19382_outer4 as (a int, b bug19382_inner4);
1005+ create function bug19382_test_var_copy() returns bug19382_outer4 as $$
1006+ declare r1 bug19382_outer4; r2 bug19382_outer4;
1007+ begin
1008+ r1 := row(1, row(10, power(2, 30)::int4)::bug19382_inner4)::bug19382_outer4;
1009+ alter type bug19382_inner4 alter attribute y type text;
1010+ r2 := r1;
1011+ return r2;
1012+ end;
1013+ $$ language plpgsql;
1014+ select bug19382_test_var_copy();
1015+ ERROR: cannot return record variable "r1" after composite type "bug19382_inner4" was altered
1016+ CONTEXT: PL/pgSQL function bug19382_test_var_copy() line 6 at assignment
1017+ drop function bug19382_test_var_copy();
1018+ drop type bug19382_outer4 cascade;
1019+ drop type bug19382_inner4 cascade;
1020+ -- Case 9: Whole-record reassignment after inner-type alter.
1021+ -- The second assignment builds fresh data matching the post-ALTER type;
1022+ -- the snapshot must refresh so the reassignment succeeds.
1023+ create type bug19382_inner3 as (x int, y int);
1024+ create type bug19382_outer3 as (a int, b bug19382_inner3);
1025+ create function bug19382_test_reassign() returns bug19382_outer3 as $$
1026+ declare r bug19382_outer3;
1027+ begin
1028+ r := row(1, row(10, 20)::bug19382_inner3)::bug19382_outer3;
1029+ alter type bug19382_inner3 alter attribute y type text;
1030+ r := row(1, row(10, 'hello')::bug19382_inner3)::bug19382_outer3;
1031+ return r;
1032+ end;
1033+ $$ language plpgsql;
1034+ select bug19382_test_reassign();
1035+ bug19382_test_reassign
1036+ ------------------------
1037+ (1,"(10,hello)")
1038+ (1 row)
1039+
1040+ drop function bug19382_test_reassign();
1041+ drop type bug19382_outer3 cascade;
1042+ drop type bug19382_inner3 cascade;
1043+ -- Case 9 (outer-type variant): Whole-record reassignment after ALTER TYPE on
1044+ -- the record's own type. The reassigned bytes match the post-ALTER definition;
1045+ -- the outer-type identifier (er_tupdesc_id) on the ExpandedRecord must be
1046+ -- refreshed so check_record_type_not_altered() does not falsely reject.
1047+ create type bug19382_foo_outer_reassign as (a int, b int);
1048+ create function bug19382_test_outer_reassign() returns bug19382_foo_outer_reassign as $$
1049+ declare r bug19382_foo_outer_reassign;
1050+ begin
1051+ r := row(1, 2)::bug19382_foo_outer_reassign;
1052+ alter type bug19382_foo_outer_reassign alter attribute b type text;
1053+ r := row(1, 'hello')::bug19382_foo_outer_reassign;
1054+ return r;
1055+ end;
1056+ $$ language plpgsql;
1057+ select bug19382_test_outer_reassign();
1058+ bug19382_test_outer_reassign
1059+ ------------------------------
1060+ (1,hello)
1061+ (1 row)
1062+
1063+ drop function bug19382_test_outer_reassign();
1064+ drop type bug19382_foo_outer_reassign cascade;
1065+ -- Case 10: Generic RECORD variable assigned via ROW::foo cast
1066+ -- The declared type is RECORDOID; effective type is discovered from the
1067+ -- assigned value's er_typeid. Must error, not crash.
1068+ create type bug19382_foo6 as (a int, b int);
1069+ create function bug19382_test_generic_record() returns record as $$
1070+ declare r record;
1071+ begin
1072+ r := row(123, power(2, 30)::int4)::bug19382_foo6;
1073+ alter type bug19382_foo6 alter attribute b type text;
1074+ return r;
1075+ end;
1076+ $$ language plpgsql;
1077+ select bug19382_test_generic_record();
1078+ ERROR: cannot return record variable "r" after composite type "bug19382_foo6" was altered
1079+ CONTEXT: PL/pgSQL function bug19382_test_generic_record() line 6 at RETURN
1080+ drop function bug19382_test_generic_record();
1081+ drop type bug19382_foo6 cascade;
1082+ -- Case 11: Generic RECORD with nested composite alter
1083+ create type bug19382_inner2 as (x int, y int);
1084+ create type bug19382_outer2 as (a int, b bug19382_inner2);
1085+ create function bug19382_test_generic_nested() returns record as $$
1086+ declare r record;
1087+ begin
1088+ r := row(1, row(10, power(2, 30)::int4)::bug19382_inner2)::bug19382_outer2;
1089+ alter type bug19382_inner2 alter attribute y type text;
1090+ return r;
1091+ end;
1092+ $$ language plpgsql;
1093+ select bug19382_test_generic_nested();
1094+ ERROR: cannot return record variable "r" after composite type "bug19382_inner2" was altered
1095+ CONTEXT: PL/pgSQL function bug19382_test_generic_nested() line 6 at RETURN
1096+ drop function bug19382_test_generic_nested();
1097+ drop type bug19382_outer2 cascade;
1098+ drop type bug19382_inner2 cascade;
1099+ -- Case 12: Anonymous rowtype baseline (RECORDOID with no ::foo cast).
1100+ -- Non-versionable rowtypes must not trigger a false positive.
1101+ create function bug19382_test_anon_baseline() returns record as $$
1102+ declare r record;
1103+ begin
1104+ select 1 as a, 2 as b into r;
1105+ return r;
1106+ end;
1107+ $$ language plpgsql;
1108+ select bug19382_test_anon_baseline();
1109+ bug19382_test_anon_baseline
1110+ -----------------------------
1111+ (1,2)
1112+ (1 row)
1113+
1114+ drop function bug19382_test_anon_baseline();
1115+ -- Case 13: RAISE NOTICE with generic RECORD (RECORDOID + reader path).
1116+ create type bug19382_foo7 as (a int, b int);
1117+ create function bug19382_test_generic_raise() returns void as $$
1118+ declare r record;
1119+ begin
1120+ r := row(1, power(2, 30)::int4)::bug19382_foo7;
1121+ alter type bug19382_foo7 alter attribute b type text;
1122+ raise notice 'r = %', r;
1123+ end;
1124+ $$ language plpgsql;
1125+ select bug19382_test_generic_raise();
1126+ ERROR: cannot return record variable "r" after composite type "bug19382_foo7" was altered
1127+ CONTEXT: PL/pgSQL function bug19382_test_generic_raise() line 6 at RAISE
1128+ drop function bug19382_test_generic_raise();
1129+ drop type bug19382_foo7 cascade;
1130+ -- Case 14: composite type reachable through an array attribute.
1131+ -- The element composite type must be tracked even though the attribute's
1132+ -- own type is an array; otherwise ALTER TYPE on the element goes undetected
1133+ -- and record_out() crashes reinterpreting the stored bytes.
1134+ create type bug19382_elem as (f1 int);
1135+ create type bug19382_arrparent as (arr bug19382_elem[]);
1136+ create function bug19382_test_array_elem() returns bug19382_arrparent as $$
1137+ declare r bug19382_arrparent;
1138+ begin
1139+ r := row(array[row(power(2, 30)::int4)::bug19382_elem]::bug19382_elem[])::bug19382_arrparent;
1140+ alter type bug19382_elem alter attribute f1 type text;
1141+ return r;
1142+ end;
1143+ $$ language plpgsql;
1144+ select bug19382_test_array_elem();
1145+ ERROR: cannot return record variable "r" after composite type "bug19382_elem" was altered
1146+ CONTEXT: PL/pgSQL function bug19382_test_array_elem() line 6 at RETURN
1147+ drop function bug19382_test_array_elem();
1148+ drop type bug19382_arrparent cascade;
1149+ drop type bug19382_elem cascade;
1150+ -- Case 15: array element composite, whole-record reassignment after ALTER
1151+ -- with fresh matching data must succeed (no false positive on arrays).
1152+ create type bug19382_elem2 as (f1 int);
1153+ create type bug19382_arrparent2 as (arr bug19382_elem2[]);
1154+ create function bug19382_test_array_reassign() returns bug19382_arrparent2 as $$
1155+ declare r bug19382_arrparent2;
1156+ begin
1157+ r := row(array[row(1)::bug19382_elem2]::bug19382_elem2[])::bug19382_arrparent2;
1158+ alter type bug19382_elem2 add attribute f2 text;
1159+ r := row(array[row(1, 'hi')::bug19382_elem2]::bug19382_elem2[])::bug19382_arrparent2;
1160+ return r;
1161+ end;
1162+ $$ language plpgsql;
1163+ select bug19382_test_array_reassign();
1164+ bug19382_test_array_reassign
1165+ ------------------------------
1166+ ("{""(1,hi)""}")
1167+ (1 row)
1168+
1169+ drop function bug19382_test_array_reassign();
1170+ drop type bug19382_arrparent2 cascade;
1171+ drop type bug19382_elem2 cascade;
0 commit comments