On 9/22/2014 10:24 AM, Nathan Andelin wrote:
I tied a few error handling options on Saturday. I used QMHSNDPM to send
appropriate error messages, but DFU and STRSQL don't display them. DFU just
reports:
"Message CPF502B was issued."
STRSQL only reports that a Trigger failure occurred.
The "real" error message gets reported in the Job Log, but is not provided
to the user.
I tried using QMHRCVPM to cherry pick the message off the call stack
message queue, but that didn't really work either. The Trigger program
wouldn't know what call stack entry to send to. The tendency is to send to
the message queue of one of the database manager, but those message queues
are deleted before control is returned to the application. If you send to
an *ESCAPE to a queue too high in the call stack, that causes the
application to end, or even the 5250 session to end.
We don't often need to keep the nuts and bolts of the OS in our
forebrains, but this is one of those times when it's important.
Understanding the call stack is the key to understanding IBM i message
handling. I use a trigger mediator (I'd use Alan Campin's today...)
which interposes another call stack entry in my design. So my call
stack looks like this:
1 QCMD
2 Program doing the I/O (fires trigger)
3 Trigger mediator
4 Actual trigger program
5 Subprocedure that sends ESCAPE message
My 'tell DB2 to abort' is 3 levels below the program performing the I/O,
so my call stack entry on QMHSNDPM is 3:
* Send message API parameters
* stack count reflects the fact that we need to send the message
* up the stack; i.e. not this pgm, but its caller. Remember
that we're
* down another level because of the subprocedure...
d msgId s 7 inz('CPF9898')
d msgFil s 20 inz('QCPFMSG *LIBL ')
d msgData s 80
d msgDataLen s 10i 0 inz(%len(msgData))
d msgType s 10 inz('*ESCAPE')
d msgStackEnt s 10 inz('*')
d msgStackCnt s 10i 0 inz(3)
d msgKey s 4
d msgErrStruc s like(errStruc)
* API error structure
d errStruc ds inz
d errSSize 10i 0 inz(%len(errStruc))
d errSUse 10i 0
d errSMsgID 7
d errSResrv 1
d errSData 80
dQMHSNDPM pr extpgm('QMHSNDPM')
d msgId 7
d msgFil 20
d msgData 80
d msgDataLen 10i 0
d msgType 10
d msgStackEnt 10
d msgStackCnt 10i 0
d msgKey 4
d msgErrStruc like(errStruc)
// sending an escape message tells DB2 that the update did not occur
msgData = 'Trigger failed: ' + %trim(%char(errMsgId));
msgErrStruc = errStruc;
callp(e) QMHSNDPM (msgId:
msgFil:
msgData:
msgDataLen:
msgType:
msgStackEnt:
msgStackCnt:
msgKey:
msgErrStruc);
errStruc = msgErrStruc;
...
I used CPF9898 here just to make it easy for someone to reproduce on
their system. Better would be actual predefined messages and
substitution data which can be pulled out of the message when received.
An additional idea is to send one or more diagnostic messages which can
further describe the issue.
Now of course, you need to catch the message, and as you've found, there
is more than one. In my case, I have a message reply ('C'), a Notify
message (CPF502B - error in trigger) and finally, the message that my
trigger sent, a Diagnostic message CPF9898 (for the purposes of ease of
reproduction here). The interesting bit is that one needs to keep doing
the RCVMSG until one gets to the one sent by the trigger.
dcl-ds QUSEC qualified;
errBytesProv int(10) inz(%size(qusec));
errBytesAvail int(10) inz;
errMsgID char(7);
reserved char(1);
errMsgDta char(512);
end-ds;
dcl-ds rcvm0200 inz qualified;
returned int(10);
available int(10);
msgSev int(10);
msgId char(7);
msgType char(2);
msgKey char(4);
msgf char(10);
msgfLib char(10);
msgfLibUsed char(10);
sendJob char(10);
sendUser char(10);
sendNbr char(6);
sendPgm char(12);
sendInstr char(4);
sendDate char(7);
sendTime char(6);
rcvPgm char(10);
rcvInstr char(4);
sendType char(1);
rcvType char(1);
reserved char(1);
textConvCCSID int(10);
dataConvCCSID int(10);
alertOpt char(9);
msgCCSID int(10);
dataCCSID int(10);
dataLenReturn int(10);
dataLenAvail int(10);
msgLenReturn int(10);
msgLenAvail int(10);
helpLenReturn int(10);
helpLenAvail int(10);
msgData char(0512);
message char(0512);
msgHelp char(0512);
end-ds;
dcl-pr rcvPgmMsg extpgm('QMHRCVPM');
messageInfo char(1) options(*varsize);
messageInfoLen int(10) const;
fmtName char(8) const;
callStackEntry char(65535) const options(*varsize);
callStackCounter int(10) const;
msgType char(10) const;
msgKey char(4);
waitTime int(10) const;
msgAction char(10) const;
errorStruc likeds(qusec);
end-pr;
dcl-s messageInfoLen int(10) inz(%size(rcvm0200));
dcl-s fmtName char(8) inz('RCVM0200');
dcl-s callStackEntry char(10) inz('*');
dcl-s callStackCounter int(10) inz(0);
dcl-s msgType char(10) inz('*LAST');
dcl-s msgKey char(4) inz(*blanks);
dcl-s waitTime int(10) inz(0);
dcl-s msgAction char(10) inz('*OLD');
// this is a simple update which will fire my trigger.
// the trigger will deliberately fall over
chain...
if %found();
update(e) recordFormat;
// get the error message details
if %error();
// the 'C'
rcvPgmMsg(rcvm0200: messageInfoLen: fmtName:
callStackEntry: callStackCounter:
msgType: msgKey: waitTime: msgAction: qusec);
// CPF502B- Error in trigger program
msgType = '*PRV';
msgKey = rcvm0200.msgKey;
rcvPgmMsg(rcvm0200: messageInfoLen: fmtName:
callStackEntry: callStackCounter:
msgType: msgKey: waitTime: msgAction: qusec);
// my CPF9898 sent by the trigger - 02, diagnostic
msgType = '*PRV';
msgKey = rcvm0200.msgKey;
rcvPgmMsg(rcvm0200: messageInfoLen: fmtName:
callStackEntry: callStackCounter:
msgType: msgKey: waitTime: msgAction: qusec);
dump(a);
endif;
endif;
*inlr = *on;
Hope this brute force baling wire helps illustrate the idea. There is
yet another way to go about this. Before doing your I/O, send a
message, receive it and remember the message key. After the I/O, start
reading *NEXT using the saved message key and you'll get only the
messages relating to the just-attempted I/O. I think that's a bit
expensive if you're running millions of rows in batch, but it might work
for a dozen or so rows updated via the web...
--buck
As an Amazon Associate we earn from qualifying purchases.