Looking for a working example of any OS/400 API wrapped in an external SQL stored procedure wrapped in a user defined SQL function

試著忘記壹切 提交于 2020-01-04 11:20:14

问题


Having two issues at the moment:

1) The below example of wrapping an OS/400 API with an external SQL stored procedure which is further wrapper in a SQL user defined table function both compiles and runs without error, but it returns blanks and zeroes for the job information when passing '*' for the job name (i.e. current job). Any tips on why would be appreciated. Note: If I pass a non-existent job, the QUSRJOBI api correctly throws an error, so the code is behaving partially correct. If I pass a correct active job name, job user, and job number, no error occurs but blanks and zeroes are still returned. I've tried both CHAR(85) and VARCHAR(85) for RECEIVER_VARIABLE. I'll try BINARY(85) next for RECEIVER_VARIABLE, but converting from BINARY back to CHAR and INT return columns might prove difficult.

2) Some OS/400 API parameters call for using data structures, which DB2 SQL at V7R1 on the System i does not yet directly support (i.e. no direct support yet for structured types). However, this article says they can be implemented using BINARY strings, but does not provide an example :(. After extensive searching, I've not been able to find an example of wrapping an OS400 api using ONLY SQL objects. If anyone has any examples of how to form a BINARY string using SQL only that is comprised of a mixture of CHAR and other data types like especially INT, please post one. The API error code parameter is an example where this is commonly needed. I have the ERROR_CODE related code commented out since it generates error CPF3CF1 "Error code parameter not valid" if that code is reactivated. If anyone can tell what is wrong with how the ERROR_CODE binary string data structure is being formed, please let me know. I've tried both CHAR(16) and BINARY(16) for the ERROR_CODE structure. I've tested the current technique of how ERROR_CODE is being formed dumping the results into a table, and viewing the table results using DSPPFM in hex mode makes it look like the "binary( hex( ERROR_CODE_BYTES_PROVIDED ) )" etc. is working correctly. However, I'm missing something.

I'm aware that there are lots of examples of using RPG to wrap OS/400 api's, but I want to keep these wrappers as SQL code only.

create or replace procedure M_GET_JOB_INFORMATION
               ( out    OUT_RECEIVER_VARIABLE            char(85)
                ,in     IN_LENGTH_OF_RECEIVER_VARIABLE   int
                ,in     IN_FORMAT_NAME                   char(8)
                ,in     IN_QUALIFIED_JOB_NAME            char(26)
                ,in     IN_INTERNAL_JOB_IDENTIFIER       char(16)
           --     ,inout  INOUT_ERROR_CODE                 binary(16)  
               )

  program type main
  external name QSYS/QUSRJOBI

  parameter style general
  not deterministic
  modifies SQL data
  specific M_JOBINFO
  set option dbgview   = *source
            ,commit    = *nc
            ,closqlcsr = *endmod
            ,tgtrls    = V7R1M0 
;


create or replace function M_GET_JOB_INFORMATION_BASIC
                   ( IN_JOB_NAME                 varchar(10)
                    ,IN_JOB_USER                 varchar(10)
                    ,IN_JOB_NUMBER               varchar(6)
                    ,IN_INTERNAL_JOB_IDENTIFIER  varchar(16)
                   )
  returns table( JOB_NAME                 char(10)
                ,JOB_USER                 char(10)
                ,JOB_NUMBER               char(6)
                ,INTERNAL_JOB_IDENTIFIER  char(16)
                ,JOB_STATUS               char(10)
                ,JOB_TYPE                 char(1)
                ,JOB_SUBTYPE              char(1)
                ,RUN_PRIORITY             int
                ,TIME_SLICE               int
                ,DEFAULT_WAIT             int
                ,ELIGIBLE_FOR_PURGE       char(10)
               )

  language SQL
  specific M_JOBINFBF
  not deterministic
  disallow parallel
  no external action
  modifies SQL data
  returns null on null input
  not fenced

  set option dbgview   = *source
            ,commit    = *nc
            ,closqlcsr = *endmod
            ,tgtrls    = V7R1M0
     --       ,output    = *PRINT

begin

declare RECEIVER_VARIABLE             char(85)      default '';          --receives "JOBI0100" format output from API
declare LENGTH_OF_RECEIVER_VARIABLE   int           default 85;          --length of "JOBI0100" Format
declare FORMAT_NAME                   char(8)       default 'JOBI0100';  --basic job information
declare QUALIFIED_JOB_NAME            char(26);
declare INTERNAL_JOB_IDENTIFIER       char(16);
declare ERROR_CODE                    binary(16);  

--ERROR_CODE "ERRC0100" Format:
declare ERROR_CODE_BYTES_PROVIDED               int         default 8;    --Size of API Error Code data structure passed to API
declare ERROR_CODE_BYTES_RETURNED               int         default 0;    --Number of exception data bytes returned by the API
declare ERROR_CODE_EXCEPTION_ID                 char(7)     default '';   --Exception / error message ID returned by the API
declare ERROR_CODE_RESERVED                     char(1)     default '';   --Reserved bytes
declare ERROR_CODE_EXCEPTION_DATA               char(1)     default '';   --Exception data returned by the API

if IN_INTERNAL_JOB_IDENTIFIER = '' then 
   set QUALIFIED_JOB_NAME = char( IN_JOB_NAME, 10 ) || char( IN_JOB_USER, 10 ) || char( IN_JOB_NUMBER, 6 );
   set INTERNAL_JOB_IDENTIFIER = '';
else  
   set QUALIFIED_JOB_NAME = '*INT';
   set INTERNAL_JOB_IDENTIFIER = IN_INTERNAL_JOB_IDENTIFIER;   
end if;

set ERROR_CODE = binary( hex( ERROR_CODE_BYTES_PROVIDED ) ) ||  
                 binary( hex( ERROR_CODE_BYTES_RETURNED ) ) ||  
                 binary( ERROR_CODE_EXCEPTION_ID ) || 
                 binary( ERROR_CODE_RESERVED ) 
          --  ||    binary( ERROR_CODE_EXCEPTION_DATA )
;  

call M_GET_JOB_INFORMATION
        ( RECEIVER_VARIABLE             --out
         ,LENGTH_OF_RECEIVER_VARIABLE   --in
         ,FORMAT_NAME                   --in
         ,QUALIFIED_JOB_NAME            --in
         ,INTERNAL_JOB_IDENTIFIER       --in
      --   ,ERROR_CODE                    --in/out  --Results in error CPF3CF1 "Error code parameter not valid" if code line reactivated
        );

return values( char( substr( RECEIVER_VARIABLE,  8, 10 ), 10 )      --JOB_NAME
              ,char( substr( RECEIVER_VARIABLE, 18, 10 ), 10 )      --JOB_USER
              ,char( substr( RECEIVER_VARIABLE, 28,  6 ),  6 )      --JOB_NUMBER
              ,char( substr( RECEIVER_VARIABLE, 28, 16 ), 16 )      --INTERNAL_JOB_IDENTIFIER
              ,char( substr( RECEIVER_VARIABLE, 50, 10 ), 10 )      --JOB_STATUS
              ,char( substr( RECEIVER_VARIABLE, 60,  1 ),  1 )      --JOB_TYPE
              ,char( substr( RECEIVER_VARIABLE, 61,  1 ),  1 )      --JOB_SUBTYPE
              ,case when substr( RECEIVER_VARIABLE, 64, 4 ) = ''
                    then 0
                    else int( substr( RECEIVER_VARIABLE, 64, 4 ) )
               end                                                  --RUN_PRIORITY
              ,case when substr( RECEIVER_VARIABLE, 68, 4 ) = ''
                    then 0
                    else int( substr( RECEIVER_VARIABLE, 68, 4 ) )
               end                                                  --TIME_SLICE
              ,case when substr( RECEIVER_VARIABLE, 72, 10 ) = ''
                    then 0
                    else int( substr( RECEIVER_VARIABLE, 72, 4 ) )
               end                                                  --DEFAULT_WAIT
              ,char( substr( RECEIVER_VARIABLE, 76, 10 ), 10 )      --ELIGIBLE_FOR_PURGE
             )
;

end    
;
select * from table( M_GET_JOB_INFORMATION_BASIC( '*', '', '', '' ) ) as JOB_INFO
;

回答1:


I use this on i 6.1 to call the QDBRTVFD API:

CREATE PROCEDURE SQLEXAMPLE.DBRTVFD ( 
    INOUT FD       CHAR(1024) , 
    IN    SZFD     INTEGER , 
    INOUT RTNFD    CHAR(20) , 
    IN    FORMAT   CHAR(8) , 
    IN    QF       CHAR(20) , 
    IN    "RCDFMT" CHAR(10) , 
    IN    OVRPRC   CHAR(1) , 
    IN    SYSTEM   CHAR(10) , 
    IN    FMTTYP   CHAR(10) , 
    IN    ERRCOD   CHAR(8) ) 
    LANGUAGE CL 
    SPECIFIC SQLEXAMPLE.DBRTVFD 
    NOT DETERMINISTIC 
    NO SQL 
    CALLED ON NULL INPUT 
    EXTERNAL NAME 'QSYS/QDBRTVFD' 
    PARAMETER STYLE GENERAL ;

First, the default is LANGUAGE C, and you probably don't want that for QUSRJOBI which is an OPM program. CL-language parameter passing can be a better choice for predictability here.

Also, you probably want to set this as NO SQL rather than modifies SQL data since you aren't modifying SQL data. It might be necessary to remove the SET OPTION in order to get things down to the minimum.

If you make those changes for your M_GET_JOB_INFORMATION procedure, see if it returns useful values. If it doesn't, we can dig a little deeper.


For your particular API, I used this code to test results on i 6.1:

CREATE PROCEDURE SQLEXAMPLE.M_GET_JOB_INFORMATION ( 
    INOUT OUT_RECEIVER_VARIABLE CHAR(85) , 
    IN IN_LENGTH_OF_RECEIVER_VARIABLE INTEGER , 
    IN IN_FORMAT_NAME CHAR(8) , 
    IN IN_QUALIFIED_JOB_NAME CHAR(26) , 
    IN IN_INTERNAL_JOB_IDENTIFIER CHAR(16) , 
    IN IN_ERROR_CODE CHAR(8) ) 
    LANGUAGE CL 
    SPECIFIC SQLEXAMPLE.M_JOBINFO 
    NOT DETERMINISTIC 
    NO SQL 
    CALLED ON NULL INPUT 
    EXTERNAL NAME 'QSYS/QUSRJOBI' 
    PARAMETER STYLE GENERAL ;

A basic wrapper was created like so:

CREATE PROCEDURE SQLEXAMPLE.GENRJOBI ( 
    INOUT JOBI       VARCHAR(85) , 
    IN       QJOB      VARCHAR(26) ) 
    LANGUAGE SQL 
    SPECIFIC SQLEXAMPLE.GENRJOBI 
    NOT DETERMINISTIC 
    MODIFIES SQL DATA 
    CALLED ON NULL INPUT 
    SET OPTION  ALWBLK = *ALLREAD , 
        ALWCPYDTA = *OPTIMIZE , 
        COMMIT = *NONE , 
        DBGVIEW = *LIST , 
        CLOSQLCSR = *ENDMOD , 
        DECRESULT = (31, 31, 00) , 
        DFTRDBCOL = *NONE , 
        DLYPRP = *NO , 
        DYNDFTCOL = *NO , 
        DYNUSRPRF = *USER , 
        RDBCNNMTH = *RUW , 
        SRTSEQ = *HEX   
    P1 : BEGIN 
DECLARE JOBII CHAR ( 85 ) ; 
DECLARE SZJOBI INTEGER ; 
DECLARE FORMATI CHAR ( 8 ) ; 
DECLARE QJOBI CHAR ( 26 ) ; 
DECLARE JOBIDI CHAR ( 16 ) ; 
DECLARE ERRCODI CHAR ( 8 ) ; 
DECLARE STKCMD CHAR ( 10 ) ; 

SET JOBII = X'00000000' ; 
SET SZJOBI = 85 ;
SET FORMATI = 'JOBI0100' ; 
SET QJOBI = QJOB ; 
SET JOBIDI = '                ' ; 
SET ERRCODI = X'0000000000000000' ; 
SET STKCMD = '*LOG' ; 

CALL SQLEXAMPLE . M_GET_JOB_INFORMATION ( JOBII , SZJOBI ,  FORMATI , QJOBI , JOBIDI , ERRCODI ) ; 
CALL SQLEXAMPLE . LOGSTACK ( STKCMD ) ; 

SET JOBI = JOBII ; 

END P1  ;

The wrapper only provides an example of calling the API proc. It does nothing with the returned structure from the API except pass it back out to its caller. Your original question included bits of code to extract sub-fields from a structure, so I didn't see a point to putting similar code here.

The two procs were tested in iNav's 'Run SQL Scripts' to grab info about an interactive job I was running, and the result looked like this:

The output area shows the structure in characters, and the integer sub-fields can be seen mixed with character sub-fields. Deconstruct the structure as needed. I might create an additional proc that takes the structure as input and returns individual structure elements.




回答2:


Were you aware that IBM has already wrapped the Get Job Info API?

All Services by version / release
https://www.ibm.com/developerworks/community/wikis/home?lang=en#!/wiki/IBM%20i%20Technology%20Updates/page/DB2%20for%20i%20-%20Services

Get Job Info
https://www.ibm.com/developerworks/community/wikis/home?lang=en#!/wiki/IBM%20i%20Technology%20Updates/page/QSYS2.GET_JOB_INFO()%20%E2%80%93%20user%20defined%20table%20function




回答3:


1/5/2015 Updated version: Enhanced to get ERROR_CODE working properly returning ERROR_ID and ERROR_DATA, and removed some unnecessary related code lines. Added M_HEX_STRING_TO_INTEGER function used to take integers returned from the API in RECEIVER_VARIABLE and turn them into real integers. The binary(hex(my_int)) code wasn't working as I'd hoped, so this version passes the ERROR_CODE "bytes provided" size in hex. To clean that up, I think I need to write a function to take an integer and return a BINARY(4) for use in binary string applications, until IBM gives us structured types on System i. /End of updates.

I figured out the issues. The main issue was the API I was calling is LANGUAGE PLI, and I think I missed trying that language setting. I had other issues including forgetting to convert some offset numbers to byte positions, and the only way I've been able to get this to execute is using VARBINARY for the ERROR_CODE related code. INOUT is needed for OUT_RECEIVER_VARIABLE. If that is set to OUT instead of INOUT, and invalid job information is passed, the API program will return whatever it returned in the prior call (returns residual memory values from prior call not current call). I also had to write a function to take BINARY INT data returned from the API to convert it to a real INTEGER. If we had structured types (user defined data structures) for SQL like other versions of DB2 we probably could avoid most or all of the BINARY stuff, making things a lot simpler. If anyone else uses this code remember to specify schema names for the procedure and function definitions, since I've removed them for this post.

Now that I have the basics of wrapping these APIs sorted out, I should be able to wrap them VERY quickly now :)

--M_GET_JOB_INFORMATION (M_JOBINFO) - external SQL user defined procedure to fetch job information.

--Wrapper around IBM api QUSRJOBI (retrieve job information).

--May be used for any of the format data structures returned by the api, assuming a UDTF function has been created to consume the desired format data structure.

create or replace procedure M_GET_JOB_INFORMATION
               ( inout  RECEIVER_VARIABLE             char(86)  for bit data  --must use inout
                ,in     LENGTH_OF_RECEIVER_VARIABLE   int
                ,in     FORMAT_NAME                   char(8)
                ,in     QUALIFIED_JOB_NAME            char(26)
                ,in     INTERNAL_JOB_IDENTIFIER       char(16)
                ,inout  ERROR_CODE                    char(48)  for bit data
               )

  program type main
  external name 'QSYS/QUSRJOBI'
  language PLI
  parameter style general
  not deterministic
  modifies SQL data

  specific M_JOBINFO

  set option dbgview   = *source
            ,commit    = *nc
            ,closqlcsr = *endmod
            ,tgtrls    = V7R1M0   
;

--M_GET_JOB_INFORMATION_BASIC (M_JOBINFBF) - SQL user defined Table function to fetch job name, user, number, and other basic job information.

--Wrapper around IBM api QUSRJOBI retrieved data format JOBI0100 (basic job information).

--From IBM manual QUSRJOBI api:
  --The JOBI0100 format information is valid for active jobs and jobs on queues.
  --For jobs on queues, this format returns zeros or blanks for the attributes.
  --If the Change Job (CHGJOB) command was run against a job on a *JOBQ, the attributes returned are the attributes specified on the CHGJOB command.
  --If the job status changes to *OUTQ, the status field returned is *OUTQ and the API returns no information other than the number of bytes returned,
  --the number of bytes available, the qualified job name, the job type, the job subtype, and the internal job identifier.

--Examples of use:

  --select * from table( M_GET_JOB_INFORMATION_BASIC( '*', '', '', '' ) ) as JOB_INFO  --Get current job and basic info

  --select * from table( M_GET_JOB_INFORMATION_BASIC( 'QZDASOINIT', 'QUSER', '123456', '' ) ) as JOB_INFO  --Get a specific job's basic info

create or replace function M_GET_JOB_INFORMATION_BASIC
               ( IN_JOB_NAME                 varchar(10)
                ,IN_JOB_USER                 varchar(10)
                ,IN_JOB_NUMBER               varchar(6)
                ,IN_INTERNAL_JOB_IDENTIFIER  varchar(16)
               )

  returns table( JOB_NAME                    char(10)
                ,JOB_USER                    char(10)
                ,JOB_NUMBER                  char(6)
                ,INTERNAL_JOB_IDENTIFIER     char(16)
                ,JOB_STATUS                  char(10)
                ,JOB_TYPE                    char(1)
                ,JOB_SUBTYPE                 char(1)
                ,RUN_PRIORITY                int
                ,TIME_SLICE                  int
                ,DEFAULT_WAIT                int
                ,ELIGIBLE_FOR_PURGE          char(10)
                ,ERROR_ID                    char(7)   --returned as blank if valid job requested, else IBM error message ID returned.
                ,ERROR_DATA                  char(32)  --returned as blank if valid job requested, else IBM error message data returned.
               )

  language SQL
  specific M_JOBINFBF
  not deterministic
  disallow parallel
  no external action
  modifies SQL data
  called on null input
  not fenced
  cardinality 1  --helps SQL optimizer

  set option dbgview   = *source
            ,commit    = *nc
            ,closqlcsr = *endmod
            ,dftrdbcol = *none
            ,tgtrls    = V7R1M0

begin

declare RECEIVER_VARIABLE             char(86)    for bit data  default '';          --receives "JOBI0100" format output from API
declare LENGTH_OF_RECEIVER_VARIABLE   int                       default 86;          --length of "JOBI0100" Format
declare FORMAT_NAME                   char(8)                   default 'JOBI0100';  --basic job information
declare QUALIFIED_JOB_NAME            char(26);
declare INTERNAL_JOB_IDENTIFIER       char(16);
declare ERROR_CODE                    char(48)    for bit data  default '';  --ERROR_CODE "ERRC0100" Format - Input: bytes provided (4); Output: bytes available (4), error message ID (7), reserved bytes (1), error data (32) = 48 bytes

if IN_INTERNAL_JOB_IDENTIFIER = '' then
   set QUALIFIED_JOB_NAME = char( IN_JOB_NAME, 10 ) || char( IN_JOB_USER, 10 ) || char( IN_JOB_NUMBER, 6 );
   set INTERNAL_JOB_IDENTIFIER = '';
else
   set QUALIFIED_JOB_NAME = '*INT';
   set INTERNAL_JOB_IDENTIFIER = IN_INTERNAL_JOB_IDENTIFIER;
end if;

set ERROR_CODE = bx'00000030';  --put size of ERROR_CODE here in hex (Bytes Provided) hex 30 = dec 48

call M_GET_JOB_INFORMATION
        ( RECEIVER_VARIABLE             --in/out
         ,LENGTH_OF_RECEIVER_VARIABLE   --in
         ,FORMAT_NAME                   --in
         ,QUALIFIED_JOB_NAME            --in
         ,INTERNAL_JOB_IDENTIFIER       --in
         ,ERROR_CODE                    --in/out
        );

return values( char( substr( RECEIVER_VARIABLE,  9, 10 ), 10 )      --JOB_NAME
              ,char( substr( RECEIVER_VARIABLE, 19, 10 ), 10 )      --JOB_USER
              ,char( substr( RECEIVER_VARIABLE, 29,  6 ),  6 )      --JOB_NUMBER
              ,char( substr( RECEIVER_VARIABLE, 35, 16 ), 16 )      --INTERNAL_JOB_IDENTIFIER
              ,char( substr( RECEIVER_VARIABLE, 51, 10 ), 10 )      --JOB_STATUS
              ,char( substr( RECEIVER_VARIABLE, 61,  1 ),  1 )      --JOB_TYPE
              ,char( substr( RECEIVER_VARIABLE, 62,  1 ),  1 )      --JOB_SUBTYPE
              ,case when substr( RECEIVER_VARIABLE, 65, 4 ) = ''
                    then int( 0 )
                    else M_HEX_STRING_TO_INTEGER( hex( substr( RECEIVER_VARIABLE, 65, 4 ) ) )
               end                                                  --RUN_PRIORITY
              ,case when substr( RECEIVER_VARIABLE, 69, 4 ) = ''
                    then int( 0 )
                    else M_HEX_STRING_TO_INTEGER( hex( substr( RECEIVER_VARIABLE, 69, 4 ) ) )
               end                                                  --TIME_SLICE
              ,case when substr( RECEIVER_VARIABLE, 73, 10 ) = ''
                    then int( 0 )
                    else M_HEX_STRING_TO_INTEGER( hex( substr( RECEIVER_VARIABLE, 73, 4 ) ) )
               end                                                  --DEFAULT_WAIT
              ,char( substr( RECEIVER_VARIABLE, 77, 10 ), 10 )      --ELIGIBLE_FOR_PURGE
              ,char( substr( ERROR_CODE,  9, min( M_HEX_STRING_TO_INTEGER( hex( substr( ERROR_CODE, 5, 4 ) ) ),  7 ) ), 7 )   --ERROR_ID
              ,char( substr( ERROR_CODE, 17, min( M_HEX_STRING_TO_INTEGER( hex( substr( ERROR_CODE, 5, 4 ) ) ), 32 ) ), 32 )  --ERROR_DATA
             );

end
;

--M_HEX_STRING_TO_INTEGER - M_HEXTOINT - scalar SQL UDF to convert a hex string of an integer to an integer return value

--Use for working with binary strings and OS/400 system APIs.

--Throws an error if an invalid byte is found inside the input hex string.

create or replace function M_HEX_STRING_TO_INTEGER
   ( IN_HEX_STRING varchar(8) )

returns int

  language SQL
  specific M_HEXTOINT
  not deterministic
  disallow parallel
  no external action
  modifies SQL data
  returns null on null input
  not fenced

  set option dbgview   = *source
            ,commit    = *nc
            ,closqlcsr = *endmod
            ,tgtrls    = V7R1M0

begin

declare BYTE_CHAR      char(1);
declare POS            int        default 1;
declare ACCUM_VALUE    int;
declare BIGINT_RESULT  bigint     default 0;
declare STRING_LENGTH  int;

set STRING_LENGTH = length( IN_HEX_STRING );

while POS <= STRING_LENGTH do
   set BYTE_CHAR = substr( IN_HEX_STRING, POS, 1 );

   if BYTE_CHAR between '0' and '9' then
      set ACCUM_VALUE = int( BYTE_CHAR );
   else
      set ACCUM_VALUE = case BYTE_CHAR
                             when 'A' then 10
                             when 'B' then 11
                             when 'C' then 12
                             when 'D' then 13
                             when 'E' then 14
                             when 'F' then 15
                             else raise_error( 'MG010', 'Function M_HEX_STRING_TO_INTEGER encountered invalid INT hex byte=' || BYTE_CHAR )
                        end;
   end if;

   if ACCUM_VALUE <> 0 then
      set BIGINT_RESULT = BIGINT_RESULT + ( ACCUM_VALUE * power( 16, STRING_LENGTH - POS ) ) ;
   end if;

   set POS = POS + 1;
end while;

return int( case when BIGINT_RESULT > 2147483647 then BIGINT_RESULT - 4294967296 else BIGINT_RESULT end );

end


来源:https://stackoverflow.com/questions/27681270/looking-for-a-working-example-of-any-os-400-api-wrapped-in-an-external-sql-store

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!