PL/SQL Continue after exception raised - oracle

I've got simple trigger with exception handling. My problem is how continue with application when user get error message.
CREATE OR REPLACE TRIGGER "AR_CHECK"
BEFORE INSERT ON TABLE
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
v_check number;
cursor c_ar_check is
select ar_no
from name_view
where name_id = :new.bill_to_name_id
and name_type in ('TRAVEL_AGENT','COMPANY');
begin
open c_ar_check;
fetch c_ar_check into v_check;
close c_ar_check;
if v_check is null then
raise_application_error(-20101, 'ERROR - NUMBER MISSING');
end if;
end;
With code above I've got error message but user cannot continue with next step.
Is it possible to get user warning about NULL value but with possibility to go on?

With raise_application_error, no. It will terminate execution.
But, if you substitute it with something else - a trivial dbms_output.put_line would do in testing phase, while some logging process - such as calling a(n autonomous transaction) procedure - might be a better option in production.
If you choose dbms_output.put_line, note that your won't see any message until the procedure is finished.

Related

How to handle ORA-00028 "your session has been killed" in Oracle 12c (12.2.0.1.0)?

The problem is that handling an error ORA-00028 is kinda tricky. Please, look at the code below.
If you run proc1 in session 1 and while it's still running you kill session 1 with ALTER SYSTEM KILL SESSION then you get ORA-00028 error message and no row in llog table.
If you run proc1 and let it finish (1 min) then error handling works as expected and you get no error message and 1 row in llog table. But the funny thing is if after that you run proc1 again and kill that session you get no error-message (ORA-00028 handled) and one more row in llog table.
So for ORA-00028 to be handled in exception clause you need to catch some other error first. It seems to be a bug. Has anyone faced this problem?
/* creating simple table with logs */
create table llog(time timestamp, error varchar2(4000));
/
/* creating package */
create or replace package my_pack
is
procedure proc1;
end;
/
/* creating package body*/
create or replace package body my_pack
is
e_session_killed EXCEPTION;
PRAGMA EXCEPTION_INIT(e_session_killed, -00028);
procedure error_log (time llog.time%type, error llog.error%type) is
pragma autonomous_transaction;
begin
insert into llog values (time, error);
commit;
end;
procedure proc1 is
begin
dbms_lock.sleep(60);
raise too_many_rows;
exception
when e_session_killed then
error_log(systimestamp, sqlerrm);
when others then
error_log(systimestamp, sqlerrm);
end;
end;
You can't catch a kill session. It interrupts the current operation (as mush as it can - there might be some low level operations that cause issues), rolling back the open transaction(s). Once the rollback is complete the client is told that it is disconnected (assuming the client is still there) and the process goes away.
There's a couple of variants of kill session that affect the order of those but you're not going to be able to insert anything into any table from a killed session.
The only exception might be through a database link or similar, where you actually have two separate sessions/processes going on at the same time.

Using a pl/sql procedure to log errors and handle exceptions

so far stack overflow and the oracle forums and docs have been my best friend in learning PLSQL. I'm running into an issue here. Any advice is appreciated. I'm writing a procedure that would be used to log any errors a package may encounter and log them into the error log table I created. here is my code thus far.
CREATE OR REPLACE PROCEDURE APMS.test_procedure AS
procedure write_error_log (errcode number, errstr varchar2, errline varchar2) is
pragma autonomous_transaction;
-- this procedure stays in its own new private transaction
begin
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_line_no)
values (CURRENT_TIMESTAMP,
errcode,
errstr,
errline);
COMMIT; -- this commit does not interfere with the caller's transaction.
end write_error_log;
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
exception when others then
write_error_log(sqlcode,sqlerrm,dbms_utility.format_error_backtrace);
raise;
END test_procedure;
/
In the procedure I currently am using a mockdata table to induce an invalid number error and log that to the error_log table. At this point the error log table proves to be functional and inserts the data needed. The next step for me is to use this procedure to be used in the exception handlers in other programs so that the error is caught and logged to the table. Currently, my procedure is only unique to the mock_data table. My mentor/superior is telling me I need to pass this program some parameters to use it in other packages and exception handlers. I'm just having a bit of trouble. Any help would be appreciated thank you!
Steven Feuerstein has written several articles in Oracle Magazine on how to handle errors in PLSQL. He offers a small framework (errpkg) for doing this. The DRY principle (Don't Repeat Yourself) is his mantra!
https://resources.oreilly.com/examples/0636920024859/blob/master/errpkg.pkg
First, you should not make your caller pass in errline. That's very tedious! And what happens when the developer needs to insert a line or two of code? Do they need to update every call to write_error_log after that point to update the line numbers? Your write_error_log should use dbms_utility.format_call_stack (or the 12c more convenient variant of that.. don't have its name handy) to figure out what line of code issued the call to write_error_log.
Then, you should have a 2nd procedure called, say, write_exception. All that needs to do is something like this:
write_error_log (SQLCODE, SUBSTR (DBMS_UTILITY.format_error_stack || DBMS_UTILITY.format_error_backtrace, 1, 4000));

Handling and logging ORA-06512 error code

I have just created an error_log table to log any errors that a procedure/package may run into. the error log table is as follows
CREATE TABLE APMS.ERROR_LOG
(
ORA_ERR_TMSP TIMESTAMP(6) NOT NULL,
ORA_ERR_NUMBER NUMBER(5) NOT NULL,
ORA_ERR_MSG VARCHAR2(200 CHAR) NOT NULL,
ORA_ERR_TXT VARCHAR2(500 CHAR) NOT NULL,
ORA_ERROR_OPTYP CHAR(1 CHAR) NOT NULL,
PROGRAM_NAME VARCHAR2(50 CHAR) NOT NULL,
ORA_IN_OUT VARCHAR2(500 CHAR) NOT NULL
)
I created a mock table an purposely induced the ORA-06512 error by inserting a character string in a timestamp field using a procedure. here is the procedure which inserts dummy data into my mock table with the purpose of inducing an error and logging it into my error_log table.
create or replace procedure test_procedure as
begin
insert into mockdata values ('data1','mockname','mockcity');
commit;
exception
when others then
insert into error_log
values
(ora_err_tmsp,ora_err_number,ora_err_msg,ora_err_txt,ora_err_optyp,program_name,ora_in_out);
values
(current_timestamp,sqlcode,'sqlerrm', 'detailed information','i','test_procedure','i');
commit;
end;
/
when I attempt to run/compile it I get the following error.
[Error] PLS-00103 (9: 1): PLS-00103: Encountered the symbol "VALUES" when expecting one of the following:
( begin case declare end exit for goto if loop mod null
pragma raise return select update when while with
<an
I am a complete beginner at pl/sql so any help is appreciated.
Before getting into your code: if you are planning to write that kind of logging in every procedure you write, maybe you should simply avoid doing it and keep at reach of your hand this trigger and enable it just when you need it: this trigger might be handy in a testing environment or if you are in deep trouble and you need to collect all possible error that happen in a given time:
CREATE OR REPLACE TRIGGER log_all_errors AFTER SERVERERROR ON DATABASE
declare
procedure log_error is
pragma autonomous_transaction;
begin
INSERT INTO error_log
VALUES (SYSDATE, SYS.LOGIN_USER, SYS.INSTANCE_NUM, SYS.DATABASE_NAME, DBMS_UTILITY.FORMAT_ERROR_STACK);
commit;
end;
BEGIN
log_error;
END log_all_errors ;
this trigger will log ALL errors that will happen in your system. it is not a good idea to keep it enabled permanently: you can use it for doing some troubleshooting in emergency and you might want adjust its code to log only some kind of errors, but it is a starting point.
if you do some research you will discover that you can even log the SQL text of the statement that caused the error.
keep in mind that there are some errors that are not catched by this trigger (i am referring to NO_DATA_FOUND and TOO_MANY_ROWS errors): there is simply too much code that normally uses these exceptions during its normal lifetime, so the guys at oracle decided not to trap these errors.
Now let's get back to your code: your approach, as other have pointed out, is not neutral to the normal execution of the program:
you are logging the error, right, but you are hiding the error to the program calling your procedure. It is not a good idea: the calling program surely would like to know that something wrong has happened and it would like to issue a "rollback" in order to cancel all previous work, instead of continuing like nothing wrong did happen.
Not only you are hiding the error, but you are also issuing a commit whenever an error happens: this is most likely the opposite of what your calling program would do in case of an error: surely the caller would issue a rollback. Instead you are making permanent all the partial work done until the error happened.
as a side note: the above problem of the unwanted commit even when no errors happen within your procedure: you are committing also when no errors are raised. this means that if the calling program enconuters some problems after having called your procedure, it won't be able to rollback anything it did before calling your code.
It generally is a bad practice to commit or rollback inside a procedure, unless you are more than sure that such procedure will never be called as part of a bigger transaction (for example: it is ok to commit if your procedure is the main body of a database job)
So: how can you write an error log without interfering with the calling program? you do it by writing the log in a dedicted autonomous transaction" procedure: whatever you do in procedure marked as "authonomous transaction" runs in its own separate transaction: you commit or rollback only what happens inside it.
(As you can see, I used an autonomous transaction also in the above trigger)
your code would be more "kind", to the calling program, if written this way:
CREATE OR REPLACE PROCEDURE test_procedure AS
procedure write_error_log (errcode number, errstr varchar2) is
pragma autonomous_transaction;
-- whatever we do in this procedure stays in its own new private transaction
begin
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_txt,
ora_err_optyp,
program_name,
ora_in_out)
values (CURRENT_TIMESTAMP,
errcode,
errstr,
'detailed information',
'i',
'test_procedure',
'i');
COMMIT; -- this commit does not interfere with the caller's transaction.
end write_error_log;
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
--here you were committing: you'd better not do it:
-- you are making impossible for the calling program to roll back
-- COMMIT;
exception when others then
write_error_log(sqlcode,sqlerrm);
raise; -- you should NOT hide the exception to the caller program, so you'd better re-raise it!
END test_procedure;
Try this:
CREATE OR REPLACE PROCEDURE test_procedure
AS
BEGIN
INSERT INTO mockdata
VALUES ('data1', 'mockname', 'mockcity');
COMMIT;
EXCEPTION
WHEN OTHERS
THEN
INSERT INTO error_log
(ora_err_tmsp,
ora_err_number,
ora_err_msg,
ora_err_txt,
ora_err_optyp,
program_name,
ora_in_out)
values (CURRENT_TIMESTAMP,
SQLCODE,
sqlerrm,
'detailed information',
'i',
'test_procedure',
'i');
COMMIT;
END;
/

Getting some extra exceptions and messages on screen when my triggers gets fired

This is my code:
CREATE OR REPLACE TRIGGER unsuccessful_logins
AFTER INSERT OR UPDATE
ON temp_logins
for each row
DECLARE
CURSOR c_unsuccessful_attempts
IS
SELECT * from temp_attempts
and user_id= :new.user_id;
max_fails EXCEPTION;
BEGIN
FOR r_unsuccessful_attempts in c_unsuccessful_attempts
LOOP
if(:new.user_id = r_unsuccessful_attempts.user_id) then
if (r_unsuccessful_attempts.locked = 'Y') then
raise max_fails;
end if;
else
null;
end if;
END LOOP;
EXCEPTION
WHEN max_fails THEN
RAISE_APPLICATION_ERROR (-20300,'User '''||:new.login_name||''' has reached maximum failed logins. Please contact your system administrator ');
END unsuccessful_logins;
The error I am getting in my oracle apps screen is:
APP-01564: ORACLE error 20300 in fdssgn
cause: fdssgn failed due to ORA-20300: You have reached maximum failed logins. Please contact your system administrator.
ORA-06512: at "APPS.UNSUCCESSFUL_LOGINS",line 24
ORA-04088: error during excution of trigger 'APPS.UNSUCCESSFUL_LOGINS'.
The SQL statement being executed at the time of the error was:
INSERT INTO TEMP_LOGINS (USER_ID, ATTEMPT_TIME,TERMINAL_ID,LOGIN_NAME)
values(:user_id, sysdate,:erminal_id,:login_name)
I just want to display ORA-20300: You have reached maximum failed logins. Please contact your system administrator part.
And want to omit:
ORA-06512: at "APPS.UNSUCCESSFUL_LOGINS",line 24
ORA-04088: error during excution of trigger 'APPS.UNSUCCESSFUL_LOGINS'.
The SQL statement being executed at the time of the error was:
INSERT INTO TEMP_LOGINS (USER_ID, ATTEMPT_TIME,TERMINAL_ID,LOGIN_NAME)
values(:user_id, sysdate,:erminal_id,:login_name)
How can I get rid off these extra messages on the screen?
I'm not sure exactly what you mean by 'oracle apps', but this link may help; it has a section on exception handling.
As I suggested in an earlier comment, I suspect you need something in your application code to catch and gracefully handle the exception raised by the trigger, so some kind of wrapper around the insert statement. The link is specifically talking about handling an exception in Forms, which (as is probably quite clear) I'm not familiar with, but the principal is likely to be the same if you're using something related.
Adapting their example slightly, something like this might fit what I think you're trying to do:
DECLARE
too_many_attempts EXCEPTION;
PRAGMA EXCEPTION_INIT(too_many_attempts, -20300);
BEGIN
INSERT INTO TEMP_LOGINS (USER_ID, ATTEMPT_TIME,TERMINAL_ID,LOGIN_NAME)
values(:user_id, sysdate,:erminal_id,:login_name);
EXCEPTION
WHEN too_many_attempts THEN
fnd_message.set_string(SQLERRM);
fnd_message.error;
RAISE FORM_TRIGGER_FAILURE;
END;
The simple answer is, you can not omit output from RAISE_APPLICATION_ERROR. You could consider using dbms_output.put_line to provide a single message.
...
WHEN max_fails THEN
dbms_output.put_line('User Message');

Oracle Forms - Commit Single SQL Statement Instead of Entire Form

I'm working on an Oracle Form (10g) that has two blocks on a single canvas. The top block is called QUERY_BLOCK which the user fills out to fill PRICING_BLOCK with rows of data.
However, in QUERY_BLOCK I also have a checkbox which needs to perform an INSERT and DELETE on the database, respectively. My WHEN-CHECKBOX-CHANGED trigger looks like this:
begin
if :query_block.profile_code is not null then
if :query_block.CHECKBOX_FLAG = 'Y' then
begin
INSERT INTO profile_table VALUES ('Y', :query_block.profile_code);
end;
else
begin
DELETE FROM profile_table WHERE profile_code = :query_block.profile_code and profile_type_code = 'FR';
end;
end if;
end if;
end;
I know that I need to add some sort of commit statement in here, otherwise the record locks and nothing actually happens. However, if I do a COMMIT; then the entire form goes through validation and updates any changed rows.
How do I execute these one-line queries I have without the rest of my form updating as well?
Without commenting on the actual wisdom of this, you could create a procedure in the database that performed an autonomous transaction:
CREATE OR REPLACE FUNCTION my_fnc(p_flag IN VARCHAR2)
RETURN VARCHAR2 IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
IF p_flag = 'Y' THEN
INSERT...
ELSE
DELETE...
END IF;
COMMIT;
RETURN 'SUCCESS';
EXCPTION
WHEN OTHERS THEN
RETURN 'FAIL';
END;
Your Forms code could then look like:
begin
if :query_block.profile_code is not null then
stat := my_fnc(:query_block.CHECKBOX_FLAG);
end if;
end;
This allows your function to commit independent of the calling transaction. Beware of this, however - if your outer transaction must roll back, the autonomous transaction will still be committed. I would think there should be a transactional way to do what you need done to solve your locking problem, which would likely be the superior approach. Without knowing the specifics of your process, I can't tell. Generally speaking, autonomous transactions are used when an update must occur regardless of whether the transaction commits or rolls back, e.g., logging.

Resources