Is it a bad practice to use global exceptions in PL/SQL? - oracle

Is it a bad practice to do what the code below does? Will bad things happen to me for writing it?
Edit: this is just an example. I would not use dbms_output for any real error reporting.
CREATE OR REPLACE PACKAGE my_package
AS
PROCEDURE master;
END;
/
CREATE OR REPLACE PACKAGE BODY my_package
AS
my_global_interrupt EXCEPTION;
PROCEDURE my_private_procedure
IS
BEGIN
-- in case some flag is raised, raise exception to stop process and prepare for resume
RAISE my_global_interrupt;
END;
PROCEDURE master
IS
BEGIN
my_private_procedure;
EXCEPTION
WHEN my_global_interrupt THEN
dbms_output.put_line('global interrupt, ');
-- prepare to resume
END;
END;
/

On the contrary globally defined user exceptions is good practice. Consider the following skeleton of a package body.
create or replace package body my_pkg
as
my_x1 exception;
my_x2 exception;
my_x3 exception;
PROCEDURE p1 is
begin
...
exception
when no_data_found then raise my_x1;
end p1;
PROCEDURE p2 is
begin
...
exception
when no_data_found then raise my_x2;
end p2;
PROCEDURE p3 is
begin
...
exception
when no_data_found then raise my_x3;
end p3;
PROCEDURE master is
begin
p1;
p2;
p3;
exception
when my_x1 then do_this;
when my_x2 then do_that;
when my_x3 then do_the_other;
end master;
end my_pkg;
/
The use of globally declared exceptions makes exception handling in the master procedure easier.
Also, bear in mind that sometimes we want to propagate the exception beyond the package, to say a program which calls our publicly declared procedure. We can do that by defining our exceptions in the package spec. This means other proecdures can reference them...
SQL> begin
2 my_pkg.master;
3 exception
4 when my_pkg.my_public_x1
5 then dbms_output.put_line('oh no!');
6 end;
7 /
oh no!
PL/SQL procedure successfully completed.
SQL>
We can also associate such exceptions with specific error numbers, so that they are recognisable even if the calling procedure doesn't explicitly handled them.
SQL> exec my_pkg.master
BEGIN my_pkg.master; END;
*
ERROR at line 1:
ORA-20999:
ORA-06512: at "APC.MY_PKG", line 32
ORA-06512: at line 1
SQL>
That's (slightly) more helpful than the generic ORA-06510 error.

Looks reasonable enough to me, provided you're happy that after the interrupt condition it's OK to resume processing. If you are going to log the interrupt in some way, it's probably better to insert a row into a log table using an autonomous transaction. You won't see anything from DBMS_OUTPUT until the whole procedure finishes. Then you'll see all the DBMS_OUTPUT at once.

Related

Best way to manage exception in package

I have a package with a several procedures and functions, the procedures are called from an external program and in turn they call functions.
Where is the best way to manage a Exception?
For example, Prog1 call Proc1 and Proc1 call the Funct1, if in function I have an exception ("TOO MANY ROW" or "NO DATA FOUND"), which is the best way to raise a specific customized message and return immediatally to Prog1.
In this moment I have this
PROCEDURE_1
BEGIN
*code*
CALL FUNCTION_1
*code*
EXCEPTION
WHEN NO DATA FOUND THEN
*print customized message*
RETURN;
END;
FUNCTION_1
BEGIN
*code*
EXCEPTION
WHEN NO DATA FOUND THEN
RAISE;
END;
It is the best way?
Regards,
Marco
Ideally a procedure would be allowed to fail with an exception and error stack if something went totally wrong, but then the difference between a fatal error and an expected condition will vary depending on the business logic you are implementing, so it's hard to say what should happen in your particular case.
If the agreed interface is that the procedure should return a formatted message, and that message may include expected business conditions such as a product being out of stock, then you can handle that within the procedure using something like this (nonsense pseudocode to illustrate approach only):
create or replace procedure procedure_1
( p_result_message out varchar2 )
as
somevar number;
begin
do_stuff('fruit','cake');
begin
somevar := function_1('bananas');
exception
when no_data_found then
p_result_message := 'No kittens are available for this mission.';
return;
end;
p_result_message := 'Cake is available in aisle 3';
exception
when something_else then
p_result_message := 'Self-destruct sequence initiated.';
end;
(For a more purist approach you might prefer to rearrange the code so that it always reaches the end with a value for p_result_message, rather than quitting partway through if some condition pops up.)
Now you have a way to handle whatever exceptions might reasonably arise in function_1, without any special handling within the function itself.
You could also have the function raise an exception defined in a package, although then you lose the ability to define a diagnostic error message at the point of failure, and in my experience this just tends to complicate things. But to illustrate:
create or replace package starfleet
as
shield_failure exception;
warp_core_malfunction exception;
pragma exception_init(shield_failure, -20998);
pragma exception_init(warp_core_malfunction, -20999);
procedure check_status
( status_message out varchar2 );
function status
return number;
end starfleet;
create or replace package body starfleet as
function status
return number
is
status_ind number;
begin
select 1 into status_ind from dual where 1=2; -- fails with NDF
return status_ind;
exception
when no_data_found then raise shield_failure;
end status;
procedure check_status
( status_message out varchar2 )
is
status_ind number;
begin
status_ind := status();
status_message := 'Everything is fine';
exception
when warp_core_malfunction then status_message := 'Abandon ship';
when shield_failure then status_message := 'Increase power to shields';
end check_status;
end starfleet;
Now, the status() function can raise exceptions defined in the package (or any other package for that matter), and procedure check_status can catch it and decide how to handle it.
Example of calling it from SQL*Plus:
SQL> var status_message varchar2(100)
SQL>
SQL> begin
2 starfleet.check_status(:status_message);
3 end;
4 /
PL/SQL procedure successfully completed.
STATUS_MESSAGE
-------------------------
Increase power to shields

Similar to finally Block (JAVA) in Oracle PL/SQL Block

As in Java there is finally block which executed in all conditions.
Is there any similar function in Oracle PL/SQL which will be executed whenever procedure completes its execution even a return statement is used?
There is no equivalent of FINALLY but you can simulate it using nested PL/SQL blocks;
DECLARE
-- Your variables.
return_early BOOLEAN := FALSE;
BEGIN
-- Do something
DECLARE
-- Local variables in "try" block
BEGIN
-- Equivalent of "try" block
-- Do something that may raise an exception
IF some_condition THEN
return_early := TRUE;
-- you could also use "GOTO end_try;" rather than surrounding the
-- following statements in an "ELSE" statement
ELSE
-- Do something else that may raise an exception
END IF;
EXCEPTION
WHEN your_exception THEN
-- Equivalent of "catch" block
END;
<<end_try>>
-- Handle "finally" here, after end of nested block.
-- Note: you can only see variables declared in this outer block
-- not variables local to the nested PL/SQL block.
IF return_early THEN
RETURN;
END IF;
-- Continue and do more stuff.
END;
/
Another thread on Stackoverflow can help out here : Exception handling in pl/sql
Where Tony says :
"You can created nested blocks:"
create or replace procedure Trial
is
Begin
begin
---Block A--
EXCEPTION
when others then
insert into error_log values('error');
end;
begin
--Block B ----
end;
end;
The solutions provided in other answers does not implement exact try-catch-finally logic. E.g. the finally should be executed even if exception is reraised and even if in did not handled at all. In other words, the finally block must be called always.
The most similar behavior to Java's finally is the following. Unfortunately, here we have to wrap finally block into a procedure. We have no choice in PL/SQL.
declare
begin
declare
EXPECTED_EXCEPTION_RECOVERABLE exception;
EXPECTED_EXCEPTION_FATAL exception;
EXPECTED_EXCEPTION_UNHANDLED exception;
procedure finally is
begin
dbms_output.put_line('FINALLY section executed');
end;
begin
dbms_output.put_line('Trying dangerous section...');
/* uncomment to try different behavior */
--raise EXPECTED_EXCEPTION_RECOVERABLE;
--raise EXPECTED_EXCEPTION_FATAL;
--raise EXPECTED_EXCEPTION_UNHANDLED;
dbms_output.put_line('Dangerous section is executed successfully');
FINALLY();
exception
when EXPECTED_EXCEPTION_RECOVERABLE then
dbms_output.put_line('Recoverable exception handled.');
FINALLY();
when EXPECTED_EXCEPTION_FATAL then
dbms_output.put_line('Fatal exception handled and will be reraised');
FINALLY();
RAISE;
when OTHERS then
dbms_output.put_line('Unhandled exception is just reraised after finally section');
FINALLY();
RAISE;
end;
end;
/
It seems, this solution looks enough readable;
You can create a custom Exception and then Raise it at the end of your other exceptions, this custom exception must be on an outside block:
BEGIN
BEGIN
--DO SOME CODE HERE
EXCEPTION
WHEN NO_DATA_FOUND THEN
--HANDLE EXCEPTION
RAISE CUSTOM_EXCEPTION;
END;
EXCEPTION
WHEN CUSTOM_EXCEPTION THEN
--HANDLE THE FINALLY CODE HERE
END;

Pl/SQL Nested Procedure Exception Handling

This is a best practice question on error handling through multiple levels of PL/SQL procedures. I've looked at a few other questions to help me out, in particular this one.
Currently, I have a program with Procedure 1, which calls Procedure 2, which calls Procedure 3. I'm trying to perform adequate error handling - but I'd like to output eventually the exact problem back to the application layer. I'm hoping to get some ideas on how I can do this efficiently and clearly.
My current solution method is below, but it seems rather messy to me, with lots of variable declarations. I am very new to PL/SQL (and SQL in general) so I'd appreciate any advice on:
Good error handling techniques when dealing with multiple layers of procedures.
Feeding error messages back up to application layer (in my procedure below, represented by "out_overall_output" variable.
Program Flow: UI -> Proc 1 -> Proc 2 -> Proc 3
Procedure 1:
--One input variable, one output.
in_id VARCHAR2;
out_overall_output VARCHAR2;
...
DECLARE
l_success BOOLEAN;
l_error_output VARCHAR2(100);
BEGIN
Proc2(id, l_success, l_error_output);
IF l_success = FALSE THEN
out_overall_output = l_error_output
END IF
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
out_overall_output:= 'Error calling Proc 2'
RETURN;
END;
--Normal flow continues if l_success is true...
Procedure 2:
-- One input variable, two output.
in_id
out_success
out_error_output
//other logic
DECLARE
l_success BOOLEAN;
l_error_output VARCHAR2(100)
BEGIN
Proc3(id, l_success, l_error_output)
IF l_success = FALSE THEN
out_error_output = l_error_output
END IF
EXCEPTION
WHEN OTHERS
out_error_output = 'Error calling Proc 3'
RETURN;
END;
Procedure 3:
--One input variable, two output.
in_id VARCHAR2;
out_success BOOLEAN;
out_error_message VARCHAR2;
...
BEGIN
DELETE
FROM table
WHERE id = in_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
out_success = FALSE;
out_error_message = 'Error - No data to delete'
WHEN OTHERS THEN
out_success = FALSE;
out_error_message = 'Error deleting data.'
END;
Note: The levels of procedure calling goes deeper than this. The snippets I have shown are greatly simplified. The error messages and variable names in my real procedures are more descriptive.
To show exact explanations of "what happens with a server" for application level you can try following. In procedures:
create or replace procedure p1 is
...
exception
when <some_error> then
<do something>
-- re-raise error:
raise_application_error(-20001, 'Client with ID '|| ID || ' has no right to perform action "' || ACTION_NAME || '"', true);
end;
create or replace procedure p2 is
begin
p1;
exception
when <another_error> then
<do something>
-- re-raise error:
raise_application_error(-20002, 'Action "' || ACTION_NAME || '" is not completed', true);
end;
create or replace procedure p3 is
begin
p2;
exception
when <another_error> then
<do something>
-- re-raise error:
raise_application_error(-20003, 'Purchasing of "' || CAR_NAME || '" cancelled', true);
end;
And in top level procedure:
create or replace procedure top_level_procedure is
begin
p1;
exception
when <one_more_error> then
<do something>
raise_application_error(-20004, dbms_utility.format_error_backtrace);
end;
After exception in p1 you will see something like this:
ORA-20003: Purchasing of "Cool red Ferrari" cancelled
ORA-20002: Action "car purchase" is not completed
ORA-20001: Client with ID 123 has no right to perform action "Spent all money of Bill Gates"
Third parameter of procedure raise_application_error with false value cuts all previous error messages. If you will use false value in procedure p3, you will see only one error message with code ORA-20003 in this example.
P. S. Also you can define your own exceptions and use them in WHEN .. THEN clause. Here you find more information and examples: https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/errors.htm#LNPLS00704
P. P. S. How to log. Log procedure:
create or replace procedure log(p_log_message varchar2) is
pragma autonomous_transaction;
begin
insert into log_table(..., log_message) values (..., p_log_message);
commit;
end;
Call log procedure:
when <one_more_error> then
<do something>
log(..., dbms_utility.format_error_backtrace);
raise_application_error(-20004, dbms_utility.format_error_backtrace);

What PRAGMA statements would be used inside an EXCEPTION block?

I was playing around with Oracle exceptions and tried to do some preprocessing between EXCEPTION and the WHERE statements. That is,
EXCEPTION
some_operation_here();
WHEN yaddayadda THEN
...
PL/SQL Developer said that that wasn't kosher – oh well – but its error message intrigued me: it was expecting either WHEN or PRAGMA. I am not thoroughly familiar with all the PRAGMA directives, but it doesn't seem like any of them are applicable in an exception block unless for some reason you waited till this point to bind an error code to an exception you had declared earlier. Why would you ever need to use a PRAGMA directive here?
A little experimentation tells me that you can, in fact put a PRAGMA in the exception block, but I can't see much use to it. The following executes successfully, but the raised error triggers the OTHER section, not the section for the newly defined exception (e.g. it returns "Old exception"). It would appear that this is an undocumented feature.
DECLARE
v NUMBER;
new_divide_zero EXCEPTION;
BEGIN
v := 1 / 0;
EXCEPTION
PRAGMA EXCEPTION_INIT (new_divide_zero, -1476);
WHEN new_divide_zero THEN
DBMS_OUTPUT.put_line ('New exception');
WHEN OTHERS THEN
DBMS_OUTPUT.put_line ('Old exception');
END;
Similarly, I tried putting a AUTONOMOUS_TRANSACTION in the exception block, but it too appears to have no effect (in this case, both rows are inserted).
CREATE TABLE test_results (result VARCHAR2 (2000));
BEGIN
DECLARE
v NUMBER;
new_divide_zero EXCEPTION;
BEGIN
insert into test_results values ('Test Value');
v := 1 / 0;
EXCEPTION
PRAGMA AUTONOMOUS_TRANSACTION;
WHEN OTHERS THEN
INSERT INTO test_results
VALUES ('Old exception');
Commit;
END;
ROLLBACK;
END;
The Oracle documentation (12g, which is the version i tested this on) does not mention using PRAGMA in the exception block, so it's definitely undocumented. On the other hand, it doesn't seem to be much of a feature, as it doesn't appear to actually do anything...
WHEN { exception_name [ OR exception_name ]... | OTHERS } THEN
statement [ statement ]...
It is syntax error. To catch exception you must use following syntax:
declare
yaddayadda exception;
begin
...
exception
WHEN yaddayadda THEN
some_operation_here();
end;
You need to use pragma exception_Init to assign you custom exception with sql error code.
For example:
declre
l_id number;
e_resource_busy EXCEPTION;
PRAGMA EXCEPTION_INIT(e_resource_busy, -54);
begin
select id
into l_id
from job_table
where status = 'RUNNING'
for update nowait;
exception
WHEN e_resource_busy THEN
-- row is locked
some_operation_here();
end;

Exception handling in pl/sql

I have a stored procedure
create or replace procedure Trial
is
Begin
---Block A--
EXCEPTION
when others then
insert into error_log values('error');
--Block A ends----
--Block B ----
----Block B ends---
end;
I want code in Block B to execute in all condition i.e if exception in Block A is raised or not.With the above code Block B executes only if exception is raised.
How to do this.
Please help.
You can created nested blocks:
create or replace procedure Trial
is
Begin
begin
---Block A--
EXCEPTION
when others then
insert into error_log values('error');
end;
begin
--Block B ----
end;
end;
Please note that it's a common antipattern to catch all exceptions without raising them. You might also want to consider an autonomous transaction in order to keep the error-log after a rollback.
So you'd probably be better off with something like this:
create or replace procedure Trial
is
procedure finally is
begin
--Block B ----
end;
procedure logerr (msg varchar2) is
PRAGMA AUTONOMOUS_TRANSACTION;
begin
insert into error_log values(msg);
commit;
end;
Begin
begin
---Block A--
EXCEPTION
when others then
logerr(SQLERRM);
finally;
raise;
end;
finally;
end;

Resources