-- -- Tests for PL/pgSQL handling of composite (record) variables --
create type two_int4s as (f1 int4, f2 int4); create type more_int4s as (f0 text, f1 int4, f2 int4); create type two_int8s as (q1 int8, q2 int8); create type nested_int8s as (c1 two_int8s, c2 two_int8s);
-- base-case return of a composite type create function retc(int) returns two_int8s language plpgsql as
$$ begin return row($1,1)::two_int8s; end $$; select retc(42);
-- ok to return a matching record type createorreplace function retc(int) returns two_int8s language plpgsql as
$$ begin return row($1::int8, 1::int8); end $$; select retc(42);
-- we don't currently support implicit casting createorreplace function retc(int) returns two_int8s language plpgsql as
$$ begin return row($1,1); end $$; select retc(42);
-- nor extra columns createorreplace function retc(int) returns two_int8s language plpgsql as
$$ begin return row($1::int8, 1::int8, 42); end $$; select retc(42);
-- same cases with an intermediate "record" variable createorreplace function retc(int) returns two_int8s language plpgsql as
$$ declare r record; begin r := row($1::int8, 1::int8); return r; end $$; select retc(42);
createorreplace function retc(int) returns two_int8s language plpgsql as
$$ declare r record; begin r := row($1,1); return r; end $$; select retc(42);
createorreplace function retc(int) returns two_int8s language plpgsql as
$$ declare r record; begin r := row($1::int8, 1::int8, 42); return r; end $$; select retc(42);
-- but, for mostly historical reasons, we do convert when assigning -- to a named-composite-type variable createorreplace function retc(int) returns two_int8s language plpgsql as
$$ declare r two_int8s; begin r := row($1::int8, 1::int8, 42); return r; end $$; select retc(42);
do $$ declare c two_int8s;
begin c := row(1,2); raise notice 'c = %', c; end$$;
do $$ declare c two_int8s;
begin for c inselect1,2loop raise notice 'c = %', c; end loop; end$$;
-- error cases
do $$ declare c two_int8s; begin c.x = 1; end $$;
do $$ declare c nested_int8s; begin c.x = 1; end $$;
do $$ declare c nested_int8s; begin c.x.q1 = 1; end $$;
do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
do $$ <<b>> declare c two_int8s; begin b.c.x = 1; end $$;
do $$ <<b>> declare c nested_int8s; begin b.c.x = 1; end $$;
do $$ <<b>> declare c nested_int8s; begin b.c.x.q1 = 1; end $$;
do $$ <<b>> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
-- check passing composite result to another function create function getq1(two_int8s) returns int8 language plpgsql as $$ declare r two_int8s; begin r := $1; return r.q1; end $$;
select getq1(retc(344)); select getq1(row(1,2));
do $$ declare r1 two_int8s; r2 record; x int8;
begin
r1 := retc(345);
perform getq1(r1);
x := getq1(r1);
raise notice 'x = %', x;
r2 := retc(346);
perform getq1(r2);
x := getq1(r2);
raise notice 'x = %', x;
end$$;
-- records, not so much
do $$ declare r1 record;
begin
raise notice 'r1 = %', r1;
raise notice 'r1.f1 = %', r1.f1;
raise notice 'r1.f2 = %', r1.f2;
raise notice 'r1 = %', r1;
end$$;
-- but OK if you assign first
do $$ declare r1 record;
begin
raise notice 'r1 = %', r1;
r1 := row(1,2);
raise notice 'r1.f1 = %', r1.f1;
raise notice 'r1.f2 = %', r1.f2;
raise notice 'r1 = %', r1;
raise notice 'r1.nosuchfield = %', r1.nosuchfield;
end$$;
-- check %type with block-qualified variable names
do $$
<<blk>> declare
v int;
r two_int8s;
v1 v%type;
v2 blk.v%type;
r1 r%type;
r2 blk.r%type;
begin
raise notice '%', pg_typeof(v1);
raise notice '%', pg_typeof(v2);
raise notice '%', pg_typeof(r1);
raise notice '%', pg_typeof(r2);
end$$;
-- check that type record can be passed through %type
do $$ declare r1 record;
r2 r1%type;
begin
r2 := row(1,2);
raise notice 'r2 = %', r2;
r2 := row(3,4,5);
raise notice 'r2 = %', r2;
end$$;
-- arrays of record are not supported at the moment
do $$ declare r1 record[];
begin
end$$;
do $$ declare r1 record;
r2 r1%type[];
begin
end$$;
-- check repeated assignments to composite fields createtable some_table (id int, data text);
do $$ declare r some_table;
begin
r := (23, 'skidoo'); for i in1 .. 10loop
r.id := r.id + i;
r.data := r.data || ' ' || i;
end loop;
raise notice 'r = %', r;
end$$;
-- check behavior of function declared to return "record"
create function returnsrecord(int) returns record language plpgsql as
$$ begin return row($1,$1+1); end $$;
select returnsrecord(42); select * from returnsrecord(42) as r(x int, y int); select * from returnsrecord(42) as r(x int, y int, z int); -- fail select * from returnsrecord(42) as r(x int, y bigint); -- fail
-- same with an intermediate record variable createorreplace function returnsrecord(int) returns record language plpgsql as
$$ declare r record; begin r := row($1,$1+1); return r; end $$;
select returnsrecord(42); select * from returnsrecord(42) as r(x int, y int); select * from returnsrecord(42) as r(x int, y int, z int); -- fail select * from returnsrecord(42) as r(x int, y bigint); -- fail
-- should work the same with a missing column in the actual result value createtable has_hole(f1 int, f2 int, f3 int); altertable has_hole dropcolumn f2;
createorreplace function returnsrecord(int) returns record language plpgsql as
$$ begin return row($1,$1+1)::has_hole; end $$;
select returnsrecord(42); select * from returnsrecord(42) as r(x int, y int); select * from returnsrecord(42) as r(x int, y int, z int); -- fail select * from returnsrecord(42) as r(x int, y bigint); -- fail
-- same with an intermediate record variable createorreplace function returnsrecord(int) returns record language plpgsql as
$$ declare r record; begin r := row($1,$1+1)::has_hole; return r; end $$;
select returnsrecord(42); select * from returnsrecord(42) as r(x int, y int); select * from returnsrecord(42) as r(x int, y int, z int); -- fail select * from returnsrecord(42) as r(x int, y bigint); -- fail
-- check access to a field of an argument declared "record" create function getf1(x record) returns int language plpgsql as
$$ begin return x.f1; end $$; select getf1(1); select getf1(row(1,2)); select getf1(row(1,2)::two_int4s); select getf1(row('foo',123,456)::more_int4s); -- the context stack is different when debug_discard_caches -- is set, so suppress context output
\set SHOW_CONTEXT never select getf1(row(1,2)::two_int8s);
\set SHOW_CONTEXT errors select getf1(row(1,2));
-- this seemingly-equivalent case behaves a bit differently, -- because the core parser's handling of $N symbols is simplistic create function getf2(record) returns int language plpgsql as
$$ begin return $1.f2; end $$; select getf2(row(1,2)); -- ideally would work, but does not select getf2(row(1,2)::two_int4s); select getf2(row('foo',123,456)::more_int4s);
-- check behavior when assignment to FOR-loop variable requires coercion
do $$ declare r two_int8s;
begin for r inselect i, i+1from generate_series(1,4) i loop
raise notice 'r = %', r;
end loop;
end$$;
-- check behavior when returning setof composite create function returnssetofholes() returns setof has_hole language plpgsql as
$$ declare r record;
h has_hole;
begin return next h;
r := (1,2);
h := (3,4); return next r; return next h; return next row(5,6); return next row(7,8)::has_hole; return query select9, 10;
end$$; select returnssetofholes();
createorreplace function returnssetofholes() returns setof has_hole language plpgsql as
$$ declare r record;
begin return next r; -- fails, not assigned yet
end$$; select returnssetofholes();
createorreplace function returnssetofholes() returns setof has_hole language plpgsql as
$$
begin return next row(1,2,3); -- fails
end$$; select returnssetofholes();
createorreplace function returnssetofholes() returns setof has_hole language plpgsql as
$$
begin return query select1, 2.0; -- fails
end$$; select returnssetofholes();
-- check behavior with changes of a named rowtype createtable mutable(f1 int, f2 text);
create function sillyaddone(int) returns int language plpgsql as
$$ declare r mutable; begin r.f1 := $1; return r.f1 + 1; end $$; select sillyaddone(42);
-- test for change of type of column f1 should be here someday; -- for now see plpgsql_cache test
altertable mutable dropcolumn f1; -- the context stack is different when debug_discard_caches -- is set, so suppress context output
\set SHOW_CONTEXT never select sillyaddone(42); -- fail
\set SHOW_CONTEXT errors
create function getf3(x mutable) returns int language plpgsql as
$$ begin return x.f3; end $$; select getf3(null::mutable); -- doesn't work yet altertable mutable addcolumn f3 int; select getf3(null::mutable); -- now it works altertable mutable dropcolumn f3; -- the context stack is different when debug_discard_caches -- is set, so suppress context output
\set SHOW_CONTEXT never select getf3(null::mutable); -- fails again
\set SHOW_CONTEXT errors
-- check behavior with creating/dropping a named rowtype set check_function_bodies = off; -- else reference to nonexistent type fails
create function sillyaddtwo(int) returns int language plpgsql as
$$ declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end $$;
reset check_function_bodies;
select sillyaddtwo(42); -- fail createtable mutable2(f1 int, f2 text); select sillyaddtwo(42); droptable mutable2; -- the context stack is different when debug_discard_caches -- is set, so suppress context output
\set SHOW_CONTEXT never select sillyaddtwo(42); -- fail
\set SHOW_CONTEXT errors createtable mutable2(f0 text, f1 int, f2 text); select sillyaddtwo(42); select sillyaddtwo(43);
-- check access to system columns in a record variable
create function sillytrig() returns trigger language plpgsql as
$$begin
raise notice 'old.ctid = %', old.ctid;
raise notice 'old.tableoid = %', old.tableoid::regclass; return new;
end$$;
create function build_ordered_int8s_2(i int8, j int8) returns ordered_int8s as $$ declare r record; begin r := row(i,j); return r; end
$$ language plpgsql;
create function build_ordered_int8s_3(i int8, j int8) returns ordered_int8s as $$ declare r two_int8s; begin r := row(i,j); return r; end
$$ language plpgsql;
create function build_ordered_int8s_4(i int8, j int8) returns ordered_int8s as $$ declare r ordered_int8s; begin r := row(i,j); return r; end
$$ language plpgsql;
-- check field assignment
do $$ declare r ordered_int8s;
begin
r.q1 := null;
r.q2 := 43;
r.q1 := 42;
r.q2 := 41; -- fail
end$$;
-- check whole-row assignment
do $$ declare r ordered_int8s;
begin
r := null;
r := row(null,null);
r := row(1,2);
r := row(2,1); -- fail
end$$;
-- check assignment in for-loop
do $$ declare r ordered_int8s;
begin for r invalues (1,2),(3,4),(6,5) loop
raise notice 'r = %', r;
end loop;
end$$;
-- check behavior with toastable fields, too
create type two_texts as (f1 text, f2 text); create domain ordered_texts as two_texts check((value).f1 <= (value).f2);
createtable sometable (id int, a text, b text); -- b should be compressed, but in-line insertinto sometable values (1, 'a', repeat('ffoob',1000)); -- this b should be out-of-line insertinto sometable values (2, 'a', repeat('ffoob',100000)); -- this pair should fail the domain check insertinto sometable values (3, 'z', repeat('ffoob',100000));
do $$ declare d ordered_texts;
begin for d inselect a, b from sometable loop
raise notice 'succeeded at "%"', d.f1;
end loop;
end$$;
do $$ declare r record; d ordered_texts;
begin for r inselect * from sometable loop
raise notice 'processing row %', r.id;
d := row(r.a, r.b);
end loop;
end$$;
do $$ declare r record; d ordered_texts;
begin for r inselect * from sometable loop
raise notice 'processing row %', r.id;
d := null;
d.f1 := r.a;
d.f2 := r.b;
end loop;
end$$;
-- check coercion of a record result to named-composite function output type create function compresult(int8) returns two_int8s language plpgsql as
$$ declare r record; begin r := row($1,$1); return r; end $$;
createtable two_int8s_tab (f1 two_int8s); insertinto two_int8s_tab values (compresult(42)); -- reconnect so we lose any local knowledge of anonymous record types
\c - table two_int8s_tab;
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.25 Sekunden
(vorverarbeitet am 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.