Discover the Error of Your Ways
Q: How does a program determine errors after calling a UNIX-type API?
— Gene Gaunt
A: The code in Figure 1 shows how to retrieve the error value when calling UNIX-type APIs.
— Carsten Flensburg
H debug
D errno Pr 10i 0
D strerror Pr 128A
**
D fclose Pr extproc('fclose')
D parm * value
D errnbr s 10i 0
D errtxt s 46A noopt
**
C callp fclose (*NULL)
C eval errnbr = errno
C errnbr dsply
C eval errtxt = strerror
C errtxt dsply
C dump
**
C seton lr
**
P errno B
D errno Pi 10I 0
D sys_errno Pr * ExtProc('__errno')
D errnbr S 10I 0 Based(errptr)
D errptr S *
**
C Eval errptr = sys_errno
C Return errnbr
**
P E
**
P strerror B
D strerror Pi 128A
D sys_strerror Pr * ExtProc('strerror')
D 10I 0 Value
D errtxt S 128A Based(txtptr) Noopt
Figure 1: This code fragment finds the error value when you call UNIX-type APIs.
Don’t Be So Quick to Pull the Trigger
Many AS/400 programmers have taken advantage of database triggers to let OS/400 protect their data integrity. For example, they use triggers to validate data when a record is added and to ensure that, if a record is deleted, any other business rules that should be in effect for related records are also enforced. For the most part, this technique works great. However, there is at least one instance when database triggers cannot protect your data. If someone runs Clear Physical File Member (CLRPFM) over a database file being protected by a trigger, that trigger is not fired, because triggers act at the row level, not the file level. That is, the trigger is not activated unless the deletion is performed one row at a time (for example, using the DELETE operation in RPG or pressing F23, the Delete function key, while in the DFU).
— Chuck Pence
Changing the Code Page of AS/400 IFS Files
For those who have used a PC-based tool such as Microsoft Word or Excel or Lotus 1-2-3 to view the contents of an AS/400 Integrated File System (AS/400 IFS) file created using Copy to Import File (CPYTOIMPF), the file probably appeared to contain gibberish. This was probably because the PC-based program expected the file to be ASCII and the file contained EBCDIC characters. One way to remedy this is to set the code page of the AS/400 IFS files to ASCII before using CPYTOIMPF to load the file with data. This translates the data being placed in the file to ASCII. Follow these six steps to create an ASCII code page AS/400 IFS file that can be used as a base to set the code page of new AS/400 IFS files:
1. CRTPF lib/temp rcdlen(1) (Add a record to this file by using a utility program such as DFU.)
2. CD DIR(‘/qdls/foldername’)
3. CPYTOIMPF FILE(lib/temp) TOSTMF(‘temp’) RCDDLM(*CRLF)
4. MOV OBJ(‘temp’) TOOBJ(‘CCSID00367’) TOCODEPAGE(00367)
5. Copy the ASCII code page file to create the output AS/400 IFS file for your CPYTOIMPF command when you first use an output AS/400 IFS file. Issue the following command to replace myASCIIfile.csv with the name of the file you are specifying on the CPYTOIMPF command:
CPY OBJ(‘CCSID00367’) TOOBJ(‘myASCIIfile.csv’)
When you specify your new AS/400 IFS file on the CPYTOIMPF command, the data is translated to ASCII as it is copied to the output AS/400 IFS file.
6. CPYTOIMPF FILE(lib/file) TOSTMF(‘myASCIIfile.csv’) RCDDLM(*CRLF) REPLACE(*YES)
— David Morris
What’s the Deal with INST?
Q: How do you map to a source line number from a job log’s INST column? I know the INST number is hexadecimal. However, I think the hexadecimal number is not a source line number but a compile listing STMT number. I used the Win95 calculator (in Scientific view) to enter the hexadecimal number to convert it to decimal. I then compiled the source to get the STMT numbers, making sure the source change date and time agreed with the program object. I knew I was looking for some kind of I/O function, because the job log message I was trying to track down was about a record lock, but the steps I took did not get me to an I/O source line. The program was not ILE, and the default GENOPT option *NOOPTIMIZE was used.
— Paul Roubekas
A: Here’s one method you can use. The INST column in the job log shows MI statement numbers. Follow these steps to determine the SEU line number:
1. Recompile the program and specify GENOPT(*LIST).
2. Look near the end of the compiler listing for a section of MI code. Look for the INST column, the second column from the left. Find the number in this column that appears in your job log.
3. Look in the far left column on the line matching your instruction number. This is the SEU line number (e.g., 12300).
— Gene Gaunt
Editor’s Note: Of course, you could always just look at the job log. It specifies the line number in error in the message text.
Finding Numeric Values in Packed Fields
Q: I have a program that writes to and updates packed decimal fields. Because of the way the program is structured, I’m not always sure that the data going into that field is valid for the data type. When invalid data goes into the field, it causes problems, such as decimal data errors if other programs try to access the data in that field. Is there a method I can use to test for valid data in packed decimal fields before I write it to a file?
— Edwin Yang
A: TESTN tests only zoned fields. To test a packed field, you could use Check/Check Right (CHECK/CHECKR) to test a character view of the field. Figure 2 shows a program I wrote that checks whether the program parameter contains a valid packed number. Call the program and pass it a packed field, the number of bytes in the packed field, and an “ok” field.
If the “ok” field returns a value of 1, the field contains valid packed data. You can easily modify this little program and include it as either a subroutine or a subprocedure in your programs.
— Barbara Morris
IBM Toronto Lab RPG Compiler Development
* =============================================================== *
* To compile: *
* *
* CRTBNDRPG PGM(xxx/BM000R1) SRCFILE(xxx/QRPGLESRC) + *
* SRCMBR(BM000R1) *
* *
* =============================================================== *
d valBegin c X'+
d 00010203040506070809+
d 10111213141516171819+
d 20212223242526272829+
d 30313233343536373839+
d 40414243444546474849+
d 50515253545556575859+
d 60616263646566676869+
d 70717273747576777879+
d 80818283848586878889+
d 90919293949596979899'
d valLast c x'+
d 0A0B0C0D0E0F+
d 1A1B1C1D1E1F+
d 2A2B2C2D2E2F+
d 3A3B3C3D3E3F+
d 4A4B4C4D4E4F+
d 5A5B5C5D5E5F+
d 6A6B6C6D6E6F+
d 7A7B7C7D7E7F+
d 8A8B8C8D8E8F+
d 9A9B9C9D9E9F'
d lastByte s 1a
c *ENTRY PLIST
c PARM charView 16
c PARM len 5 0
c PARM ok 1
* Check the last byte for digit (0-9) + sign (A-F)
C 1 SUBST charView:len lastByte
c valBegin CHECK lastByte 11=FD
* Check the first n-1 bytes for digits (0-9) in both nibbles
c EVAL len = len - 1
c valLast CHECKR charView:len 10=FD
*
c EVAL ok = NOT (*IN10 OR *IN11)
c IF ok = '0'
c 'invalid' DSPLY
c ELSE
c 'ok' DSPLY
c ENDIF
*
c RETURN
Figure 2: This program tests for numeric values in packed variables.
Separate Test and Production Environments
We often have separate test and production environments with separate copies of application database files. A problem I encounter when running a test or production program against the test database is that test results accidentally go to a production printer, thus confusing users, or that a test run in the test system accidentally triggers an EDI communication. The first problem is caused by the hardcoding of production OUTQs in CL programs, a simple way of getting output to an OUTQ other than the job’s default OUTQ. I added permanent code to the CL programs so the program uses production facilities (such as printers or EDI) if run in production but bypasses these facilities automatically if run in test. The code I added is shown in Figure 3.
In my example, “filename” is the name of a file found in either your test or your production database library. You find the name and type of that library to test whether you are using a test or production library. For this technique to work, you must ensure that all your database libraries are correctly classified as either TEST or PROD. This library type is easy to set:
CHGLIB LIB(library) TYPE(*TEST) or TYPE(*PROD)
Note that IBM has introduced a bit of inconsistency. With Change Library (CHGLIB), you must prefix TEST and PROD with an asterisk, but Retrieve Library Description (RTVLIBD) returns the library type without an asterisk.
— Richard Leitch
DCL VAR(&XCLIB) TYPE(*CHAR) LEN(10)
DCL VAR(&XCTYPE) TYPE(*CHAR) LEN(10)
:
:
RTVOBJD OBJ(filename) OBJTYPE(*FILE) +
RTNLIB(&XCLIB)
RTVLIBD LIB(&XCLIB) TYPE(&XCTYPE)
IF COND(&XCTYPE *EQ PROD) THEN(DO)
OVRPRTF FILE(printfile) OUTQ(production)
ENDDO
ELSE CMD(DO) /* You are in test */
OVRPRTF FILE(printfile) HOLD(*YES)
ENDDO
Figure 3: Add this code to your applications to ensure that your programs run in the correct environment.
Who Hosts That Address?
Looking for a way to determine, via a program, which TCP/IP host name is assigned to which TCP/IP address? File QATOCHOST in library QUSRSYS contains host table entries added using option 10 from the Configure TCP/IP (CFGTCP) menu. QATOCHOST contains IP addresses, host names, and descriptions. You can create a query or RPG program to generate a report of this information, which would be an improvement over the OS/400 TCP/IP Host Table Entries Information report, which you create via F6 from the Work with TCP/IP Host Table Entries panel. You can also use the Client Access file transfer function to download this information into the HOSTS file on your PC.
— Tim Cortez Worley Warehousing, Inc.
Editor’s Note: To the best of our knowledge, this is not a supported interface. Be aware that IBM may rename this file or store this information in another object in a future release of OS/400.
Using SQL to Merge Files
In years past, the RPG cycle and MR indicator allowed RPG programmers to merge the contents of files based on common keys. This technique was commonly known as matching records processing. Lately, however, the RPG cycle (and matching records processing in particular) has fallen out of favor, but the need to merge the contents of files still exists.
For those who have SQL on their AS/400s, there is an alternative to matching records processing. The trick is to use a union to merge data via a left join combined with an exception join. The left join selects all records from the primary file; the exception join picks up unmatched records from the secondary file. Using ORDER BY on the last union presents merged records in order.
You enter the SQL statement as shown in Figure 4. I have used this technique with IBM-supplied output files to match related information. For example, you can merge the output of the Display Object Description (DSPOBJD) command with corresponding Display File Description (DSPFD) *MBR information and use the merged information to generate reports via SQL (see Figure 5).
After you run these two commands, you can run the SQL statement shown in Figure 6 to merge the object and member creation information generated by the commands. The output of the SQL statement is a list of objects and their corresponding source members. You can run this statement interactively or run it within an SQL program. To handle null values returned for unmatched records when this statement is embedded in an SQL program, you must define a null indicator area or surround the field names with an
IFNULL function. For character fields, use IFNULL(FldNam, ‘ ‘); for numeric fields, use IFNULL(FldNam, 0).
— David Morris
SELECT Table1.Column1, Table2.Column2... From Table1
Figure 4: These SQL statements perform the matching record function.
Left Join Table2 On Table1.Column1 = Table2.Column1
UNION ALL
SELECT Table1.Column1, ifnull(Table2.Column2, ' ')... From Table2
Exception Join Table1 On Table1.Column1 = Table2.Column1
Order By Table1.Column1, Table2.Column2
DSPOBJD OBJ(OBJLIB/*ALL) OBJTYPE(*ALL) OUTPUT(*OUTFILE) OUTFILE(QTEMP/OBJ)
DSPFD FILE(SRCLIB/Q*) TYPE(*MBR) OUTPUT(*OUTFILE) OUTFILE(QTEMP/MBR)
Figure 5: This command loads a list of all objects in a given location to an *OUTFILE.
SELECT odlbnm, odobnm, mblib, mbfile, mbname, ... * From obj Left Join mbr On odsrcl = mblib And odsrcf =
mbfile And odsrcm = mbname Where Substr(odobnm,1,1) ¨= 'Q'
UNION ALL Select odlbnm, odobnm, mblib, mbfile, mbname ... From mbr Exception Join obj On mblib = odsrcl
And mbfile = odsrcf And mbname = odsrcm Where mbname ¨= ' ' Order By odlbnm, odobnm, mblib, mbfile,
mbname
Figure 6: These SQL statements merge the object and member creation information generated by the previous commands.
TESTN Negative
If you use the test numeric (TESTN) operation in your RPG programs, you should be aware of how negative numbers are tested. To review, TESTN determines whether a character field contains all numeric data, numeric data with leading spaces, all spaces, or none of the above. When stored in a character field, negative numbers encode the sign into the rightmost character.
Uppercase letters J through R represent -1 through -9, respectively (hexadecimals D1 through D9), and the right brace (}) represents zero in a negative number (hexadecimal D0). Consequently, a field defined as 5a with a value of 1234} becomes -12340 when moved to a numeric field defined as 5.0. This means that your character field turns on the HIGH indicator in a TESTN operation, indicating all numeric data. Furthermore, if the rightmost character is an uppercase letter from A through I (hexadecimals C1 through C9) or the left brace ({) (hexadecimal C0) and all other characters are numeric, this field also tests as all numeric data. Try it yourself. Uppercase letters A through I translate to positive 1 through 9, respectively, and the left brace ({) becomes zero when moved to a numeric field.
Suppose your system receives an electronic data interchange (EDI) purchase order from your trading partner and one of the fields received is an order quantity sent to you in a mutually agreed upon character field. In addition, suppose your batch RPG program always edits this field for actual numeric data before moving the character field to a numeric field, subsequently creating a sales order. Would receiving a value of 1000A cause a problem? A TESTN on this field passes, and congratulations! You’ve just ordered 10,000 widgets for your trading partner when they wanted only 1,000. Imagine the surprise if a shipment actually hits their receiving dock.
Fortunately, the workaround for this particular situation is simple, as shown in the code fragment in Figure 7. Just move your character field left into a bigger character field before the TESTN statement. Make certain that the rightmost character of the bigger field is a number from 0 through 9, and TESTN will work as you expect. Another solution would be to use the CHECK operation to scan explicitly for just the numbers 0 through 9.
— Chris Ringer
* =============================================================== *
* To compile: *
* *
* CRTBNDRPG PGM(xxx/CR000R1) SRCFILE(xxx/QRPGLESRC) + *
* SRCMBR(CR000R1) *
* *
* =============================================================== *
D CharQty s 5a Inz('1000A')
D IntQty s 5s 0
D Char30 s 30a
D Wk0to9 c Const('0123456789')
* Using TESTN HiLoEq
C move *ZEROS Char30
C movel CharQty Char30
C testn Char30 50
C If (*IN50 = *On)
C move CharQty IntQty
C else
* ..not numeric
C endif
* Using CHECK
C Wk0to9 Check CharQty 50
C If (*IN50 = *Off)
C move CharQty IntQty
C else
* ..not numeric
C endif
Figure 7: This code fragment tests for numeric values in negative numbers.
LATEST COMMENTS
MC Press Online