Skip to content Skip to sidebar Skip to footer

Looking For A Working Example Of Any Os/400 Api Wrapped In An External Sql Stored Procedure Wrapped In A User Defined Sql Function

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

Solution 1:

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

CREATEPROCEDURE 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) , 
    INSYSTEMCHAR(10) , 
    IN    FMTTYP   CHAR(10) , 
    IN    ERRCOD   CHAR(8) ) 
    LANGUAGE CL 
    SPECIFIC SQLEXAMPLE.DBRTVFD 
    NOTDETERMINISTICNOSQLCALLEDONNULL 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:

CREATEPROCEDURE 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 
    NOTDETERMINISTICNOSQLCALLEDONNULL INPUT 
    EXTERNAL NAME 'QSYS/QUSRJOBI'PARAMETER STYLE GENERAL ;

A basic wrapper was created like so:

CREATEPROCEDURE SQLEXAMPLE.GENRJOBI ( 
    INOUT JOBI       VARCHAR(85) , 
    IN       QJOB      VARCHAR(26) ) 
    LANGUAGESQLSPECIFIC SQLEXAMPLE.GENRJOBI 
    NOTDETERMINISTICMODIFIESSQL DATA 
    CALLEDONNULL 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 : BEGINDECLARE 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:

JOBI proc output

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.

Solution 2:

Solution 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.createor 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
  notdeterministicmodifiesSQL 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 infocreateor 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)
               )

  returnstable( 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.
               )

  languageSQLspecific M_JOBINFBF
  notdeterministic
  disallow parallel
  noexternal action
  modifiesSQL data
  calledonnull input
  not fenced
  cardinality1--helps SQL optimizerset option dbgview   =*source
            ,commit=*nc
            ,closqlcsr =*endmod
            ,dftrdbcol =*none
            ,tgtrls    = V7R1M0

begindeclare RECEIVER_VARIABLE             char(86)    for bit data  default'';          --receives "JOBI0100" format output from APIdeclare LENGTH_OF_RECEIVER_VARIABLE   intdefault86;          --length of "JOBI0100" Formatdeclare FORMAT_NAME                   char(8)                   default'JOBI0100';  --basic job informationdeclare 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 =''thenset QUALIFIED_JOB_NAME =char( IN_JOB_NAME, 10 ) ||char( IN_JOB_USER, 10 ) ||char( IN_JOB_NUMBER, 6 );
   set INTERNAL_JOB_IDENTIFIER ='';
elseset 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 48call 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
        );

returnvalues( 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
              ,casewhen substr( RECEIVER_VARIABLE, 65, 4 ) =''thenint( 0 )
                    else M_HEX_STRING_TO_INTEGER( hex( substr( RECEIVER_VARIABLE, 65, 4 ) ) )
               end--RUN_PRIORITY
              ,casewhen substr( RECEIVER_VARIABLE, 69, 4 ) =''thenint( 0 )
                    else M_HEX_STRING_TO_INTEGER( hex( substr( RECEIVER_VARIABLE, 69, 4 ) ) )
               end--TIME_SLICE
              ,casewhen substr( RECEIVER_VARIABLE, 73, 10 ) =''thenint( 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.createor replace function M_HEX_STRING_TO_INTEGER
   ( IN_HEX_STRING varchar(8) )

returnsintlanguageSQLspecific M_HEXTOINT
  notdeterministic
  disallow parallel
  noexternal action
  modifiesSQL data
  returnsnullonnull input
  not fenced

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

begindeclare BYTE_CHAR      char(1);
declare POS            intdefault1;
declare ACCUM_VALUE    int;
declare BIGINT_RESULT  bigintdefault0;
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'thenset ACCUM_VALUE =int( BYTE_CHAR );
   elseset ACCUM_VALUE =case BYTE_CHAR
                             when'A'then10when'B'then11when'C'then12when'D'then13when'E'then14when'F'then15else raise_error( 'MG010', 'Function M_HEX_STRING_TO_INTEGER encountered invalid INT hex byte='|| BYTE_CHAR )
                        end;
   end if;

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

   set POS = POS +1;
end while;

returnint( casewhen BIGINT_RESULT >2147483647then BIGINT_RESULT -4294967296else BIGINT_RESULT end );

end

Post a Comment for "Looking For A Working Example Of Any Os/400 Api Wrapped In An External Sql Stored Procedure Wrapped In A User Defined Sql Function"