Friday, 26 August 2011

Communicate application-specific errors

Oracle raises exceptions, and you can, too. Every application we write has application-specific exceptions (balance in account is too low, person is too young to be an employee, etc.). We usually need to communicate such errors back to the user so they can correct or report the problem.
PL/SQL provides the raise_application_error built-in to do just that. When you call raise_application_error, you specify the error number and message. Here is an example of calling this built-in:

BEGIN
IF l_balance < 1000
THEN
RAISE_APPLICATION_ERROR (-20100, 'Balance too low!');
END IF;

The problem with the above code is that I have hard-coded both error number and message, both things to be avoided because these literals make code maintenance more challenging. Generally, you should centralize error messages as much as possible in a repository, and you should avoid hard-coding of error numbers in the call to raise_application_error (which must be in the -20999 to -20000 range).
In this Q&A I will focus on avoiding the hard-coding of -20100. The solution is the same as that shown for ORA-24381. I will create a package to hold declarations of my applications-specific exceptions:

CREATE OR REPLACE PACKAGE my_exceptions
IS
e_balance_too_low EXCEPTION;
en_balance_too_low CONSTANT PLS_INTEGER := -20100;
PRAGMA EXCEPTION_INIT (e_balance_too_low, -20100);

/* And a function that returns message text stored in a
repository table to avoid hard-coding those as well! */
FUNCTION errmsg_for_errnum (errnum_in IN PLS_INTEGER)
RETURN VARCHAR2;
END my_exceptions;
Now, I bet that a number of my very sharp readers will ask why I violate my oft-stated best practice of avoiding repetition of code, data values, etc. Specially, why do I use the literal -20100 twice? Why not reference the en_balance_too_low value in the PRAGMA statement, as follows:

SQL> CREATE OR REPLACE PACKAGE my_exceptions
2 IS
3 e_balance_too_low EXCEPTION;
4 en_balance_too_low CONSTANT PLS_INTEGER DEFAULT -20100;
5 PRAGMA EXCEPTION_INIT (e_balance_too_low, en_balance_too_low);
6 END my_exceptions;
7 /

The answer is simple and unfortunate: Oracle doesn't yet support this, as shown in the compile error below:

Warning: Package created with compilation errors.

SQL> sho err
Errors for PACKAGE MY_EXCEPTIONS:

LINE/COL ERROR
-------- -----------------------------------------------------------------
5/4 PL/SQL: Declaration ignored
5/46 PLS-00702: second argument to PRAGMA EXCEPTION_INIT must be a
numeric literal

And now I can raise my application-specific exception by referencing the pre-defined constant:

BEGIN
IF l_balance < 1000
THEN
RAISE_APPLICATION_ERROR (
my_exceptions.en_balance_too_low
, my_exceptions.errmsg_for_errnum (
my_exceptions.en_balance_too_low);
END IF;

Why not use a central package goblal for the error msg too? (It seems unlike you not to.) You don't show the point of declaring "e_balance_to_low".
SF: OK, will do!
Note that one potential downside to having a centralized exceptions package like my_exceptions is that whenever I change that package, for example to add a new exception, I will need to recompile all programs that reference that package.
To summarize: declare and use your own named exceptions to improve the readability of your code and avoid hard-coding of exception codes.

No comments:

Post a Comment