1-- 2-- Tests for PL/pgSQL handling of composite (record) variables 3-- 4 5create type two_int4s as (f1 int4, f2 int4); 6create type two_int8s as (q1 int8, q2 int8); 7 8-- base-case return of a composite type 9create function retc(int) returns two_int8s language plpgsql as 10$$ begin return row($1,1)::two_int8s; end $$; 11select retc(42); 12 13-- ok to return a matching record type 14create or replace function retc(int) returns two_int8s language plpgsql as 15$$ begin return row($1::int8, 1::int8); end $$; 16select retc(42); 17 18-- we don't currently support implicit casting 19create or replace function retc(int) returns two_int8s language plpgsql as 20$$ begin return row($1,1); end $$; 21select retc(42); 22 23-- nor extra columns 24create or replace function retc(int) returns two_int8s language plpgsql as 25$$ begin return row($1::int8, 1::int8, 42); end $$; 26select retc(42); 27 28-- same cases with an intermediate "record" variable 29create or replace function retc(int) returns two_int8s language plpgsql as 30$$ declare r record; begin r := row($1::int8, 1::int8); return r; end $$; 31select retc(42); 32 33create or replace function retc(int) returns two_int8s language plpgsql as 34$$ declare r record; begin r := row($1,1); return r; end $$; 35select retc(42); 36 37create or replace function retc(int) returns two_int8s language plpgsql as 38$$ declare r record; begin r := row($1::int8, 1::int8, 42); return r; end $$; 39select retc(42); 40 41-- but, for mostly historical reasons, we do convert when assigning 42-- to a named-composite-type variable 43create or replace function retc(int) returns two_int8s language plpgsql as 44$$ declare r two_int8s; begin r := row($1::int8, 1::int8, 42); return r; end $$; 45select retc(42); 46 47do $$ declare c two_int8s; 48begin c := row(1,2); raise notice 'c = %', c; end$$; 49 50do $$ declare c two_int8s; 51begin for c in select 1,2 loop raise notice 'c = %', c; end loop; end$$; 52 53do $$ declare c4 two_int4s; c8 two_int8s; 54begin 55 c8 := row(1,2); 56 c4 := c8; 57 c8 := c4; 58 raise notice 'c4 = %', c4; 59 raise notice 'c8 = %', c8; 60end$$; 61 62-- check passing composite result to another function 63create function getq1(two_int8s) returns int8 language plpgsql as $$ 64declare r two_int8s; begin r := $1; return r.q1; end $$; 65 66select getq1(retc(344)); 67select getq1(row(1,2)); 68 69do $$ 70declare r1 two_int8s; r2 record; x int8; 71begin 72 r1 := retc(345); 73 perform getq1(r1); 74 x := getq1(r1); 75 raise notice 'x = %', x; 76 r2 := retc(346); 77 perform getq1(r2); 78 x := getq1(r2); 79 raise notice 'x = %', x; 80end$$; 81 82-- check assignments of composites 83do $$ 84declare r1 two_int8s; r2 two_int8s; r3 record; r4 record; 85begin 86 r1 := row(1,2); 87 raise notice 'r1 = %', r1; 88 r1 := r1; -- shouldn't do anything 89 raise notice 'r1 = %', r1; 90 r2 := r1; 91 raise notice 'r1 = %', r1; 92 raise notice 'r2 = %', r2; 93 r2.q2 = r1.q1 + 3; -- check that r2 has distinct storage 94 raise notice 'r1 = %', r1; 95 raise notice 'r2 = %', r2; 96 r1 := null; 97 raise notice 'r1 = %', r1; 98 raise notice 'r2 = %', r2; 99 r1 := row(7,11)::two_int8s; 100 r2 := r1; 101 raise notice 'r1 = %', r1; 102 raise notice 'r2 = %', r2; 103 r3 := row(1,2); 104 r4 := r3; 105 raise notice 'r3 = %', r3; 106 raise notice 'r4 = %', r4; 107 r4.f1 := r4.f1 + 3; -- check that r4 has distinct storage 108 raise notice 'r3 = %', r3; 109 raise notice 'r4 = %', r4; 110 r1 := r3; 111 raise notice 'r1 = %', r1; 112 r4 := r1; 113 raise notice 'r4 = %', r4; 114 r4.q2 := r4.q2 + 1; -- r4's field names have changed 115 raise notice 'r4 = %', r4; 116end$$; 117 118-- fields of named-type vars read as null if uninitialized 119do $$ 120declare r1 two_int8s; 121begin 122 raise notice 'r1 = %', r1; 123 raise notice 'r1.q1 = %', r1.q1; 124 raise notice 'r1.q2 = %', r1.q2; 125 raise notice 'r1 = %', r1; 126end$$; 127 128do $$ 129declare r1 two_int8s; 130begin 131 raise notice 'r1.q1 = %', r1.q1; 132 raise notice 'r1.q2 = %', r1.q2; 133 raise notice 'r1 = %', r1; 134 raise notice 'r1.nosuchfield = %', r1.nosuchfield; 135end$$; 136 137-- records, not so much 138do $$ 139declare r1 record; 140begin 141 raise notice 'r1 = %', r1; 142 raise notice 'r1.f1 = %', r1.f1; 143 raise notice 'r1.f2 = %', r1.f2; 144 raise notice 'r1 = %', r1; 145end$$; 146 147-- but OK if you assign first 148do $$ 149declare r1 record; 150begin 151 raise notice 'r1 = %', r1; 152 r1 := row(1,2); 153 raise notice 'r1.f1 = %', r1.f1; 154 raise notice 'r1.f2 = %', r1.f2; 155 raise notice 'r1 = %', r1; 156 raise notice 'r1.nosuchfield = %', r1.nosuchfield; 157end$$; 158 159-- check repeated assignments to composite fields 160create table some_table (id int, data text); 161 162do $$ 163declare r some_table; 164begin 165 r := (23, 'skidoo'); 166 for i in 1 .. 10 loop 167 r.id := r.id + i; 168 r.data := r.data || ' ' || i; 169 end loop; 170 raise notice 'r = %', r; 171end$$; 172 173-- check behavior of function declared to return "record" 174 175create function returnsrecord(int) returns record language plpgsql as 176$$ begin return row($1,$1+1); end $$; 177 178select returnsrecord(42); 179select * from returnsrecord(42) as r(x int, y int); 180select * from returnsrecord(42) as r(x int, y int, z int); -- fail 181select * from returnsrecord(42) as r(x int, y bigint); -- fail 182 183-- same with an intermediate record variable 184create or replace function returnsrecord(int) returns record language plpgsql as 185$$ declare r record; begin r := row($1,$1+1); return r; end $$; 186 187select returnsrecord(42); 188select * from returnsrecord(42) as r(x int, y int); 189select * from returnsrecord(42) as r(x int, y int, z int); -- fail 190select * from returnsrecord(42) as r(x int, y bigint); -- fail 191 192-- should work the same with a missing column in the actual result value 193create table has_hole(f1 int, f2 int, f3 int); 194alter table has_hole drop column f2; 195 196create or replace function returnsrecord(int) returns record language plpgsql as 197$$ begin return row($1,$1+1)::has_hole; end $$; 198 199select returnsrecord(42); 200select * from returnsrecord(42) as r(x int, y int); 201select * from returnsrecord(42) as r(x int, y int, z int); -- fail 202select * from returnsrecord(42) as r(x int, y bigint); -- fail 203 204-- same with an intermediate record variable 205create or replace function returnsrecord(int) returns record language plpgsql as 206$$ declare r record; begin r := row($1,$1+1)::has_hole; return r; end $$; 207 208select returnsrecord(42); 209select * from returnsrecord(42) as r(x int, y int); 210select * from returnsrecord(42) as r(x int, y int, z int); -- fail 211select * from returnsrecord(42) as r(x int, y bigint); -- fail 212 213-- check access to a field of an argument declared "record" 214create function getf1(x record) returns int language plpgsql as 215$$ begin return x.f1; end $$; 216select getf1(1); 217select getf1(row(1,2)); 218-- a CLOBBER_CACHE_ALWAYS build will report this error with a different 219-- context stack than other builds, so suppress context output 220\set SHOW_CONTEXT never 221select getf1(row(1,2)::two_int8s); 222\set SHOW_CONTEXT errors 223select getf1(row(1,2)); 224 225-- check behavior when assignment to FOR-loop variable requires coercion 226do $$ 227declare r two_int8s; 228begin 229 for r in select i, i+1 from generate_series(1,4) i 230 loop 231 raise notice 'r = %', r; 232 end loop; 233end$$; 234 235-- check behavior when returning setof composite 236create function returnssetofholes() returns setof has_hole language plpgsql as 237$$ 238declare r record; 239 h has_hole; 240begin 241 return next h; 242 r := (1,2); 243 h := (3,4); 244 return next r; 245 return next h; 246 return next row(5,6); 247 return next row(7,8)::has_hole; 248end$$; 249select returnssetofholes(); 250 251create or replace function returnssetofholes() returns setof has_hole language plpgsql as 252$$ 253declare r record; 254begin 255 return next r; -- fails, not assigned yet 256end$$; 257select returnssetofholes(); 258 259create or replace function returnssetofholes() returns setof has_hole language plpgsql as 260$$ 261begin 262 return next row(1,2,3); -- fails 263end$$; 264select returnssetofholes(); 265 266-- check behavior with changes of a named rowtype 267create table mutable(f1 int, f2 text); 268 269create function sillyaddone(int) returns int language plpgsql as 270$$ declare r mutable; begin r.f1 := $1; return r.f1 + 1; end $$; 271select sillyaddone(42); 272 273-- test for change of type of column f1 should be here someday; 274-- for now see plpgsql_cache test 275 276alter table mutable drop column f1; 277select sillyaddone(42); -- fail 278 279create function getf3(x mutable) returns int language plpgsql as 280$$ begin return x.f3; end $$; 281select getf3(null::mutable); -- doesn't work yet 282alter table mutable add column f3 int; 283select getf3(null::mutable); -- now it works 284alter table mutable drop column f3; 285-- a CLOBBER_CACHE_ALWAYS build will report this error with a different 286-- context stack than other builds, so suppress context output 287\set SHOW_CONTEXT never 288select getf3(null::mutable); -- fails again 289\set SHOW_CONTEXT errors 290 291-- check behavior with creating/dropping a named rowtype 292set check_function_bodies = off; -- else reference to nonexistent type fails 293 294create function sillyaddtwo(int) returns int language plpgsql as 295$$ declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end $$; 296 297reset check_function_bodies; 298 299select sillyaddtwo(42); -- fail 300create table mutable2(f1 int, f2 text); 301select sillyaddtwo(42); 302drop table mutable2; 303select sillyaddtwo(42); -- fail 304create table mutable2(f0 text, f1 int, f2 text); 305select sillyaddtwo(42); 306select sillyaddtwo(43); 307 308-- check access to system columns in a record variable 309 310create function sillytrig() returns trigger language plpgsql as 311$$begin 312 raise notice 'old.ctid = %', old.ctid; 313 raise notice 'old.tableoid = %', old.tableoid::regclass; 314 return new; 315end$$; 316 317create trigger mutable_trig before update on mutable for each row 318execute procedure sillytrig(); 319 320insert into mutable values ('foo'), ('bar'); 321update mutable set f2 = f2 || ' baz'; 322table mutable; 323 324-- check returning a composite datum from a trigger 325 326create or replace function sillytrig() returns trigger language plpgsql as 327$$begin 328 return row(new.*); 329end$$; 330 331update mutable set f2 = f2 || ' baz'; 332table mutable; 333 334create or replace function sillytrig() returns trigger language plpgsql as 335$$declare r record; 336begin 337 r := row(new.*); 338 return r; 339end$$; 340 341update mutable set f2 = f2 || ' baz'; 342table mutable; 343 344-- 345-- Domains of composite 346-- 347 348create domain ordered_int8s as two_int8s check((value).q1 <= (value).q2); 349 350create function read_ordered_int8s(p ordered_int8s) returns int8 as $$ 351begin return p.q1 + p.q2; end 352$$ language plpgsql; 353 354select read_ordered_int8s(row(1, 2)); 355select read_ordered_int8s(row(2, 1)); -- fail 356 357create function build_ordered_int8s(i int8, j int8) returns ordered_int8s as $$ 358begin return row(i,j); end 359$$ language plpgsql; 360 361select build_ordered_int8s(1,2); 362select build_ordered_int8s(2,1); -- fail 363 364create function build_ordered_int8s_2(i int8, j int8) returns ordered_int8s as $$ 365declare r record; begin r := row(i,j); return r; end 366$$ language plpgsql; 367 368select build_ordered_int8s_2(1,2); 369select build_ordered_int8s_2(2,1); -- fail 370 371create function build_ordered_int8s_3(i int8, j int8) returns ordered_int8s as $$ 372declare r two_int8s; begin r := row(i,j); return r; end 373$$ language plpgsql; 374 375select build_ordered_int8s_3(1,2); 376select build_ordered_int8s_3(2,1); -- fail 377 378create function build_ordered_int8s_4(i int8, j int8) returns ordered_int8s as $$ 379declare r ordered_int8s; begin r := row(i,j); return r; end 380$$ language plpgsql; 381 382select build_ordered_int8s_4(1,2); 383select build_ordered_int8s_4(2,1); -- fail 384 385create function build_ordered_int8s_a(i int8, j int8) returns ordered_int8s[] as $$ 386begin return array[row(i,j), row(i,j+1)]; end 387$$ language plpgsql; 388 389select build_ordered_int8s_a(1,2); 390select build_ordered_int8s_a(2,1); -- fail 391 392-- check field assignment 393do $$ 394declare r ordered_int8s; 395begin 396 r.q1 := null; 397 r.q2 := 43; 398 r.q1 := 42; 399 r.q2 := 41; -- fail 400end$$; 401 402-- check whole-row assignment 403do $$ 404declare r ordered_int8s; 405begin 406 r := null; 407 r := row(null,null); 408 r := row(1,2); 409 r := row(2,1); -- fail 410end$$; 411 412-- check assignment in for-loop 413do $$ 414declare r ordered_int8s; 415begin 416 for r in values (1,2),(3,4),(6,5) loop 417 raise notice 'r = %', r; 418 end loop; 419end$$; 420 421-- check behavior with toastable fields, too 422 423create type two_texts as (f1 text, f2 text); 424create domain ordered_texts as two_texts check((value).f1 <= (value).f2); 425 426create table sometable (id int, a text, b text); 427-- b should be compressed, but in-line 428insert into sometable values (1, 'a', repeat('ffoob',1000)); 429-- this b should be out-of-line 430insert into sometable values (2, 'a', repeat('ffoob',100000)); 431-- this pair should fail the domain check 432insert into sometable values (3, 'z', repeat('ffoob',100000)); 433 434do $$ 435declare d ordered_texts; 436begin 437 for d in select a, b from sometable loop 438 raise notice 'succeeded at "%"', d.f1; 439 end loop; 440end$$; 441 442do $$ 443declare r record; d ordered_texts; 444begin 445 for r in select * from sometable loop 446 raise notice 'processing row %', r.id; 447 d := row(r.a, r.b); 448 end loop; 449end$$; 450 451do $$ 452declare r record; d ordered_texts; 453begin 454 for r in select * from sometable loop 455 raise notice 'processing row %', r.id; 456 d := null; 457 d.f1 := r.a; 458 d.f2 := r.b; 459 end loop; 460end$$; 461 462-- check coercion of a record result to named-composite function output type 463create function compresult(int8) returns two_int8s language plpgsql as 464$$ declare r record; begin r := row($1,$1); return r; end $$; 465 466create table two_int8s_tab (f1 two_int8s); 467insert into two_int8s_tab values (compresult(42)); 468-- reconnect so we lose any local knowledge of anonymous record types 469\c - 470table two_int8s_tab; 471