-- -- PLPGSQL -- -- Scenario: -- -- A building with a modern TP cable installation where any -- of the wall connectors can be used to plug in phones, -- ethernet interfaces or local office hubs. The backside -- of the wall connectors is wired to one of several patch- -- fields in the building. -- -- In the patchfields, there are hubs and all the slots -- representing the wall connectors. In addition there are -- slots that can represent a phone line from the central -- phone system. -- -- Triggers ensure consistency of the patching information. -- -- Functions are used to build up powerful views that let -- you look behind the wall when looking at a patchfield -- or into a room. --
createtable Room (
roomno char(8),
comment text
);
createuniqueindex Room_rno on Room using btree (roomno bpchar_ops);
createuniqueindex PHone_name on PHone using btree (slotname bpchar_ops);
-- ************************************************************ -- * -- * Trigger procedures and functions for the patchfield -- * test of PL/pgSQL -- * -- ************************************************************
-- ************************************************************ -- * AFTER UPDATE on Room -- * - If room no changes let wall slots follow -- ************************************************************ create function tg_room_au() returns triggeras'
begin if new.roomno != old.roomno then update WSlot set roomno = new.roomno where roomno = old.roomno;
end if; return new;
end; ' language plpgsql;
createtrigger tg_room_au after update on Room foreach row execute procedure tg_room_au();
-- ************************************************************ -- * AFTER DELETE on Room -- * - delete wall slots in this room -- ************************************************************ create function tg_room_ad() returns triggeras'
begin deletefrom WSlot where roomno = old.roomno; return old;
end; ' language plpgsql;
createtrigger tg_room_ad after delete on Room foreach row execute procedure tg_room_ad();
-- ************************************************************ -- * BEFORE INSERT or UPDATE on WSlot -- * - Check that room exists -- ************************************************************ create function tg_wslot_biu() returns triggeras $$
begin if count(*) = 0from Room where roomno = new.roomno then
raise exception 'Room % does not exist', new.roomno;
end if; return new;
end;
$$ language plpgsql;
createtrigger tg_wslot_biu beforeinsertorupdate on WSlot foreach row execute procedure tg_wslot_biu();
-- ************************************************************ -- * AFTER UPDATE on PField -- * - Let PSlots of this field follow -- ************************************************************ create function tg_pfield_au() returns triggeras'
begin if new.name != old.name then update PSlot set pfname = new.name where pfname = old.name;
end if; return new;
end; ' language plpgsql;
createtrigger tg_pfield_au after update on PField foreach row execute procedure tg_pfield_au();
-- ************************************************************ -- * AFTER DELETE on PField -- * - Remove all slots of this patchfield -- ************************************************************ create function tg_pfield_ad() returns triggeras'
begin deletefrom PSlot where pfname = old.name; return old;
end; ' language plpgsql;
createtrigger tg_pfield_ad after delete on PField foreach row execute procedure tg_pfield_ad();
-- ************************************************************ -- * BEFORE INSERT or UPDATE on PSlot -- * - Ensure that our patchfield does exist -- ************************************************************ create function tg_pslot_biu() returns triggeras $proc$ declare
pfrec record;
ps alias for new;
begin selectinto pfrec * from PField where name = ps.pfname; ifnot found then
raise exception $$Patchfield "%" does not exist$$, ps.pfname;
end if; return ps;
end;
$proc$ language plpgsql;
createtrigger tg_pslot_biu beforeinsertorupdate on PSlot foreach row execute procedure tg_pslot_biu();
-- ************************************************************ -- * AFTER UPDATE on System -- * - If system name changes let interfaces follow -- ************************************************************ create function tg_system_au() returns triggeras'
begin if new.name != old.name then update IFace set sysname = new.name where sysname = old.name;
end if; return new;
end; ' language plpgsql;
createtrigger tg_system_au after update on System foreach row execute procedure tg_system_au();
-- ************************************************************ -- * BEFORE INSERT or UPDATE on IFace -- * - set the slotname to IF.sysname.ifname -- ************************************************************ create function tg_iface_biu() returns triggeras $$ declare
sname text;
sysrec record;
begin selectinto sysrec * from system where name = new.sysname; ifnot found then
raise exception $q$system "%" does not exist$q$, new.sysname;
end if;
sname := 'IF.' || new.sysname;
sname := sname || '.';
sname := sname || new.ifname; if length(sname) > 20then
raise exception 'IFace slotname "%" too long (20 char max)', sname;
end if;
new.slotname := sname; return new;
end;
$$ language plpgsql;
createtrigger tg_iface_biu beforeinsertorupdate on IFace foreach row execute procedure tg_iface_biu();
-- ************************************************************ -- * AFTER INSERT or UPDATE or DELETE on Hub -- * - insert/delete/rename slots as required -- ************************************************************ create function tg_hub_a() returns triggeras' declare
hname text;
dummy integer;
begin if tg_op = ''INSERT''then
dummy := tg_hub_adjustslots(new.name, 0, new.nslots); return new;
end if; if tg_op = ''UPDATE''then if new.name != old.name then update HSlot set hubname = new.name where hubname = old.name;
end if;
dummy := tg_hub_adjustslots(new.name, old.nslots, new.nslots); return new;
end if; if tg_op = ''DELETE''then
dummy := tg_hub_adjustslots(old.name, old.nslots, 0); return old;
end if;
end; ' language plpgsql;
createtrigger tg_hub_a after insertorupdateordelete on Hub foreach row execute procedure tg_hub_a();
-- ************************************************************ -- * Support function to add/remove slots of Hub -- ************************************************************ create function tg_hub_adjustslots(hname bpchar,
oldnslots integer,
newnslots integer)
returns integeras'
begin if newnslots = oldnslots then return0;
end if; if newnslots < oldnslots then deletefrom HSlot where hubname = hname and slotno > newnslots; return0;
end if; for i in oldnslots + 1 .. newnslots loop insertinto HSlot (slotname, hubname, slotno, slotlink) values (''HS.dummy'', hname, i, '''');
end loop; return0;
end ' language plpgsql;
-- Test comments
COMMENT ON FUNCTION tg_hub_adjustslots_wrong(bpchar, integer, integer) IS'function with args';
COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) IS'function with args';
COMMENT ON FUNCTION tg_hub_adjustslots(bpchar, integer, integer) ISNULL;
-- ************************************************************ -- * BEFORE INSERT or UPDATE on HSlot -- * - prevent from manual manipulation -- * - set the slotname to HS.hubname.slotno -- ************************************************************ create function tg_hslot_biu() returns triggeras' declare
sname text;
xname HSlot.slotname%TYPE;
hubrec record;
begin selectinto hubrec * from Hub where name = new.hubname; ifnot found then
raise exception ''no manual manipulation of HSlot'';
end if; if new.slotno < 1or new.slotno > hubrec.nslots then
raise exception ''no manual manipulation of HSlot'';
end if; if tg_op = ''UPDATE''and new.hubname != old.hubname then if count(*) > 0from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
sname := ''HS.'' || trim(new.hubname);
sname := sname || ''.'';
sname := sname || new.slotno::text; if length(sname) > 20then
raise exception ''HSlot slotname "%" too long (20char max)'', sname;
end if;
new.slotname := sname; return new;
end; ' language plpgsql;
createtrigger tg_hslot_biu beforeinsertorupdate on HSlot foreach row execute procedure tg_hslot_biu();
-- ************************************************************ -- * BEFORE DELETE on HSlot -- * - prevent from manual manipulation -- ************************************************************ create function tg_hslot_bd() returns triggeras' declare
hubrec record;
begin selectinto hubrec * from Hub where name = old.hubname; ifnot found then return old;
end if; if old.slotno > hubrec.nslots then return old;
end if;
raise exception ''no manual manipulation of HSlot'';
end; ' language plpgsql;
createtrigger tg_hslot_bd beforedelete on HSlot foreach row execute procedure tg_hslot_bd();
-- ************************************************************ -- * BEFORE INSERT on all slots -- * - Check name prefix -- ************************************************************ create function tg_chkslotname() returns triggeras'
begin if substr(new.slotname, 1, 2) != tg_argv[0] then
raise exception ''slotname must begin with %'', tg_argv[0];
end if; return new;
end; ' language plpgsql;
createtrigger tg_chkslotname beforeinsert on PSlot foreach row execute procedure tg_chkslotname('PS');
createtrigger tg_chkslotname beforeinsert on WSlot foreach row execute procedure tg_chkslotname('WS');
createtrigger tg_chkslotname beforeinsert on PLine foreach row execute procedure tg_chkslotname('PL');
createtrigger tg_chkslotname beforeinsert on IFace foreach row execute procedure tg_chkslotname('IF');
createtrigger tg_chkslotname beforeinsert on PHone foreach row execute procedure tg_chkslotname('PH');
-- ************************************************************ -- * BEFORE INSERT or UPDATE on all slots with slotlink -- * - Set slotlink to empty string if NULL value given -- ************************************************************ create function tg_chkslotlink() returns triggeras'
begin if new.slotlink isnull then
new.slotlink := '''';
end if; return new;
end; ' language plpgsql;
createtrigger tg_chkslotlink beforeinsertorupdate on PSlot foreach row execute procedure tg_chkslotlink();
createtrigger tg_chkslotlink beforeinsertorupdate on WSlot foreach row execute procedure tg_chkslotlink();
createtrigger tg_chkslotlink beforeinsertorupdate on IFace foreach row execute procedure tg_chkslotlink();
createtrigger tg_chkslotlink beforeinsertorupdate on HSlot foreach row execute procedure tg_chkslotlink();
createtrigger tg_chkslotlink beforeinsertorupdate on PHone foreach row execute procedure tg_chkslotlink();
-- ************************************************************ -- * BEFORE INSERT or UPDATE on all slots with backlink -- * - Set backlink to empty string if NULL value given -- ************************************************************ create function tg_chkbacklink() returns triggeras'
begin if new.backlink isnull then
new.backlink := '''';
end if; return new;
end; ' language plpgsql;
createtrigger tg_chkbacklink beforeinsertorupdate on PSlot foreach row execute procedure tg_chkbacklink();
createtrigger tg_chkbacklink beforeinsertorupdate on WSlot foreach row execute procedure tg_chkbacklink();
createtrigger tg_chkbacklink beforeinsertorupdate on PLine foreach row execute procedure tg_chkbacklink();
-- ************************************************************ -- * BEFORE UPDATE on PSlot -- * - do delete/insert instead of update if name changes -- ************************************************************ create function tg_pslot_bu() returns triggeras'
begin if new.slotname != old.slotname then deletefrom PSlot where slotname = old.slotname; insertinto PSlot (
slotname,
pfname,
slotlink,
backlink
) values (
new.slotname,
new.pfname,
new.slotlink,
new.backlink
); returnnull;
end if; return new;
end; ' language plpgsql;
createtrigger tg_pslot_bu beforeupdate on PSlot foreach row execute procedure tg_pslot_bu();
-- ************************************************************ -- * BEFORE UPDATE on WSlot -- * - do delete/insert instead of update if name changes -- ************************************************************ create function tg_wslot_bu() returns triggeras'
begin if new.slotname != old.slotname then deletefrom WSlot where slotname = old.slotname; insertinto WSlot (
slotname,
roomno,
slotlink,
backlink
) values (
new.slotname,
new.roomno,
new.slotlink,
new.backlink
); returnnull;
end if; return new;
end; ' language plpgsql;
createtrigger tg_wslot_bu beforeupdate on WSlot foreach row execute procedure tg_Wslot_bu();
-- ************************************************************ -- * BEFORE UPDATE on PLine -- * - do delete/insert instead of update if name changes -- ************************************************************ create function tg_pline_bu() returns triggeras'
begin if new.slotname != old.slotname then deletefrom PLine where slotname = old.slotname; insertinto PLine (
slotname,
phonenumber,
comment,
backlink
) values (
new.slotname,
new.phonenumber,
new.comment,
new.backlink
); returnnull;
end if; return new;
end; ' language plpgsql;
createtrigger tg_pline_bu beforeupdate on PLine foreach row execute procedure tg_pline_bu();
-- ************************************************************ -- * BEFORE UPDATE on IFace -- * - do delete/insert instead of update if name changes -- ************************************************************ create function tg_iface_bu() returns triggeras'
begin if new.slotname != old.slotname then deletefrom IFace where slotname = old.slotname; insertinto IFace (
slotname,
sysname,
ifname,
slotlink
) values (
new.slotname,
new.sysname,
new.ifname,
new.slotlink
); returnnull;
end if; return new;
end; ' language plpgsql;
createtrigger tg_iface_bu beforeupdate on IFace foreach row execute procedure tg_iface_bu();
-- ************************************************************ -- * BEFORE UPDATE on HSlot -- * - do delete/insert instead of update if name changes -- ************************************************************ create function tg_hslot_bu() returns triggeras'
begin if new.slotname != old.slotname or new.hubname != old.hubname then deletefrom HSlot where slotname = old.slotname; insertinto HSlot (
slotname,
hubname,
slotno,
slotlink
) values (
new.slotname,
new.hubname,
new.slotno,
new.slotlink
); returnnull;
end if; return new;
end; ' language plpgsql;
createtrigger tg_hslot_bu beforeupdate on HSlot foreach row execute procedure tg_hslot_bu();
-- ************************************************************ -- * BEFORE UPDATE on PHone -- * - do delete/insert instead of update if name changes -- ************************************************************ create function tg_phone_bu() returns triggeras'
begin if new.slotname != old.slotname then deletefrom PHone where slotname = old.slotname; insertinto PHone (
slotname,
comment,
slotlink
) values (
new.slotname,
new.comment,
new.slotlink
); returnnull;
end if; return new;
end; ' language plpgsql;
createtrigger tg_phone_bu beforeupdate on PHone foreach row execute procedure tg_phone_bu();
-- ************************************************************ -- * AFTER INSERT or UPDATE or DELETE on slot with backlink -- * - Ensure that the opponent correctly points back to us -- ************************************************************ create function tg_backlink_a() returns triggeras' declare
dummy integer;
begin if tg_op = ''INSERT''then if new.backlink != ''''then
dummy := tg_backlink_set(new.backlink, new.slotname);
end if; return new;
end if; if tg_op = ''UPDATE''then if new.backlink != old.backlink then if old.backlink != ''''then
dummy := tg_backlink_unset(old.backlink, old.slotname);
end if; if new.backlink != ''''then
dummy := tg_backlink_set(new.backlink, new.slotname);
end if; else if new.slotname != old.slotname and new.backlink != ''''then
dummy := tg_slotlink_set(new.backlink, new.slotname);
end if;
end if; return new;
end if; if tg_op = ''DELETE''then if old.backlink != ''''then
dummy := tg_backlink_unset(old.backlink, old.slotname);
end if; return old;
end if;
end; ' language plpgsql;
createtrigger tg_backlink_a after insertorupdateordelete on PSlot foreach row execute procedure tg_backlink_a('PS');
createtrigger tg_backlink_a after insertorupdateordelete on WSlot foreach row execute procedure tg_backlink_a('WS');
createtrigger tg_backlink_a after insertorupdateordelete on PLine foreach row execute procedure tg_backlink_a('PL');
-- ************************************************************ -- * Support function to set the opponents backlink field -- * if it does not already point to the requested slot -- ************************************************************ create function tg_backlink_set(myname bpchar, blname bpchar)
returns integeras' declare
mytype char(2);
link char(4);
rec record;
begin
mytype := substr(myname, 1, 2);
link := mytype || substr(blname, 1, 2); if link = ''PLPL''then
raise exception ''backlink between two phone lines does not make sense'';
end if; if link in (''PLWS'', ''WSPL'') then
raise exception ''direct link of phone line to wall slot not permitted'';
end if; if mytype = ''PS''then selectinto rec * from PSlot where slotname = myname; ifnot found then
raise exception ''% does not exist'', myname;
end if; if rec.backlink != blname then update PSlot set backlink = blname where slotname = myname;
end if; return0;
end if; if mytype = ''WS''then selectinto rec * from WSlot where slotname = myname; ifnot found then
raise exception ''% does not exist'', myname;
end if; if rec.backlink != blname then update WSlot set backlink = blname where slotname = myname;
end if; return0;
end if; if mytype = ''PL''then selectinto rec * from PLine where slotname = myname; ifnot found then
raise exception ''% does not exist'', myname;
end if; if rec.backlink != blname then update PLine set backlink = blname where slotname = myname;
end if; return0;
end if;
raise exception ''illegal backlink beginning with %'', mytype;
end; ' language plpgsql;
-- ************************************************************ -- * Support function to clear out the backlink field if -- * it still points to specific slot -- ************************************************************ create function tg_backlink_unset(bpchar, bpchar)
returns integeras' declare
myname alias for $1;
blname alias for $2;
mytype char(2);
rec record;
begin
mytype := substr(myname, 1, 2); if mytype = ''PS''then selectinto rec * from PSlot where slotname = myname; ifnot found then return0;
end if; if rec.backlink = blname then update PSlot set backlink = ''''where slotname = myname;
end if; return0;
end if; if mytype = ''WS''then selectinto rec * from WSlot where slotname = myname; ifnot found then return0;
end if; if rec.backlink = blname then update WSlot set backlink = ''''where slotname = myname;
end if; return0;
end if; if mytype = ''PL''then selectinto rec * from PLine where slotname = myname; ifnot found then return0;
end if; if rec.backlink = blname then update PLine set backlink = ''''where slotname = myname;
end if; return0;
end if;
end ' language plpgsql;
-- ************************************************************ -- * AFTER INSERT or UPDATE or DELETE on slot with slotlink -- * - Ensure that the opponent correctly points back to us -- ************************************************************ create function tg_slotlink_a() returns triggeras' declare
dummy integer;
begin if tg_op = ''INSERT''then if new.slotlink != ''''then
dummy := tg_slotlink_set(new.slotlink, new.slotname);
end if; return new;
end if; if tg_op = ''UPDATE''then if new.slotlink != old.slotlink then if old.slotlink != ''''then
dummy := tg_slotlink_unset(old.slotlink, old.slotname);
end if; if new.slotlink != ''''then
dummy := tg_slotlink_set(new.slotlink, new.slotname);
end if; else if new.slotname != old.slotname and new.slotlink != ''''then
dummy := tg_slotlink_set(new.slotlink, new.slotname);
end if;
end if; return new;
end if; if tg_op = ''DELETE''then if old.slotlink != ''''then
dummy := tg_slotlink_unset(old.slotlink, old.slotname);
end if; return old;
end if;
end; ' language plpgsql;
createtrigger tg_slotlink_a after insertorupdateordelete on PSlot foreach row execute procedure tg_slotlink_a('PS');
createtrigger tg_slotlink_a after insertorupdateordelete on WSlot foreach row execute procedure tg_slotlink_a('WS');
createtrigger tg_slotlink_a after insertorupdateordelete on IFace foreach row execute procedure tg_slotlink_a('IF');
createtrigger tg_slotlink_a after insertorupdateordelete on HSlot foreach row execute procedure tg_slotlink_a('HS');
createtrigger tg_slotlink_a after insertorupdateordelete on PHone foreach row execute procedure tg_slotlink_a('PH');
-- ************************************************************ -- * Support function to set the opponents slotlink field -- * if it does not already point to the requested slot -- ************************************************************ create function tg_slotlink_set(bpchar, bpchar)
returns integeras' declare
myname alias for $1;
blname alias for $2;
mytype char(2);
link char(4);
rec record;
begin
mytype := substr(myname, 1, 2);
link := mytype || substr(blname, 1, 2); if link = ''PHPH''then
raise exception ''slotlink between two phones does not make sense'';
end if; if link in (''PHHS'', ''HSPH'') then
raise exception ''link of phone to hub does not make sense'';
end if; if link in (''PHIF'', ''IFPH'') then
raise exception ''link of phone to hub does not make sense'';
end if; if link in (''PSWS'', ''WSPS'') then
raise exception ''slotlink from patchslot to wallslot not permitted'';
end if; if mytype = ''PS''then selectinto rec * from PSlot where slotname = myname; ifnot found then
raise exception ''% does not exist'', myname;
end if; if rec.slotlink != blname then update PSlot set slotlink = blname where slotname = myname;
end if; return0;
end if; if mytype = ''WS''then selectinto rec * from WSlot where slotname = myname; ifnot found then
raise exception ''% does not exist'', myname;
end if; if rec.slotlink != blname then update WSlot set slotlink = blname where slotname = myname;
end if; return0;
end if; if mytype = ''IF''then selectinto rec * from IFace where slotname = myname; ifnot found then
raise exception ''% does not exist'', myname;
end if; if rec.slotlink != blname then update IFace set slotlink = blname where slotname = myname;
end if; return0;
end if; if mytype = ''HS''then selectinto rec * from HSlot where slotname = myname; ifnot found then
raise exception ''% does not exist'', myname;
end if; if rec.slotlink != blname then update HSlot set slotlink = blname where slotname = myname;
end if; return0;
end if; if mytype = ''PH''then selectinto rec * from PHone where slotname = myname; ifnot found then
raise exception ''% does not exist'', myname;
end if; if rec.slotlink != blname then update PHone set slotlink = blname where slotname = myname;
end if; return0;
end if;
raise exception ''illegal slotlink beginning with %'', mytype;
end; ' language plpgsql;
-- ************************************************************ -- * Support function to clear out the slotlink field if -- * it still points to specific slot -- ************************************************************ create function tg_slotlink_unset(bpchar, bpchar)
returns integeras' declare
myname alias for $1;
blname alias for $2;
mytype char(2);
rec record;
begin
mytype := substr(myname, 1, 2); if mytype = ''PS''then selectinto rec * from PSlot where slotname = myname; ifnot found then return0;
end if; if rec.slotlink = blname then update PSlot set slotlink = ''''where slotname = myname;
end if; return0;
end if; if mytype = ''WS''then selectinto rec * from WSlot where slotname = myname; ifnot found then return0;
end if; if rec.slotlink = blname then update WSlot set slotlink = ''''where slotname = myname;
end if; return0;
end if; if mytype = ''IF''then selectinto rec * from IFace where slotname = myname; ifnot found then return0;
end if; if rec.slotlink = blname then update IFace set slotlink = ''''where slotname = myname;
end if; return0;
end if; if mytype = ''HS''then selectinto rec * from HSlot where slotname = myname; ifnot found then return0;
end if; if rec.slotlink = blname then update HSlot set slotlink = ''''where slotname = myname;
end if; return0;
end if; if mytype = ''PH''then selectinto rec * from PHone where slotname = myname; ifnot found then return0;
end if; if rec.slotlink = blname then update PHone set slotlink = ''''where slotname = myname;
end if; return0;
end if;
end; ' language plpgsql;
-- ************************************************************ -- * Describe the backside of a patchfield slot -- ************************************************************ create function pslot_backlink_view(bpchar)
returns text as'
<<outer>> declare
rec record;
bltype char(2);
retval text;
begin selectinto rec * from PSlot where slotname = $1; ifnot found then return'''';
end if; if rec.backlink = ''''then return''-'';
end if;
bltype := substr(rec.backlink, 1, 2); if bltype = ''PL''then declare
rec record;
begin selectinto rec * from PLine where slotname = "outer".rec.backlink;
retval := ''Phone line '' || trim(rec.phonenumber); if rec.comment != ''''then
retval := retval || '' ('';
retval := retval || rec.comment;
retval := retval || '')'';
end if; return retval;
end;
end if; if bltype = ''WS''then selectinto rec * from WSlot where slotname = rec.backlink;
retval := trim(rec.slotname) || ''in room '';
retval := retval || trim(rec.roomno);
retval := retval || '' -> ''; return retval || wslot_slotlink_view(rec.slotname);
end if; return rec.backlink;
end; ' language plpgsql;
-- ************************************************************ -- * Describe the front of a patchfield slot -- ************************************************************ create function pslot_slotlink_view(bpchar)
returns text as' declare
psrec record;
sltype char(2);
retval text;
begin selectinto psrec * from PSlot where slotname = $1; ifnot found then return'''';
end if; if psrec.slotlink = ''''then return''-'';
end if;
sltype := substr(psrec.slotlink, 1, 2); if sltype = ''PS''then
retval := trim(psrec.slotlink) || '' -> ''; return retval || pslot_backlink_view(psrec.slotlink);
end if; if sltype = ''HS''then
retval := comment from Hub H, HSlot HS where HS.slotname = psrec.slotlink and H.name = HS.hubname;
retval := retval || '' slot '';
retval := retval || slotno::text from HSlot where slotname = psrec.slotlink; return retval;
end if; return psrec.slotlink;
end; ' language plpgsql;
-- ************************************************************ -- * Describe the front of a wall connector slot -- ************************************************************ create function wslot_slotlink_view(bpchar)
returns text as' declare
rec record;
sltype char(2);
retval text;
begin selectinto rec * from WSlot where slotname = $1; ifnot found then return'''';
end if; if rec.slotlink = ''''then return''-'';
end if;
sltype := substr(rec.slotlink, 1, 2); if sltype = ''PH''then selectinto rec * from PHone where slotname = rec.slotlink;
retval := ''Phone '' || trim(rec.slotname); if rec.comment != ''''then
retval := retval || '' ('';
retval := retval || rec.comment;
retval := retval || '')'';
end if; return retval;
end if; if sltype = ''IF''then declare
syrow System%RowType;
ifrow IFace%ROWTYPE;
begin selectinto ifrow * from IFace where slotname = rec.slotlink; selectinto syrow * from System where name = ifrow.sysname;
retval := syrow.name || ''IF'';
retval := retval || ifrow.ifname; if syrow.comment != ''''then
retval := retval || '' ('';
retval := retval || syrow.comment;
retval := retval || '')'';
end if; return retval;
end;
end if; return rec.slotlink;
end; ' language plpgsql;
-- ************************************************************ -- * View of a patchfield describing backside and patches -- ************************************************************ create view Pfield_v1 asselect PF.pfname, PF.slotname,
pslot_backlink_view(PF.slotname) as backside,
pslot_slotlink_view(PF.slotname) as patch from PSlot PF;
-- -- First we build the house - so we create the rooms -- insertinto Room values ('001', 'Entrance'); insertinto Room values ('002', 'Office'); insertinto Room values ('003', 'Office'); insertinto Room values ('004', 'Technical'); insertinto Room values ('101', 'Office'); insertinto Room values ('102', 'Conference'); insertinto Room values ('103', 'Restroom'); insertinto Room values ('104', 'Technical'); insertinto Room values ('105', 'Office'); insertinto Room values ('106', 'Office');
-- -- This patchfield will be renamed later into PF0_2 - so its -- slots references in pfname should follow -- insertinto PField values ('PF0_X', 'Phonelines basement');
-- -- Now we wire the wall connectors 1a-2a in room 001 to the -- patchfield. In the second update we make an error, and -- correct it after -- update PSlot set backlink = 'WS.001.1a'where slotname = 'PS.base.a1'; update PSlot set backlink = 'WS.001.1b'where slotname = 'PS.base.a3'; select * from WSlot where roomno = '001'orderby slotname; select * from PSlot where slotname ~ 'PS.base.a'orderby slotname; update PSlot set backlink = 'WS.001.2a'where slotname = 'PS.base.a3'; select * from WSlot where roomno = '001'orderby slotname; select * from PSlot where slotname ~ 'PS.base.a'orderby slotname; update PSlot set backlink = 'WS.001.1b'where slotname = 'PS.base.a2'; select * from WSlot where roomno = '001'orderby slotname; select * from PSlot where slotname ~ 'PS.base.a'orderby slotname;
-- -- Same procedure for 2b-3b but this time updating the WSlot instead -- of the PSlot. Due to the triggers the result is the same: -- WSlot and corresponding PSlot point to each other. -- update WSlot set backlink = 'PS.base.a4'where slotname = 'WS.001.2b'; update WSlot set backlink = 'PS.base.a6'where slotname = 'WS.001.3a'; select * from WSlot where roomno = '001'orderby slotname; select * from PSlot where slotname ~ 'PS.base.a'orderby slotname; update WSlot set backlink = 'PS.base.a6'where slotname = 'WS.001.3b'; select * from WSlot where roomno = '001'orderby slotname; select * from PSlot where slotname ~ 'PS.base.a'orderby slotname; update WSlot set backlink = 'PS.base.a5'where slotname = 'WS.001.3a'; select * from WSlot where roomno = '001'orderby slotname; select * from PSlot where slotname ~ 'PS.base.a'orderby slotname;
insertinto PField values ('PF1_2', 'Phonelines first floor');
-- -- Buy some phones, plug them into the wall and patch the -- phone lines to the corresponding patchfield slots. -- insertinto PHone values ('PH.hc001', 'Hicom standard', 'WS.001.1a'); update PSlot set slotlink = 'PS.base.ta1'where slotname = 'PS.base.a1'; insertinto PHone values ('PH.hc002', 'Hicom standard', 'WS.002.1a'); update PSlot set slotlink = 'PS.base.ta5'where slotname = 'PS.base.b1'; insertinto PHone values ('PH.hc003', 'Hicom standard', 'WS.002.2a'); update PSlot set slotlink = 'PS.base.tb2'where slotname = 'PS.base.b3'; insertinto PHone values ('PH.fax001', 'Canon fax', 'WS.001.2a'); update PSlot set slotlink = 'PS.base.ta2'where slotname = 'PS.base.a3';
-- -- Install a hub at one of the patchfields, plug a computers -- ethernet interface into the wall and patch it to the hub. -- insertinto Hub values ('base.hub1', 'Patchfield PF0_1 hub', 16); insertinto System values ('orion', 'PC'); insertinto IFace values ('IF', 'orion', 'eth0', 'WS.002.1b'); update PSlot set slotlink = 'HS.base.hub1.1'where slotname = 'PS.base.b2';
-- -- Now we take a look at the patchfield -- select * from PField_v1 where pfname = 'PF0_1'orderby slotname; select * from PField_v1 where pfname = 'PF0_2'orderby slotname;
-- -- The following tests are unrelated to the scenario outlined above; -- they merely exercise specific parts of PL/pgSQL --
-- -- Test recursion, per bug report 7-Sep-01 -- CREATE FUNCTION recursion_test(int,int) RETURNS text AS' DECLARE rslt text;
BEGIN IF $1 <= 0THEN
rslt = CAST($2AS TEXT); ELSE
rslt = CAST($1AS TEXT) || '','' || recursion_test($1 - 1, $2);
END IF; RETURN rslt;
END;' LANGUAGE plpgsql;
SELECT recursion_test(4,3);
-- -- Test the FOUND magic variable -- CREATETABLE found_test_tbl (a int);
create function test_found()
returns boolean as' declare
begin insertinto found_test_tbl values (1); if FOUND then insertinto found_test_tbl values (2);
end if;
update found_test_tbl set a = 100where a = 1; if FOUND then insertinto found_test_tbl values (3);
end if;
deletefrom found_test_tbl where a = 9999; -- matches no rows ifnot FOUND then insertinto found_test_tbl values (4);
end if;
for i in1 .. 10loop -- no need to do anything
end loop; if FOUND then insertinto found_test_tbl values (5);
end if;
-- never executes the loop for i in2 .. 1loop -- no need to do anything
end loop; ifnot FOUND then insertinto found_test_tbl values (6);
end if; returntrue;
end;' language plpgsql;
select test_found(); select * from found_test_tbl;
-- -- Test set-returning functions for PL/pgSQL --
create function test_table_func_rec() returns setof found_test_tbl as' DECLARE
rec RECORD;
BEGIN FOR rec INselect * from found_test_tbl LOOP RETURN NEXT rec;
END LOOP; RETURN;
END;' language plpgsql;
select * from test_table_func_rec();
create function test_table_func_row() returns setof found_test_tbl as' DECLARE
row found_test_tbl%ROWTYPE;
BEGIN FOR row INselect * from found_test_tbl LOOP RETURN NEXT row;
END LOOP; RETURN;
END;' language plpgsql;
select * from test_table_func_row();
create function test_ret_set_scalar(int,int) returns setof intas' DECLARE
i int;
BEGIN FOR i IN $1 .. $2LOOP RETURN NEXT i + 1;
END LOOP; RETURN;
END;' language plpgsql;
select * from test_ret_set_scalar(1,10);
create function test_ret_set_rec_dyn(int) returns setof record as' DECLARE
retval RECORD;
BEGIN IF $1 > 10THEN SELECTINTO retval 5, 10, 15; RETURN NEXT retval; RETURN NEXT retval; ELSE SELECTINTO retval 50, 5::numeric, ''xxx''::text; RETURN NEXT retval; RETURN NEXT retval;
END IF; RETURN;
END;' language plpgsql;
SELECT * FROM test_ret_set_rec_dyn(1500) AS (a int, b int, c int); SELECT * FROM test_ret_set_rec_dyn(5) AS (a int, b numeric, c text);
create function test_ret_rec_dyn(int) returns record as' DECLARE
retval RECORD;
BEGIN IF $1 > 10THEN SELECTINTO retval 5, 10, 15; RETURN retval; ELSE SELECTINTO retval 50, 5::numeric, ''xxx''::text; RETURN retval;
END IF;
END;' language plpgsql;
SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int); SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text);
-- -- Test some simple polymorphism cases. --
create function f1(x anyelement) returns anyelement as $$
begin return x + 1;
end$$ language plpgsql;
select f1(42) asint, f1(4.5) as num; select f1(point(3,4)); -- fail for lack of + operator
drop function f1(x anyelement);
create function f1(x anyelement) returns anyarray as $$
begin return array[x + 1, x + 2];
end$$ language plpgsql;
select f1(42) asint, f1(4.5) as num;
drop function f1(x anyelement);
create function f1(x anyarray) returns anyelement as $$
begin return x[1];
end$$ language plpgsql;
select f1(array[2,4]) asint, f1(array[4.5, 7.7]) as num;
select f1(stavalues1) from pg_statistic; -- fail, can't infer element type
drop function f1(x anyarray);
create function f1(x anyarray) returns anyarray as $$
begin return x;
end$$ language plpgsql;
select f1(array[2,4]) asint, f1(array[4.5, 7.7]) as num;
select f1(stavalues1) from pg_statistic; -- fail, can't infer element type
drop function f1(x anyarray);
-- fail, can't infer type: create function f1(x anyelement) returns anyrange as $$
begin return array[x + 1, x + 2];
end$$ language plpgsql;
create function f1(x anyrange) returns anyarray as $$
begin return array[lower(x), upper(x)];
end$$ language plpgsql;
select f1(int4range(42, 49)) asint, f1(float8range(4.5, 7.8)) as num;
drop function f1(x anyrange);
create function f1(x anycompatible, y anycompatible) returns anycompatiblearray as $$
begin return array[x, y];
end$$ language plpgsql;
select f1(2, 4) asint, f1(2, 4.5) as num;
drop function f1(x anycompatible, y anycompatible);
create function f1(x anycompatiblerange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
begin return array[lower(x), upper(x), y, z];
end$$ language plpgsql;
select f1(int4range(42, 49), 11, 4.5) as fail; -- range type doesn't fit
drop function f1(x anycompatiblerange, y anycompatible, z anycompatible);
-- fail, can't infer type: create function f1(x anycompatible) returns anycompatiblerange as $$
begin return array[x + 1, x + 2];
end$$ language plpgsql;
create function f1(x anycompatiblerange, y anycompatiblearray) returns anycompatiblerange as $$
begin return x;
end$$ language plpgsql;
select f1(int4range(42, 49), array[11]) asint, f1(float8range(4.5, 7.8), array[7]) as num;
drop function f1(x anycompatiblerange, y anycompatiblearray);
create function f1(a anyelement, b anyarray,
c anycompatible, d anycompatible, OUT x anyarray, OUT y anycompatiblearray) as $$
begin
x := a || b;
y := array[c, d];
end$$ language plpgsql;
select x, pg_typeof(x), y, pg_typeof(y) from f1(11, array[1, 2], 42, 34.5); select x, pg_typeof(x), y, pg_typeof(y) from f1(11, array[1, 2], point(1,2), point(3,4)); select x, pg_typeof(x), y, pg_typeof(y) from f1(11, '{1,2}', point(1,2), '(3,4)'); select x, pg_typeof(x), y, pg_typeof(y) from f1(11, array[1, 2.2], 42, 34.5); -- fail
drop function f1(a anyelement, b anyarray,
c anycompatible, d anycompatible);
-- -- Test handling of OUT parameters, including polymorphic cases. -- Note that RETURN is optional with OUT params; we try both ways. --
-- wrong way to do it: create function f1(in i int, out j int) returns intas $$
begin return i+1;
end$$ language plpgsql;
create function f1(in i int, out j int) as $$
begin
j := i+1; return;
end$$ language plpgsql;
select f1(42); select * from f1(42);
createorreplace function f1(inout i int) as $$
begin
i := i+1;
end$$ language plpgsql;
select f1(42); select * from f1(42);
drop function f1(int);
create function f1(in i int, out j int) returns setof intas $$
begin
j := i+1; return next;
j := i+2; return next; return;
end$$ language plpgsql;
select * from f1(42);
drop function f1(int);
create function f1(in i int, out j int, out k text) as $$
begin
j := i;
j := j+1;
k := 'foo';
end$$ language plpgsql;
select f1(42); select * from f1(42);
drop function f1(int);
create function f1(in i int, out j int, out k text) returns setof record as $$
begin
j := i+1;
k := 'foo'; return next;
j := j+1;
k := 'foot'; return next;
end$$ language plpgsql;
select * from f1(42);
drop function f1(int);
create function duplic(in i anyelement, out j anyelement, out k anyarray) as $$
begin
j := i;
k := array[j,j]; return;
end$$ language plpgsql;
select * from duplic(42); select * from duplic('foo'::text);
drop function duplic(anyelement);
create function duplic(in i anycompatiblerange, out j anycompatible, out k anycompatiblearray) as $$
begin
j := lower(i);
k := array[lower(i),upper(i)]; return;
end$$ language plpgsql;
select * from duplic(int4range(42,49)); select * from duplic(textrange('aaa', 'bbb'));
drop function duplic(anycompatiblerange);
-- -- test PERFORM --
createtable perform_test (
a INT,
b INT
);
create function perform_simple_func(int) returns boolean as'
BEGIN IF $1 < 20THEN INSERTINTO perform_test VALUES ($1, $1 + 10); RETURNTRUE; ELSE RETURNFALSE;
END IF;
END;' language plpgsql;
create function perform_test_func() returns void as'
BEGIN IF FOUND then INSERTINTO perform_test VALUES (100, 100);
END IF;
PERFORM perform_simple_func(5);
IF FOUND then INSERTINTO perform_test VALUES (100, 100);
END IF;
PERFORM perform_simple_func(50);
IF FOUND then INSERTINTO perform_test VALUES (100, 100);
END IF;
RETURN;
END;' language plpgsql;
SELECT perform_test_func(); SELECT * FROM perform_test;
droptable perform_test;
-- -- Test proper snapshot handling in simple expressions --
create temp table users(login text, id serial);
create function sp_id_user(a_login text) returns intas $$ declare x int;
begin selectinto x id from users where login = a_login; if found thenreturn x; end if; return0;
end$$ language plpgsql stable;
drop function sp_add_user(text); drop function sp_id_user(text);
-- -- tests for refcursors -- createtable rc_test (a int, b int);
copy rc_test from stdin; 510 50100 5001000
\.
create function return_unnamed_refcursor() returns refcursor as $$ declare
rc refcursor;
begin
open rc forselect a from rc_test; return rc;
end
$$ language plpgsql;
create function use_refcursor(rc refcursor) returns intas $$ declare
rc refcursor;
x record;
begin
rc := return_unnamed_refcursor(); fetch next from rc into x; return x.a;
end
$$ language plpgsql;
select use_refcursor(return_unnamed_refcursor());
create function return_refcursor(rc refcursor) returns refcursor as $$
begin
open rc forselect a from rc_test; return rc;
end
$$ language plpgsql;
create function refcursor_test1(refcursor) returns refcursor as $$
begin
perform return_refcursor($1); return $1;
end
$$ language plpgsql;
begin;
select refcursor_test1('test1'); fetch next in test1;
create function refcursor_test2(int, int) returns boolean as $$ declare
c1 cursor (param1 int, param2 int) forselect * from rc_test where a > param1 and b > param2;
nonsense record;
begin
open c1($1, $2); fetch c1 into nonsense;
close c1; if found then returntrue; else returnfalse;
end if;
end
$$ language plpgsql;
select refcursor_test2(20000, 20000) as"Should be false",
refcursor_test2(20, 20) as"Should be true";
-- should fail create function constant_refcursor() returns refcursor as $$ declare
rc constant refcursor;
begin
open rc forselect a from rc_test; return rc;
end
$$ language plpgsql;
select constant_refcursor();
-- but it's okay like this createorreplace function constant_refcursor() returns refcursor as $$ declare
rc constant refcursor := 'my_cursor_name';
begin
open rc forselect a from rc_test; return rc;
end
$$ language plpgsql;
select constant_refcursor();
-- -- tests for cursors with named parameter arguments -- create function namedparmcursor_test1(int, int) returns boolean as $$ declare
c1 cursor (param1 int, param12 int) forselect * from rc_test where a > param1 and b > param12;
nonsense record;
begin
open c1(param12 := $2, param1 := $1); fetch c1 into nonsense;
close c1; if found then returntrue; else returnfalse;
end if;
end
$$ language plpgsql;
select namedparmcursor_test1(20000, 20000) as"Should be false",
namedparmcursor_test1(20, 20) as"Should be true";
-- mixing named and positional argument notations create function namedparmcursor_test2(int, int) returns boolean as $$ declare
c1 cursor (param1 int, param2 int) forselect * from rc_test where a > param1 and b > param2;
nonsense record;
begin
open c1(param1 := $1, $2); fetch c1 into nonsense;
close c1; if found then returntrue; else returnfalse;
end if;
end
$$ language plpgsql; select namedparmcursor_test2(20, 20);
-- mixing named and positional: param2 is given twice, once in named notation -- and second time in positional notation. Should throw an error at parse time create function namedparmcursor_test3() returns void as $$ declare
c1 cursor (param1 int, param2 int) forselect * from rc_test where a > param1 and b > param2;
begin
open c1(param2 := 20, 21);
end
$$ language plpgsql;
-- mixing named and positional: same as previous test, but param1 is duplicated create function namedparmcursor_test4() returns void as $$ declare
c1 cursor (param1 int, param2 int) forselect * from rc_test where a > param1 and b > param2;
begin
open c1(20, param1 := 21);
end
$$ language plpgsql;
-- duplicate named parameter, should throw an error at parse time create function namedparmcursor_test5() returns void as $$ declare
c1 cursor (p1 int, p2 int) for select * from tenk1 where thousand = p1 and tenthous = p2;
begin
open c1 (p2 := 77, p2 := 42);
end
$$ language plpgsql;
-- not enough parameters, should throw an error at parse time create function namedparmcursor_test6() returns void as $$ declare
c1 cursor (p1 int, p2 int) for select * from tenk1 where thousand = p1 and tenthous = p2;
begin
open c1 (p2 := 77);
end
$$ language plpgsql;
-- division by zero runtime error, the context given in the error message -- should be sensible create function namedparmcursor_test7() returns void as $$ declare
c1 cursor (p1 int, p2 int) for select * from tenk1 where thousand = p1 and tenthous = p2;
begin
open c1 (p2 := 77, p1 := 42/0);
end $$ language plpgsql; select namedparmcursor_test7();
-- check that line comments work correctly within the argument list -- (this used to require a special hack in the code; it no longer does, -- but let's keep the test anyway) create function namedparmcursor_test8() returns int4as $$ declare
c1 cursor (p1 int, p2 int) for select count(*) from tenk1 where thousand = p1 and tenthous = p2;
n int4;
begin
open c1 (77-- test
, 42); fetch c1 into n; return n;
end $$ language plpgsql; select namedparmcursor_test8();
-- cursor parameter name can match plpgsql variable or unreserved keyword create function namedparmcursor_test9(p1 int) returns int4as $$ declare
c1 cursor (p1 int, p2 int, debug int) for select count(*) from tenk1 where thousand = p1 and tenthous = p2 and four = debug;
p2 int4 := 1006;
n int4;
begin -- use both supported syntaxes for named arguments
open c1 (p1 := p1, p2 => p2, debug => 2); fetch c1 into n; return n;
end $$ language plpgsql; select namedparmcursor_test9(6);
-- -- tests for "raise" processing -- create function raise_test1(int) returns intas $$
begin
raise notice 'This message has too many parameters!', $1; return $1;
end;
$$ language plpgsql;
create function raise_test2(int) returns intas $$
begin
raise notice 'This message has too few parameters: %, %, %', $1, $1; return $1;
end;
$$ language plpgsql;
create function raise_test3(int) returns intas $$
begin
raise notice 'This message has no parameters (despite having %% signs in it)!'; return $1;
end;
$$ language plpgsql;
select raise_test3(1);
-- Test re-RAISE inside a nested exception block. This case is allowed -- by Oracle's PL/SQL but was handled differently by PG before 9.1.
CREATE FUNCTION reraise_test() RETURNS void AS $$
BEGIN
BEGIN
RAISE syntax_error;
EXCEPTION WHEN syntax_error THEN
BEGIN
raise notice 'exception % thrown in inner block, reraising', sqlerrm;
RAISE;
EXCEPTION WHEN OTHERS THEN
raise notice 'RIGHT - exception % caught in inner block', sqlerrm;
END;
END;
EXCEPTION WHEN OTHERS THEN
raise notice 'WRONG - exception % caught in outer block', sqlerrm;
END;
$$ LANGUAGE plpgsql;
SELECT reraise_test();
-- -- reject function definitions that contain malformed SQL queries at -- compile-time, where possible -- create function bad_sql1() returns intas $$ declare a int;
begin
a := 5;
Johnny Yuma;
a := 10; return a;
end$$ language plpgsql;
create function bad_sql2() returns intas $$ declare r record;
begin for r inselect I fought the law, the law won LOOP
raise notice 'in loop';
end loop; return5;
end;$$ language plpgsql;
-- a RETURN expression is mandatory, except for void-returning -- functions, where it is not allowed create function missing_return_expr() returns intas $$
begin return ;
end;$$ language plpgsql;
create function void_return_expr() returns void as $$
begin return5;
end;$$ language plpgsql;
-- VOID functions are allowed to omit RETURN create function void_return_expr() returns void as $$
begin
perform 2+2;
end;$$ language plpgsql;
select void_return_expr();
-- but ordinary functions are not create function missing_return_expr() returns intas $$
begin
perform 2+2;
end;$$ language plpgsql;
select missing_return_expr();
drop function void_return_expr(); drop function missing_return_expr();
-- -- EXECUTE ... INTO test --
createtable eifoo (i integer, y integer); create type eitype as (i integer, y integer);
createorreplace function execute_into_test(varchar) returns record as $$ declare
_r record;
_rt eifoo%rowtype;
_v eitype;
i int;
j int;
k int;
begin
execute 'insert into '||$1||' values(10,15)';
execute 'select (row).* from (select row(10,1)::eifoo) s'into _r;
raise notice '% %', _r.i, _r.y;
execute 'select * from '||$1||' limit 1'into _rt;
raise notice '% %', _rt.i, _rt.y;
execute 'select *, 20 from '||$1||' limit 1'into i, j, k;
raise notice '% % %', i, j, k;
execute 'select 1,2'into _v; return _v;
end; $$ language plpgsql;
select execute_into_test('eifoo');
droptable eifoo cascade; drop type eitype cascade;
-- -- SQLSTATE and SQLERRM test --
create function excpt_test1() returns void as $$
begin
raise notice '% %', sqlstate, sqlerrm;
end; $$ language plpgsql; -- should fail: SQLSTATE and SQLERRM are only in defined EXCEPTION -- blocks select excpt_test1();
create function excpt_test2() returns void as $$
begin
begin
begin
raise notice '% %', sqlstate, sqlerrm;
end;
end;
end; $$ language plpgsql; -- should fail select excpt_test2();
create function excpt_test3() returns void as $$
begin
begin
raise exception 'user exception';
exception when others then
raise notice 'caught exception % %', sqlstate, sqlerrm;
begin
raise notice '% %', sqlstate, sqlerrm;
perform 10/0;
exception when substring_error then -- this exception handler shouldn't be invoked
raise notice 'unexpected exception: % %', sqlstate, sqlerrm; when division_by_zero then
raise notice 'caught exception % %', sqlstate, sqlerrm;
end;
raise notice '% %', sqlstate, sqlerrm;
end;
end; $$ language plpgsql; select excpt_test3();
create function excpt_test4() returns text as $$
begin
begin perform 1/0;
exception when others thenreturn sqlerrm; end;
end; $$ language plpgsql; select excpt_test4();
drop function excpt_test1(); drop function excpt_test2(); drop function excpt_test3(); drop function excpt_test4();
-- parameters of raise stmt can be expressions create function raise_exprs() returns void as $$ declare
a integer[] = '{10,20,30}';
c varchar = 'xyz';
i integer;
begin
i := 2;
raise notice '%; %; %; %; %; %', a, a[i], c, (select c || 'abc'), row(10,'aaa',NULL,30), NULL;
end;$$ language plpgsql;
select raise_exprs(); drop function raise_exprs();
-- regression test: verify that multiple uses of same plpgsql datum within -- a SQL command all get mapped to the same $n parameter. The return value -- of the SELECT is not important, we only care that it doesn't fail with -- a complaint about an ungrouped column reference. create function multi_datum_use(p1 int) returns bool as $$ declare
x int;
y int;
begin selectinto x,y unique1/p1, unique1/$1from tenk1 groupby unique1/p1; return x = y;
end$$ language plpgsql;
select multi_datum_use(42);
-- -- Test STRICT limiter in both planned and EXECUTE invocations. -- Note that a data-modifying query is quasi strict (disallow multi rows) -- by default in the planned case, but not in EXECUTE. --
create temp table foo (f1 int, f2 int);
insertinto foo values (1,2), (3,4);
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should work insertinto foo values(5,6) returning * into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should fail due to implicit strict insertinto foo values(7,8),(9,10) returning * into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should work
execute 'insert into foo values(5,6) returning *'into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- this should work since EXECUTE isn't as picky
execute 'insert into foo values(7,8),(9,10) returning *'into x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
select * from foo;
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should work select * from foo where f1 = 3into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should fail, no rows select * from foo where f1 = 0into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should fail, too many rows select * from foo where f1 > 3into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should work
execute 'select * from foo where f1 = 3'into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should fail, no rows
execute 'select * from foo where f1 = 0'into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- should fail, too many rows
execute 'select * from foo where f1 > 3'into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
drop function stricttest();
-- test printing parameters after failure due to STRICT
set plpgsql.print_strict_params totrue;
createorreplace function stricttest() returns void as $$ declare
x record;
p1 int := 2;
p3 text := 'foo';
begin -- no rows select * from foo where f1 = p1 and f1::text = p3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare
x record;
p1 int := 2;
p3 text := $a$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$a$;
begin -- no rows select * from foo where f1 = p1 and f1::text = p3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare
x record;
p1 int := 2;
p3 text := 'foo';
begin -- too many rows select * from foo where f1 > p1 or f1::text = p3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- too many rows, no params select * from foo where f1 > 3into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- no rows
execute 'select * from foo where f1 = $1 or f1::text = $2'using0, 'foo'into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- too many rows
execute 'select * from foo where f1 > $1'using1into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ declare x record;
begin -- too many rows, no parameters
execute 'select * from foo where f1 > 3'into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
createorreplace function stricttest() returns void as $$ -- override the global
#print_strict_params off declare
x record;
p1 int := 2;
p3 text := 'foo';
begin -- too many rows select * from foo where f1 > p1 or f1::text = p3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
reset plpgsql.print_strict_params;
createorreplace function stricttest() returns void as $$ -- override the global
#print_strict_params on declare
x record;
p1 int := 2;
p3 text := 'foo';
begin -- too many rows select * from foo where f1 > p1 or f1::text = p3 into strict x;
raise notice 'x.f1 = %, x.f2 = %', x.f1, x.f2;
end$$ language plpgsql;
select stricttest();
-- test warnings and errors set plpgsql.extra_warnings to'all'; set plpgsql.extra_warnings to'none'; set plpgsql.extra_errors to'all'; set plpgsql.extra_errors to'none';
-- test warnings when shadowing a variable
set plpgsql.extra_warnings to'shadowed_variables';
-- simple shadowing of input and output parameters createorreplace function shadowtest(in1 int)
returns table (out1 int) as $$ declare
in1 int;
out1 int;
begin
end
$$ language plpgsql; select shadowtest(1);
set plpgsql.extra_warnings to'shadowed_variables'; select shadowtest(1); createorreplace function shadowtest(in1 int)
returns table (out1 int) as $$ declare
in1 int;
out1 int;
begin
end
$$ language plpgsql; select shadowtest(1); drop function shadowtest(int);
-- shadowing in a second DECLARE block createorreplace function shadowtest()
returns void as $$ declare
f1 int;
begin declare
f1 int;
begin
end;
end$$ language plpgsql; drop function shadowtest();
-- several levels of shadowing createorreplace function shadowtest(in1 int)
returns void as $$ declare
in1 int;
begin declare
in1 int;
begin
end;
end$$ language plpgsql; drop function shadowtest(int);
-- shadowing in cursor definitions createorreplace function shadowtest()
returns void as $$ declare
f1 int;
c1 cursor (f1 int) forselect1;
begin
end$$ language plpgsql; drop function shadowtest();
-- test errors when shadowing a variable
set plpgsql.extra_errors to'shadowed_variables';
createorreplace function shadowtest(f1 int)
returns boolean as $$ declare f1 int; begin return1; end $$ language plpgsql;
set plpgsql.extra_warnings to'strict_multi_assignment';
do $$ declare
x int;
y int;
begin select1into x, y; select1,2into x, y; select1,2,3into x, y;
end
$$;
set plpgsql.extra_errors to'strict_multi_assignment';
do $$ declare
x int;
y int;
begin select1into x, y; select1,2into x, y; select1,2,3into x, y;
end
$$;
createtable test_01(a int, b int, c int);
altertable test_01 dropcolumn a;
-- the check is active only when source table is not empty insertinto test_01 values(10,20);
do $$ declare
x int;
y int;
begin select * from test_01 into x, y; -- should be ok
raise notice 'ok'; select * from test_01 into x; -- should to fail
end;
$$;
do $$ declare
t test_01;
begin select1, 2into t; -- should be ok
raise notice 'ok'; select1, 2, 3into t; -- should fail;
end;
$$;
do $$ declare
t test_01;
begin select1into t; -- should fail;
end;
$$;
create function sc_test() returns setof integeras $$ declare
c scroll cursorforselect f1 from int4_tbl;
x integer;
begin
open c; fetch last from c into x; while found loop return next x; fetch prior from c into x;
end loop;
close c;
end;
$$ language plpgsql;
select * from sc_test();
createorreplace function sc_test() returns setof integeras $$ declare
c no scroll cursorforselect f1 from int4_tbl;
x integer;
begin
open c; fetch last from c into x; while found loop return next x; fetch prior from c into x;
end loop;
close c;
end;
$$ language plpgsql;
select * from sc_test(); -- fails because of NO SCROLL specification
createorreplace function sc_test() returns setof integeras $$ declare
c refcursor;
x integer;
begin
open c scroll forselect f1 from int4_tbl; fetch last from c into x; while found loop return next x; fetch prior from c into x;
end loop;
close c;
end;
$$ language plpgsql;
select * from sc_test();
createorreplace function sc_test() returns setof integeras $$ declare
c refcursor;
x integer;
begin
open c scroll for execute 'select f1 from int4_tbl'; fetch last from c into x; while found loop return next x; fetch relative -2from c into x;
end loop;
close c;
end;
$$ language plpgsql;
select * from sc_test();
createorreplace function sc_test() returns setof integeras $$ declare
c refcursor;
x integer;
begin
open c scroll for execute 'select f1 from int4_tbl'; fetch last from c into x; while found loop return next x;
move backward 2from c; fetch relative -1from c into x;
end loop;
close c;
end;
$$ language plpgsql;
select * from sc_test();
createorreplace function sc_test() returns setof integeras $$ declare
c cursorforselect * from generate_series(1, 10);
x integer;
begin
open c; loop
move relative 2in c; ifnot found then exit;
end if; fetch next from c into x; if found then return next x;
end if;
end loop;
close c;
end;
$$ language plpgsql;
select * from sc_test();
createorreplace function sc_test() returns setof integeras $$ declare
c cursorforselect * from generate_series(1, 10);
x integer;
begin
open c;
move forward allin c; fetch backward from c into x; if found then return next x;
end if;
close c;
end;
$$ language plpgsql;
select * from sc_test();
drop function sc_test();
-- test qualified variable names
create function pl_qual_names (param1 int) returns void as $$
<<outerblock>> declare
param1 int := 1;
begin
<<innerblock>> declare
param1 int := 2;
begin
raise notice 'param1 = %', param1;
raise notice 'pl_qual_names.param1 = %', pl_qual_names.param1;
raise notice 'outerblock.param1 = %', outerblock.param1;
raise notice 'innerblock.param1 = %', innerblock.param1;
end;
end;
$$ language plpgsql;
select pl_qual_names(42);
drop function pl_qual_names(int);
-- tests for RETURN QUERY create function ret_query1(outint, outint) returns setof record as $$
begin
$1 := -1;
$2 := -2; return next; return query select x + 1, x * 10from generate_series(0, 10) s (x); return next;
end;
$$ language plpgsql;
select * from ret_query1();
create type record_type as (x text, y int, z boolean);
createorreplace function ret_query2(lim int) returns setof record_type as $$
begin return query select fipshash(s.x::text), s.x, s.x > 0 from generate_series(-8, lim) s (x) where s.x % 2 = 0;
end;
$$ language plpgsql;
select * from ret_query2(8);
-- test EXECUTE USING create function exc_using(int, text) returns intas $$ declare i int;
begin for i in execute 'select * from generate_series(1,$1)'using $1+1loop
raise notice '%', i;
end loop;
execute 'select $2 + $2*3 + length($1)'into i using $2,$1; return i;
end
$$ language plpgsql;
select exc_using(5, 'foobar');
drop function exc_using(int, text);
createorreplace function exc_using(int) returns void as $$ declare
c refcursor;
i int;
begin
open c for execute 'select * from generate_series(1,$1)'using $1+1; loop fetch c into i; exitwhennot found;
raise notice '%', i;
end loop;
close c; return;
end;
$$ language plpgsql;
select exc_using(5);
drop function exc_using(int);
-- test FOR-over-cursor
createorreplace function forc01() returns void as $$ declare
c cursor(r1 integer, r2 integer) forselect * from generate_series(r1,r2) i;
c2 cursor forselect * from generate_series(41,43) i;
begin -- assign portal names to cursors to get stable output
c := 'c';
c2 := 'c2'; for r in c(5,7) loop
raise notice '% from %', r.i, c;
end loop; -- again, to test if cursor was closed properly -- (and while we're at it, test named-parameter notation) for r in c(r2 := 10, r1 => 9) loop
raise notice '% from %', r.i, c;
end loop; -- and test a parameterless cursor for r in c2 loop
raise notice '% from %', r.i, c2;
end loop; -- and try it with a hand-assigned name
raise notice 'after loop, c2 = %', c2;
c2 := 'special_name'; for r in c2 loop
raise notice '% from %', r.i, c2;
end loop;
raise notice 'after loop, c2 = %', c2; -- and try it with a generated name -- (which we can't show in the output because it's variable)
c2 := null; for r in c2 loop
raise notice '%', r.i;
end loop;
raise notice 'after loop, c2 = %', c2; return;
end;
$$ language plpgsql;
select forc01();
-- try updating the cursor's current row
create temp table forc_test as select n as i, n as j from generate_series(1,10) n;
createorreplace function forc01() returns void as $$ declare
c cursorforselect * from forc_test;
begin for r in c loop
raise notice '%, %', r.i, r.j; update forc_test set i = i * 100, j = r.j * 2where current of c;
end loop;
end;
$$ language plpgsql;
select forc01();
select * from forc_test;
-- same, with a cursor whose portal name doesn't match variable name createorreplace function forc01() returns void as $$ declare
c refcursor := 'fooled_ya';
r record;
begin
open c forselect * from forc_test; loop fetch c into r; exitwhennot found;
raise notice '%, %', r.i, r.j; update forc_test set i = i * 100, j = r.j * 2where current of c;
end loop;
end;
$$ language plpgsql;
select forc01();
select * from forc_test;
drop function forc01();
-- it's okay to re-use a cursor variable name, even when bound
do $$ declare cnt int := 0;
c1 cursorforselect * from forc_test;
begin for r1 in c1 loop declare c1 cursorforselect * from forc_test;
begin for r2 in c1 loop
cnt := cnt + 1;
end loop;
end;
end loop;
raise notice 'cnt = %', cnt;
end $$;
-- fail because cursor has no query bound to it
createorreplace function forc_bad() returns void as $$ declare
c refcursor;
begin for r in c loop
raise notice '%', r.i;
end loop;
end;
$$ language plpgsql;
-- test RETURN QUERY EXECUTE
createorreplace function return_dquery()
returns setof intas $$
begin return query execute 'select * from (values(10),(20)) f'; return query execute 'select * from (values($1),($2)) f'using40,50;
end;
$$ language plpgsql;
select * from return_dquery();
drop function return_dquery();
-- test RETURN QUERY with dropped columns
createtable tabwithcols(a int, b int, c int, d int); insertinto tabwithcols values(10,20,30,40),(50,60,70,80);
createorreplace function returnqueryf()
returns setof tabwithcols as $$
begin return query select * from tabwithcols; return query execute 'select * from tabwithcols';
end;
$$ language plpgsql;
select * from returnqueryf();
altertable tabwithcols dropcolumn b;
select * from returnqueryf();
altertable tabwithcols dropcolumn d;
select * from returnqueryf();
altertable tabwithcols addcolumn d int;
select * from returnqueryf();
drop function returnqueryf(); droptable tabwithcols;
-- -- Tests for composite-type results --
create type compostype as (x int, y varchar);
-- test: use of variable of composite type in return statement createorreplace function compos() returns compostype as $$ declare
v compostype;
begin
v := (1, 'hello'); return v;
end;
$$ language plpgsql;
select compos();
-- test: use of variable of record type in return statement createorreplace function compos() returns compostype as $$ declare
v record;
begin
v := (1, 'hello'::varchar); return v;
end;
$$ language plpgsql;
select compos();
-- test: use of row expr in return statement createorreplace function compos() returns compostype as $$
begin return (1, 'hello'::varchar);
end;
$$ language plpgsql;
select compos();
-- this does not work currently (no implicit casting) createorreplace function compos() returns compostype as $$
begin return (1, 'hello');
end;
$$ language plpgsql;
select compos();
-- ... but this does createorreplace function compos() returns compostype as $$
begin return (1, 'hello')::compostype;
end;
$$ language plpgsql;
select compos();
drop function compos();
-- test: return a row expr as record. createorreplace function composrec() returns record as $$ declare
v record;
begin
v := (1, 'hello'); return v;
end;
$$ language plpgsql;
select composrec();
-- test: return row expr in return statement. createorreplace function composrec() returns record as $$
begin return (1, 'hello');
end;
$$ language plpgsql;
select composrec();
drop function composrec();
-- test: row expr in RETURN NEXT statement. createorreplace function compos() returns setof compostype as $$
begin for i in1..3 loop return next (1, 'hello'::varchar);
end loop; return next null::compostype; return next (2, 'goodbye')::compostype;
end;
$$ language plpgsql;
select * from compos();
drop function compos();
-- test: use invalid expr in return statement. createorreplace function compos() returns compostype as $$
begin return1 + 1;
end;
$$ language plpgsql;
select compos();
-- RETURN variable is a different code path ... createorreplace function compos() returns compostype as $$ declare x int := 42;
begin return x;
end;
$$ language plpgsql;
select * from compos();
drop function compos();
-- test: invalid use of composite variable in scalar-returning function createorreplace function compos() returns intas $$ declare
v compostype;
begin
v := (1, 'hello'); return v;
end;
$$ language plpgsql;
select compos();
-- test: invalid use of composite expression in scalar-returning function createorreplace function compos() returns intas $$
begin return (1, 'hello')::compostype;
end;
$$ language plpgsql;
select compos();
drop function compos(); drop type compostype;
-- -- Tests for 8.4's new RAISE features --
createorreplace function raise_test() returns void as $$
begin
raise notice '% % %', 1, 2, 3 using errcode = '55001', detail = 'some detail info', hint = 'some hint';
raise '% % %', 1, 2, 3 using errcode = 'division_by_zero', detail = 'some detail info';
end;
$$ language plpgsql;
select raise_test();
-- Since we can't actually see the thrown SQLSTATE in default psql output, -- test it like this; this also tests re-RAISE
createorreplace function raise_test() returns void as $$
begin
raise 'check me' using errcode = 'division_by_zero', detail = 'some detail info';
exception when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
createorreplace function raise_test() returns void as $$
begin
raise 'check me' using errcode = '1234F', detail = 'some detail info';
exception when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
-- SQLSTATE specification in WHEN createorreplace function raise_test() returns void as $$
begin
raise 'check me' using errcode = '1234F', detail = 'some detail info';
exception whensqlstate'1234F'then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
createorreplace function raise_test() returns void as $$
begin
raise division_by_zero using detail = 'some detail info';
exception when others then
raise notice 'SQLSTATE: % SQLERRM: %', sqlstate, sqlerrm;
raise;
end;
$$ language plpgsql;
select raise_test();
createorreplace function raise_test() returns void as $$
begin
raise division_by_zero;
end;
$$ language plpgsql;
select raise_test();
createorreplace function raise_test() returns void as $$
begin
raise sqlstate'1234F';
end;
$$ language plpgsql;
select raise_test();
createorreplace function raise_test() returns void as $$
begin
raise division_by_zero using message = 'custom' || ' message';
end;
$$ language plpgsql;
select raise_test();
createorreplace function raise_test() returns void as $$
begin
raise using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
-- conflict on message createorreplace function raise_test() returns void as $$
begin
raise notice 'some message'using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
-- conflict on errcode createorreplace function raise_test() returns void as $$
begin
raise division_by_zero using message = 'custom' || ' message', errcode = '22012';
end;
$$ language plpgsql;
select raise_test();
-- nothing to re-RAISE createorreplace function raise_test() returns void as $$
begin
raise;
end;
$$ language plpgsql;
select raise_test();
-- test access to exception data create function zero_divide() returns intas $$ declare v int := 0;
begin return10 / v;
end;
$$ language plpgsql parallel safe;
createorreplace function raise_test() returns void as $$
begin
raise exception 'custom exception' using detail = 'some detail of custom exception',
hint = 'some hint related to custom exception';
end;
$$ language plpgsql;
create function stacked_diagnostics_test() returns void as $$ declare _sqlstate text;
_message text;
_context text;
begin
perform zero_divide();
exception when others then
get stacked diagnostics
_sqlstate = returned_sqlstate,
_message = message_text,
_context = pg_exception_context;
raise notice 'sqlstate: %, message: %, context: [%]',
_sqlstate, _message, replace(_context, E'\n', ' <- ');
end;
$$ language plpgsql;
select stacked_diagnostics_test();
createorreplace function stacked_diagnostics_test() returns void as $$ declare _detail text;
_hint text;
_message text;
begin
perform raise_test();
exception when others then
get stacked diagnostics
_message = message_text,
_detail = pg_exception_detail,
_hint = pg_exception_hint;
raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint;
end;
$$ language plpgsql;
select stacked_diagnostics_test();
-- fail, cannot use stacked diagnostics statement outside handler createorreplace function stacked_diagnostics_test() returns void as $$ declare _detail text;
_hint text;
_message text;
begin
get stacked diagnostics
_message = message_text,
_detail = pg_exception_detail,
_hint = pg_exception_hint;
raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint;
end;
$$ language plpgsql;
select stacked_diagnostics_test();
drop function stacked_diagnostics_test();
-- Test that an error recovery subtransaction is parallel safe
create function error_trap_test() returns text as $$
begin
perform zero_divide(); return'no error detected!';
exception when division_by_zero then return'division_by_zero detected';
end;
$$ language plpgsql parallel safe;
drop function error_trap_test(); drop function zero_divide();
-- check cases where implicit SQLSTATE variable could be confused with -- SQLSTATE as a keyword, cf bug #5524 createorreplace function raise_test() returns void as $$
begin
perform 1/0;
exception whensqlstate'22012'then
raise notice using message = sqlstate;
raise sqlstate'22012'using message = 'substitute message';
end;
$$ language plpgsql;
select raise_test();
drop function raise_test();
-- test passing column_name, constraint_name, datatype_name, table_name -- and schema_name error fields
createorreplace function stacked_diagnostics_test() returns void as $$ declare _column_name text;
_constraint_name text;
_datatype_name text;
_table_name text;
_schema_name text;
begin
raise exception using column = '>>some column name<<', constraint = '>>some constraint name<<',
datatype = '>>some datatype name<<', table = '>>some table name<<', schema = '>>some schema name<<';
exception when others then
get stacked diagnostics
_column_name = column_name,
_constraint_name = constraint_name,
_datatype_name = pg_datatype_name,
_table_name = table_name,
_schema_name = schema_name;
raise notice 'column %, constraint %, type %, table %, schema %',
_column_name, _constraint_name, _datatype_name, _table_name, _schema_name;
end;
$$ language plpgsql;
select stacked_diagnostics_test();
drop function stacked_diagnostics_test();
-- test variadic functions
createorreplace function vari(variadic int[])
returns void as $$
begin for i in array_lower($1,1)..array_upper($1,1) loop
raise notice '%', $1[i];
end loop; end;
$$ language plpgsql;
-- coercion test createorreplace function pleast(variadic numeric[])
returns numericas $$ declare aux numeric = $1[array_lower($1,1)];
begin for i in array_lower($1,1)+1..array_upper($1,1) loop if $1[i] < aux then aux := $1[i]; end if;
end loop; return aux;
end;
$$ language plpgsql immutable strict;
-- in case of conflict, non-variadic version is preferred createorreplace function pleast(numeric)
returns numericas $$
begin
raise notice 'non-variadic function called'; return $1;
end;
$$ language plpgsql immutable strict;
select pleast(10);
drop function pleast(numeric[]); drop function pleast(numeric);
-- test table functions
create function tftest(int) returns table(a int, b int) as $$
begin return query select $1, $1+i from generate_series(1,5) g(i);
end;
$$ language plpgsql immutable strict;
select * from tftest(10);
createorreplace function tftest(a1 int) returns table(a int, b int) as $$
begin
a := a1; b := a1 + 1; return next;
a := a1 * 10; b := a1 * 10 + 1; return next;
end;
$$ language plpgsql immutable strict;
select * from tftest(10);
drop function tftest(int);
create function rttest()
returns setof intas $$ declare rc int;
begin return query values(10),(20);
get diagnostics rc = row_count;
raise notice '% %', found, rc; return query select * from (values(10),(20)) f(a) wherefalse;
get diagnostics rc = row_count;
raise notice '% %', found, rc; return query execute 'values(10),(20)';
get diagnostics rc = row_count;
raise notice '% %', found, rc; return query execute 'select * from (values(10),(20)) f(a) where false';
get diagnostics rc = row_count;
raise notice '% %', found, rc;
end;
$$ language plpgsql;
select * from rttest();
-- check some error cases, too
createorreplace function rttest()
returns setof intas $$
begin return query select10into no_such_table;
end;
$$ language plpgsql;
select * from rttest();
createorreplace function rttest()
returns setof intas $$
begin return query execute 'select 10 into no_such_table';
end;
$$ language plpgsql;
select * from rttest();
select * from no_such_table;
drop function rttest();
-- Test for proper cleanup at subtransaction exit. This example -- exposed a bug in PG 8.2.
CREATE FUNCTION leaker_1(fail BOOL) RETURNS INTEGERAS $$ DECLARE
v_var INTEGER;
BEGIN
BEGIN
v_var := (leaker_2(fail)).error_code;
EXCEPTION WHEN others THENRETURN0;
END; RETURN1;
END;
$$ LANGUAGE plpgsql;
CREATE FUNCTION leaker_2(fail BOOL, OUT error_code INTEGER, OUT new_id INTEGER)
RETURNS RECORD AS $$
BEGIN IF fail THEN
RAISE EXCEPTION 'fail ...';
END IF;
error_code := 1;
new_id := 1; RETURN;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM leaker_1(false); SELECT * FROM leaker_1(true);
DROP FUNCTION leaker_1(bool); DROP FUNCTION leaker_2(bool);
-- Test for appropriate cleanup of non-simple expression evaluations -- (bug in all versions prior to August 2010)
CREATE FUNCTION nonsimple_expr_test() RETURNS text[] AS $$ DECLARE
arr text[];
lr text;
i integer;
BEGIN
arr := array[array['foo','bar'], array['baz', 'quux']];
lr := 'fool';
i := 1; -- use sub-SELECTs to make expressions non-simple
arr[(SELECT i)][(SELECT i+1)] := (SELECT lr); RETURN arr;
END;
$$ LANGUAGE plpgsql;
SELECT nonsimple_expr_test();
DROP FUNCTION nonsimple_expr_test();
CREATE FUNCTION nonsimple_expr_test() RETURNS integerAS $$ declare
i integerNOTNULL := 0;
begin
begin
i := (SELECTNULL::integer); -- should throw error
exception WHEN OTHERS THEN
i := (SELECT1::integer);
end; return i;
end;
$$ LANGUAGE plpgsql;
SELECT nonsimple_expr_test();
DROP FUNCTION nonsimple_expr_test();
-- -- Test cases involving recursion and error recovery in simple expressions -- (bugs in all versions before October 2010). The problems are most -- easily exposed by mutual recursion between plpgsql and sql functions. --
create function recurse(float8) returns float8as
$$
begin if ($1 > 0) then return sql_recurse($1 - 1); else return $1;
end if;
end;
$$ language plpgsql;
-- "limit" is to prevent this from being inlined create function sql_recurse(float8) returns float8as
$$ select recurse($1) limit1; $$ language sql;
select recurse(10);
create function error1(text) returns text language sqlas
$$ SELECT relname::text FROM pg_class c WHERE c.oid = $1::regclass $$;
create function error2(p_name_table text) returns text language plpgsql as $$
begin return error1(p_name_table);
end$$;
drop function cast_invoker(integer); drop function sql_to_date(integer) cascade;
-- Test handling of cast cache inside DO blocks -- (to check the original crash case, this must be a cast not previously -- used in this session)
begin;
do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$;
do $$ declare x text[]; begin x := '{1.23, 4.56}'::numeric[]; end $$;
end;
-- Test for consistent reporting of error context
create function fail() returns int language plpgsql as $$
begin return1/0;
end
$$;
select fail(); select fail();
drop function fail();
-- Test handling of string literals.
set standard_conforming_strings = off;
createorreplace function strtest() returns text as $$
begin
raise notice 'foo\\bar\041baz'; return'foo\\bar\041baz';
end
$$ language plpgsql;
select strtest();
createorreplace function strtest() returns text as $$
begin
raise notice E'foo\\bar\041baz'; return E'foo\\bar\041baz';
end
$$ language plpgsql;
select strtest();
set standard_conforming_strings = on;
createorreplace function strtest() returns text as $$
begin
raise notice 'foo\\bar\041baz\'; return'foo\\bar\041baz\';
end
$$ language plpgsql;
select strtest();
createorreplace function strtest() returns text as $$
begin
raise notice E'foo\\bar\041baz'; return E'foo\\bar\041baz';
end
$$ language plpgsql;
select strtest();
drop function strtest();
-- Test anonymous code blocks.
DO $$ DECLARE r record;
BEGIN FOR r INSELECT rtrim(roomno) AS roomno, comment FROM Room ORDERBY roomno LOOP
RAISE NOTICE '%, %', r.roomno, r.comment;
END LOOP;
END$$;
-- these are to check syntax error reporting
DO LANGUAGE plpgsql $$begin return1; end$$;
DO $$ DECLARE r record;
BEGIN FOR r INSELECT rtrim(roomno) AS roomno, foo FROM Room ORDERBY roomno LOOP
RAISE NOTICE '%, %', r.roomno, r.comment;
END LOOP;
END$$;
-- Check handling of errors thrown from/into anonymous code blocks.
do $outer$
begin for i in1..10loop
begin
execute $ex$
do $$ declare x int = 0;
begin
x := 1 / x;
end;
$$;
$ex$;
exception when division_by_zero then
raise notice 'caught division by zero';
end;
end loop;
end;
$outer$;
-- Check variable scoping -- a var is not available in its own or prior -- default expressions, but it is available in later ones.
do $$ declare x int := x + 1; -- error
begin
raise notice 'x = %', x;
end;
$$;
do $$ declare y int := x + 1; -- error
x int := 42;
begin
raise notice 'x = %, y = %', x, y;
end;
$$;
do $$ declare x int := 42;
y int := x + 1;
begin
raise notice 'x = %, y = %', x, y;
end;
$$;
do $$ declare x int := 42;
begin declare y int := x + 1;
x int := x + 2;
z int := x * 10;
begin
raise notice 'x = %, y = %, z = %', x, y, z;
end;
end;
$$;
-- Check handling of conflicts between plpgsql vars and table columns.
set plpgsql.variable_conflict = error;
create function conflict_test() returns setof int8_tbl as $$ declare r record;
q1 bigint := 42;
begin for r inselect q1,q2 from int8_tbl loop return next r;
end loop;
end;
$$ language plpgsql;
select * from conflict_test();
createorreplace function conflict_test() returns setof int8_tbl as $$
#variable_conflict use_variable declare r record;
q1 bigint := 42;
begin for r inselect q1,q2 from int8_tbl loop return next r;
end loop;
end;
$$ language plpgsql;
select * from conflict_test();
createorreplace function conflict_test() returns setof int8_tbl as $$
#variable_conflict use_column declare r record;
q1 bigint := 42;
begin for r inselect q1,q2 from int8_tbl loop return next r;
end loop;
end;
$$ language plpgsql;
select * from conflict_test();
drop function conflict_test();
-- Check that an unreserved keyword can be used as a variable name
create function unreserved_test() returns intas $$ declare
forward int := 21;
begin
forward := forward * 2; return forward;
end
$$ language plpgsql;
select unreserved_test();
createorreplace function unreserved_test() returns intas $$ declare returnint := 42;
begin return := return + 1; returnreturn;
end
$$ language plpgsql;
select unreserved_test();
createorreplace function unreserved_test() returns intas $$ declare
comment int := 21;
begin
comment := comment * 2;
comment on function unreserved_test() is'this is a test'; return comment;
end
$$ language plpgsql;
create function foreach_test(anyarray)
returns void as $$ declare x int;
begin
foreach x in array $1 loop
raise notice '%', x;
end loop;
end;
$$ language plpgsql;
createorreplace function foreach_test(anyarray)
returns void as $$ declare x int;
begin
foreach x slice 1in array $1 loop
raise notice '%', x;
end loop;
end;
$$ language plpgsql;
-- should fail select foreach_test(ARRAY[1,2,3,4]); select foreach_test(ARRAY[[1,2],[3,4]]);
createorreplace function foreach_test(anyarray)
returns void as $$ declare x int[];
begin
foreach x slice 1in array $1 loop
raise notice '%', x;
end loop;
end;
$$ language plpgsql;
-- higher level of slicing createorreplace function foreach_test(anyarray)
returns void as $$ declare x int[];
begin
foreach x slice 2in array $1 loop
raise notice '%', x;
end loop;
end;
$$ language plpgsql;
-- should fail select foreach_test(ARRAY[1,2,3,4]); -- ok select foreach_test(ARRAY[[1,2],[3,4]]); select foreach_test(ARRAY[[[1,2]],[[3,4]]]);
create type xy_tuple AS (x int, y int);
-- iteration over array of records createorreplace function foreach_test(anyarray)
returns void as $$ declare r record;
begin
foreach r in array $1 loop
raise notice '%', r;
end loop;
end;
$$ language plpgsql;
createorreplace function foreach_test(anyarray)
returns void as $$ declare x int; y int;
begin
foreach x, y in array $1 loop
raise notice 'x = %, y = %', x, y;
end loop;
end;
$$ language plpgsql;
-- slicing over array of composite types createorreplace function foreach_test(anyarray)
returns void as $$ declare x xy_tuple[];
begin
foreach x slice 1in array $1 loop
raise notice '%', x;
end loop;
end;
$$ language plpgsql;
drop function foreach_test(anyarray); drop type xy_tuple;
-- -- Assorted tests for array subscript assignment --
create temp table rtype (id int, ar text[]);
create function arrayassign1() returns text[] language plpgsql as $$ declare
r record;
begin
r := row(12, '{foo,bar,baz}')::rtype;
r.ar[2] := 'replace'; return r.ar;
end$$;
select arrayassign1(); select arrayassign1(); -- try again to exercise internal caching
create function testoa(x1 int, x2 int, x3 int) returns orderedarray
language plpgsql as $$ declare res orderedarray;
begin
res := array[x1, x2];
res[2] := x3; return res;
end$$;
select testoa(1,2,3); select testoa(1,2,3); -- try again to exercise internal caching select testoa(2,1,3); -- fail at initial assign select testoa(1,2,1); -- fail at update
drop function arrayassign1(); drop function testoa(x1 int, x2 int, x3 int);
-- -- Test handling of expanded arrays --
create function returns_rw_array(int) returns int[]
language plpgsql as $$ declare r int[];
begin r := array[$1, $1]; return r; end;
$$ stable;
create function consumes_rw_array(int[]) returns int
language plpgsql as $$
begin return $1[1]; end;
$$ stable;
select consumes_rw_array(returns_rw_array(42));
-- bug #14174 explain (verbose, costs off) select i, a from
(select returns_rw_array(1) as a offset 0) ss,
lateral consumes_rw_array(a) i;
select i, a from
(select returns_rw_array(1) as a offset 0) ss,
lateral consumes_rw_array(a) i;
explain (verbose, costs off) select consumes_rw_array(a), a from returns_rw_array(1) a;
select consumes_rw_array(a), a from returns_rw_array(1) a;
explain (verbose, costs off) select consumes_rw_array(a), a from
(values (returns_rw_array(1)), (returns_rw_array(2))) v(a);
select consumes_rw_array(a), a from
(values (returns_rw_array(1)), (returns_rw_array(2))) v(a);
do $$ declare a int[] := array[1,2];
begin
a := a || 3;
raise notice 'a = %', a;
end$$;
-- -- Test access to call stack --
create function inner_func(int)
returns intas $$ declare _context text;
begin
get diagnostics _context = pg_context;
raise notice '***%***', _context; -- lets do it again, just for fun..
get diagnostics _context = pg_context;
raise notice '***%***', _context;
raise notice 'lets make sure we didnt break anything'; return2 * $1;
end;
$$ language plpgsql;
createorreplace function outer_func(int)
returns intas $$ declare
myresult int;
begin
raise notice 'calling down into inner_func()';
myresult := inner_func($1);
raise notice 'inner_func() done'; return myresult;
end;
$$ language plpgsql;
createorreplace function outer_outer_func(int)
returns intas $$ declare
myresult int;
begin
raise notice 'calling down into outer_func()';
myresult := outer_func($1);
raise notice 'outer_func() done'; return myresult;
end;
$$ language plpgsql;
select outer_outer_func(10); -- repeated call should work select outer_outer_func(20);
drop function outer_outer_func(int); drop function outer_func(int); drop function inner_func(int);
-- access to call stack from exception create function inner_func(int)
returns intas $$ declare
_context text;
sx int := 5;
begin
begin
perform sx / 0;
exception when division_by_zero then
get diagnostics _context = pg_context;
raise notice '***%***', _context;
end;
-- lets do it again, just for fun..
get diagnostics _context = pg_context;
raise notice '***%***', _context;
raise notice 'lets make sure we didnt break anything'; return2 * $1;
end;
$$ language plpgsql;
createorreplace function outer_func(int)
returns intas $$ declare
myresult int;
begin
raise notice 'calling down into inner_func()';
myresult := inner_func($1);
raise notice 'inner_func() done'; return myresult;
end;
$$ language plpgsql;
createorreplace function outer_outer_func(int)
returns intas $$ declare
myresult int;
begin
raise notice 'calling down into outer_func()';
myresult := outer_func($1);
raise notice 'outer_func() done'; return myresult;
end;
$$ language plpgsql;
select outer_outer_func(10); -- repeated call should work select outer_outer_func(20);
drop function outer_outer_func(int); drop function outer_func(int); drop function inner_func(int);
-- Test pg_routine_oid create function current_function(text)
returns regprocedure as $$ declare
fn_oid regprocedure;
begin
get diagnostics fn_oid = pg_routine_oid; return fn_oid;
end;
$$ language plpgsql;
select current_function('foo');
drop function current_function(text);
-- shouldn't fail in DO, even though there's no useful data
do $$ declare
fn_oid oid;
begin
get diagnostics fn_oid = pg_routine_oid;
raise notice 'pg_routine_oid = %', fn_oid;
end;
$$;
-- -- Test ASSERT --
do $$
begin
assert 1=1; -- should succeed
end;
$$;
do $$
begin
assert 1=0; -- should fail
end;
$$;
do $$
begin
assert NULL; -- should fail
end;
$$;
-- check controlling GUC set plpgsql.check_asserts = off;
do $$
begin
assert 1=0; -- won't be tested
end;
$$;
reset plpgsql.check_asserts;
-- test custom message
do $$ declare var text := 'some value';
begin
assert 1=0, format('assertion failed, var = "%s"', var);
end;
$$;
-- ensure assertions are not trapped by 'others'
do $$
begin
assert 1=0, 'unhandled assertion';
exception when others then null; -- do nothing
end;
$$;
-- Test use of plpgsql in a domain check constraint (cf. bug #14414)
create function plpgsql_domain_check(val int) returns boolean as $$
begin return val > 0; end
$$ language plpgsql immutable;
do $$ declare v_test plpgsql_arr_domain;
begin
v_test := array[1];
v_test := v_test || 2;
end;
$$;
do $$ declare v_test plpgsql_arr_domain := array[1];
begin
v_test := 0 || v_test; -- fail
end;
$$;
-- -- test usage of transition tables in AFTER triggers --
CREATETABLE transition_table_base (id intPRIMARYKEY, val text);
CREATE FUNCTION transition_table_base_ins_func()
RETURNS trigger
LANGUAGE plpgsql AS $$ DECLARE
t text;
l text;
BEGIN
t = ''; FOR l IN EXECUTE
$q$ EXPLAIN (TIMING off, COSTS off, VERBOSE on) SELECT * FROM newtable
$q$ LOOP
t = t || l || E'\n';
END LOOP;
RAISE INFO '%', t; RETURN new;
END;
$$;
CREATETRIGGER transition_table_base_ins_trig
AFTER INSERTON transition_table_base
REFERENCING OLD TABLEAS oldtable NEW TABLEAS newtable FOREACH STATEMENT
EXECUTE PROCEDURE transition_table_base_ins_func();
CREATETRIGGER transition_table_base_ins_trig
AFTER INSERTON transition_table_base
REFERENCING NEW TABLEAS newtable FOREACH STATEMENT
EXECUTE PROCEDURE transition_table_base_ins_func();
CREATEORREPLACE FUNCTION transition_table_base_upd_func()
RETURNS trigger
LANGUAGE plpgsql AS $$ DECLARE
t text;
l text;
BEGIN
t = ''; FOR l IN EXECUTE
$q$ EXPLAIN (TIMING off, COSTS off, VERBOSE on) SELECT * FROM oldtable ot FULL JOIN newtable nt USING (id)
$q$ LOOP
t = t || l || E'\n';
END LOOP;
RAISE INFO '%', t; RETURN new;
END;
$$;
CREATETRIGGER transition_table_base_upd_trig
AFTER UPDATEON transition_table_base
REFERENCING OLD TABLEAS oldtable NEW TABLEAS newtable FOREACH STATEMENT
EXECUTE PROCEDURE transition_table_base_upd_func();
UPDATE transition_table_base SET val = '*' || val || '*' WHERE id BETWEEN2AND3;
CREATETABLE transition_table_level1
(
level1_no serial NOTNULL ,
level1_node_name varchar(255), PRIMARYKEY (level1_no)
) WITHOUT OIDS;
CREATETABLE transition_table_level2
(
level2_no serial NOTNULL ,
parent_no intNOTNULL,
level1_node_name varchar(255), PRIMARYKEY (level2_no)
) WITHOUT OIDS;
CREATETABLE transition_table_status
(
level intNOTNULL,
node_no intNOTNULL,
status int, PRIMARYKEY (level, node_no)
) WITHOUT OIDS;
CREATE FUNCTION transition_table_level1_ri_parent_del_func()
RETURNS TRIGGER
LANGUAGE plpgsql AS $$ DECLARE n bigint;
BEGIN
PERFORM FROM p JOIN transition_table_level2 c ON c.parent_no = p.level1_no; IF FOUND THEN
RAISE EXCEPTION 'RI error';
END IF; RETURNNULL;
END;
$$;
CREATETRIGGER transition_table_level1_ri_parent_del_trigger
AFTER DELETEON transition_table_level1
REFERENCING OLD TABLEAS p FOREACH STATEMENT EXECUTE PROCEDURE
transition_table_level1_ri_parent_del_func();
CREATE FUNCTION transition_table_level1_ri_parent_upd_func()
RETURNS TRIGGER
LANGUAGE plpgsql AS $$ DECLARE
x int;
BEGIN WITH p AS (SELECT level1_no, sum(delta) cnt FROM (SELECT level1_no, 1AS delta FROM i UNIONALL SELECT level1_no, -1AS delta FROM d) w GROUPBY level1_no HAVING sum(delta) < 0) SELECT level1_no FROM p JOIN transition_table_level2 c ON c.parent_no = p.level1_no INTO x; IF FOUND THEN
RAISE EXCEPTION 'RI error';
END IF; RETURNNULL;
END;
$$;
CREATETRIGGER transition_table_level1_ri_parent_upd_trigger
AFTER UPDATEON transition_table_level1
REFERENCING OLD TABLEAS d NEW TABLEAS i FOREACH STATEMENT EXECUTE PROCEDURE
transition_table_level1_ri_parent_upd_func();
CREATE FUNCTION transition_table_level2_ri_child_insupd_func()
RETURNS TRIGGER
LANGUAGE plpgsql AS $$
BEGIN
PERFORM FROM i LEFTJOIN transition_table_level1 p ON p.level1_no ISNOTNULLAND p.level1_no = i.parent_no WHERE p.level1_no ISNULL; IF FOUND THEN
RAISE EXCEPTION 'RI error';
END IF; RETURNNULL;
END;
$$;
CREATETRIGGER transition_table_level2_ri_child_ins_trigger
AFTER INSERTON transition_table_level2
REFERENCING NEW TABLEAS i FOREACH STATEMENT EXECUTE PROCEDURE
transition_table_level2_ri_child_insupd_func();
CREATETRIGGER transition_table_level2_ri_child_upd_trigger
AFTER UPDATEON transition_table_level2
REFERENCING NEW TABLEAS i FOREACH STATEMENT EXECUTE PROCEDURE
transition_table_level2_ri_child_insupd_func();
-- create initial test data INSERTINTO transition_table_level1 (level1_no) SELECT generate_series(1,200); ANALYZE transition_table_level1;
-- behave reasonably if someone tries to modify a transition table CREATE FUNCTION transition_table_level2_bad_usage_func()
RETURNS TRIGGER
LANGUAGE plpgsql AS $$
BEGIN INSERTINTO dx VALUES (1000000, 1000000, 'x'); RETURNNULL;
END;
$$;
CREATETRIGGER transition_table_level2_bad_usage_trigger
AFTER DELETEON transition_table_level2
REFERENCING OLD TABLEAS dx FOREACH STATEMENT EXECUTE PROCEDURE
transition_table_level2_bad_usage_func();
DELETEFROM transition_table_level2 WHERE level2_no BETWEEN301AND305;
DROPTRIGGER transition_table_level2_bad_usage_trigger ON transition_table_level2;
-- attempt modifications which would break RI (should all fail) DELETEFROM transition_table_level1 WHERE level1_no = 25;
UPDATE transition_table_level1 SET level1_no = -1 WHERE level1_no = 30;
UPDATE transition_table_level2 SET parent_no = 2000 WHERE level2_no = 40;
-- attempt modifications which would not break RI (should all succeed) DELETEFROM transition_table_level1 WHERE level1_no BETWEEN201AND1000;
DELETEFROM transition_table_level1 WHERE level1_no BETWEEN100000000AND100000010;
SELECT count(*) FROM transition_table_level1;
DELETEFROM transition_table_level2 WHERE level2_no BETWEEN211AND220;
SELECT count(*) FROM transition_table_level2;
CREATETABLE alter_table_under_transition_tables
(
id intPRIMARYKEY,
name text
);
CREATE FUNCTION alter_table_under_transition_tables_upd_func()
RETURNS TRIGGER
LANGUAGE plpgsql AS $$
BEGIN
RAISE WARNING 'old table = %, new table = %',
(SELECT string_agg(id || '=' || name, ',') FROM d),
(SELECT string_agg(id || '=' || name, ',') FROM i);
RAISE NOTICE 'one = %', (SELECT1FROM alter_table_under_transition_tables LIMIT1); RETURNNULL;
END;
$$;
-- should fail, TRUNCATE is not compatible with transition tables CREATETRIGGER alter_table_under_transition_tables_upd_trigger
AFTER TRUNCATE ORUPDATEON alter_table_under_transition_tables
REFERENCING OLD TABLEAS d NEW TABLEAS i FOREACH STATEMENT EXECUTE PROCEDURE
alter_table_under_transition_tables_upd_func();
-- should work CREATETRIGGER alter_table_under_transition_tables_upd_trigger
AFTER UPDATEON alter_table_under_transition_tables
REFERENCING OLD TABLEAS d NEW TABLEAS i FOREACH STATEMENT EXECUTE PROCEDURE
alter_table_under_transition_tables_upd_func();
INSERTINTO alter_table_under_transition_tables VALUES (1, '1'), (2, '2'), (3, '3'); UPDATE alter_table_under_transition_tables SET name = name || name;
-- now change 'name' to an integer to see what happens... ALTERTABLE alter_table_under_transition_tables ALTERCOLUMN name TYPE intUSING name::integer; UPDATE alter_table_under_transition_tables SET name = (name::text || name::text)::integer;
-- now drop column 'name' ALTERTABLE alter_table_under_transition_tables DROPcolumn name; UPDATE alter_table_under_transition_tables SET id = id;
-- -- Test multiple reference to a transition table --
CREATETABLE multi_test (i int); INSERTINTO multi_test VALUES (1);
CREATEORREPLACE FUNCTION multi_test_trig() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
RAISE NOTICE 'count = %', (SELECT COUNT(*) FROM new_test);
RAISE NOTICE 'count union = %',
(SELECT COUNT(*) FROM (SELECT * FROM new_test UNIONALLSELECT * FROM new_test) ss); RETURNNULL;
END$$;
CREATETRIGGER my_trigger AFTER UPDATEON multi_test
REFERENCING NEW TABLEAS new_test OLD TABLEas old_test FOREACH STATEMENT EXECUTE PROCEDURE multi_test_trig();
UPDATE multi_test SET i = i;
DROPTABLE multi_test; DROP FUNCTION multi_test_trig();
-- -- Check type parsing and record fetching from partitioned tables --
CREATETABLE partitioned_table (a int, b text) PARTITION BY LIST (a); CREATETABLE pt_part1 PARTITION OF partitioned_table FORVALUESIN (1); CREATETABLE pt_part2 PARTITION OF partitioned_table FORVALUESIN (2);
CREATEORREPLACE FUNCTION get_from_partitioned_table(partitioned_table.a%type)
RETURNS partitioned_table AS $$ DECLARE
a_val partitioned_table.a%TYPE;
result partitioned_table%ROWTYPE;
BEGIN
a_val := $1; SELECT * INTO result FROM partitioned_table WHERE a = a_val; RETURN result;
END; $$ LANGUAGE plpgsql;
SELECT * FROM get_from_partitioned_table(1) AS t;
CREATEORREPLACE FUNCTION list_partitioned_table()
RETURNS SETOF public.partitioned_table.a%TYPE AS $$ DECLARE
row public.partitioned_table%ROWTYPE;
a_val public.partitioned_table.a%TYPE;
BEGIN FOR row INSELECT * FROM public.partitioned_table ORDERBY a LOOP
a_val := row.a; RETURN NEXT a_val;
END LOOP; RETURN;
END; $$ LANGUAGE plpgsql;
SELECT * FROM list_partitioned_table() AS t;
-- -- Check argument name is used instead of $n in error message -- CREATE FUNCTION fx(x WSlot) RETURNS void AS $$
BEGIN
GET DIAGNOSTICS x = ROW_COUNT; RETURN;
END; $$ LANGUAGE plpgsql;
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.87 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.