Exceptions
User-defined and built-in exceptions help handle possible inconsistencies of data and script blocks.
Overview
An exception is a special data type in Scripting. Exceptions could be declared, thrown and handled.
User-specified handlers catch certain types of exceptions. Exceptions can be declared in a block and then used to catch that type of exception in the nested blocks of that block as well.
When an exception is raised, the system tries to find a handler for this exception:
- First in the block in which the exception occurred.
- Then in the enclosing block if this block does not have its own exception handler.
Execution continues at the beginning of this exception handler.
If the exception occurs deeper than the "first level", the exception is sent up level by level until:
- either an appropriate exception handler handles the exception
- or the outermost layer is reached. In this case an error is raised.
Therefore, if no suitable handler is found, the script execution will stop and an error will be reported.
An exception handler can contain nested exception handlers in case an exception occurs while processing another exception.
Exception declaration
User defined exceptions can be declared in the DECLARE section of the block.
...
DECLARE
my_exception EXCEPTION (-20001, 'I have triggered this exception at your request.');
...See Exception declaration syntax for syntax information and Example 3 | EXCEPTION Declaration as one of the examples.
Raising an exception
With RAISE command an exception is thrown
DECLARE
my_exception EXCEPTION (-20001, 'I have triggered this exception at your request.');
BEGIN
LET count INTEGER := 0;
IF (count = 0) THEN
RAISE my_exception;
END IF;
count := count + 1;
RETURN count;
END;At this point the script stops, the exception is raised and error message is returned.
I have triggered this exception at your request.
at my_folder.my_script_name (6:5)
at my_folder.my_script_name (1:1)Below you find out, how to to raise the same exception again and write exception handlers.
Define exception handlers
With an EXCEPTION clause (also called exception handler), exceptions can be handled explicitly or passed on to the parent blocks.
Within an EXCEPTION clause, exceptions can also be handled by name using the WHEN clause. This can be done for both user-declared and built-in exceptions.
Following system variables contains information about raised exception:
- SQLCODE: Exception 'id', which is represented as a 5-digit signed integer and can be specified by user in
DECLAREsection for user defined exceptions. - SQLERRM: An error message. The message can be specified by user in
DECLAREsection for user defined exceptions.
With WHEN OTHER THEN you can handle all other exceptions that do not have a WHEN clause.
For example:
DECLARE
MY_EXCEPTION EXCEPTION (-20001, 'I have triggered this exception at your request.');
BEGIN
LET NUMERATOR INTEGER := 0;
IF (NUMERATOR = 0) THEN
RAISE MY_EXCEPTION;
END IF;
NUMERATOR := NUMERATOR + 1;
RETURN NUMERATOR;
EXCEPTION
WHEN MY_EXCEPTION THEN -- handler for user declared exception
SELECT 'Excp.Type: '|| 'MY_EXCEPTION' ||
'; Excp.Code: ' || :SQLCODE ||
'; Excp.Message: ' || :SQLERRM AS MESSAGE;
WHEN OTHER THEN -- handler for all other exception
SELECT 'Excp.Type: '|| 'Unknown exception' ||
'; Excp.Code: ' || :SQLCODE ||
'; Excp.Message: ' || :SQLERRM AS MESSAGE;
END;+-----------------------------------------------------------------------------------------------------------+
| MESSAGE |
+-----------------------------------------------------------------------------------------------------------+
| Excp.Type: MY_EXCEPTION; Excp.Code: 20001; Excp.Message: I have triggered this exception at your request. |
+-----------------------------------------------------------------------------------------------------------+DECLARE section, but no handler for this exception is found in the EXCEPTION section, an error is returned: DECLARE
MY_EXCEPTION EXCEPTION (-20001, 'I have triggered this exception at your request.');
BEGIN
LET NUMERATOR INTEGER := 0;
IF (NUMERATOR = 0) THEN
RAISE MY_EXCEPTION;
END IF;
NUMERATOR := NUMERATOR + 1;
RETURN NUMERATOR;
END;And
DECLARE
MY_EXCEPTION EXCEPTION (-20001, 'I have triggered this exception at your request.');
SECOND_EXCEPTION EXCEPTION (-20002, 'I have triggered this exception at your request.');
BEGIN
LET NUMERATOR INTEGER := 0;
IF (NUMERATOR = 0) THEN
RAISE MY_EXCEPTION;
END IF;
NUMERATOR := NUMERATOR + 1;
RETURN NUMERATOR;
EXCEPTION
WHEN SECOND_EXCEPTION THEN -- handler for SECOND_EXCEPTION
RETURN 'Excp.Type: ' || 'SECOND_EXCEPTION' ||
'; Excp.Code: ' || SQLCODE ||
'; Excp.Message: ' || SQLERRM;
END;Both have the following output:
I have triggered this exception at your request.
at my_folder.my_script_name (6:5)
at my_folder.my_script_name (1:1)Ignore exceptions with NULL command
The command NULL handles an exception by explicitely "doing nothing": in this case, the execution of the code will be continued without stop.
In this example, an "Unknown Exception" exception is thrown because a string value "x" is added to a numeric variable NUMERATOR. The error second exception handler ignores this exception:
DECLARE
MY_EXCEPTION EXCEPTION (-20001, 'I have triggered this exception at your request.');
BEGIN
LET NUMERATOR INTEGER := 0;
NUMERATOR := NUMERATOR + 'x';
RETURN NUMERATOR;
EXCEPTION
WHEN MY_EXCEPTION THEN -- handler for user declared exception
RETURN 'Excp.Type: '|| 'MY_EXCEPTION' ||
'; Excp.Code: ' || SQLCODE ||
'; Excp.Message: ' || SQLERRM;
WHEN OTHER THEN -- handler for all other exception
NULL; -- do nothing
END;Note
This can lead to unexpected results.
See NULL for more information.
Re-Raising of the caught exception
Sometimes it is helpful not only to trigger steps defined in a handler for an exception, but also to trigger the exception itself after the steps.
In this case, the "RAISE" command can be used without arguments.
In this example, we handle the exception "Object 'MY_EXCEPTIONS' already exists" by writing the exception message to the table "MY_EXCEPTIONS".
CREATE TABLE IF NOT EXISTS my_folder.MY_EXCEPTIONS (CAPTURED STRING);
BEGIN
CREATE TABLE my_folder.MY_EXCEPTIONS (CAPTURED STRING);
EXCEPTION
WHEN OTHER THEN
LET LINE := SQLCODE || ': ' || SQLERRM;
INSERT INTO my_folder.MY_EXCEPTIONS VALUES (:LINE);
END;+-----------------+
| anonymous block |
+-----------------+
| null |
+-----------------+And:
select * from my_exceptions;+-----------------------------------------------------------------------------------------------------------------------------------+
| CAPTURED |
+-----------------------------------------------------------------------------------------------------------------------------------+
| undefined: Failed to create table 'my_folder.MY_EXCEPTIONS': There already exists a table at this path (my_folder.MY_EXCEPTIONS)! |
+-----------------------------------------------------------------------------------------------------------------------------------+Now, in addition to storing the error message in the table, we can raise this exception using the RAISE command:
CREATE TABLE IF NOT EXISTS my_folder.MY_EXCEPTIONS (CAPTURED STRING);
BEGIN
CREATE TABLE my_folder.MY_EXCEPTIONS (CAPTURED STRING);
EXCEPTION
WHEN OTHER THEN
LET LINE := SQLCODE || ': ' || SQLERRM;
INSERT INTO my_folder.MY_EXCEPTIONS VALUES (:LINE);
RAISE;
END;Exception is raised and web ui shows message:
RuntimeError: Failed to create table 'my_folder.MY_EXCEPTIONS': There already exists a table at this path (my_folder.MY_EXCEPTIONS)!
at my_folder.my_script_name (3:3)
at my_folder.my_script_name (2:1)And:
SELECT * FROM MY_EXCEPTIONS;+-----------------------------------------------------------------------------------------------------------------------------------+
| CAPTURED |
+-----------------------------------------------------------------------------------------------------------------------------------+
| undefined: Failed to create table 'my_folder.MY_EXCEPTIONS': There already exists a table at this path (my_folder.MY_EXCEPTIONS)! |
+-----------------------------------------------------------------------------------------------------------------------------------+Use variables in an exception handler
An exception handler can execute code based on the values of a passed variable, or the variable value can be returned in an error message.
Such a variable must be declared in the DECLARE section of the code.
Variables declared in the BEGIN ... END section of the code cannot be used in the EXCEPTION section.
Stored procedure arguments can also be used in an exception handler.
In this example, variable NUMERATOR is used in EXCEPTION section:
DECLARE
NUMERATOR INTEGER;
MY_EXCEPTION EXCEPTION (-20001, 'I have triggered this exception at your request.');
BEGIN
NUMERATOR := 0;
IF (NUMERATOR = 0) THEN
RAISE MY_EXCEPTION;
END IF;
NUMERATOR := NUMERATOR + 1;
RETURN NUMERATOR;
EXCEPTION
WHEN MY_EXCEPTION THEN -- handler for user declared exception
RETURN 'Excp.Type: '|| 'MY_EXCEPTION' || '; NUMERATOR = '|| NUMERATOR::STRING;
END;Excp.Type: MY_EXCEPTION; NUMERATOR = 0As you can see, NUMERATOR is still equal to 0 because an exception was raised before the line of code: NUMERATOR := NUMERATOR + 1;.
In the following example we use argument of the stored procedure in an exception handler, by referencing the argument DIVISOR in the SELECT statement. All arguments of a procedure are accessible in the exception handlers of the procedure:
CREATE OR REPLACE PROCEDURE my_folder.TEST_DIVISOR(DIVISOR INTEGER)
RETURNS STRING
AS
DECLARE
DIVISION_EXCEPTION EXCEPTION (20001, ' Exception: Divisor is 0! ');
QUOTIENT DECIMAL;
BEGIN
IF (DIVISOR = 0) THEN
RAISE DIVISION_EXCEPTION;
END IF;
QUOTIENT := 10 / DIVISOR;
RETURN 'Quotient is: ' || QUOTIENT::STRING;
EXCEPTION
WHEN DIVISION_EXCEPTION THEN
SELECT 'Message ' || :SQLCODE || '; Divisor = ' || :DIVISOR AS MESSAGE;
END;
CALL my_folder.TEST_DIVISOR(0);+----------------------------+
| MESSSAGE |
+----------------------------+
| Message 20001; Divisor = 0 |
+----------------------------+