By Craig Pelkie
Copyright © 2001, Craig Pelkie
ALL RIGHTS RESERVED
Overview: One of the features that makes Net.Data easy to work with is that it automatically creates and processes a table (result set) when you run an SQL SELECT statement. In some cases, it may be difficult to get and format the data you want using SQL. Using a Net.Data table variable, you can fill the table in your RPG program with data and return it for processing to your Net.Data macro.
If you’re an AS/400 programmer working with Net.Data, chances are you also know and use RPG or COBOL. You’re probably aware that you can directly call an RPG or COBOL program from Net.Data, passing it Net.Data variables and getting results back in a parameter list. The parameter passing technique works well for relatively simple functions, but is not that good an option when you need to pass a lot of data back to the Net.Data macro.
Using a set of Net.Data functions that are available in an AS/400 service program, you can access a Net.Data table variable in an RPG or COBOL program. You can use functions to set the number of rows and columns in the table, set or alter column headings, set or alter values in each table cell, and determine the characteristics of the table. After formatting the table with the required data, you can return the table variable to the Net.Data macro, which can then process the table using Net.Data table manipulation functions.
It is probably advantageous to code your entire Web application in Net.Data if you can, so that you don’t have to maintain both the Net.Data macro and the called program. However, there are some cases where it might be more convenient to have a program work with and return a table to Net.Data:
· The business logic you need to get data from your database can be more easily expressed in native file operations, rather than SQL in Net.Data. This is particularly the case where a row of data is made up of values from several files, or the row values are the result of calculations on data from the files.
· You want to perform edit-word type editing on row values, above and beyond the formatting provided by Net.Data. Although you can use substring operations in Net.Data to patch together edited fields, that technique is very cumbersome compared to the simplicity of using an ILE RPG built-in function such as %editw (edit a field using an edit word).
· Your data is most easily represented in an array. Net.Data does not have an array construct, although you can use Net.Data string and word functions to emulate an array. If you use a table, you can represent array data in a row or column of the array. The table structure gives you a two-dimensional array construct to work with.
In my experiments, I found that I needed to define the table in Net.Data before calling an ILE RPG program to work with the table. Because the Net.Data macro calls the RPG program, Net.Data is considered to “own” the memory used by the table. After defining the table variable, I can call the RPG program and pass the table variable to the program as an input/output parameter.
Once in the RPG program, I can use any of the 16 Net.Data Language Environment Interface (LEI) function calls. The functions are provided in AS/400 service program (object type *SRVPGM) QHTTPSVR/QTMJLE. Table 1 is a list of the function names and a description of the functions.
|
Function |
Description |
|
dtw_table_AppendRow |
Add one or more rows to the end of the table. |
|
dtw_table_Cols |
Return current number of columns in the table. |
|
dtw_table_Delete |
Delete the table. |
|
dtw_table_DeleteCol |
Delete one or more columns beginning at the specified column. |
|
dtw_table_DeleteRow |
Delete one or more rows beginning at the specified row. |
|
dtw_table_GetN |
Retrieve a column heading. |
|
dtw_table_GetV |
Retrieve a value from the table. |
|
dtw_table_InsertCol |
Insert one or more columns after the specified column. |
|
dtw_table_InsertRow |
Insert one or more rows after the specified row. |
|
dtw_table_MaxRows |
Return maximum number of rows allowed in the table. |
|
dtw_table_New |
Create a new table. |
|
dtw_table_QueryColnoNj |
Return column number associated with a column heading. |
|
dtw_table_Rows |
Return current number of rows in the table. |
|
dtw_table_SetCols |
Set number of columns for a table. |
|
dtw_table_SetN |
Assign a name to a column heading. |
|
dtw_table_SetV |
Assign a value in a table. |
Table 1: Functions provided in service program QHTTPSVR/QTMJLE.
When the RPG program is called, the table passed to it is “formless”. There are no rows, columns, column headers or values in the table. Before I can put anything into the table, I set the number of columns in the table and add a row to the table. I then set the column heading values and put values into individual table cells by specifying the value, the row and the column.
All values that are put into table cells must be null-terminated character strings. If you have numeric fields that you want in the table, you must convert them to a null-terminated character string.
Upon returning from the program, the formatted table is available to the Net.Data macro. At that point, you can let Net.Data display the table automatically by using default report processing, or use the Net.Data table functions to work with the table in the Net.Data macro.
Figure 1 shows a sample of the Net.Data macro in the browser. The macro displays the 1-row table that was created and filled in the RPG program. The Net.Data macro is called conventionally; there is nothing special you need to do in terms of HTTP server configuration or the URL to invoke a macro that calls an RPG program.

Figure 1: The sample table returned from the RPG program.
%{-------------------------------------------------------------%}
%{
TBTEST.ndm -- Net.Data table processing. Call RPG program %}
%{
to fill in values for table. %}
%{ %}
%{
Copyright (c) 2001, Craig Pelkie
%}
%{
ALL RIGHTS RESERVED %}
%{-------------------------------------------------------------%}
%{-------------------------------------------------------------%}
%{
Section A - Define section for the macro %}
%{-------------------------------------------------------------%}
%define
{
DTW_HTML_TABLE = "YES"
tbtest = %table
%}
%include
"ErrorMsg_Block.ndm"
%{-------------------------------------------------------------%}
%{
Section B - GETTABLE - get data for Net.Data table %}
%{-------------------------------------------------------------%}
%function(DTW_DIRECTCALL)
GETTABLE(inout dtwtable tbtest) {
%EXEC { /QSYS.LIB/NETDATA.LIB/TBTEST.PGM
%}
%}
%{-------------------------------------------------------------%}
%{
Section C - INPUT - initial section called %}
%{-------------------------------------------------------------%}
%html(INPUT)
{
<html>
<head>
<title>Net.Data macro TBTEST.ndm</title>
</head>
<body>
<center>
<h1>Net.Data macro
TBTEST.ndm6</h1>
@GETTABLE(tbtest)
</center>
</body>
</html>
%}
Net.Data macro tbtest.ndm shown above contains three sections of code:
The code in this section indicates that the default report is to be displayed using HTML table tags, and that the variable named “tbtest” is a Net.Data table variable.
This section uses the DTW_DIRECTCALL language environment to call RPG program TBTEST. The name of this section is GETTABLE, and it is invoked in Section C (the INPUT section). The table variable tbtest is passed to the called program as an input/output parameter of type dtwtable. It is important that the parameter type be specified as dtwtable so that Net.Data will properly pass the table variable to and receive the table variable from the called program.
You can pass up to 50 parameters to a program from a Net.Data macro. You can mix parameter types as required; for example, you may want to pass additional parameters from the Net.Data macro along with the Net.Data table variable. You can also pass multiple Net.Data table variables if necessary.
This is the initial section of the macro called from the invoking URL (see the Address entry in the browser, Figure 1). Because this is defined as an %html section, Net.Data emits the HTML inside this section and returns it to the browser. The @GETTABLE(tbtest) line of code invokes the GETTABLE section, passing it the name of the Net.Data table variable.
*************************************************************
* TBTEST - sample RPGLE program showing
the following:
*
*
- receive Net.Data table variable as input parameter
*
- add 1 row, 3 columns to table variable
* - assign column
headers to the 3 rows
*
- put edited values into the row
*
- return table variable to Net.Data
*
* Program compile commands:
*
*
crtrpgmod module(netdata/tbtest)
*
srcfile(netdata/qrpglesrc)
*
*
crtpgm pgm(netdata/tbtest)
*
bndsrvpgm(qhttpsvr/qtmjle)
*************************************************************
* Copyright (c) 2001, Craig Pelkie
* ALL RIGHTS RESERVED
* craig@web400.com
*************************************************************
D*************************************************************
D* dtw_table_AppendRow - add one or more
rows to end of table
D*************************************************************
Ddtw_table_AppendRow...
D pr
10I 0 extproc('dtw_table_AppendRow')
D
table *
D
rows 10I
0 value
D*************************************************************
D* dtw_table_SetCols - set number of
columns for a table
D*************************************************************
Ddtw_table_SetCols...
D pr 10I 0 extproc('dtw_table_SetCols')
D
table *
D
cols 10I
0 value
D*************************************************************
D* dtw_table_SetN - assign a name to a
column heading
D*************************************************************
Ddtw_table_SetN...
D pr
10I 0 extproc('dtw_table_SetN')
D
table *
D
src
* value options(*string)
D
col 10I
0 value
D*************************************************************
D* dtw_table_SetV - assign a value in a
table
D*************************************************************
Ddtw_table_SetV...
D pr
10I 0 extproc('dtw_table_SetV')
D
table *
D
src
* value options(*string)
D
row 10I
0 value
D
col 10I
0 value
D*************************************************************
D* SetTable - set a table column heading
or table value
D*************************************************************
DSetTable...
D pr
D
table *
D
value
256 value varying
D
type
1 value
D
col 10I
0 value
D
row 10I 0 value options(*nopass)
D*************************************************************
D* Work fields and Constants for testing
D*************************************************************
D amtField s 11
D charField s 20
D dateField s 8
D rc s
10I 0
D table s *
D COLHDG1 c
'Amount Field'
D COLHDG2 c 'Character Field'
D COLHDG3 c
'Date Field'
*************************************************************
* Receive table variable from Net.Data
macro
*************************************************************
C
*entry plist
C parm table
*************************************************************
* Create a 1 x 3 (rows x columns) table
*************************************************************
C eval
rc = dtw_table_SetCols(table : 3)
C eval
rc = dtw_table_AppendRow(table : 1)
*************************************************************
* Set column headings
*************************************************************
C callp
SetTable(table : COLHDG1: 'N' : 1)
C callp
SetTable(table : COLHDG2: 'N' : 2)
C callp
SetTable(table : COLHDG3: 'N' : 3)
*************************************************************
* Set values for each column
*************************************************************
C eval
amtField = %editw(12345.67 : '$ ,
0. ')
C eval
charField = 'Test Net.Data tables'
C eval
dateField = %editc(udate: 'Y')
C callp
SetTable(table : amtField : 'V'
: 1 : 1)
C callp
SetTable(table : charField : 'V' : 2 : 1)
C callp
SetTable(table : dateField : 'V' : 3 : 1)
C eval
*inlr = *on
C*************************************************************
C* SetTable - set a table column heading
or table value
C*
C*
table - Net.Data table to work with
C*
value - column heading or column value
C*
type - type of function call
C* N = dtw_table_SetN
C* V = dtw_table_SetV
C*
col - column number to work
with
C*
row - row number to work with
C*************************************************************
PSetTable b
D pi
D
table *
D
value
256 value varying
D
type
1 value
D
col 10I
0 value
D
row 10I 0 value
D options(*nopass)
D n s
5 0
D ptr s
*
D rc s
10I 0
C eval
n = %len(value) + 1
C alloc
n ptr
C eval
%str(ptr : n) = value
C if
type = 'N'
C eval
rc = dtw_table_setN(table : ptr : col)
C endif
C if
type = 'V'
C eval
rc = dtw_table_setV(table : ptr : row : col)
C endif
C dealloc ptr
PSetTable e
RPG program TBTEST shown above is called from the Net.Data macro.
The first four sets of D-specs are used to define function prototypes for the Net.Data Language Environment Interface functions used in the program. These functions are documented by IBM in the IBM Net.Data Language Environment Interface Reference manual, which is available for on-line viewing or download at http://www.iseries.ibm.com/netdata in the documentation section. The documentation shows the C programming language definitions.
Each of the functions used in the program returns an integer value, which is defined in RPG as an “I” type field of length 10. Each function also uses the Net.Data table variable that was passed to the RPG program on the call from the Net.Data macro. The table is defined as a pointer. The pointer points to the area of memory that is “owned” by Net.Data and associated with the name of the table variable. Numeric parameters used in the functions are also integers.
Following the function prototypes, there is an additional function prototype for the SetTable program defined function (described below) and D-specs for data fields used in the program.
The *ENTRY PLIST that opens the program defines the RPG variable “table”, which was defined as a pointer variable. By accepting this variable into the program, the RPG program now has access to the Net.Data table.
The first two eval statements in the program are used to set the number of columns in the table and to append a row to the end of the table. The functions dtw_table_SetCols and dtw_table_AppendRow each take the pointer variable “table” as their first parameter, followed by an integer value. The number of columns in the table is set to three. The number of rows appended to the table is one.
Note that the column headings row does not count as one of the data rows. The table is assumed to have a “row” that is used for column headings, which is distinct from rows used to contain data values.
The next section of code is used to set the column heading values. The SetTable program defined function is called to set the column heading values, using the values defined as named constants (COLHDG1).
In this section, I first set the values for the work fields I am using in the program. I use editing functions to prepare the values, then call the SetTable function to insert the values into the table.
Because the two Net.Data LEI functions dtw_table_SetN and dtw_table_SetV require similar processing, I created the SetTable function. The parameters are described in the program comments.
Both of the LEI functions require a null-terminated character string to insert as either a column heading or table cell value. The null-terminated character string itself is not passed to the LEI functions, instead, a pointer to the null-terminated character string is used. To create the null terminated string and the pointer, I first had to calculate the length of the value passed to the SetTable function (either a column heading or a cell value) and add one to that length. The extra byte is used for the null character.
After determining the length, I use the RPG ALLOC operation code to allocate length number of bytes of storage. The storage is pointed to by a pointer variable. After allocating the memory, I use the ILE RPG %str built-in function to set the value of the allocated memory to the null-terminated character string. At this point, I have satisfied the LEI function requirements of having a null-terminated character string and a pointer that points to the value.
I then call either the dtw_table_SetN or dtw_table_SetV functions, depending upon the value of the type parameter passed to the SetTable function. The dtw_table_SetN function takes as its parameters the Net.Data table (pointer), the pointer to the column heading null-terminated character string, and the column number that the heading should appear in. The dtw_table_SetV function takes the same parameters, with the addition of a row parameter to indicate which row the table cell is in.
Following the function calls, I use the dealloc operation to deallocate the work memory that is pointed to by the pointer variable.
You’ve now seen a sample of how you can extend the power of Net.Data by using calls to your own programs. By using the techniques shown to pass a Net.Data table variable to your program, assign column headings and data values, and return the table to Net.Data, you can have complete control over the content of each table cell.
Craig Pelkie is an independent consultant based in Southern California. He frequently speaks at AS/400 seminars and user group meetings, and provides consulting services for companies using Net.Data, WebSphere, and Microsoft Active Server Pages. Craig can be contacted at craig@web400.com or at his Web site at http://www.web400.com, where you can also download a complete set of Net.Data LEI function prototypes for ILE RPG.