Death To Decimal Data Errors!
March 17, 2015 Jon Paris
Note: The code accompanying this article is available for download here.
We have all encountered decimal data errors at some time or another. The biggest difficulty they present is that, by the time they have been detected, no recovery is possible. Or to be more precise, no practical recovery is possible. In my previous tip, I mentioned that one of the benefits of data structure I/O is that you can avoid decimal data errors. In this tip I’m going to show you how and why that works.
The code package associated with this tip contains three test programs that demonstrate the different scenarios. The first is a straightforward RPG program with no defenses. It reads a file in a loop and will encounter decimal data errors. The second is intended to show the basic use of DS I/O. It still has errors but they are subtly different. The third program demonstrates how to extend the program to fully defend against such errors. See the Readme.txt file for instructions on how to install the source code on your system.
One factor that adds to the difficulty of handling data decimal errors is that that they may occur on a READ or CHAIN operation, making it difficult to determine exactly which field is in error. This happens because the system detects the error while moving the data from the buffer to the internal variable. When we use DS I/O, the entire record is moved as if it were a large character field. In other words the numeric data is not differentiated. Since numeric fields are not differentiated they can’t cause errors! Let’s walk through the process of running each of the three programs so that you can see the differences between them.
First, here are relevant portions of program DATAERRS1.
FBadData IF E DISK DoU %EOF(BadData); Read BadData; If %EOF(BadData); Leave; EndIf; records += 1; total += amount; date = %Date(numDate: *YMD); EndDo;
If you run this program, you will receive an error when reading the second record. Using F1 to look at the actual details of the error reveals that it occurred on one of the compiler-generated lines associated with the READ. This is even more obvious when you run the program in debug. If you tell the program to go (option G) you will find a similar error occurs on the reading of the third record. In both cases, determining which field is in error is problematic and the only valid option is to cancel the program.
Now run program DATAERRS2.
FBadData IF E DISK D inputData E DS ExtName(BadData: *Input) DoU %EOF(BadData); Read BadData inputData; // Read into DS If %EOF(BadData); Leave; EndIf; records += 1; total += amount; date = %Date(numDate: *YMD); EndDo;
The only difference is that the program uses a DS to receive the record. You will find that while we still get a decimal data error, this time it occurs not on the READ but when we attempt to add the input value to the total. At least this has the advantage of making it easy to determine the field in error. Similarly if we take the G option again, the next error will occur on the attempt to convert the date using the %DATE BIF. Those of you familiar with date processing will realize that since we avoided the error on the READ, we could have defended against any conversion error by using the TEST opcode and conditioning the conversion with the result. We will in fact use that method with the date in the third program.
Talking of the third program, let’s take a look at the changes in the source code that were made to allow us to control the errors.
(A) D inputData E DS ExtName(BadData: *Input) D charAmount 5a Overlay(amount) D charDate 6a Overlay(numDate) DoU %EOF(BadData); Read BadData inputData; // Read into DS If %EOF(BadData); Leave; EndIf; records += 1; (B) Monitor; total += amount; (C) On-Error; // Report error and set default value Dsply ('Record ' + %char(records) + ' - Error in amount (' + charAmount + ')' ) ' ' wait; amount = 0; EndMon; (D) Test(DE) *YMD numDate; // Valid date in YMD form? (E) If Not %Error; date = %Date(numDate: *YMD); // Date OK so use it Else; date = *LoVal; // Set default value on error Dsply ('Record ' + %char(records) + ' - Error in date (' + chardate + ')' ) ' ' wait; EndIf;
At (A) you’ll see the definition of the data structure used to receive the record. Note that because we are using this DS with a READ we need to specify that we want the input fields. I know this doesn’t seem to make an awful lot of sense because we tend to think of the input and output layouts of a record being the same whether we read it or write it. But from RPG’s perspective there can be differences and so we need to be specific.
At (B) we introduced the MONITOR opcode. This is the method that we are going to use to trap the decimal data error on the amount field. When we begin a monitor block we are telling the compiler that we want the opportunity to trap any errors that occur in the operations between the MONITOR itself and the first, or as in this case only, ON-ERROR clause. In this case we have limited this to the single addition operation, but if we wished we could effectively enclose the entire program within such a monitor block.
Because of the MONITOR, when the decimal data error occurs while processing the second record, the compiler will transfer control to the ON-ERROR (C). In this instance we have used a generic ON-ERROR (i.e., it does not reference any specific error conditions) and so it will trap any error that occurs during the addition. For instance, it would have triggered the same response had numeric overflow occurred. If we want to differentiate between the different types of errors that can occur then we need to specify the specific status codes that any specific ON-ERROR is going to deal with. The lines of code immediately following the ON-ERROR are executed when the error is detected. As you can see in this test program, the error is simply reported and a default value of zero used to replace the invalid entry.
I could have used the same approach in handling the invalid date. I chose instead to use the TEST opcode, which you can see at (D). For those of you unfamiliar with this opcode, in the event that it encounters a value in NumDate that is not valid as a YMD format date field, then %Error will be set. This is used as shown at (E) to condition the subsequent date processing.
Why would I use TEST instead of MONITOR? For me it is simply a matter of how likely the error is to occur. If I anticipate that errors in the date will be frequent, then using TEST makes the most sense. On the other hand, if errors are extremely rare, then I would use the MONITOR approach.
For me, one of the nice things about MONITOR is that it allows me to specify the sequence of operations that I expect to happen, uninterrupted by having to anticipate potential errors. In other words when I encounter a MONITOR block in a program I immediately know the sequence of actions that the programmer expected to perform in the normal case. The ON-ERROR blocks identify the possible errors that were anticipated and the corrective action that was taken. In many cases this gives me a far greater insight into the thinking of the programmer in question than any number of comments would give. Of course sometimes a view into somebody else’s thought process can be a scary business.
In summary, DS I/O offers a lot of benefits, not the least of which being that it can make your programs more robust. For those of you still struggling with a legacy that originated in the days of program-described files and who deal daily with decimal data errors, data structure I/O can be a very valuable addition to your tool box.
Jon Paris is one of the world’s most knowledgeable experts on programming on the System i platform. Paris cut his teeth on the System/38 way back when, and in 1987 he joined IBM’s Toronto software lab to work on the COBOL compilers for the System/38 and System/36. He also worked on the creation of the COBOL/400 compilers for the original AS/400s back in 1988, and was one of the key developers behind RPG IV and the CODE/400 development tool. In 1998, he left IBM to start his own education and training firm, a job he does to this day with his wife, Susan Gantner–also an expert in System i programming. Paris and Gantner, along with Paul Tuohy and Skip Marchesani, are co-founders of System i Developer, which hosts the new RPG & DB2 Summit conference. Send your questions or comments for Jon to Ted Holt via the IT Jungle Contact page.