Tuesday, October 18, 2022

Creating a list of commands and their command processing programs

Update

I now use the method described in this post as, IMHO, it is easier to use a SQL View than it is this API.




The questions was is there a way to list the command processing programs for all of the commands in a library without having to look at each one individually with the Display Command command, DSPCMD. I decided to take this a small step further and want to have the name of the Validity Checking Program too.

Having looked at all of the lists I know of SQL Views, Table Functions, etc. I could not find one for commands. Alas, all of the CL commands only output to screen or spool file. It meant I had to use an API, QCDRCMDI. While writing this example I did have issues with QCDRCMDI. After over four hours the only way I found I can call the API it is call it from a separate program. More about that later.

As a result I have two programs:

  1. The first program makes a list of all the commands in the library, calls the program with the API in it, and inserts the data into a SQL Table.

  2. The second program calls the API and returns the Command Processing and Validity Checking programs' names and libraries to the calling program.

Let me start with the first program. I am going to show it in two parts, the first for the definitions and the second the "doing" part. Below is the definition part:

01  **free
02  ctl-opt option(*srcstmt) ;

03  dcl-s Library char(10) inz('MYLIB') ;

04  dcl-pr PGMCMDDTA extpgm ;
05    *n char(10) const ;
06    *n char(10) const ;
07    *n char(10) ;
08    *n char(10) ;
09    *n char(10) ;
10    *n char(10) ;
11  end-pr ;

12  dcl-ds Cmds qualified dim(*auto:9999) ;
13    Name char(10) ;
14    Library char(10) ;
15  end-ds ;

16  dcl-ds Data likeds(Cmds) ;
17  dcl-s CmdPgmName char(10) ;
18  dcl-s CmdPgmLib char(10) ;
19  dcl-s CmdValName char(10) ;
20  dcl-s CmdValLib char(10) ;

Line 1: My RPG is always totally free.

Line 2: If a program is more than few lines long I always add this control option to it.

Line 3: As this is an example program I have "hard coded" the library I will looking for commands within, MYLIB.

Lines 4 – 11: This is the parameter list I will be using to call the program with the QCDRCMDI API within it. First two parameters will be the command name and library, I have made them CONST so that the called program cannot change these parameters. The remaining parameters are for: Command Processing Program and Library, Validity Checking Program and Library.

Lines 12 - 15: This is the data structure array I will be placing the list of commands in my library into.

Line 12: As the partition I am coding this example on is IBM i 7.5 I can use an automatically expanding array. These were introduced in IBM i 7.4 and I find that they have become my favorite type of array.

Line 16: I have defined this data structure, Data, to have the same attributes as the data structure array Cmds, without being an array.

Lines 17 – 20: Variables I will be using these to contain the returned values from the second program.

Onto the interesting "doing" part of the program:

25  exec sql SET OPTION COMMIT = *NONE ;

26  exec sql DECLARE C0 CURSOR FOR
           SELECT CHAR(OBJNAME,10),CHAR(OBJLIB,10)
           FROM TABLE(QSYS2.OBJECT_STATISTICS(:Library,'*CMD'))
           FOR READ ONLY ;

27  exec sql OPEN C0 ;

28  exec sql FETCH C0 FOR 9999 ROWS INTO :Cmds ;

30  exec sql CLOSE C0 ;

31  exec sql DROP TABLE IF EXISTS QTEMP.CMD_DATA ;

32  exec sql CREATE TABLE QTEMP.CMD_DATA
             (CMDNAME CHAR(10),CMDLIB CHAR(10),
              CMDPGMNAME CHAR(10),CMDPGMLIB CHAR(10),
              VALPGMNAME CHAR(10),VALPGMLIB CHAR(10)) ;

33  for-each Data in Cmds ;
34    PGMCMDDTA(Data.Name : Data.Library :
                CmdPgmName : CmdPgmLib :
                CmdValName : CmdValLib) ;

37    exec sql INSERT INTO QTEMP.CMD_DATA
              VALUES(:Data.Name,:Data.Library,
                     :CmdPgmName,:CmdPgmLib,
                     :CmdValName,:CmdValLib) ;
38  endfor ;

39  *inlr = *on ;

I am sure you have noticed that the numbers are not sequential, there are some numbers missing from the line numbers above. I have not missed them. I know that the majority of the world is not using IBM i 7.5 . After showing the code for this release I am going to provide modifications you can make to this code to make it compatible with IBM i 7.3 .

Line 25: I do not want to use commitment control as I am only outputting to a work file in QTEMP. This line turns commitment control off.

Line 26: I have declared a cursor for the OBJECT_STATISTICS Table Function. I have passed the library name, defined on line 3, and the command object type. I only want two columns in my results: the object (command) name and the library it is found in. I have used the CHAR Scalar Function to convert these from variable to fixed length columns.

Line 27: Open the cursor.

Line 28: Fetch up to 9,999 rows of results into Cmds data structure array. Not even the library QSYS has that many commands within it.

Line 30: As I have all the information I need; I close the cursor.

Line 31: I am going to create my output table. First I want to delete it if it already exists. The IF EXSITS prevents an error from being returned if the table is not found.

Line 32: Create the output Table with the following columns:

  • CMDNAME:  Command name
  • CMDLIB:  Library the command is in
  • CMDPGMNAME:  Program called by command
  • CMDPGMLIB:  Library that is in
  • CMDVALNAME:  Validity check program
  • CMDVALLIB:  Library that is in

Line 33: With an autoexpanding array it only has the number of elements that I wrote data to it. If I retrieve three rows from the cursor the array only has three elements. The FOR-EACH operation reads all of the elements in the array and will return the data from that element into the Data data structure.

Line 34: Here I call the second program, and the values for the names and libraries for the Command Processing and Validity Checking programs are returned.

Line 37: I am using the returned results from second program to insert a row into the output Table.

What changes would I need to make for this program to be compatible with IBM i 7.3?

12  dcl-ds Cmds qualified dim(9999) ;

21  dcl-s CmdName char(10) ;
22  dcl-s CmdLib char(10) ;
23  dcl-s Rows int(5) ;
24  dcl-s X int(5) ;

29  exec sql GET DIAGNOSTICS :Rows = ROW_COUNT ;

31  exec sql DROP TABLE QTEMP.CMD_DATA ;

33  for X = 1 to Rows ;

34    PGMCMDDTA(Cmds(X).Name : Cmds(X).Library :

35    CmdName = Cmds(X).Name ;
36    CmdLib = Cmds(X).Library ;

37    exec sql INSERT INTO QTEMP.CMD_DATA
              VALUES(:CmdName,:CmdLib,
                     :CmdPgmName,:CmdPgmLib,
                     :CmdValName,:CmdValLib) ;

Line 12: Replace existing line 12. In 7.3 RPG only supports fixed length arrays.

Lines 21 – 24: Insert into the source.

Line 29: Insert. I need to know the number of rows fetched from the cursor.

Line 31: Replace existing line 31.

Line 33: Replace existing line 33. I need to use a regular FOR operation group that will be performed the same number of times as the number of rows fetched by the cursor.

Line 34: Replace line 34. I use the Cmds array elements as parameters to the second program.

Line 35 and 36: Insert. I cannot use data structure array elements in the SQL insert, therefore, I have to move them to their own variables.

Line 37: Replace line 37. The first two columns in the VALUES need to be changed to be the variables I updated in lines 35 and 36.

I tried for four hours to put the logic to call the QCDRCMDI API in a subprocedure within the program, a procedure in a module I bound to the program, in a subroutine, in the "main line" of the program. They all gave me problems where in certain scenarios the API would fail. I am not sure if I am just not seeing something, or there is a bug in the API. I did get the API to work every time when I put it in its own program. I called the program PGMCMDDTA.

This program would be the same in IBM i 7.3 as it is in 7.5 .

It looks long, but almost ¾ of the code is definitions.

01  **free
02  ctl-opt option(*srcstmt) ;

03  dcl-pi *n ;
04    CmdName char(10) const ;
05    CmdLib char(10) const ;
06    CmdPgmName char(10) ;
07    CmdPgmLib char(10) ;
08    CmdValName char(10) ;
09    CmdValLib char(10) ;
10  end-pi ;

11  /copy qsysinc/qrpglesrc,qcdrcmdi
12  /copy qsysinc/qrpglesrc,qusec

13  dcl-pr CmdInfo extpgm('QCDRCMDI') ;
14    *n likeds(QCDI0100) options(*varsize) ;
15    *n int(5) const ;
16    *n char(8) const ;
17    *n char(20) const ;
18    *n likeds(QUSEC) options(*varsize) ;
19    *n char(1) const ;
20  end-pr ;

21  dcl-s Command char(20) ;
22  dcl-ds Output likeds(QCDI0100) ;
23  dcl-s OutputLength int(5) ;

24  %subst(Command:1:10) = CmdName ;
25  %subst(Command:11:10) = CmdLib ;

26  CmdInfo(Output : OutputLength : 'CMDI0100' : Command : QUSEC : '1') ;

27  CmdPgmName = Output.QCDCPN ;
28  CmdPgmLib = Output.QCDCPLN ;
29  CmdValName = Output.QCDVN ;
30  CmdValLib = Output.QCDVLN ;

31  *inlr = *on ;

Lines 3 – 10: The entry parameter list.

Lines 11 and 12: I am copying in source members from the QSYSINC library so I do not have to define the standard data structures. I can use their definitions to define my data strucutres.

Lines 13 – 20: Procedure prototype for the API.

Line 14: I am defining the first parameter, with the LIKEDS, to be a data structure like QCDI0100, which is found in the first copied member.

Line 18: Doing the same with the LIKEDS to define a data structure to be the same as the standard API error data structure.

Lines 21 – 23: Definition of variables and a data structure that will be used by the API.

Line 24 and 25: Making the qualified command name parameter for the API.

Line 26: Call the API with the following parameters:

  1. Output:  Data structure containing the data returned from the API
  2. OutputLength:  Length of the returned data
  3. 'CMDI0100':  The API's format name used by the API to know what data to return
  4. Command:  Qualified command name
  5. QUSEC:  Standard API error data structure
  6. '1':  If the command is a proxy command it will follow the proxy chain and return the results for the original command

Line 27 – 30: I am moving the data structure subfields from the results data structure into the program's entry parameters.

After compiling programs I called the first program. When it completed I looked in the output file CMD_DATA and found the following results:

CMDNAME  CMDLIB  CMDPGMNAME  CMDPGMLIB  VALPGMNAME  VALPGMLIB
-------  ------  ----------  ---------  ----------  ---------
INLPGM  MYLIB    INLPGMCL    MYLIB      *NONE
WM      MYLIB    QUOCPP      QPDA       *NONE
WO      MYLIB    QUOCPP      QPDA       *NONE

I know there are only a few results as I only have these three commands in MYLIB. The second result is a proxy command for WRKMBRPDM, and the third is a proxy for WRKOBJPDM.

None of the program have a Validity Checking program.

The API proved a "tough nut to crack", when I did I can get the information I need for commands in any library or libraries.

 

You can learn more about the QCDRCMDI API from the IBM website here.

 

This article was written for IBM i 7.5, and should work for some earlier releases too.

1 comment:

  1. Thank you and nice work. It looks like they added a QSYS2.CMD_INFO view in 7.5 in one the DB PTFs that makes getting most of this info pretty easy now.

    ReplyDelete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.