• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • Guru: Qualified Files – Underused and Unappreciated

    September 14, 2020 Ted Holt

    When IBM adds a new feature to the RPG compiler, they do so for a reason. That’s why I try to learn new techniques. I hope they’ll improve the quality of the source code I write. One relatively new feature that I do not see widely used is the qualified file. In the following paragraphs, I’d like to tell you why I like qualified files and how to use them.

    To understand the need for qualified files, it may be good to begin with a brief lesson on the history of the RPG language. When RPG was originally developed, the designers decided that memory would be allocated by variable name. If two files have a field named STATUS, and you read both files, the value of STATUS in memory will be the value from the latest read. If you need both values, you must save the value of STATUS from the first read into another variable before you execute the second read. This is fine, provided that you are aware that STATUS is altered by the second read. I confess that at times I have not been aware of such a situation, and only became aware through a lengthy debugging session. For good reason debuggers allow us to watch the value of a variable.

    Why Qualify Files?

    When you do not qualify a file, you’re telling the compiler to use this historic behavior. That’s fine, if that’s what you want to do. I have noticed, however, that that is often NOT what programmers want to do, as it is very common for RPG programmers to circumvent this behavior. A technique I have seen for many years is to add a prefix to each variable of a record format. I’ve seen many display files with prefixes like S1, S2, S3, etc. on the field names within the different formats of a display file. There’s nothing wrong with this technique. I’ve used it myself. Later IBM added the PREFIX keyword to do this work for us. PREFIX was a great improvement, but now we have an even greater improvement, namely the qualified file.

    This story contains code, which you can download here.

    When you add the QUALIFIED keyword to a file definition, RPG no longer copies data between the record format and memory by field name, because the compiler no longer generates input and output specifications. Instead, all I/O operations interact with data structures. One read will not overlay the data of a previous read unless both use the same data structure.

    That’s reason number one to qualify files. But wait, there’s more! All fields of an unqualified file are global in scope. That is, they can be used anywhere in the program. The data structures through which qualified files pass data may be global as well, but they may also be local to subprocedures. Reducing global data improves program reliability.

    There you have two good reasons to qualify files. So much for the why. Let’s talk about the how.

    The Example

    To illustrate the differences between unqualified and qualified files, I wrote a little green-screen program (WHS0020R) that uses a display file and a database file, then converted it to use qualified files (WHS0010R). This is by no means industrial-strength programming, but I wanted to keep the source code as small as possible.

    The display file has four record formats — a subfile, a subfile control record, a command key legend and a window for data entry. Here’s the DDS for the unqualified display file, WHS0020D:

    A                                      DSPSIZ(24 80 *DS3)
    A                                      REF(WAREHOUSE1)
    
    A          R S1                        SFL
    A            OPTION         1A  B  6  3
    A            ID        R        O  6  8
    A            NAME      R        O  6 12
    A            MFGWHS    R        O  6 38
    A            STATUS    R        O  6 43
    
    A          R C1                        SFLCTL(S1)
    A                                      SFLSIZ(0013)
    A                                      SFLPAG(0012)
    A                                      CA03(03 'Exit')
    A                                      CA05(05 'Refresh')
    A                                      OVERLAY
    A  62                                  SFLDSP
    A  61                                  SFLDSPCTL
    A  63                                  SFLCLR
    A  61                                  SFLEND(*MORE)
    A            SCREEN        14A  O  1  2
    A                                  1 27'Warehouse Master Maintenance'
    A                                      COLOR(WHT)
    A                                  1 72DATE  EDTCDE(Y)
    A            OPTIONS       78A  O  3  2
    A                                  5  2'Opt'     DSPATR(UL)
    A                                  5  8'ID'      DSPATR(UL)
    A                                  5 12'Name                    '
    A                                                DSPATR(UL)
    A                                  5 38'Mfg'     DSPATR(UL)
    A                                  5 43'Status'  DSPATR(UL)
    
    A          R S2
    A                                 23  5'F3=Exit  F5=Refresh'
    
    A          R W1
    A                                      CA03(03 'Exit')
    A                                      CA12(12 'Cancel')
    A                                      WINDOW(8 21 9 50)
    A                                  2 13'ID:'
    A            ID        R        B  2 17
    A                                  3 11'Name:'
    A            NAME      R        B  3 17
    A                                  4  3'Manufactures:'
    A            MFGWHS    R        B  4 17
    A                                  5  9'Status:'
    A            STATUS    R        B  5 17
    A                                  8  3'F12=Cancel'
    

    And here’s what it looks like with all four formats on the screen:

    Here’s the DDS for the database file.

    A                                      UNIQUE
    A          R WAREHOUSE                 PFILE(WAREHOUSES)
    A            ID
    A            NAME
    A            MFGWHS
    A            STATUS
    A          K ID
    

    Notice that all four field names are used in three places: the database file, subfile format S1, and the window W1. The compiler allocates four areas in memory, and all three record formats will share the four. If I qualify the files, I will have 12 areas in memory instead.

    And here, for your reference, is the RPG program without qualified file names.

         H option(*srcstmt: *nodebugio) dftactgrp(*no) actgrp(*new)
    
         FWHS0020D  cf   e             workstn sfile(S1: RRN1)
         FWarehouse1uf a e           k disk
    
         D Reload          s               n   inz(*on)
    
         D RRN1            s              4s 0
         D Size1           s                   like(RRN1)
    
         D PSDS           sds
         D  PgmName                      10A   overlay(PSDS: 1)
    
            *inlr = *on;
    
            dow '1';
               if reload;
                  LoadS1 ();
               endif;
    
               *in61 = *on;
               *in62 = (Size1 > *zero);
               *in63 = *off;
    
               Screen = %trimr(PgmName) + '-C1';
               Options = '2=Change, 5=Display';
    
               write S2;
               exfmt C1;
    
               select;
                  when *in03;
                     leave;
                  when *in05;
                     reload = *on;
                  other;
                     ProcessS1 ();
               endsl;
    
            enddo;
            return;
    
            dcl-proc  LoadS1;
    
               *in61 = *off;
               *in62 = *off;
               *in63 = *on;
               write C1;
               *in63 =*off;
    
               RRN1 = *zero;
    
               setll *loval  warehouse;
               dow '1';
                  read warehouse;
                  if %eof();
                     leave;
                  endif;
                  RRN1 += 1;
                  write s1;
               enddo;
    
               Size1  = RRN1;
               Reload = *off;
    
            end-proc  LoadS1;
    
            dcl-proc  ProcessS1;
    
               dow '1';
                  readc S1;
                  if %eof();
                     leave;
                  endif;
    
                  select;
                     when Option = '2';
                        chain   ID warehouse;
                        if %found();
                           exfmt W1;
                        else;
                           // should never happen
                           // do error routine
                        endif;
                        if not *in12;
                           update warehouse;
                        endif;
                     when Option = '5';
                        chain(n) ID warehouse;
                        if %found();
                           exfmt W1;
                        else;
                           // should never happen
                           // do error routine
                        endif;
                  endsl;
    
                  clear Option;
                  update S1;
    
               enddo;
    
            end-proc  ProcessS1;
    

    Adding Qualification

    So what do we need to do differently in order to use qualified files? Let’s begin with the display file. I did not specify the INDARA keyword in the display file originally, since many programmers don’t use it. When you qualify a display file, you must specify INDARA in the display file because you can’t have predefined indicators (in this example, 03, 05, 12, 61, 62, 63) in a data structure. Adding INDARA is the only change that must be made to the display file in this example.

    Unqualified:
    A                                      DSPSIZ(24 80 *DS3)
    A                                      REF(WAREHOUSE1)
    
    A          R S1                        SFL
    . . . more code . . .
    
    Qualified:
    A                                      DSPSIZ(24 80 *DS3)
    A                                      REF(WAREHOUSE1)
    A                                      INDARA
    
    A          R S1                        SFL
    . . . more code . . .
    

    You’ll also need to add the INDDS keyword to the file specification in the RPG program.

    Unqualified:
    FWHS0020D  cf   e             workstn sfile(S1: RRN1)
    
    Qualified:
    FWHS0010D  cf   e             workstn sfile(S1: RRN1)
    F                                     indds(WsInd)
    
      dcl-ds  WsInd    len(99)  qualified;
         ExitKey       ind      pos( 3);
         RefreshKey    ind      pos( 5);
         CancelKey     ind      pos(12);
         SflDspCtl     ind      pos(61);
         SflDsp        ind      pos(62);
         SflClr        ind      pos(63);
      end-ds  WsInd;
    

    The indicators for the display file are now accessed through data structure WSIND. (Unless I have a reason not to, I write new code in free-form RPG, even in source members that are completely fixed-format.) I chose to qualify the data structure because I prefer qualified data structures. The compiler doesn’t require the indicator data structure to be qualified.

    The next change is not mandatory, but I highly recommend it. Wrap the main calculations in a subprocedure.

    Unqualified:
    H option(*srcstmt: *nodebugio) dftactgrp(*no) actgrp(*caller)
    
    Qualified:
    H option(*srcstmt: *nodebugio) dftactgrp(*no) actgrp(*caller)
    H main(WHS0010R_Main)
    
       dcl-proc  WHS0010R_Main;
       . . . more code . . .
       end-proc  WHS0010R_Main;
    

    Putting the main routine into a subprocedure lets me avoid defining any I/O data structures globally. That is, I can define all the data structures for I/O operations in subprocedures. To me, anything that eliminates global data is beneficial.

    Now let’s look at the changes to the file specs.

    Unqualified:
    FWHS0020D  cf   e             workstn sfile(S1: RRN1)
    FWarehouse1uf a e           k disk
    
    Qualified:
    FDisplay   cf   e             workstn qualified
    F                                     extdesc('WHS0010D')
    F                                     extfile(*extdesc)
    F                                     sfile(S1: RRN1)
    F                                     indds(WsInd)
    F                                     usropn
    
    FW         uf a e           k disk    qualified
    F                                     extdesc('WAREHOUSE1')
    F                                     extfile(*extdesc)
    F                                     usropn
    
      dcl-ds S1_t   likerec(Display.S1: *all)    template;
      dcl-ds C1_t   likerec(Display.C1: *all)    template;
      dcl-ds S2_t   likerec(Display.S2: *all)    template;
      dcl-ds W1_t   likerec(Display.w1: *all)    template;
    
      dcl-ds Warehouse_t  likerec(W.Warehouse: *all)    template;
    
      dcl-proc  WHS0010R_Main;
    
         open Display;
         open W;
         . . . more code . . .
         close *all;
      end-proc  WHS0010R_Main;
    

    I went from two lines to a lot of lines. Is it worth it? Yes!

    I added the QUALIFIED keyword to each file. This is not all or nothing. You can qualify some files and leave others unqualified.

    Because of the MAIN keyword, I added the USROPN keyword so that I can control file open and close.

    I also chose to give the files more manageable names — Display and W — since these are the names I use for qualification. This is not necessary, but I think it makes the code more legible. Since the file names are not the real file names, I added the EXTDESC and EXTFILE keywords to point to the real files.

    I defined a template data structure for each record type. These serve as a pattern I can use to define data structures for I/O operations. I can also use these templates to define parameters when I/O data structures are passed between subprocedures. I like to end my template names with _t.

    In each subprocedure in which there are I/O operations, I define data structures based on these templates. For example:

    Qualified:
    dcl-proc  WHS02R_Main;
      dcl-ds C1_rec   likeds(C1_t);
      dcl-ds S2_rec   likeds(S2_t);
         . . . more code . . .
    end-proc  WHS02R_Main;
    

    The indicators that control the display file are now defined in the indicator data structure, so the calculations that reference display file indicators must be revised.

    Unqualified:
      *in61 = *on;
      *in62 = (Size1 > *zero);
      *in63 = *off;
    
    Qualified:
      WsInd.SflDspCtl = *on;
      WsInd.SflDsp    = (Size1 > *zero);
      WsInd.sflClr    = *off;
    

    Make two changes to the I/O operations. First, qualify the record format name by prefixing the filename and a period. Second, add a data structure for the data.

    Unqualified:
    write S2;
    exfmt C1;
    
    write s1;
    
    Qualified:
    dcl-ds C1_rec    likeds(C1_t);
    dcl-ds S1_rec    likeds(S1_t);
    
    write  Display.S2  S2_rec;
    exfmt  Display.C1  C1_rec;
    
    write  Display.s1  S1_rec;
    

    The compiler qualifies these data structures automatically because they’re based on other data structures that were defined with LIKEREC. Since the data is in qualified data structures, qualify all references to the fields with a data structure name and a period.

    Unqualified:
      Screen = %trimr(PgmName) + '-C1';
      Options = '2=Change, 5=Display';
    
    Qualified:
      C1_rec.Screen = %trimr(PgmName) + '-C1';
      C1_rec.Options = '2=Change, 5=Display';
    

    So far I’ve shown differences in the display file. Since I qualified the database file, I have to make the same sorts of changes to work with it.

    Unqualified:
    FWarehouse1uf a e           k disk
    
    setll *loval  warehouse;
    read warehouse;
    chain(n) ID warehouse;
    
    Qualified:
    FW         uf a e           k disk    qualified
    F                                     extdesc('WAREHOUSE1')
    F                                     extfile(*extdesc)
    F                                     usropn
    
    dcl-ds Warehouse_t  likerec(W.Warehouse: *all)    template;
    dcl-ds Whs_rec   likeds(Warehouse_t);
    
    open W;
    setll  *loval   W.warehouse;
    read  W.Warehouse  Whs_rec;
    chain(n) S1_rec.ID  W.Warehouse  Whs_rec;
    close  *all;
    

    Do you see why I chose a single letter W as a file name? Using the true file name would have meant not only a lot more typing, but more cluttered and less legible source code.

    Moving The Data

    The field names in the database file and display file formats are the same, but they no longer overlay one another in memory. EVAL-CORR makes it easy to copy the data back and forth between the I/O data structures. I much prefer this method to copying prefixed fields one at a time.

    Unqualified:
    exfmt W1;
    update warehouse;
    
    Qualified:
    eval-corr  W1_rec = Whs_rec;
    exfmt      Display.W1 W1_rec;
    eval-corr  Whs_rec = W1_rec;
    update     W.Warehouse      Whs_rec;
    eval-corr  S1_rec = W1_rec;
    

    I have to shuttle the data back and forth myself, but it’s not a hardship.

    Here’s the RPG program with qualified files:

         H option(*srcstmt: *nodebugio) dftactgrp(*no) actgrp(*new)
         H main(WHS02R_Main)
    
         FDisplay   cf   e             workstn qualified
         F                                     extdesc('WHS0010D')
         F                                     extfile(*extdesc)
         F                                     sfile(S1: RRN1)
         F                                     indds(WsInd)
         F                                     usropn
    
         FW         uf a e           k disk    qualified
         F                                     extdesc('WAREHOUSE1')
         F                                     extfile(*extdesc)
         F                                     usropn
    
           dcl-ds S1_t   likerec(Display.S1: *all)    template;
           dcl-ds C1_t   likerec(Display.C1: *all)    template;
           dcl-ds S2_t   likerec(Display.S2: *all)    template;
           dcl-ds W1_t   likerec(Display.w1: *all)    template;
    
           dcl-ds Warehouse_t  likerec(W.Warehouse: *all)    template;
    
           dcl-ds  WsInd    len(99)  qualified;
              ExitKey       ind      pos( 3);
              RefreshKey    ind      pos( 5);
              CancelKey     ind      pos(12);
              SflDspCtl     ind      pos(61);
              SflDsp        ind      pos(62);
              SflClr        ind      pos(63);
           end-ds  WsInd;
    
         D Reload          s               n   inz(*on)
    
         D RRN1            s              4s 0
         D Size1           s                   like(RRN1)
    
         D PSDS           sds
         D  PgmName                      10A   overlay(PSDS: 1)
    
            dcl-proc  WHS02R_Main;
    
            dcl-ds C1_rec   likeds(C1_t);
            dcl-ds S2_rec   likeds(S2_t);
    
            open Display;
            open W;
    
            dow '1';
               if reload;
                  LoadS1 ();
               endif;
    
               WsInd.SflDspCtl = *on;
               WsInd.SflDsp    = (Size1 > *zero);
               WsInd.sflClr    = *off;
    
               C1_rec.Screen = %trimr(PgmName) + '-C1';
               C1_rec.Options = '2=Change, 5=Display';
    
               write  Display.S2  S2_rec;
               exfmt  Display.C1  C1_rec;
    
               select;
                  when WsInd.ExitKey;
                     leave;
                  when WsInd.RefreshKey;
                     reload = *on;
                  other;
                     ProcessS1 ();
               endsl;
    
            enddo;
    
            close *all;
    
            return;
    
            end-proc  WHS02R_Main;
    
            dcl-proc  LoadS1;
    
               dcl-ds C1_rec    likeds(C1_t);
               dcl-ds S1_rec    likeds(S1_t);
               dcl-ds Whs_rec   likeds(Warehouse_t);
    
               WsInd.SflDspCtl   = *off;
               WsInd.SflDsp      = *off;
               WsInd.SflClr      = *on;
               write  Display.C1  C1_rec;
               WsInd.SflClr    =*off;
    
               RRN1 = *zero;
    
               setll *loval  W.warehouse;
               dow '1';
                  read  W.Warehouse  Whs_rec;
                  if %eof();
                     leave;
                  endif;
                  RRN1 += 1;
                  eval-corr S1_rec = Whs_rec;
                  write  Display.s1  S1_rec;
               enddo;
    
               Size1  = RRN1;
               Reload = *off;
    
            end-proc  LoadS1;
    
            dcl-proc  ProcessS1;
    
               dcl-ds S1_rec    likeds(S1_t);
               dcl-ds W1_rec    likeds(W1_t);
               dcl-ds Whs_rec   likeds(Warehouse_t);
    
               dow '1';
                  readc Display.S1 S1_rec;
                  if %eof();
                     leave;
                  endif;
    
                  select;
                     when S1_rec.Option = '2';
                        chain   S1_rec.ID W.Warehouse Whs_rec;
                        if %found();
                           eval-corr  W1_rec = Whs_rec;
                           exfmt Display.W1 W1_rec;
                        else;
                           // should never happen
                           // do error routine
                        endif;
                        if not WsInd.CancelKey;
                           eval-corr  Whs_rec = W1_rec;
                           update W.Warehouse Whs_rec;
                           eval-corr  S1_rec = W1_rec;
                        endif;
                     when S1_rec.Option = '5';
                        chain(n) S1_rec.ID W.Warehouse Whs_rec;
                        if %found();
                           eval-corr  W1_rec = Whs_rec;
                           exfmt Display.W1 W1_rec;
                        else;
                           // should never happen
                           // do error routine
                        endif;
                  endsl;
    
                  clear S1_rec.Option;
                  update Display.S1 S1_rec;
    
               enddo;
    
            end-proc  ProcessS1;
    

    Parting Thoughts

    Please allow me to end with a few short observations.

    First, when adding qualification to the files, I could have changed the F specs to free form, but I didn’t see a need to do so. Same for the H specs. I typically make new additions in free form and tend to leave old code as is unless I have a reason to change it.

    Second, I didn’t try to eliminate global variables. I left the files at the global level. Same with the variables associated with the files, including the RELOAD variable. Moving files into subprocedures requires a different architecture, which is beyond the scope of this article and probably unnecessary for typical business applications.

    Last, the RPG source code went from 103 to 161 lines, and many of the lines became more complex. This doesn’t bother me, because the extra code brings peace of mind. It’s similar to another characteristic of my programs: I could use the RPG cycle to write shorter programs, but I don’t.

    RELATED STORIES

    IBM Knowledge Center – QUALIFIED

    No More Global Variables!

    Four Ways to Avoid Problems Caused by Global Data

     

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags: Tags: 400guru, FHG, Four Hundred Guru, I/O, IBM i, RPG

    Sponsored by
    Raz-Lee Security

    Start your Road to Zero Trust!

    Firewall Network security, controlling Exit Points, Open DB’s and SSH. Rule Wizards and graphical BI.

    Request Demo

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    A Guide To This Week’s Virtual POWERUp 2020 Thoroughly Modern: The Smart Approach to Modernization – Know Before You Go!

    3 thoughts on “Guru: Qualified Files – Underused and Unappreciated”

    • Les Turner says:
      September 15, 2020 at 8:05 am

      Great article, Ted! Definitely food for thought for my next program.

      Reply
    • Jim Rothwell says:
      September 15, 2020 at 6:31 pm

      Hi Ted. Good read, thanks. A totally off-the-subject, random observation if I may…

      I noticed that you have the screen name and date in the screen heading, just as I always did, probably out of muscle-memory more than anything else. But having been away from the 400 for several years now, those two little innocuous fields jumped out at me, because it occurred to me that you never see a screen name on any GUI app or webpage — and no dynamic PC app has today’s date on it, because everyone has that displayed in their task bar. But yet, you’ll continue to see both on every green screen display. Muscle-memory…we just can’t help it. 😉

      Reply
    • Wynn Osborne says:
      April 18, 2022 at 9:23 am

      This is one of the better RPG articles (a top 10 contender), and your layout is easily understandable. This is the minimum level of coding we should be practicing (speaking for me). Reducing globals – YES.

      Would love to see your take on pushing all display file I/O out of the application, into a service program, under the guise of “Separation of concerns”.

      Reply

    Leave a Reply Cancel reply

TFH Volume: 30 Issue: 55

This Issue Sponsored By

  • New Generation Software
  • Fresche Solutions
  • ARCAD Software
  • Computer Keyes
  • Manta Technologies

Table of Contents

  • Jenkins Gets Closer IBM i Hooks, Courtesy Of ARCAD
  • Thoroughly Modern: The Smart Approach to Modernization – Know Before You Go!
  • Guru: Qualified Files – Underused and Unappreciated
  • A Guide To This Week’s Virtual POWERUp 2020
  • Just How Big Is The Whole Power Systems Business?

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • Public Preview For Watson Code Assistant for i Available Soon
  • COMMON Youth Movement Continues at POWERUp 2025
  • IBM Preserves Memory Investments Across Power10 And Power11
  • Eradani Uses AI For New EDI And API Service
  • Picking Apart IBM’s $150 Billion In US Manufacturing And R&D
  • FAX/400 And CICS For i Are Dead. What Will IBM Kill Next?
  • Fresche Overhauls X-Analysis With Web UI, AI Smarts
  • Is It Time To Add The Rust Programming Language To IBM i?
  • Is IBM Going To Raise Prices On Power10 Expert Care?
  • IBM i PTF Guide, Volume 27, Number 20

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle