You are on page 1of 23

for ODTUG 2005

Programming Humility:
Dealing with the Reality of Errors

Steven Feuerstein
steven.feuerstein@quest.com
www.quest.com
www.oracleplsqlprogramming
www.qnxo.com
Copyright 2000-2005 Steven Feuerstein - Page 1
Follow-up from this presentation

 Visit the following Oracle PL/SQL portal and


click on Resources:
www.oracleplsqlprogramming.com

 Download training materials and also a zip


file of all the scripts I will run today.

Copyright 2000-2005 Steven Feuerstein - Page 2


Robust PL/SQL applications require....

 A thorough understanding of the intricacies,


strengths and weaknesses of PL/SQL error
handling;
 A clearly-defined strategy for raising,
handling and communicating errors within
your application;
 Error management code that can be shared
by all developers on the team.
Warning: we can't do this in one hour!
Copyright 2000-2005 Steven Feuerstein - Page 3
So we will...

1. Cover a variety of topics regarding error


handling that are less than
straightforward.
2. Review strengths and weaknesses of
the PL/SQL error handling mechanism.
3. Take a look at the approach I have taken
in Qnxo to build an error management
framework.

Copyright 2000-2005 Steven Feuerstein - Page 4


Different types of exceptions

 Deliberate
– The code architecture itself deliberately relies on an exception.
Example: UTL_FILE.GET_LINE exec_ddl_from_file.sql
get_nextline.sf
 Unfortunate
– It is an error, but one that is to be expected and may not even
indicate a problem. Example: SELECT INTO ->
NO_DATA_FOUND fullname.pkb

 Unexpected
– A "hard" error that indicates a problem within the application.
Example: Primary key lookup raises TOO_MANY ROWS
Copyright 2000-2005 Steven Feuerstein - Page 5 fullname.pkb
What the Exception Section Covers

 The exception section only handles exceptions


raised in the executable section of a block
– Note: for a package, this means that the exception section only handles
errors raised in the initialization section valerr.pkg

DECLARE
Declarations
BEGIN
Executable Statements
EXCEPTION

Exception Handlers
END
Copyright 2000-2005 Steven Feuerstein - Page 6
Exceptions and DML

 DML statements generally are not rolled back


when an exception is raised.
– This gives you more control over your transaction.
 Rollbacks occur with...
– Unhandled exception from the outermost PL/SQL block;
– Exit from autonomous transaction without commit/rollback;
– Other serious errors, such as "Rollback segment too small".
 Corollary: error logs should rely on autonomous
transactions to avoid sharing the same
transaction as the application.
log8i.pkg
Copyright 2000-2005 Steven Feuerstein - Page 7
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE

Long-standing challenge in PL/SQL:

How can I find the line number on which an


error was raised in PL/SQL?

 Before Oracle Database 10g, the only way is to let


the error go unhandled in your PL/SQL code!
 DBMS_UTILITY.FORMAT_ERROR_STACK only
gives you the full error message.
– And is recommended by Oracle in place of SQLERRM.

But in Oracle Database 10g, we have "back trace"!


backtrace.sql
Copyright 2000-2005 Steven Feuerstein - Page 8 bt.pkg
Strengths and weaknesses

Oracle PL/SQL
Error Management:
Strengths and Weaknesses

Copyright 2000-2005 Steven Feuerstein - Page 9


PL/SQL error management strengths

 Like other modern languages, you define all


error-handling logic in a separate area in
your code.
– A single handler will catch and respond to an exception,
regardless of where or how it was raised.
 You can raise and handle not only system
exceptions, but also your own exceptions.
– Either with RAISE_APPLICATION_ERROR (when you need
to communicate the error to your user) or with user-defined
EXCEPTIONs.

Copyright 2000-2005 Steven Feuerstein - Page 10


PL/SQL error management weaknesses

 The EXCEPTION is a limited type of data.


– Only has two attributes: code and message. Unlike with Java,
it cannot be extended to include more information.
– You can RAISE and handle an exception, but it cannot be
passed as an argument in a program.
 No way to specify the possible exceptions that
can be raised by a program.
– You have to examine the implementation to deduce such
information.
 That somewhat silly -20,999 - -20,000 range.
Copyright 2000-2005 Steven Feuerstein - Page 11
Shoring up the Weaknesses

Let's take a look at the


error handling
architecture of Qnxo
for an example.

Copyright 2000-2005 Steven Feuerstein - Page 12


The Qnxo approach to error handling

 Object-like representation of an error


– Oracle's EXCEPTION is very limited, so why not create our
own "error instance"?
 Bypass restrictions on the error number range
entirely.
– Let's use error names in our code instead!
– Or use positive error numbers...
 Rely on a single package to raise and handle
errors.
– Make it harder for programmers to not follow the standards.

Copyright 2000-2005 Steven Feuerstein - Page 13


Object-like representation of an exception

 An error is a row in the error table, with many


more attributes than simply code and
message, including:
– Dynamic message (substitution variables)
– Help message (how to recover from the problem)
 An error instance is one particular
occurrence of an error.
– Associated with it are one or more values that reflect the
context in which the error was raised.
qd_error.erd
Copyright 2000-2005 Steven Feuerstein - Page 14
ERD for error definition tables

Copyright 2000-2005 Steven Feuerstein - Page 15


Specifying the error

How should I raise (and handle)


my application errors?

Just use -20000 all the time?


Pick one of those 1000 numbers?
What about all those positive numbers?
What about error names?

Copyright 2000-2005 Steven Feuerstein - Page 16


Raise/handle errors by name or number

BEGIN
IF employee_rp.is_to_young (:new.hire_date)
THEN
RAISE_APPLICATION_ERROR (
-20175, 'You must be at least 18 years old!');
END IF;

 The above trigger fragment illustrates a common


problem: Hard-coding of error numbers and
messages.
 Certainly, it is better to use named constants, as in:
BEGIN
IF employee_rp.is_to_young (:new.hire_date)
THEN
RAISE_APPLICATION_ERROR (
employee_rp.en_too_young But...
, employee_rp.em_too_young);
END IF;

Copyright 2000-2005 Steven Feuerstein - Page 17


Stop-and-go programming

 The reality is we can't know in advance


all of the different errors we'll have to
handle. With this approach, for each new
error, I must:
– Stop writing my current program.
– Update my error package (or repository that then
generates the error package).
– Recompile the package and recompile all programs
marked invalid.
– Finally return to my program. Where was I?

Copyright 2000-2005 Steven Feuerstein - Page 18


How about "top-down" error definition?

BEGIN
IF employee_rp.is_to_young (:new.hire_date)
THEN
qd_runtime.raise_error (
'EMPLOYEE-TOO-YOUNG'
, name1_in => 'LAST_NAME'
, value1_in => :new.last_name);
END IF;

 Use an error name (literal value).


– The code compiles now.
– Later, I define that error in the repository.
– No central point of failure.
 Downsides: risk of typos, runtime notification of
"undefined error."
Copyright 2000-2005 Steven Feuerstein - Page 19
Package to raise, handle and report errors

 Rather than have each programmer write


their own error handling code, provide a
single package to standardize the way errors
are raised, handled and reported.
WHEN e_integ_constraint_failure THEN
DECLARE
l_owner ALL_CONSTRAINTS.OWNER%TYPE;
l_name ALL_CONSTRAINTS.CONSTRAINT_NAME%TYPE;
BEGIN
get_constraint_info (l_owner, l_name);
qd_runtime.raise_error (
error_name_in => 'INTEGRITY-CONSTRAINT-FAILURE'
,name1_in => 'OWNER', value1_in => l_owner
,name2_in => 'CONSTRAINT_NAME', value2_in => l_name
,name3_in => 'TABLE_NAME', value3_in => 'SA_APPLICATION');
END;

Copyright 2000-2005 Steven Feuerstein - Page 20


Runtime error gathering/construction

 When an error occurs, you want to gather up


as much information as possible and make it
available for support and users.
– The default is a number and string. That is just a little bit
limiting.
 Other useful information...
– Error stack, backtrace stack (Oracle10g only), call stack,
environment info (machine name, OS, etc.) and of course all
that context-specific, application-specific information.
qd_runtime.pkb
Copyright 2000-2005 Steven Feuerstein - Page 21
Retrieve error information through the API

 Lots of information was gathered at the time the


error was raised or handled.
– Now the challenge is to make it easy to extract that information.
– For a non-PL/SQL host environment, that means avoiding composite
structures.
PROCEDURE qd_runtime.get_error_info (
code_out OUT qd_Error_Tp.code_t
,name_out OUT qd_Error_Tp.name_t
,text_out OUT qd_Err_Instance_Tp.message_t
,system_error_code_out OUT qd_Err_Instance_Tp.system_error_code_t
,system_error_message_out OUT qd_Err_Instance_Tp.system_error_message_t
,recommendation_out OUT qd_Error_Tp.recommendation_t
,error_stack_out OUT qd_Err_Instance_Tp.error_stack_t
,call_stack_out OUT qd_Err_Instance_Tp.call_stack_t
,environment_info_out OUT qd_Err_Instance_Tp.environment_info_t
);

Copyright 2000-2005 Steven Feuerstein - Page 22


Conclusions for PL/SQL error management

 Make sure you understand how it all works


– Exception handling is tricky stuff.
 Set standards before you start coding
– It's not the kind of thing you can easily add in later.
 Use shared infrastructure components
– Everyone and all programs need to handle errors the same way.
 Don't accept the limitations of Oracle's current
implementation.
– There is often a lot you can do to improve the situation.

Copyright 2000-2005 Steven Feuerstein - Page 23

You might also like