Extension files future-proof your data, but you also have to future-proof your extension files!
In my previous article, I explained how to use extension files to encapsulate a legacy database. This allows you to add new data points to the database without the impact of changing an existing master file. This is important because changing a master file requires that you recompile every program with a file specification for that file, and that can be an expensive proposition. It may even be impossible if you're missing current source for any of those programs. But if you're not careful with your extension files, you'll wind up in a similar bind. I ran into exactly that situation with the customer extension file I talked about in the previous article. While it wasn't used as extensively as the customer master, it had begun to proliferate throughout the system to the point where adding or changing a field in that file had all the same difficulties as changing the original master file.
There are two basic ways to future-proof an extension file. The original method is to add a lot of unused fields that you can then use later. This works, sort of, but eventually it catches up to you. For example, you might decide to add a bunch of numeric values. How many do you add? Ten fields named ZZNUM1 through ZZNUM9? How big do you make them? You can make them all 31-digit fields with 15 decimal places, but then you can end up with a lot of extra code moving values between the database and your program variables. Alpha fields are even worse; do you add a bunch of single-character fields, a bunch of 10-character fields, and a bunch of 80-character fields? Inevitably, fields end up getting reused and overridden. Add the fact that these field names have no relation to their eventual use and it's just not a great solution.
The second method is better. Use SQL access! Make sure that every program that accesses the extension file uses SQL to do so. Once you've done that, changing the file becomes much easier. You can add new fields as needed with appropriate field names and attributes, and existing programs don't need to change. There are limitations to this approach, but nothing terribly difficult. The biggest issue is that you must explicitly define the columns you plan to use in your program. You can't just use the "SELECT *" syntax to read all the fields into a data structure. This in turn means that you must define individual work fields to receive the SQL variables.
exec sql select CXCNTY into :wCNTY from CUSEXT where CXCUST = :iCust;
This is roughly equivalent to:
chain (iCust) CUSMAS;
wCNTY = CMCNTY;
And while the SELECT…INTO technique works well, you may prefer the newer SET syntax, which is much more like an EVAL:
exec sql set :wCNTY = (select CXCNTY from CUSEXT where CXCUST = :iCust);
This syntax isn't just more similar to high-level language; it also serves an important development purpose. Go into any SQL client (including STRSQL) and replace the "set :wCNTY =" with "VALUES" and replace the variable markers with specific literal values. Then you can test your statement directly:
values (select CXCNTY from CUSEXT where CXCUST = 9901128)
You will see the value of the CXCNTY field for the record whose customer number is 9901128. I find this makes it very easy to test my SQL before embedding it into the program. So, no matter how complex your SQL statement gets, you can test it directly in an interactive SQL client. That's really helpful during development or debugging.
One important issue when using SQL is error checking. A number of error-checking techniques exist, and the one you use depends on the situation. Here is a brief list of error techniques on SELECT (or SET) statements:
- Check the SQLCOD field (I wrote an article on SQLCOD and SQLSTT a long time ago).
- Add a null indicator; this will be set if the data is not found.
- Prefill a default value into the field and ignore any errors.
Testing the SQLCOD/SQLSTT fields is probably the most comprehensive technique, but it does require a bit of extra code and some knowledge of how the codes work. The null indicator is okay, but the syntax can get a little clunky, especially since the null indicator isn't actually an indicator, it's an integer field. Skipping the test entirely after prefilling the field is like executing a CHAIN without testing the indicator. Each has its place, and which you use depends on a combination of coding style and functional requirement.
Updating the Extension-File Data
So far, I've only talked about reading data from the file using SQL. I haven't discussed updating the file. That's because, in many cases, you may update the extension file using standard Record-Level Access (RLA) syntax. If you're unfamiliar with the term, RLA is simply the collective name that identifies the standard RPG I/O opcodes such as CHAIN and UPDATE.
Updates to an extension file are usually localized to a small number of programs. When you're first creating an extension file, it tends to consist of master file configuration such as processing flags or additional data points (like the county). Attributes like these are usually updated in the same program that maintains the primary master file record. In my case of the customer master extension file, the customer master maintenance program would update the extension file record. In most cases, I think it's acceptable to allow the maintenance program to access the extension file using traditional record-level access. The update is isolated to a single program, and when you're changing the layout of the customer master, even the extension file portion of it, you have to change that program anyway. So I don't worry about recompiling it; it will need to be recompiled anyway!
However, you may eventually have data in the extension file that must be updated by programs other than the maintenance program. These could be accumulators (total orders, for example) or process flags (customer currently locked for tax processing). Whatever the case may be, you need to update the file. That just means you'll need to use SQL syntax, such as this:
exec sql update CUSEXT set CXTXLK = 'Y' where CXCUST = :iCust;
That's the free-form syntax to set the tax lock field (CMTXLK) to Y for a specific customer. In my next installment, I'll go through a variety of syntactical examples. But as you can see, the code is minimal.
More to Come
We’ve covered the basics of how to future-proof your extension files. In a follow-up article, I'll go into more depth on not only the various syntaxes for these techniques, but also how this encapsulation then allows you to make changes any time you need to, without having to take your applications down. And that's the main purpose of the whole exercise!