/* SQLERRM returns the standard Oracle error message */ NOTE: You will not be able to write a statement like WHEN DBMS_DEFER.UPDATECONFLICT in the Oracle Developer/2000 Release 1 environment. See Section 1.3.6, "Calling Built−in Packaged Code from Oracle Developer/2000 Release 1"" for more information on this restriction. 1.3.4.2 Package−defined exception In this scenario, the package declares one or more exceptions by name only; these exceptions do not have message text or a unique number associated with them. When this exception has been raised, SQLCODE will always return 1 and SQLERRM will always return the "Unhandled user−defined exception" message. As a consequence, you have two basic options for handling these exceptions: • You handle by name; write an exception handler that references the packaged exception by name. • You rely on the WHEN OTHERS clause, in which case there is no way for you to know precisely which exception was raised. Let's look at the UTL_FILE package for an example. The following exceptions are defined in the package specification: PACKAGE UTL_FILE IS invalid_path EXCEPTION; invalid_mode EXCEPTION; invalid_filehandle EXCEPTION; invalid_operation EXCEPTION; read_error EXCEPTION; write_error EXCEPTION; internal_error EXCEPTION; END; The UTL_FILE.FOPEN function can raise the INVALID_MODE, INVALID_OPERATION, or INVALID_PATH exceptions. I can write an exception section for a program using UTL_FILE.FOPEN in one of two ways: PROCEDURE my_program IS fid UTL_FILE.FILE_TYPE; BEGIN fid := UTL_FILE.FOPEN ('/tmp', 'myfile.txt', 'R'); EXCEPTION WHEN UTL_FILE.INVALID_OPERATION THEN WHEN UTL_FILE.INVALID_MODE THEN WHEN UTL_FILE.INVALID_PATH THEN END; or: PROCEDURE my_program [Appendix A] What's on the Companion Disk? 1.3.4 Exception Handling and Built−in Packages 21 IS fid UTL_FILE.FILE_TYPE; BEGIN fid := UTL_FILE.FOPEN ('/tmp', 'myfile.txt', 'R'); EXCEPTION WHEN OTHERS /* Not recommended! Information is lost */ THEN END; When working with this kind of exception, always use the first approach. With the WHEN OTHERS clause, there is no way for you to know which of the three UTL_FILE exceptions was raised. SQLCODE returns the same value of 1 regardless of the specific exception raised. NOTE: You will not be able to write a statement like WHEN UTL_FILE.INVALID_MODE in the Oracle Developer/2000 Release 1 environment. See Section 1.3.6" for more information on this restriction. 1.3.4.3 Standard system exception In this scenario, the package does not contain any statements that define new exceptions, nor does it give names to existing Oracle error numbers. Instead, a program in the package simply raises one of the errors defined in the Oracle documentation. You can then handle this exception by its name (if there is one) or by its number within a WHEN OTHERS clause. Let's look at an example. The UTL_FILE.GET LINE procedure raises the NO_DATA_FOUND exception (ORA−01403, but SQLCODE actually returns a value of 100) if you try to read past the end of a file. You can handle this error in either of the following ways: EXCEPTION WHEN NO_DATA_FOUND THEN or: EXCEPTION WHEN OTHERS THEN IF SQLCODE = 100 THEN /* SQLERRM returns the standard Oracle error message */ END; Of course, if you need to handle an exception that does not have a name associated with it, you can only rely on the WHEN OTHERS clause and an IF statement with SQLCODE to handle that error specifically. 1.3.4.4 Package−specific exception In some packages, Oracle developers decided to appropriate for their own use error numbers in the range set aside by Oracle Corporation for customer use (−20999 through −20000). This is very poor practice, as it can cause conflicts with your ownuse of these values. Unfortunately, it does happen and you need to know what to do about it. For example, the DBMS_OUTPUT package uses the −20000 error number to communicate back to the calling program either one of these errors: [Appendix A] What's on the Companion Disk? 1.3.4 Exception Handling and Built−in Packages 22 ORU−10027: buffer overflow, limit of <buf_limit> bytes. ORU−10028: line length overflow, limit of 255 bytes per line. Here is a attempt to call DBMS_OUTPUT.PUT_LINE that raises an unhandled exception in a SQL*Plus session: SQL> exec dbms_output.put_line (rpad ('abc', 300, 'def')) * ERROR at line 1: ORA−20000: ORU−10028: line length overflow, limit of 255 bytes per line ORA−06512: at "SYS.DBMS_OUTPUT", line 99 ORA−06512: at "SYS.DBMS_OUTPUT", line 65 ORA−06512: at line 1 I can handle this error if I call the built−in procedure from within a PL /SQL block as follows: /* Filename on companion disk: myput.sp /* CREATE OR REPLACE PROCEDURE myput (str IN VARCHAR2) IS BEGIN DBMS_OUTPUT.PUT_LINE (str); EXCEPTION WHEN OTHERS THEN IF SQLCODE = −20000 THEN IF SQLERRM LIKE '%ORU−10027%' THEN DBMS_OUTPUT.ENABLE (1000000); myput (str); ELSIF SQLERRM LIKE '%ORU−10028%' THEN myput (SUBSTR (str, 1, 255)); myput (SUBSTR (str, 256)); END IF; END IF; END; / The myput procedure implements the following logic: try to display the string. If an exception is raised, check to see if it is a −20000 error. If so, see if the error message indicates that it is a "buffer too small" error. If so, expand the buffer to the maximum size and try again to display the string. If the error message indicates a "string too long" error, display the first 255 bytes and then call myput again recursively to display the rest of the string. 1.3.4.5 Same exception, different causes One interesting situation you may run into when working with some of the built−in packages is that the same exception can be raised from different circumstances. Specifically, the NO_DATA_FOUND exception is raised by the PL /SQL runtime engine under any of these conditions: • You execute a SELECT INTO query (an implicit cursor) that does not identify any rows. • You attempt to access a row in a PL /SQL or index−by table that is not yet defined. • You try to read past the end of a file using UTL_FILE.GET_LINE. • [Appendix A] What's on the Companion Disk? 1.3.4 Exception Handling and Built−in Packages 23 You read past the end of a large object with DBMS_LOB.READ. If you are writing code that could raise NO_DATA_FOUND for different reasons, you may not be able to get by with a single exception handler like this: EXCEPTION WHEN NO_DATA_FOUND THEN /* ?? What caused the problem? */ END; You will want to know in the exception handler whether the problem was that the query returned no rows, or you read past the end of the file, or you tried to access an undefined row in an index−by table, or something else. If you face this problem, you may want to use a technique I call exception aliasing. Consider the very short program below: CREATE OR REPLACE PROCEDURE just_a_demo (file IN UTL_FILE.FILE_TYPE, empno_in IN emp.empno%TYPE) IS line VARCHAR2(1000); end_of_file EXCEPTION; v_ename emp.ename%TYPE; BEGIN SELECT ename INTO v_ename FROM emp WHERE empno = empno_in; BEGIN UTL_FILE.GET_LINE (file, line); EXCEPTION WHEN NO_DATA_FOUND THEN RAISE end_of_file; END; EXCEPTION WHEN end_of_file THEN DBMS_OUTPUT.PUT_LINE ('Read past end of file!'); WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.PUT_LINE ('No employee found for ' || TO_CHAR (empno_in)); END: / I have embedded the call to UTL_FILE.GET_LINE inside its own block. If that program reads past the end of a file and raises NO_DATA_FOUND, that block's exception section "translates" NO_DATA_FOUND into another, distinct exception: end_of_file (declared in the procedure itself ). The exception section of the procedure as a whole can then distinguish between the two different NO_DATA_FOUND scenarios. 1.3.5 Encapsulating Access to the Built−in Packages You will discover (both through reading this book and through your own experience) that there are many reasons to avoid directly accessing built−in packaged functionality. In a number of cases, you will want to build your own package on top of the built−in package. This process is usually referred to as encapsulation. Why would you bother with an encapsulation package? Any of the following reasons will do: • [Appendix A] What's on the Companion Disk? 1.3.5 Encapsulating Access to the Built−in Packages 24 The built−in packages offer lots of interesting technology, but they are not always very easy to use. You can hide the complexity, or in some cases, the poor design, and make it much easier for yourself and others to reap the benefits. • Some of the packages contain programs that you would not want to make generally or widely available. Conversely, other programs in that same package might be very useful for the "general public." An encapsulation package can offer only those programs that should be available, while hiding the others. (In this case, you will want to revoke any EXECUTE privileges on the underlying package.) • Write less code. Have you ever noticed the really impressive lengths of the names of Oracle built−in packages and their programs? Studies conducted by the Institute for Study Conduction estimate that by the year 2000, developers using PL /SQL will have experienced a $150 trillion loss in productivity due to having to type names like DBMS_DESCRIBE.DESCRIBE_PROCEDURE. Your encapsulations can use shorter names and thereby erase the federal deficit. • Take full advantage of built−in packages from Oracle Developer/2000 Release 1. As you will read in the next section, there are many restrictions on accessing stored code from products like Oracle Forms. Encapsulation can help you work around these restrictions. Roughly speaking, there are two types of encapsulation to consider when working with the built−in packages: Extension encapsulation This is the most common type of encapsulation for built−in packages. In this case, you provide one or more programs that extend the functionality or usability of the underlying package. Covering encapsulation When you create a cover for a built−in package, you create a package with a specification that matches that of the built−in package (same program names, same parameter lists). You can even give your package the same name as the built−in package, but you install it in a schema other than SYS. When you revoke EXECUTE authority on the built−in package and grant EXECUTE authority on your package, users of the built−in package will automatically be directed to your replacement. Oracle recommends this technique, for example, with the DBMS_APPLICATION_INFO package. 1.3.5.1 Examples of encapsulation packages This book (and the accompanying disk) contains many packages that encapsulate or cover an underlying built−in package (or, in some cases, a subset of the package). Table 1.3 shows the encapsulation packages in the book. Table 1.3: Encapsulation Packages for Built−ins Built−in Package/ Program Encapsulation Package Name File Description DBMS_AQ DBMS_AQADM aq aq.spp Hides details of creating, starting, stopping, and dropping queues and queue tables. The package allows you to write less code and also handles [Appendix A] What's on the Companion Disk? 1.3.5 Encapsulating Access to the Built−in Packages 25 . exceptions, nor does it give names to existing Oracle error numbers. Instead, a program in the package simply raises one of the errors defined in the Oracle documentation. You can then handle this exception. advantage of built−in packages from Oracle Developer/2000 Release 1. As you will read in the next section, there are many restrictions on accessing stored code from products like Oracle Forms as the built−in package, but you install it in a schema other than SYS. When you revoke EXECUTE authority on the built−in package and grant EXECUTE authority on your package, users of the built−in