| René Nyffenegger's collection of things on the web | |
|
René Nyffenegger on Oracle - Most wanted - Feedback
|
Logging a procedure's progress using autonomous transactions in PL/SQL | ||
|
Each message will be assigned to a type that determines the nature of the message. For example (as shown below), there are error messages, progress messages, debug messages and simply messages.
create table msg_type (
tp varchar2(3),
dsc varchar2(40),
constraint pk_msg_type primary key (tp)
);
insert into msg_type (tp,dsc) values
('err', 'Error');
insert into msg_type (tp,dsc) values
('prg', 'Progress');
insert into msg_type (tp,dsc) values
('dbg', 'Debug');
insert into msg_type (tp,dsc) values
('msg', 'Message');
A message has also a source. This source is the originator of the message, in most cases the name of the programm.
create table msg_source (
src varchar2(3),
dsc varchar2(40),
constraint pk_msg_source primary key (src)
);
insert into msg_source (src,dsc) values
('pfc', 'Upload Peformance');
insert into msg_source (src,dsc) values
('fsr', 'Upload Fund Shr');
insert into msg_source (src,dsc) values
('sr', 'Upload Fund Shr SR');
Anticipating anomalies while loading the data, but also to have a means to have place to report Messages (for example Debug, but also Success and Progress) the tables msg_head and msg_tail are created.:
create table msg_head ( dt date not null, id number not null, tp not null, src not null, cd varchar2(30) not null, sql_cd number, constraint pk_msg_hd primary key (id), constraint fk_msg_hd_tp foreign key (tp) references msg_type, constraint fk_msg_hd_src foreign key (src) references msg_source ) storage (pctincrease 0 initial 512k next 512k); create sequence seq_msg_head start with 1 increment by 1; create or replace trigger ai_msg_hd before insert on msg_head for each row begin if :new.id is null then select seq_msg_head.nextval into :new.id from dual; end if; select sysdate into :new.dt from dual; end; /
The sequence seq_fund_error_head guarantees unique IDs for the table error_head.
Msg_Head stands in a 1:n relationship to Msg_Tail, that is, for each Msg In Msg_Head, zero, one, or more Msg_Tails can be appended, thus making it possible to further describe the message.
create table msg_tail (
id not null,
description varchar2(100),
constraint fk_er_tl foreign key (id)
references msg_head
on delete cascade
) storage (pctincrease 0 initial 512k next 512k);
create or replace package msg as
p_def_src msg_source.src%type;
p_hd_id msg_head.id%type;
procedure id(v_id in number);
procedure tp(
v_tp in msg_head.tp%type,
tail in boolean default true);
procedure clean;
procedure head(
c in msg_head.cd%type,
t in msg_head.tp%type,
s in msg_head.src%type default p_def_src);
-- sql_err is similar to the procedure head
-- in that it writes an entry into msg_head. However,
-- it differs in the following ways: it writes sql_code
-- and writes an entry with the error msg into err_tail
procedure sql_err(
c in msg_head.cd%type,
s in msg_head.src%type default p_def_src,
t in msg_head.tp%type default 'err');
procedure tail(
d in msg_tail.description%type);
-- This procedure shows and deletes the
-- next v_n entries of msg_head
procedure nxt(v_n in number default 1);
-- This procedure shows the last v_n entries
-- in msg_head
procedure lst(v_n in number default 1);
-- This procedure shows the first v_n entries
-- in msg_head
procedure fst(v_n in number default 1);
procedure def_src(v_def_src in msg_source.src%type);
end;
/
create or replace package body msg as
procedure tp(
v_tp in msg_head.tp%type,
tail in boolean default true) is
cursor c(v_tp in msg_head.tp%type) is
select id from msg_head where tp=v_tp
order by id;
begin
for r in c(v_tp) loop
id(r.id);
end loop;
end;
procedure id(v_id in number) is
v_cd msg_head.cd%type;
cursor c(v_id in number)
is select description from msg_tail where
id = v_id;
begin
select cd into v_cd from msg_head
where id=v_id;
dbms_output.put_line('');
dbms_output.put_line(' [' ||
to_char(v_id,'999999999') || '] ' || v_cd);
for r in c(v_id) loop
dbms_output.put_line (' ' || r.description);
end loop;
exception
when no_data_found then
dbms_output.put_line (' # no entry for ' || v_id);
end;
procedure clean is
begin
execute immediate
'alter table msg_tail modify constraint fk_er_tl disable';
execute immediate 'truncate table msg_tail';
execute immediate 'truncate table msg_head';
execute immediate
'alter table msg_tail modify constraint fk_er_tl enable';
end;
procedure nxt(v_n in number default 1) is
cursor c is
select id from msg_head order by id for update;
a_n number:=0;
begin
for r in c loop
exit when a_n >= v_n;
id(r.id);
delete msg_head where current of c;
a_n := a_n + 1;
end loop;
commit;
end;
procedure sql_err(
c in msg_head.cd%type,
s in msg_head.src%type default p_def_src,
t in msg_head.tp%type default 'err')
is
PRAGMA AUTONOMOUS_TRANSACTION;
v_sqlcode number;
v_sqlerrm varchar2(200);
begin
v_sqlcode := sqlcode;
v_sqlerrm := sqlerrm(v_sqlcode);
insert into msg_head(cd,tp,src,sql_cd)
values (c,t,s,v_sqlcode) returning id into p_hd_id;
-- commit;
tail(v_sqlerrm);
end;
procedure head(
c in msg_head.cd%type,
t in msg_head.tp%type,
s in msg_head.src%type default p_def_src)
is
PRAGMA AUTONOMOUS_TRANSACTION;
begin
insert into msg_head(cd,tp,src) values (c,t,s)
returning id into p_hd_id;
commit;
end;
procedure tail(d in msg_tail.description%type) is
PRAGMA AUTONOMOUS_TRANSACTION;
begin
insert into msg_tail(id,description) values (p_hd_id,d);
-- commit;
end;
procedure lst(v_n in number default 1) is
stmt varchar2(200);
type rct is ref cursor;
c rct;
v_id number;
begin
stmt := 'select id from (
select id, rank() over(order by id desc) rn
from msg_head
) where rn <= :n order by id';
open c for stmt using v_n;
loop
fetch c into v_id;
exit when c%notfound;
id(v_id);
end loop;
end;
procedure fst(v_n in number default 1) is
stmt varchar2(200);
type rct is ref cursor;
c rct;
v_id number;
begin
stmt := 'select id from (
select id, rank() over(order by id) rn
from msg_head where src = :1
) where rn <= :2 ';
open c for stmt using p_def_src, v_n;
loop
fetch c into v_id;
exit when c%notfound;
id(v_id);
end loop;
end;
procedure def_src(v_def_src in msg_source.src%type)
is
begin
p_def_src := v_def_src;
end;
end;
/
Writing MessagesSQL Errors
Whenever an SQL Error occurs, you can use sql_err to write an according
message to msg_head. Sql_err checks the PL/SQL variable sqlcode and uses it
to get an error string that then is written into msg_head.
Consider the following example:
create table d (a number, b number, c number);
insert into d values(25,5,null);
insert into d values(64,4,null);
insert into d values(81,3,null);
insert into d values(17,0,null);
insert into d values(28,2,null);
commit;
declare
cursor cr is select a, b from d;
begin
msg.def_src('tst');
for r in cr loop
msg.head('updating d','prg');
msg.tail('a: ' || r.a);
msg.tail('b: ' || r.b);
update d set c = r.a/r.b where a=r.a and b=r.b;
end loop;
exception
when others then
msg.sql_err('sql error');
msg.tail('tail');
rollback;
end;
/
select * from d;
drop table d;
Reading from MSG
Use fst to read the first n entries of msg_head (and msg_tail)
Use lst to read the last n entries of msg_head (and msg_tail)
Use nxt to read and then delte the next n entries of msg_head (and msg_tail)
|