By Craig Pelkie
Synopsis: If your Net.Data macros have programming or run-time errors, by default Net.Data will report the error to you and end the macro. If you handle the errors yourself, you can assign meaningful error messages and continue processing the macro. This article shows some samples of how Net.Data handles macros with errors and describes an error reporting include module that you may want to incorporate in your macros.
Against all hope and experience, many programmers maintain a sunny optimism that most bugs have been shaken out while coding, and the few remaining bugs that manifest themselves at run-time will be easily identified and corrected. In actual practice, it is not too difficult to find and exterminate design-time bugs: you simply need to be sure that you exercise every section of your code. Run-time bugs are another matter entirely; in addition to peculiar failings of program logic that only a user can create, there are also errors that are not always easy to predict or code for, such as hardware and authorization failures. Nevertheless, a bug is a bug, and when fighting back, you want to be armed with all possible information about the bug.
Unfortunately, Net.Data by itself does not provide much help in catching bugs. If you do not include any error handling code in your macros, Net.Data will simply report the bug (as a message displayed in the browser), then end the macro. This is especially irksome while developing a macro; chances are that there is more than one bug in the macro, but you only get to see one at a time.
Fortunately, Net.Data does make it relatively easy to add error handling to your macros. The technique is very similar to the Monitor Message (MONMSG) concept used in AS/400 Control Language (CL) programming. Net.Data lets you indicate which messages you are interested in catching and how you want to handle a particular message. You can also choose to continue or exit the macro, unlike Net.Data’s default action, which is simply to end the macro. Also like CL, you can create a global message handler that applies to all sections of you macro, or scope message handlers to a function block or IF block.
I’ll show some sample Net.Data macros and explain your options for message handling. These samples are not exhaustive, but simply serve to show some techniques that I have found useful in Net.Data programming.
Figure 1 is a typical Net.Data macro under development. It is meant to retrieve and display a single row from the QCUSTCDT file. Also, there is a Net.Data syntax error at the @dtw_assign function.
%{----------------------------------------------------------------------%}
%{ Table1.ndm - Net.Data message handling, example 1. %}
%{----------------------------------------------------------------------%}
%{----------------------------------------------------------------------%}
%{ Define section for the macro. %}
%{----------------------------------------------------------------------%}
%define {
DTW_HTML_TABLE =
"YES"
%}
%{----------------------------------------------------------------------%}
%{ RUNSQL - run the SQL statement, generate default report %}
%{----------------------------------------------------------------------%}
%function(DTW_SQL) RUNSQL() {
select * from
qiws.qcustcdt where cusnum = 111111
%}
%{----------------------------------------------------------------------%}
%{ INPUT - initial section called, runs RUNSQL macro
function %}
%{----------------------------------------------------------------------%}
%html(INPUT) {
<html>
<head>
<title>Net.Data
macro Table1.ndm</title>
</head>
<body>
<center>
<h1>Net.Data
macro Table1.ndm</h1>
@RUNSQL()
@dtw_assign("a")
</center>
<br>
<br>
** END **
</body>
</html>
%}
Figure 1: A simple Net.Data macro that contains no error handling.
When this macro runs, it produces the output shown in Figure 2. Although the default output indicates that the error occurred in the RUNSQL function, the message is not very descriptive if you have not seen it before. You can use the SQLSTATE and SQLCODE values to look up the actual SQL error message. The error is that the requested row is not in the table (the WHERE clause specified an invalid customer number).

Figure 2: Net.Data default error handling, as a result of running the Table1.ndm macro.
Before putting a macro like this into production, you will probably want to improve the message. Also, there is no indication from Net.Data that there is anything else wrong with the macro; the invalid usage of @dtw_assign will not be reported until the invalid customer number is corrected.
Figure 3 shows a revised version of the same Net.Data macro. The difference is that there is now a global message handling block near the beginning of the macro. Inside the message block, the SQLSTATE 02000 condition is specifically handled. When this version of the macro runs, the output shown in Figure 4 is displayed. You now have a more meaningful error message and you also have the syntax error for the invalid @dtw_assign reported.
%{----------------------------------------------------------------------%}
%{ Table2.ndm - Net.Data message handling, example 2. %}
%{----------------------------------------------------------------------%}
%{----------------------------------------------------------------------%}
%{ Define section for macro %}
%{----------------------------------------------------------------------%}
%define {
DTW_HTML_TABLE = "YES"
%}
%{----------------------------------------------------------------------%}
%{ Message section for macro %}
%{----------------------------------------------------------------------%}
%message {
SQLSTATE : 02000 :
"<b>Customer number not in file.</b><br>" :
Continue
%}
%{----------------------------------------------------------------------%}
%{ RUNSQL - run the SQL statement, generate default report %}
%{----------------------------------------------------------------------%}
%function(DTW_SQL) RUNSQL() {
select * from
qiws.qcustcdt where cusnum = 111111
%}
%{----------------------------------------------------------------------%}
%{ INPUT - initial section called, runs RUNSQL macro
function %}
%{----------------------------------------------------------------------%}
%html(INPUT) {
<html>
<head>
<title>Net.Data
macro Table2.ndm</title>
</head>
<body>
<center>
<h1>Net.Data
macro Table2.ndm</h1>
@RUNSQL()
@dtw_assign("a")
</center>
<br>
<br>
** END **
</body>
</html>
%}
Figure 3: A revised version of the macro that uses a message block for the SQLSTATE error.

Figure 4: The Net.Data macro now displays the user-defined error message and the Net.Data message for the function.
To understand how the message block works, look at the line of code beginning SQLSTATE. To monitor a message in Net.Data, you can specify one of the following values:
· The return code from the error. In the case of the SQL statement, the return code is the SQLCODE value of 100 (see Figure 2). In the case of the @dtw_assign function, the return code value is 1003 (see Figure 4). If you want, you can specify those particular return code values to set up specific message handlers for those errors.
· SQLSTATE : state_id. This is the option used in the macro code shown in Figure 3. In this sample, the actual SQLSTATE is being handled, rather than the SQLCODE. I will have more to say about this later.
· +default or ‑default. You can also enter the value +default (or just default) or ‑default. The +default means that any positive return code value that is not otherwise specifically handled will be handled. The ‑default is a handler for negative return codes. The default handlers are similar to using MONMSG CPF0000 in a CL program.
Following the error message identifier, you code a colon (:) character, usually followed by the text that you want to use for the message. Net.Data provides a great deal of flexibility with the text; rather than just simple text, you can include HTML code or a call to a Net.Data function that will generate either text or HTML. The value specified at this section of the message handler is what is sent back to the browser.
You complete a message handler with an optional continue or exit clause. As shown in the sample in Figure 3, the clause is separated from the message text with another colon, followed by the word continue or exit. If you do not specify continue, Net.Data exits the macro by default.
Now look back at Figure 4. You can see that the message “Customer number not in file” is produced from the message block where SQLSTATE 02000 is handled. The Net.Data error in Figure 4 is produced when the invalid @dtw_assign statement is encountered. Because there is no specific message handler for error 1003, Net.Data’s default message handling is used, which reports the error then exits the macro. You can tell that the macro was exited when the invalid statement was encountered because the text ** END ** (near the bottom of the INPUT section in the macro) was not written to the browser.
If you are not that familiar with SQL, you are probably wondering where the values of SQLCODE and SQLSTATE are described. The IBM manual DB2 UDB for AS/400 SQL Programming Concepts describes the codes and states in Appendix B. If you do not have that manual available to you, you can go to the AS/400 Information Center at IBM’s AS/400 web site and use the on-line version of the manual.
When you run any SQL statement, the SQL processor updates the values of SQLCODE and SQLSTATE. You can monitor either one or the other, but there are some instances where the SQLSTATE is used to further describe the SQLCODE. For example, an SQLCODE value of zero means that the statement was processed successfully. The SQLSTATE values are used to describe any warnings that are associated with the successful completion. For example, you will get SQLCODE value of 0 with SQLSTATE 01504 if you run an UPDATE or DELETE statement without a WHERE clause.
There are 36 positive SQLCODE values defined, which may be recoverable warnings or errors. For example, SQLCODE 100 and SQLSTATE 02000 are used to define the row not found error (in RPG terms, this would be the equivalent of the not found indicator or built-in function). You will probably want to specifically handle that SQL condition so that you can provide a meaningful response to your users, rather than let Net.Data generate the mystifying message shown in Figure 2. For the rest of the positive SQLCODE values, you might want to create a default message handler.
There are several hundred negative SQLCODE values. These values are generally considered severe errors, which you will probably not be able to recover from at run-time. The best approach for those errors is to gather as much information as possible so that you can recreate the error, then correct the macro or the condition causing the error. You will probably want to create a ‑default error handler and include it in your macros.
In addition to the SQLCODE and SQLSTATE values, there are over 40 Net.Data error codes. For example, Net.Data error code 1003 shown in Figure 4 is issued for an incorrect number of parameters error, in this case for the @dtw_assign function. You can get the list of Net.Data error codes at http://www.iseries.ibm.com/netdata in the “Library” link. As with the SQL errors, you may be able to recover from some of the Net.Data errors at run-time. Other errors will require you to recode your macro.
When you specify a message handler of the format
return_code : message : continue
the return_code value can be either an SQLCODE value or a Net.Data error value. Although it looks like there are no duplicates between SQLCODE and Net.Data, it is probably a better idea to specify your SQL message handlers in the format
SQLSTATE : sql_state : message : continue
where the keyword SQLSTATE is used to explicitly identify the message handler as pertaining to an SQLSTATE.
In addition to the message handling text that you can code into your macros, you can also access several Net.Data built-in variables that return current status information about your macro. You can also access HTTP server environment variables to give you the complete picture of how your macro is being processed.
Because I decided that I wanted a standardized way to work with all of the available information, I created a Net.Data include file that I routinely put into my macros. A sample of using the include is shown in Figure 5, which is a further revision of the previous macros. In place of the global message block, I now use the Net.Data %include directive to include the ErrorMsg_Block.ndm file, which is shown in 6. The ErrorMsg_Block.ndm file includes the ErrorMsg_Table.ndm file (Figure 7), which is responsible for generating the HTML to display the complete information. Samples of the complete information generated by ErrorMsg_Table.ndm are shown in Figures 8 and 9.
%{----------------------------------------------------------------------%}
%{ Table3.ndm - Net.Data message handling, example 3. %}
%{----------------------------------------------------------------------%}
%{----------------------------------------------------------------------%}
%{ Define section for macro %}
%{----------------------------------------------------------------------%}
%define {
DTW_HTML_TABLE =
"YES"
%}
%include "ErrorMsg_Block.ndm"
%{----------------------------------------------------------------------%}
%{ RUNSQL - run the SQL statement, generate default report %}
%{----------------------------------------------------------------------%}
%function(DTW_SQL) RUNSQL() {
select * from
qiws.qcustcdt where cusnum = 111111
%message {
SQLSTATE : 02000 :
"<b>Customer number not in file.</b><br>" :
Continue
%}
%}
%{----------------------------------------------------------------------%}
%{ INPUT - initial section called, runs RUNSQL macro
function %}
%{----------------------------------------------------------------------%}
%html(INPUT) {
<html>
<head>
<title>Net.Data
macro Table3.ndm</title>
</head>
<body>
<center>
<h1>Net.Data macro
Table3.ndm</h1>
@RUNSQL()
@dtw_assign("a")
</center>
<br>
<br>
** END **
</body>
</html>
%}
Figure 5: Another version of the macro that uses the ErrorMsg_Block.ndm include file and defines a block-specific message handler.
%{-----------------------------------------------------------------%}
%{ ErrorMsg_Block.ndm -- default error message block %}
%{ Provides default message handlers for Net.Data and SQL
errors %}
%{ %}
%{ Usage: %}
%{ %}
%{ %include
ErrorMsg_Block.ndm %}
%{-----------------------------------------------------------------%}
%include "ErrorMsg_Table.ndm"
%{-----------------------------------------------------------------%}
%{ Global message block section %}
%{ -default -- SQL
errors
%}
%{ +default --
SQL/Net.Data warnings %}
%{-----------------------------------------------------------------%}
%message {
+default : { @ErrorMsg()
%} : Continue
-default : {
@ErrorMsg() %} : Exit
%}
Figure 6: The ErrorMsg_Block.ndm include file defines a global message block for a macro.
%{-----------------------------------------------------------------%}
%{ ErrorMsg_Table.ndm -- display error message table,
showing %}
%{ current values of Net.Data macro variables. %}
%{ %}
%{ Usage: %}
%{ %}
%{ %include
ErrorMsg_Table.ndm %}
%{ %message {
%}
%{ -default: {
@ErrorMsg() % } : Exit %}
%{-----------------------------------------------------------------%}
%{-----------------------------------------------------------------%}
%{ Make an error message table row %}
%{-----------------------------------------------------------------%}
%macro_function MakeErrorMsgTR (rowCaption,
rowLabel,
rowValue) {
<tr
bgcolor="ivory">
<td>$(rowCaption)</td>
<td>$(rowLabel)</td>
<td>$(rowValue)</td>
</tr>
%}
%{-----------------------------------------------------------------%}
%{ Make an environment variable table row %}
%{-----------------------------------------------------------------%}
%macro_function MakeEnvVarTR (rowCaption,
rowValue) {
<tr
bgcolor="ivory">
<td
colspan="2">$(rowCaption)</td>
<td>$(rowValue)</td>
</tr>
%}
%{-----------------------------------------------------------------%}
%{ Output an HTML message page %}
%{-----------------------------------------------------------------%}
%macro_function ErrorMsg () {
<br>
<br>
<!--------------------------------------------------------------
Begin table for
message display
--------------------------------------------------------------->
<table bgcolor="navy"
border="0">
<tr
bgcolor="silver">
<td
colspan="3">
<font
size="+1" color="navy">
Error
when processing Net.Data macro $(DTW_MACRO_FILENAME)
</font>
</td>
</tr>
<tr
bgcolor="mintcream" align="center">
<td><b>Description</b></td>
<td><b>Net.Data Variable</b></td>
<td><b>Value</b></td>
</tr>
<!-------------------------------------------------------------
Display
Net.Data built-in variables
-------------------------------------------------------------->
@MakeErrorMsgTR("Return Code",
"RETURN_CODE",
$(RETURN_CODE))
@MakeErrorMsgTR("SQL State",
"SQL_STATE",
$(SQL_STATE))
<!-------------------------------------------------------------
Display HTTP
server environment variables
-------------------------------------------------------------->
<tr
bgcolor="mintcream" align="left" >
<td
colspan="3"><b>Environment Variables</b></td>
</tr>
@MakeEnvVarTR("AUTH_TYPE",
@dtw_rgetenv("AUTH_TYPE"))
@MakeEnvVarTR("CGI_ASCII_CCSID",
@dtw_rgetenv("CGI_ASCII_CCSID"))
</table>
%}
Figure 7: The ErrorMsg_Table.ndm file displays the Net.Data variables and HTTP server environment variables. (Note: this is a partial listing, download the complete file from http://www.web400.com)
The ErrorMsg_Block.ndm file also defines the global message handler for the macro that it is included in. The global message handler simply captures all of the positive or negative messages and directs handling to the @ErrorMsg function, which is defined in ErrorMsg_Table.ndm.
When you use the code shown in the samples, you can override message handling for a particular block by including the specific message handler in the block itself. For example, in Figure 5 the RUNSQL function includes a specific message handler for the SQLSTATE 02000. When you run the code shown in Figure 5, the browser looks like Figure 8. You can see the message from the SQLSTATE 02000 message handler, followed by the output of the ErrorMsg_Table.ndm, reporting the problem with the @dtw_assign function.
Figure 8 shows the values of the Net.Data built-in variables. The figure shows the names of the variables. You can see where the variables are used in Figure 7.

Figure 8: The Net.Data variables are displayed when an error is encountered.
Figure 9 shows the values of the HTTP server environment variables. Those values are documented in the HTTP server manuals, not the Net.Data manuals. In addition to the environment variables shown, there is also a set of environment variables for Secure Sockets Layer (SSL) processing, which are not shown in the figures.

Figure 9: The HTTP server environment variables are displayed when an error is encountered.
One of the primary debugging techniques you can use with the information shown in Figures 8 and 9 is to check that the macro that was executed is actually the macro that you meant to execute. You can check the current file name and last modified values to make sure you have the correct version. If things don’t seem right, you can use the environment variables to determine exactly how the HTTP server selected the macro, using the PATH_INFO and PATH_TRANSLATED values.
The code in the ErrorMsg_Table.ndm include file (Figure 7) is actually trivial. It consists of two “helper” functions, MakeErrorMsgTR and MakeEnvVarTR, to make table rows for error message and environment variable values.
The ErrorMsg function is called from the global message handler defined in ErrorMsg_Block.ndm. This function simply works through the list of all of the Net.Data built-in variables, the HTTP environment variables, and the SSL environment variables.
For the sake of space, only a short amount of the actual code in ErrorMsg_Table.ndm is shown here. You can download all of the code from http://www.web400.com.
Before using the include files, there is one additional step you should take. If you look at the %include directives in Figures 5 and 6, you will notice that they simply specify the name of the file to include, not the path where the file is located. To use those directives, you need to include the statement
INCLUDE_PATH /path_name
in your Net.Data INI file. The INI file is in the library where your copy of the DB2WWW program is located. Add the INCLUDE_PATH statement to source member DB2WWW in the INI file. In place of path_name, substitute the actual directory in the Integrated File System (IFS) where the ErrorMsg_Block.ndm and ErrorMsg_Table.ndm files are located.
If you don’t add the INCLUDE_PATH statement to your INI file, you can in fact specify the %include directive like this:
%include “/nd01/ErrorMsg_Block.ndm”
where /nd01 is a sample path name in the IFS. However, this is not a good practice, since you end up with a hardcoded path name in your macros. If you ever change the directories, you will need to go back and recode your macros.
Although Net.Data can be a very productive Web development tool to work with, it is not very helpful when you do encounter errors, either when coding or at run-time. Rather than rely on the relatively terse Net.Data error messages, you should take steps to provide more information to yourself or your users. Using the techniques described in this article and the sample error reporting code, you will be better able to diagnose and correct Net.Data errors when they occur.
Craig Pelkie is a programmer based in Southern California. His primary interests are programming Web solutions using WebSphere, Net.Data and other environments, not necessarily on the AS/400 system. You can view other articles by him at http://www.web400.com.