In PL/SQL (Oracle), can exceptions propagate across function/procedure calls? - oracle

as it happens in enclosed blocks
If an anonymous block calls a function that raises exceptions, those are neither shown on console nor trapped in enclosing blocks...
What's more, after being caught by handler in the function, lines after function call in the anonymous block are executed normally!
The called procedure is:
CREATE OR REPLACE PROCEDURE qt(pno number, qty OUT number)
IS
BEGIN
select sum(qty_on_hand) into qty from products where productno=pno;
END;
The calling block is:
DECLARE
qty number;
BEGIN
qt(&pno, qty);
dbms_output.put_line('qty is: '||qty);
END;
In case of invalid product number, no error is shown; why?

If the exception is caught by the handler in the function and not re-raised then no exception will be triggered in the caller. This is correct behaviour. If you want the exception to be visible to the caller you must re-raise it in the function using the RAISE command:
FUNCTION fun ...
...
EXCEPTION
WHEN some_exception THEN
...
RAISE;
END;
Alternatively you can raise a different exception e.g.
EXCEPTION
WHEN some_exception THEN
...
RAISE_APPLICATION_ERROR(-20001,'My error message');
END;

In your specific example, I think that no exception is being raised at all. You say "in the case of an invalid product number", by which I assume you mean a product number that does not exist. That sounds like you expect your query to throw NO_DATA_FOUND, but since it is using an aggregate function without a GROUP BY, it will actually return a single row containing NULL if there are no matching rows.

Just to add to Tony's answer.
You may not know what type of exception a calling function may throw. In this case you can do:
EXCEPTION
WHEN exception_that_may_occur_in_my_function THEN
...
RAISE_APPLICATION_ERROR(-20001,'My error message');
WHEN others THEN -- Any exception that can come from a function I'm calling
RAISE;
END;

Related

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;

when others then exception handling

Background:
I've used a few Oracle articles to develop an error package, which consists of five procedures.
Two of these are Log_And_Return and Log_And_Continue. They are called throughout the program. Each takes input and passes it to the Handle procedure. For example:
PROCEDURE Log_And_Return (error_name)
IS
BEGIN
Handle (error_name, TRUE, TRUE);
END Log_And_Return;
The Handle procedure then calls the Log procedure and the Raise_To_Application procedure depending on the variables passed to it, like so:
PROCEDURE Handle (error_name, log_error, reraise_error)
IS
BEGIN
// Code to fetch error code and message using error_name input parameter.
IF log_error THEN
LOG (error_code, error_message);
END IF;
IF in_reraise_error THEN
Raise_To_Application (error_code, error_message);
END IF;
END Handle;
The log procedure stores the date, stacktrace, error code, error message and id and finally the Raise_To_Application procedure does what is says:
raise_application_error (error_code, error_message);
Problem:
My problem is this. Let's say I have a procedure, which performs a query, e.g. fetching a customer record. If this query fails, it's a big problem. So I could do this:
BEGIN
SELECT *something*
FROM *some table*
WHERE *some field* = *some user input*
// more logic
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Log_And_Return('unknown_id');
WHEN OTHERS THEN
ERR.Log_And_Return('unknown_error');
END;
Here, my Log_And_Return procedure takes the input, goes off to a table and returns a string to display to the user. I've a specific error for if the query doesn't find the user's record and a generic error for an unknown error. In both cases, logging is performed which takes the full stacktrace of the error.
However, in my example I've got a "// more logic" section. Let's say, I amend the code to this:
BEGIN
SELECT *something* INTO *some variable*
FROM *some table*
WHERE *some field* = *user id*
Call_Another_Procedure(*user id*, *some variable*)
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Log_And_Return('unknown_id');
WHEN OTHERS THEN
ERR.Log_And_Return('unknown_error');
END;
Now, after the select query, I'm calling another procedure with the result of the select query. Inside this new query, I'm doing a few things, including an update statement, like so:
// bunch of logic
BEGIN
UPDATE *another table*
SET *some field* = *some value*
WHERE *some field* = *variable passed into method*
EXCEPTION
WHEN NO_DATA_FOUND THEN
Err.Log_And_Return('some_error')
END;
Question:
My problem here is that I throw the NO_DATA_FOUND error if the query returns no results, I log the problem and I then raise an application error in my "Raise_To_Application" procedure... which will then be caught by the "when others" clause in the parent procedure, which will return the wrong message to the user.
What is the workaround to this? Note: If more code needs to be posted, just let me know.
Edit:
One workaround I had considered, and I've no idea if this is recommended or not, would be to wrap every stored procedure with a BEGIN END EXCEPTION block, where every procedure had a "When Others" block that just logged and reraised the most recent error (i.e. using SQLCODE). Then, in my application layer I could specify that if the error is between -20000 and -20999, show it along with its message, otherwise show a generic message (and the DBA can find out what happened in the database by looking at the log table, along with a full stacktrace). Any thoughts on this?
Edit 2:
If anything doesn't make sense, I can clarify. I've heavily changed and simplifier the code to remove things like id parameters and a few other things.
This is pretty much the approach I've been using, since I want to log every entry and exit point in my code:
application_error EXCEPTION;
PRAGMA EXCEPTION_INIT (application_error, -20000);
BEGIN
SELECT *something* INTO *some variable*
FROM *some table*
WHERE *some field* = *user id*
Call_Another_Procedure(*user id*, *some variable*)
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Log_And_Return('unknown_id');
WHEN application_error THEN -- ordinary exception raised by a subprocedure
ERR.Log_And_Return('application_error');
RAISE;
WHEN OTHERS THEN
ERR.Log_And_Return('unknown_error');
RAISE;
END;
And for subprocedures:
BEGIN
UPDATE *another table*
SET *some field* = *some value*
WHERE *some field* = *variable passed into method*
EXCEPTION
WHEN NO_DATA_FOUND THEN
Err.Log_And_Return('some_error'); -- this raises ORA-20000
END;
For the when_others exceptions consider of using AFTER SERVERERROR triggers. Something like bellow
create or replace trigger TRG_SERVERERROR
after servererror on database
declare
<some_variable_for_logging_the_call_stack>
begin
ERR.Log;
end;
I will quote from Tom Kytes when he was permitted to submit three requests for new features in PL/SQL and this is what he say
I jumped at the chance. My first suggestion was simply, “Remove the
WHEN OTHERS clause from the language.”
You can also read the following article from Tom Kyte - Why You Really Want to Let Exceptions Propagate
UPD: The whole workflow for the solution in your case is the following(in my subjective opinion)
I'll sugges to Include no WHEN OTHERS. I prefer to receive unfriendly error message, instead of the seamless message - something like "Ooops, something goes wrong.". In the end of the day you can also wrap all the unexpected exceptions to the some message for the user on your application layer and wrap the details about the database, to not be used by 3rd parties, etc.
My suggestion is have some ERR.
create or replace package ERR
ci_NoDataFound constant int := -20100;
NoDataFound exception;
pragma exception_init(NoDataFound, -20100);
procedure Raise;
procedure Log;
end P_PRSRELIAB;
In your parent procedure, you will handle the excpetion of the current particular procedure, and no other ones.
BEGIN
SELECT *something* INTO *some variable*
FROM *some table*
WHERE *some field* = *user id*
Call_Another_Procedure(*user id*, *some variable*)
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Raise(-20100, 'unknown user id');
END;
The procedure which is calling from the parent one, will handle only the excpetion of this particular procedure.
BEGIN
SELECT *something*
FROM *some table*
WHERE *some field* = *some user input*
EXCEPTION
WHEN NO_DATA_FOUND THEN
ERR.Raise(-20100, 'unknown some user input');
END;
In the application layer, we will have proper messages - "uknown some user input" or "unknown user id". On the other side the trigger will log all the information about the particular exception.
You need to re-raise the error in the underlying procedures using RAISE.
When an error occurs, and if you have an exception block, the handle moves to the exception block. The caller will remain unaware until you re-raise it using RAISE.
keep all the underlying procedures inside BEGIN-END block.
Also, use dbms_utility.format_error_stack and dbms_utility.format_error_backtrace to get the call stack.

Pragma Exception init with constant

I store the error codes my stored procedures may throw (by calling raise_application_error) as constants. One of my procedures has to catch the error another one throws, and I am trying to do this with pragma exception_init.
It seems that it only accepts numeric literals (as it explicitly says so in the error message I get), but it would be great to be able not to hardwire the error codes I otherwise store in constants. Is there some workaround for this, so that I could achieve the same as if I wrote:
pragma exception_init(myException, pkg_constants.error_not_null);
You can't use the package constant, or any variable, in the pragma.
What you could do is define the exception itself and its associated pragma in your package:
create package pkg_constants as
MyException exception;
error_not_null constant pls_integer := -20001;
pragma exception_init(myException, -20001);
end pkg_constants;
/
You still have to repeat the error number, but at least only in the same file. You can then refer to that package-defined exception in a handler without having to redeclare it:
exception
when pkg_constants.MyException then
...
For example:
set serveroutput on
begin
raise_application_error(pkg_constants.error_not_null, 'Threw it');
exception
when pkg_constants.MyException then
dbms_output.put_line('Caught it');
raise;
end;
/
Error report -
ORA-20001: Threw it
ORA-06512: at line 6
Caught it

Checking for Error during execution of a procedure in oracle

create or replace procedure proc_advertisement(CustomerID in Number,
NewspaperID in number,
StaffID in Number,
OrderDate in date,
PublishDate in date,
Type in varchar,
Status in varchar,
Units in number) is
begin
insert into PMS.Advertisement(CustomerID, NewspaperID, StaffID, OrderDate, PublishDate,
Type, Status, Units)
values(CustomerID,NewspaperID, StaffID, OrderDate, PublishDate,
Type, Status, Units);
dbms_output.put_line('Advertisement Order Placed Successfully');
end;
How to check for if any error has occurred during the execution of the procedure and if any error has occurred then I wish to display an error message.
First of all, Oracle itself will raise an error message if any error occurs while running the procedure - for example:
ORA-02291: integrity constraint (EMP.MGR_FK) violated - parent key not Found
You can handle errors explicitly by writing an exception handler, but unless you do this well you are quite likely to just obfuscate the problem. For example you could simply add this (just before the END of your procedure:
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error occured');
But now your user won't know what kind of error, whereas before they could infer that it was that the specified Manager did not exist. You could show the original error also like this:
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'An error occured: '||SQLERRM);
if that adds any value. Or you could just show a generic error and then write the value of SQLERRM to a log table.
You can also handle particular exceptions: for example
PROCEDURE ... IS
e_invalid_fk EXCEPTION;
PRAGMA EXCEPTION_INIT(e_invalid_fk,-2291);
BEGIN
...
EXCEPTION
WHEN e_invalid_fk THEN
IF SQLERRM LIKE '%(EMP.MGR_FK)%' THEN
raise_application_error(-20001,'Invalid manager specified');
ELSE
RAISE;
END IF;
END;
Note the RAISE: if any part of your exception handler doesn't issue either a RAISE or a RAISE_APPLICATION_ERROR then you are effectively sweeping the exception under the carpet - the user will think the procedure worked.
By the way, DBMS_OUTPUT.PUT_LINE is great for trying things out and debugging, in SQL Plus or an IDE, but it has no place in real code as users and applications that call the procedure will never see the output it produces.

Providing a more meaningful message when an error is raised in PL/SQL

Suppose I have a PL/SQL function that selects one value from a table. If the query returns no records, I wish for the NO_DATA_FOUND error to propagate (so that the calling code can catch it), but with a more meaningful error message when SQLERRM is called.
Here is an example of what I am trying to accomplish:
FUNCTION fetch_customer_id(customer_name VARCHAR2) RETURN NUMBER;
customer_id NUMBER;
BEGIN
SELECT customer_id
INTO customer_id
FROM CUSTOMERS
WHERE customer_name = fetch_customer_id.customer_name;
RETURN customer_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
meaningful_error_message := 'Customer named ' || customer_name || ' does not exist';
RAISE;
END;
Is there a way to associate meaningful_error_message with the NO_DATA_FOUND error?
Update: It has been suggested that I use RAISE_APPLICATION_ERROR to raise a custom error code when NO_DATA_FOUND is encountered. The purpose of this question was to determine if this technique could be avoided so that the calling code can catch NO_DATA_FOUND errors rather than a custom error code. Catching NO_DATA_FOUND seems more semantically correct, but I could be wrong.
Use RAISE_APPLICATION_ERROR (-20001, 'your message');
This will return an error number -20001, and your message instead of the NO_DATA_FOUND message. Oracle has reserved the error numbers between -20001 and -210000 for user use in their applications, so you won't be hiding another Oracle error by using these numbers.
EDIT: RAISE_APPLICATION_ERROR is specifically designed to allow you to create your own error messages. So Oracle does not have another method of allowing dynamic error messages. To further refine this you can define your own exception in the package where you define your procedure. Add the following:
CUSTOMER_NO_DATA_FOUND EXCEPTION;
EXCEPTION_INIT (CUSTOMER_NO_DATA_FOUND, -20001);
In your procedure code, you do the RAISE_APPLICATION_ERROR, and the client code can do a
WHEN CUSTOMER_NO_DATA_FOUND THEN which looks better, and they still have the error message captured in SQLERRM.
As suggested by Thomas you can use RAISE_APPLICATION_ERROR. If you also want to keep the NO_DATA_FOUND error on the error stack you can add TRUE as a third parameter to the function:
DECLARE
l NUMBER;
BEGIN
SELECT NULL INTO l FROM dual WHERE 1 = 2;
EXCEPTION
WHEN no_data_found THEN
raise_application_error(-20001, 'Meaningful Message', TRUE);
END;
ORA-20001: Meaningful Message
ORA-06512: at line 8
ORA-01403: no data found (*)
The line tagged (*) is the original error message.

Resources