Summary:
See also: Flow Control, Fgl Errors.
If an instructions executes abnormally, the runtime system throws exceptions that can be handled by the program. Actions can be taken based on the class of the exception. There is no way to raise exceptions explicitly; only the runtime system can throw exceptions. Runtime errors (i.e. exceptions) can be trapped by a WHENEVER exception handler or by a TRY / CATCH block. Note that some specific errors cannot be trapped.
There are five actions that can be executed if an exception is raised:
There are four types of exceptions, defining the kind of errors that can occur:
Type | Reason | Examples |
ET_STATEMENT |
Error occurred in a statement. | DISPLAY AT invalid coordinates. |
ET_EXPRESSION |
Expression evaluation error. | Division by zero. |
ET_NOTFOUND |
An SQL statement returns status NOTFOUND. | FETCH when cursor is on last row. |
ET_WARNING |
An SQL statement sets sqlca.sqlawarn flags. | Fetched CHAR value has been truncated. |
The exception classes indirectly define the exception type:
Class | Related Exception Type (defines the error reason) | Default Action |
ERROR (or SQLERROR) | ET_STATEMENT |
STOP |
ANY ERROR (or ANY SQLERROR) | ET_STATEMENT or ET_EXPRESSION |
CONTINUE (1) |
NOT FOUND | ET_NOTFOUND |
CONTINUE |
WARNING | ET_WARNING |
CONTINUE |
Notes:
The WHENEVER instruction defines exception handling in a program module, by associating an exception class with an exception action.
WHENEVER exception-class
exception-action
were exception-class is one of:
{ [ANY] ERROR
| [ANY] SQLERROR
| NOT FOUND
| WARNING
}
and exception-action is one of:
{ CONTINUE
| STOP
| CALL function
| RAISE
| GOTO label
}
The scope of a WHENEVER instruction is similar to a C pre-processor macro. It is local to the module and valid until the end of the module, unless a new WHENEVER instruction is encountered by the compiler.
Next code example shows a typical WHENEVER instruction usage:
01
WHENEVER ERROR CONTINUE02
DROP TABLE mytable -- SQL error will be ignored03
CREATE TABLE mytable ( k INT, c VARCHAR(20) )04
WHENEVER ERROR STOP05
IF SQLCA.SQLCODE != 0 THEN06
ERROR "Could not create the table..."07
END IF
Exception classes ERROR and SQLERROR are synonyms (compatibility issue). The previous example could have used WHENEVER SQLERROR instead of WHENEVER ERROR.
Actions for classes ERROR, WARNING and NOT FOUND can be set independently:
01
WHENEVER ERROR STOP02
WHENEVER WARNING CONTINUE03
WHENEVER NOT FOUND GOTO not_found_handler04
...
For SQL instructions that can potentially generate errors, it is recommended that you define an exception handler locally; errors in the rest of the program can be handled by the default exception handler. See example 2 for more details.
The RAISE option can be used to propagate the error to the caller, which typically traps the error in a TRY/CATCH block.
Note that WHENEVER [ANY] ERROR RAISE is not supported in a REPORT routine.
Any Genero BDL statement in the TRY block will be executed until an exception is thrown. After an exception the program execution continues in the CATCH block. If no CATCH block is provided, the execution continues after END TRY.
If no exception is raised by the statements between the TRY and CATCH keywords, the instructions in the CATCH section are ignored and the program flow continues after END TRY.
The next code example shows a TRY block executing an SQL statement:
01
TRY02
SELECT COUNT(*) INTO num_cust FROM customers WHERE ord_date <= max_date03
CATCH04
ERROR "Error caught during SQL statement execution:", SQLCA.SQLCODE05
END TRY
A TRY block be compared with WHENEVER ANY ERROR GOTO. Here is the equivalent of the above code example:
01
WHENEVER ANY ERROR GOTO catch_error02
SELECT COUNT(*) INTO num_cust FROM customers WHERE ord_date <= max_date03
GOTO no_error04
LABEL catch_error:05
WHENEVER ERROR STOP06
ERROR "Error caught during SQL statement execution:", SQLCA.SQLCODE07
LABEL no_error
The TRY statement can be nested in other TRY statements. In the next example, the instruction in line #5 will be executed in case of SQL error:
01
TRY02
TRY03
SELECT COUNT(*) INTO num_cust FROM customers04
CATCH05
ERROR "Try block 2: ", SQLCA.SQLCODE06
END TRY07
CATCH08
ERROR "Try block 1: ", SQLCA.SQLCODE09
END TRY
The WHENEVER ERROR RAISE instruction can be used module-wide to define the behavior when an exception occurs in a function that is called from a TRY / CATCH block. If an exception occurs in a statement after the WHENEVER ERROR RAISE instruction, the program flow returns from the function and raises the exception as if it had occurred in the code of the caller. If the exception in thrown in the MAIN block, the program stops because the exception cannot be processed by a caller. In the following example, the instruction in line #5 will be executed if an exception occurs in the cust_report() function:
01
MAIN02
TRY03
CALL cust_report()04
CATCH05
ERROR "An error occurred during report execution: ", STATUS06
END TRY07
END MAIN08
09
FUNCTION cust_report()10
WHENEVER ERROR RAISE11
START REPORT cust_rep ...12
...13
END FUNCTION
Note that it is not possible to set a debugger break point at TRY, CATCH or END TRY: The TRY statement is a pseudo statement, the compiler does not generate p-code for this statement.
After executing an SQL statement, you can query STATUS, SQLSTATE, SQLERRMESSAGE and the SQLCA record to get the description of the error. When the statement has been executed with errors, STATUS and SQLCA.SQLCODE contain the SQL Error Code. If no error occurs, STATUS and SQLCA.SQLCODE are set to zero.
You control the result of an SQL statement execution by using the WHENEVER ERROR exception handler:
01
MAIN02
03
DATABASE stores04
05
WHENEVER ERROR CONTINUE06
SELECT COUNT(*) FROM customer07
IF sqlca.sqlcode THEN08
ERROR "SQL Error occurred:", sqlca.sqlcode09
END IF10
WHENEVER ERROR STOP11
12
END MAIN
The SQL Error Codes are not standard. For example, ORACLE returns 903 when a table name does not exist.
By convention, the STATUS and SQLCA.SQLCODE variables always use IBM Informix SQL Error Codes. When using IBM Informix, both STATUS and SQLCA.SQLCODE variables contain the native Informix error code. When using other database servers, the database interface automatically converts native SQL Error Codes to IBM Informix Error Codes. If no equivalent Informix Error Code can be found, the interface returns -6372 in SQLCA.SQLCODE.
If an SQL error occurs when using IBM Informix, the SQLCA variable is filled with standard information as described in the Informix documentation. When using other database servers, the native SQL Error Code is available in the SQLCA.SQLERRD[2] register. SQL Error Codes in SQLCA.SQLERRD[2] are always negative, even if the database server defines positives SQL Error Codes. Additionally, if the target database API supports ANSI SQL states, the SQLSTATE code is returned in SQLCA.SQLERRM.
The NOTFOUND (100) execution status is returned after a FETCH, when no rows are found.
See also: Connections.
Some SQL instructions can produce SQL Warnings. Compared to SQL Errors which do normally stop the program execution, SQL Warnings indicate a minor issue that can often be ignored. For example, when connecting to an IBM Informix database, a warning is returned to indicate that a database was opened, and an other warning might be returned if that database supports transactions. None of these facts are critical problems, but knowing that information can help for further program execution.
If an SQL Warning is raised, SQLCA.SQLCODE / STATUS remain zero, and the program flow continues. To detect if an SQL Warning occurs, the SQLCA.SQLAWARN register must be used. SQLCA.SQLAWARN is defined as a CHAR(7) variable. If SQLCA.SQLAWARN[1] contains the W letter, it means that the last SQL instruction has returned a warning. The other character positions (SQLCA.SQLAWARN[2-8]) may contain W letters. Each position from 2 to 8 has a special meaning according to the database server type, and the SQL instructions type.
If SQLCA.SQLAWARN is set, you can also check the SQLSTATE and SQLCA.SQLERRD[2] registers to get more details about the warning. The SQLERRMESSAGE register might also contain the warning description.
In the next example, the program connects to a database and displays the
content of the SQLCA.SQLAWARN register. When connecting to an IBM Informix
database with transactions, the program will display [WW W ]
:
01
MAIN02
DATABASE stores03
DISPLAY "[", sqlca.sqlawarn, "]"04
END MAIN
By default SQL Warnings do not stop the program execution. To trap SQL Warnings with an exception handle, use the WHENEVER WARNING instruction, as shown in the example below.
01
MAIN02
DEFINE cust_name VARCHAR(50)03
DATABASE stores04
WHENEVER WARNING STOP05
SELECT cust_lname, cust_address INTO cust_name06
FROM customer WHERE cust_id = 10107
WHENEVER WARNING CONTINUE08
END MAIN
The SELECT statement in the above example uses two columns in the select list, but only one INTO variable is provided. This is legal and does not raise an SQL Error, however, it will set the SQLCA.SQLAWARN register to indicate that the number of target variables does not match the select-list items.
See also WHENEVER WARNING exception.
By default, WHENEVER ANY ERROR action is to CONTINUE the program flow. You can force the runtime system to execute the action defined with WHENEVER ERROR exception class with the following FGLPROFILE entry:
fglrun.mapAnyErrorToError = true
When this entry is set to true, ET_EXPRESSION
expression errors
such as a division by zero will be trapped and execute the action defined by the
last WHENEVER ERROR instruction, the default being STOP the
program with error display.
01
-- FGLPROFILE env var is defined to file with:02
-- fglrun.mapAnyErrorToError = true03
MAIN04
DEFINE x INT05
WHENEVER ERROR CALL my_error_handler06
LET x = 1 / 0 -- error handler will be called here07
DISPLAY "It continues...."08
END MAIN09
FUNCTION my_error_handler()10
DISPLAY "Handler: ", STATUS11
END FUNCTION
Some specific Genero runtime errors are not trappable. If such error occurs, neither WHENEVER instructions, nor TRY/CATCH blocks can trap the error, and the runtime system will display the error message to the standard error stream, file a log record if STARTLOG was previously called, and the program is stopped.
Non-trappable errors are typically fatal errors that generally deny further program execution. For example, the errors -1320, -1328 cannot be trapped.
This first code example defines an error handler function called my_error_handler. After connecting to the database, a SELECT statements tries to fetch a row from a table that does not exist, and raises SQL error -217 when connected to Informix:
01
MAIN02
WHENEVER ERROR CALL my_error_handler03
DATABASE stores04
SELECT dummy FROM systables WHERE tabid=105
END MAIN06
07
FUNCTION my_error_handler()08
DISPLAY "Error:", STATUS09
EXIT PROGRAM 110
END FUNCTION
Program output:
01
Error: -217
The code example below shows a typical SQL error handling block from line 9 to 22, it uses WHENEVER ERROR CONTINUE before executing SQL statements, tests the SQLCA.SQLCODE register for errors after each SQL instruction, and resets the default exception handler with WHENEVER ERROR STOP after the set of SQL commands to be controlled:
01
MAIN02
03
DEFINE tabname VARCHAR(50), sqlstmt STRING, rowcount INTEGER04
05
# In the DATABASE statement, no error should occur.06
DATABASE stores07
08
# But in the next procedure, user may enter a wrong table.09
WHENEVER ERROR CONTINUE10
PROMPT "Enter a table name:" FOR tabname11
LET sqlstmt = "SELECT COUNT(*) FROM " || tabname12
PREPARE s FROM sqlstmt13
IF sqlca.sqlcode THEN14
DISPLAY "SQL Error occurred:", sqlca.sqlcode15
EXIT PROGRAM 116
END IF17
EXECUTE s INTO rowcount18
IF sqlca.sqlcode THEN19
DISPLAY "SQL Error occurred:", sqlca.sqlcode20
EXIT PROGRAM 121
END IF22
WHENEVER ERROR STOP24
... (more instructions, stopping the program in case of error)26
END MAIN
Program output in case of invalid table name:
01
SQL Error occurred: -217
This example uses a TRY/CATCH block to trap errors. In this case, we try to connect to an invalid database, which will raise an SQL error and make the program flow go to the line after the CATCH statement:
01
MAIN02
TRY03
DATABASE invalid_database_name04
DISPLAY "Will not be displayed"05
CATCH06
DISPLAY "Exception caught, SQL error: ", SQLCA.SQLCODE07
END TRY08
END MAIN
Program output (with Informix):
01
Exception caught, SQL error: -329
The code below illustrates the fact that a TRY/CATCH block can be used in conjunction with a WHENEVER instruction: The program first executes a WHENEVER ANY ERROR to define an error handler named foo and later it uses a TRY/CATCH block to trap expression errors. In this example, we intentionally force a division by zero. After the TRY/CATCH block we force another division by zero error, which will call the foo error handler:
01
MAIN02
DEFINE i INTEGER03
WHENEVER ANY ERROR CALL foo04
TRY05
DISPLAY "Next exception should be handled by the catch statement"06
LET i = i / 007
CATCH08
DISPLAY "Exception caught, status: ", STATUS09
END TRY10
-- The previous error handler is restored after the TRY - CATCH block11
LET status = 012
DISPLAY "Next exception should be handled by the foo function"13
LET i = i / 014
END MAIN15
16
FUNCTION foo()17
DISPLAY "Function foo called, status: ", STATUS18
END FUNCTION
Program output:
01
Next exception should be handled by the catch statement02
Exception caught, status: -120203
Next exception should be handled by the foo function04
Function foo called, status: -1202
The next example shows the usage of WHENEVER ... RAISE to propagate a potential exception to the caller. First the program defines the foo function as exception handler with WHENEVER ANY ERROR CALL foo, then it calls the do_exception function, which instructs the runtime system to propagate a potential error to the caller. As result, the division by zero in line #13 will be caught by the error handler defined in the MAIN block and call the foo function:
01
MAIN02
DEFINE i INTEGER03
WHENEVER ANY ERROR CALL foo04
DISPLAY "Next function call will generate an exception"05
DISPLAY do_exception(100, 0)06
WHENEVER ANY ERROR STOP -- reset default handler for rest of program07
...08
END MAIN09
10
FUNCTION do_exception(a, b)11
DEFINE a, b INTEGER12
WHENEVER ANY ERROR RAISE13
RETURN a / b14
END FUNCTION15
16
FUNCTION foo()17
DISPLAY "Exception caught, status: ", STATUS18
END FUNCTION
Program output:
01
Next function call will generate an exception02
Exception caught, status: -1202