Writing new records is a standard task in any system, and this article makes one of the steps of that process a whole lot easier.
One of the most critical functions of any business application is writing data to the database, which not coincidentally is one of the things that RPG does best. The whole concept of an externally described file is incredibly powerful: the ability to store data in individual fields and leave the aggregation to the compiler makes data generation a quick and simple task. However, even good techniques can still get better, and in this article, I'll show how a couple of keywords can be combined in a way that can shave precious time off of your development.
The Anatomy of a Database Write
Let's take a look at the process: in this case, I think we should write to a customer order file. I'm going to skip a lot of the detail and jump straight to the meat of the issue. We'll start with an excerpted version of our customer order detail file:
ODRCID 2A TEXT('Record ID - OD')
ODORD 10S TEXT('Order Number')
ODLINE 4S 0 TEXT('Line Number')
ODSTS 2A TEXT('Line Status')
ODITEM 15A TEXT('Item Number')
ODQORD 9S 3 TEXT('Quantity Ordered')
ODQSHP 9S 3 TEXT('Quantity Shipped')
ODQPCK 9S 3 TEXT('Quantity Picked')
ODUPRC 9S 4 TEXT('Unit Price')
ODENTS Z TEXT('Entry Timestamp')
ODENUS 10A TEXT('Entry User')
ODENPG 10A TEXT('Entry Program')
This is just a selection of the fields, with some basics. For example, I'm showing the item and quantity fields (three quantities, in fact: ordered, shipped and picked) and the price. I'm showing the key fields of order and line, as well as a number of status and maintenance fields, including the record ID, the status, and the entry information (timestamp, user, and program). I know that the idea of a record ID is a bit quaint, but some of us old-timers still put them in. It's just a good way to know that the record didn't come from some completely off-the-wall source (can you say ODBC?).
Now, let's take a look at a program that writes a dummy record to the file. This won't be close to a complete program; in fact, it will only set a few fields and write a single record to the database. But how it sets those fields is the subject of this article.
fORDDTL o e disk
Here's the file specification that defines the ORDDTL file, the same file shown above. I define the file as an output-only file, externally described.
d C_ODRCID c 'OD'
d C_ORDSTS_OPEN c '00'
d C_PGMNAME c 'OE0300'
Next are some named constants. I use named constants a lot, and I have gotten into the habit of naming them a certain way: I use all caps in the name, and I prefix the name with C_ to indicate a constant. I'm not afraid to make the names a little longer if a chance of ambiguity exists. C_ORDSTS_OPEN would live alongside C_ORDSTS_SHIPPED and C_ORDSTS_CANCELED, most likely in a copy file. But I wanted to show you the constants here for a reason, which will become obvious in just a moment.
d dsORDDTL e ds extname(ORDDTL:*output)
d qualified inz
This is how you set up a data structure to write to an externally described file. Specify the filename on the extname keyword along with the option *output, which tells the compiler to set up a data structure that matches the output buffer for the file. This program requires the output buffer because I'll be doing a write to the file. Input and output buffers on most files are the same, but the requirement still exists. I've also set up the data structure to be qualified, which allows me to have multiple data structures with the same field name. This is important if you have to do I/O to different logicals of the same file or if you have a shop where the same field name is shared among different files. Finally, I added the inz keyword, which initializes all the fields in the data structure. If I didn't do this, any field that I did not explicitly set would have blanks in it, which is painful for packed fields.
d ODRCID e inz(C_ODRCID)
d ODSTS e inz(C_ORDSTS_OPEN)
d ODENUS e inz(C_PGMNAME)
These lines are the focus of this article. I am able to pre-initialize various fields in the data structure. Note that I can initialize them to other values, provided they are values that the compiler can determine at compile time. For example, I can't set the value of ODENTS to the results of the %timestamp BIF; I get an error saying the compiler can't determine the value of the argument at compile time. In this case, though, I can set three fields: the record ID, the initial status, and the name of this program. That being done, it's time to move on to the working part of the program.
This statement is deceptively simple. Most times, RESET and CLEAR function almost identically, but in this case, they are very different. CLEAR would initialize all the values in the data structure to the appropriate default for that data type. Usually, RESET would do the same thing, but in this example it "re-initializes" the data structure to the values stored in the three fields in the data specifications shown earlier (ODRCID, ODSTS, and ODENUS). What's critical about this is that I can execute this single line of code anywhere and it is equivalent to clearing the data structure and initializing each of the other fields.
dsORDDTLX.ODENTS = %timestamp;
This line represents the "rest" of the application—that is, all the code required to actually set up the data in the record. I'm only updating one field, the entry timestamp. A real application would fill all the other fields as well. But you get the point.
write ORDDTLR dsORDDTLX;
*inlr = *on;
And that's it. I write the record to the file from the data structure and then close the application. This is just a stub version of the program, but it demonstrates the power of the concept. What's really neat about this, though, is that whenever I need to write a new record, I can simply execute the RESET opcode and I'm starting with a pre-initialized data structure. You may not need this particular functionality in a lot of programs, but when you do need it, it's nice to have this technique. It leads to an excellent separation of code: you define the data structure and all the initializations in one place in the D-specs and leave the rest of your program very clean.
In another article, I'll show you how this technique and a little magic from our friend EVAL-CORR can make writing database conversions a breeze. Thanks for reading this edition of "Practical RPG"!