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:
- 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.
- 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:
- Output: Data structure containing the data returned from the API
- OutputLength: Length of the returned data
- 'CMDI0100': The API's format name used by the API to know what data to return
- Command: Qualified command name
- QUSEC: Standard API error data structure
- '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.
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