04 - System Administration

Rimuovere i vecchi spool file per tenere sotto controllo il numero di lavori nel sistema

Il valore di sistema QMAXJOB (intervallo valido: 32.000 – 970.000; valore predefinito = 163.520) definisce il numero massimo di lavori che possono esistere su un sistema IBM i. Questo valore tiene conto dei lavori attivi, dei lavori in coda e dei lavori che sono terminati ma che hanno file di spool collegati (*KEEP è il valore predefinito sia per il valore di sistema QSPLFACN che per l’attributo del lavoro SPLFACN).

Raggiungere questo limite rappresenta una condizione critica che può compromettere l’operatività del sistema rendendo impossibile avviare nuovi lavori, sino a renderlo inaccessibile se non tramite la console (il sistema riserva una voce della tabella dei lavori per la console per garantire l’accesso).

Il rischio maggiore è che, in assenza di un monitoraggio proattivo e di una gestione efficace dei lavori non più attivi, l’esaurimento del limite QMAXJOB si presenti improvvisamente, lasciando poche opzioni di intervento se non un IPL (Initial Program Load).

Quando il sistema si avvicina al numero massimo di lavori, il messaggio CPI1468, Tabella lavori di sistema quasi piena, viene inviato alla coda di messaggi QSYSOPR. Per impostazione predefinita, questo messaggio viene inviato per la prima volta quando le tabelle di lavoro del sistema raggiungono il 90% della loro capacità in base all’impostazione QMAXJOB. Il messaggio viene inviato nuovamente ogni volta che le tabelle di lavoro vengono estese per aggiungere altre strutture di lavoro.

È quindi fondamentale monitorare costantemente il numero di lavori attivi (visibile, ad esempio, tramite i comandi WRKSYSSTS e DSPSYSSTS o tramite la vista di sistema QSYS2.SYSTEM_STATUS_INFO_BASIC oppure controllabile con il servizio di sistema QSYS2.SYSLIMITS o monitorando la coda messaggi QSYSOPR) e adottare politiche di gestione automatica dei lavori terminati ma con file di spool collegati) oltre a valutare periodicamente l’adeguatezza del valore assegnato a QMAXJOB rispetto alla crescita e alla complessità del sistema IBM i.

Il programma RMVOLDSPLF, oggetto di questo articolo, può aiutare a limitare la crescita dei lavori nel sistema fornendo un aiuto nella gestione dei lavori terminati con file di spool collegati (che occupano entrate nella tabella dei lavori e partecipano ad incrementare il conteggio dei lavori presenti nel sistema) rimuovendo file di spool datati o privi di utilità per tentare di liberare entrate nella tabella dei lavori (visualizzabile con il comando DSPJOBTBL).

Nota. Per rimuovere dalla tabella lavori del sistema un lavoro terminato con file di spool collegati bisogna scollegare (CHGJOB JOB(…/…/…) .SPLFACN(*DETACH)) o cancellare (DLTSPLF FILE(…) JOB(…/…/…)) tutti i file di spool ad esso collegati.

Si tratta di un programma CLLE che fa uso della table function QSYS2.SPOOLED_FILE_INFO per selezionare i file di spool da rimuovere secondo i valori passati nel comando:

                  Remove old spooled files (SQL) (RMVOLDSPLF)          
                                                                       
Immettere le scelte e premere Invio.                                   
                                                                       
Job name . . . . . . . . . . . .                 Nome, *ALL            
User name  . . . . . . . . . . .                 Nome, *ALL, *CURRENT  
File name  . . . . . . . . . . .   *ALL          Nome, *ALL            
User data  . . . . . . . . . . .   *ALL          Valore carattere, *ALL
Output queue . . . . . . . . . .   *ALL          Nome, *ALL            
  Library  . . . . . . . . . . .                 Nome, *CURLIB, *LIBL  
Retention days . . . . . . . . .                 Numero                

Codice sorgente comando:

 RMVOLDSPLF: CMD        PROMPT('Remove old spooled files (SQL)')
             PARM       KWD(JOBNAME) TYPE(*NAME) LEN(10) SPCVAL((*ALL)) +
                          MIN(1) ALWVAR(*YES) EXPR(*YES) PROMPT('Job name' 1)
             PARM       KWD(USERNAME) TYPE(*NAME) LEN(10) SPCVAL((*ALL) +
                          (*CURRENT)) MIN(1) ALWVAR(*YES) EXPR(*YES) +
                          PROMPT('User name' 2)
             PARM       KWD(DAYS) TYPE(*UINT2) MIN(1) ALWVAR(*YES) +
                          EXPR(*YES) PROMPT('Retention days' 6)
             PARM       KWD(FILENAME) TYPE(*NAME) LEN(10) DFT(*ALL) +
                          SPCVAL((*ALL)) ALWVAR(*YES) EXPR(*YES) +
                          PROMPT('File name' 3)
             PARM       KWD(USRDTA) TYPE(*CHAR) LEN(10) DFT(*ALL) +
                          SPCVAL((*ALL)) ALWVAR(*YES) EXPR(*YES) +
                          CASE(*MIXED) PROMPT('User data' 4)
             PARM       KWD(OUTQ) TYPE(QOUTQ) PROMPT('Output queue' 5)
 QOUTQ:      QUAL       TYPE(*NAME) LEN(10) EXPR(*YES) DFT(*ALL) SPCVAL(*ALL)
             QUAL       TYPE(*NAME) LEN(10) SPCVAL((*CURLIB *CURLIB) +
                          (*LIBL *LIBL)) EXPR(*YES) PROMPT('Library')
             PARM       KWD(RMV) TYPE(*CHAR) LEN(4) RSTD(*YES) DFT(*YES) +
                          SPCVAL((*YES) (*NO)) EXPR(*YES) PMTCTL(*PMTRQS) +
                          PROMPT('Really remove?' 7)
             PARM       KWD(VERBOSE) TYPE(*CHAR) LEN(4) RSTD(*YES) +
                          DFT(*NO) SPCVAL((*YES) (*NO)) EXPR(*YES) +
                          PMTCTL(*PMTRQS) PROMPT('Verbose' 8)

Codice sorgente programma:

/* Before compiling: +
   CREATE TABLE QTEMP.SQLOUT AS ( +
   SELECT SFI.CREATION_TIMESTAMP CRTTIMSTM, SFI.SPOOLED_FILE_NAME SPLFNAME, +
   SFI.SPOOLED_FILE_NUMBER SPLFNBR, SFI.QUALIFIED_JOB_NAME JOB +
   FROM TABLE(QSYS2.SPOOLED_FILE_INFO()) SFI LIMIT 1 ) +
   WITH NO DATA */

/* RMVOLDSPLF JOBNAME(QP0ZSPWP) USERNAME(*ALL) DAYS(60) +
              [USRDTA(*ALL) OUTQ(*ALL)] */

             PGM        PARM(&PJOBNAM &PUSRNAM &PDAYS &PFILNAM &PUSRDTA +
                          &PQOUTQ &PRMV &PVERBOSE)

/* PARM: Start ****************************************************************/
             DCL        VAR(&PJOBNAM) TYPE(*CHAR) LEN(10)
             DCL        VAR(&PUSRNAM) TYPE(*CHAR) LEN(10)
             DCL        VAR(&PDAYS) TYPE(*UINT) LEN(2)
             DCL        VAR(&PFILNAM) TYPE(*CHAR) LEN(10)
             DCL        VAR(&PUSRDTA) TYPE(*CHAR) LEN(10)
             DCL        VAR(&PQOUTQ) TYPE(*CHAR) LEN(20)
             DCL        VAR(&OUTQNAM) TYPE(*CHAR) STG(*DEFINED) LEN(10) +
                          DEFVAR(&PQOUTQ 1)
             DCL        VAR(&OUTQLIB) TYPE(*CHAR) STG(*DEFINED) LEN(10) +
                          DEFVAR(&PQOUTQ 11)
             DCL        VAR(&PRMV) TYPE(*CHAR) LEN(4)
             DCL        VAR(&PVERBOSE) TYPE(*CHAR) LEN(4)
/* PARM: End ******************************************************************/

/* SUBR(RTVENDDAT): Start *****************************************************/
             DCL        VAR(&RTVENDDAT) TYPE(*INT) LEN(4)

             DCL        VAR(&CURDATE) TYPE(*CHAR) LEN(20)
             DCL        VAR(&CURYYMD) TYPE(*CHAR) STG(*DEFINED) LEN(8) +
                          DEFVAR(&CURDATE 1)
             DCL        VAR(&CURHHMISS) TYPE(*CHAR) STG(*DEFINED) LEN(6) +
                          DEFVAR(&CURDATE 9)
             DCL        VAR(&CURHH) TYPE(*CHAR) STG(*DEFINED) LEN(2) +
                          DEFVAR(&CURHHMISS 1)
             DCL        VAR(&CURMI) TYPE(*CHAR) STG(*DEFINED) LEN(2) +
                          DEFVAR(&CURHHMISS 3)
             DCL        VAR(&CURSS) TYPE(*CHAR) STG(*DEFINED) LEN(2) +
                          DEFVAR(&CURHHMISS 5)
             DCL        VAR(&CURMICSEC) TYPE(*CHAR) STG(*DEFINED) LEN(6) +
                          DEFVAR(&CURDATE 15)
             DCL        VAR(&LILDATE) TYPE(*INT)
             DCL        VAR(&YYYY_MM_DD) TYPE(*CHAR) LEN(10)
             DCL        VAR(&HH_MI_SS) TYPE(*CHAR) LEN(8)
             DCL        VAR(&CURTIMSTMP) TYPE(*CHAR) LEN(26)
             DCL        VAR(&TIMSEP) TYPE(*CHAR) LEN(1) VALUE(':')
/* SUBR(RTVENDDAT): End *******************************************************/

/* SUBR(RUNSQLSEL): Start *****************************************************/
             DCL        VAR(&RUNSQLSEL) TYPE(*INT) LEN(4)

/* RUNSQL */
             DCL        VAR(&SQL) TYPE(*CHAR) LEN(5000)
             DCL        VAR(&OUTQ) TYPE(*CHAR) LEN(21)
             DCL        VAR(&SQLLIB) TYPE(*CHAR) LEN(10) VALUE('QTEMP')
             DCL        VAR(&SQLFIL) TYPE(*CHAR) LEN(10) VALUE('SQLOUT')
             DCL        VAR(&SQLMBR) TYPE(*CHAR) LEN(10) VALUE('SQLOUT')
/* SUBR(RUNSQLSEL): End *******************************************************/

/* SUBR(RMVSPLFIL): Start *****************************************************/
             DCL        VAR(&RMVSPLFIL) TYPE(*INT) LEN(4)

             DCLF       FILE(SQLOUT) OPNID(SQLOUT) ALWVARLEN(*YES)
             DCL        VAR(&SQLROWS) TYPE(*DEC) LEN(10 0)
             DCL        VAR(&RCVSQLOUT) TYPE(*LGL) VALUE('1')
             DCL        VAR(&RCDNUM) TYPE(*UINT) LEN(4)
             DCL        VAR(&CMD_OK) TYPE(*UINT) LEN(4)
             DCL        VAR(&CMD_KO) TYPE(*UINT) LEN(4)
/* SUBR(RMVSPLFIL): End *******************************************************/

/* stat64 */
             DCL        VAR(&STATRTNVAL) TYPE(*INT) LEN(4)
             DCL        VAR(&STATPATH) TYPE(*CHAR) LEN(256)
             DCL        VAR(&STATBUFFER) TYPE(*CHAR) LEN(4096)
             DCL        VAR(&STATOBJTYP) TYPE(*CHAR) STG(*DEFINED) LEN(10) +
                          DEFVAR(&STATBUFFER 61)
/*           DCL        VAR(&NULL) TYPE(*CHAR) LEN(1) VALUE(X'00') */

/* strlen */
             DCL        VAR(&STRTMP) TYPE(*CHAR) LEN(32767)
             DCL        VAR(&LENINT) TYPE(*UINT) LEN(4)
/*           DCL        VAR(&NULL) TYPE(*CHAR) LEN(1) VALUE(X'00') */

/* QCMDEXC */
             DCL        VAR(&CMD) TYPE(*CHAR) LEN(5000)
             DCL        VAR(&CMDLEN) TYPE(*DEC) LEN(15 5) VALUE(5000)

/* RUNSQL */
/*           DCL        VAR(&SQL) TYPE(*CHAR) LEN(5000) */

/* API Error */
             DCL        VAR(&APIERROR) TYPE(*CHAR) LEN(1040)
             DCL        VAR(&AEBYTPRO) TYPE(*INT) STG(*DEFINED) LEN(4) +
                          DEFVAR(&APIERROR 1)
             DCL        VAR(&AEBYTAVL) TYPE(*INT) STG(*DEFINED) LEN(4) +
                          DEFVAR(&APIERROR 5)
             DCL        VAR(&AEEXCPID) TYPE(*CHAR) STG(*DEFINED) LEN(7) +
                          DEFVAR(&APIERROR 9)
             DCL        VAR(&AEEXCPDTA) TYPE(*CHAR) STG(*DEFINED) +
                          LEN(1024) DEFVAR(&APIERROR 17)

/* _MATPGMNM */
             DCL        VAR(&DATA) TYPE(*CHAR) LEN(80)
             DCL        VAR(&PGMNAME) TYPE(*CHAR) LEN(10)
             DCL        VAR(&PGMLIB) TYPE(*CHAR) LEN(10)

/* RTVNETA */
             DCL        VAR(&SYSNAME) TYPE(*CHAR) LEN(8)
             DCL        VAR(&CUST3) TYPE(*CHAR) STG(*DEFINED) LEN(3) +
                          DEFVAR(&SYSNAME 4)

/* RTVJOBA */
             DCL        VAR(&JOBNAME) TYPE(*CHAR) LEN(10)
             DCL        VAR(&JOBUSER) TYPE(*CHAR) LEN(10)
             DCL        VAR(&JOBNBR) TYPE(*CHAR) LEN(6)
             DCL        VAR(&LOGCLPGM) TYPE(*CHAR) LEN(10) VALUE(' ')

/* RCVMSG & SNDPGMMSG */
             DCL        VAR(&PRCNAME) TYPE(*CHAR) LEN(256) /* CALLPRC */
             DCL        VAR(&CMDNAME) TYPE(*CHAR) LEN(10)
             DCL        VAR(&ERROR) TYPE(*LGL) VALUE('0')
             DCL        VAR(&PGMERROR) TYPE(*CHAR) VALUE('-')
             DCL        VAR(&DUMP) TYPE(*LGL) VALUE('0')
             DCL        VAR(&SUBRNAME) TYPE(*CHAR) LEN(10)
             DCL        VAR(&ERR_PGM) TYPE(*CHAR) LEN(10)
             DCL        VAR(&ERR_FUN) TYPE(*CHAR) LEN(10)
             DCL        VAR(&ERR_TEXT) TYPE(*CHAR) LEN(100) /* For handled +
                          errors only */
             DCL        VAR(&MSGID) TYPE(*CHAR) LEN(7)
             DCL        VAR(&MSGDTA) TYPE(*CHAR) LEN(512)
             DCL        VAR(&MSGDTALEN) TYPE(*DEC) LEN(5 0)
             DCL        VAR(&MSG) TYPE(*CHAR) LEN(1024)
             DCL        VAR(&MSGLEN) TYPE(*DEC) LEN(5 0)
             DCL        VAR(&SECLVL) TYPE(*CHAR) LEN(1024)
             DCL        VAR(&SECLVLLEN) TYPE(*DEC) LEN(5 0)
             DCL        VAR(&MSGF) TYPE(*CHAR) LEN(10)
             DCL        VAR(&MSGFLIB) TYPE(*CHAR) LEN(10)
             DCL        VAR(&MSGKEY) TYPE(*CHAR) LEN(4)
             DCL        VAR(&MSGKEYRQS) TYPE(*CHAR) LEN(4)
             DCL        VAR(&MSGTYPE) TYPE(*CHAR) LEN(7)
             DCL        VAR(&RTNTYPE) TYPE(*CHAR) LEN(2)
             DCL        VAR(&SENDER) TYPE(*CHAR) LEN(80)
             DCL        VAR(&SD_PGMSDR) TYPE(*CHAR) STG(*DEFINED) LEN(10) +
                          DEFVAR(&SENDER 27)
/*           DCL        VAR(&TOMSGQ) TYPE(*CHAR) LEN(10) */
/*           DCL        VAR(&TOMSGQLIB) TYPE(*CHAR) LEN(10) */

/* Constants */
             DCL        VAR(&NULL) TYPE(*CHAR) LEN(1) VALUE(X'00')
             DCL        VAR(&QUOTE) TYPE(*CHAR) LEN(1) VALUE(X'7D')

/* Global monitor for error messages not handled */
             MONMSG     MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))

/* Retrieving program name & library */
             CHGVAR     VAR(%BIN(&DATA 1 4)) VALUE(80)
             CHGVAR     VAR(%BIN(&DATA 5 4)) VALUE(80)
             CHGVAR     VAR(%BIN(&DATA 9 4)) VALUE(0)
             CHGVAR     VAR(%BIN(&DATA 13 4)) VALUE(0)
             CALLPRC    PRC('_MATPGMNM') PARM((&DATA))
             CHGVAR     VAR(&PGMNAME) VALUE(%SST(&DATA 51 10))
             CHGVAR     VAR(&PGMLIB) VALUE(%SST(&DATA 19 10))

/* Retrieving job attributes */
             RTVJOBA    JOB(&JOBNAME) USER(&JOBUSER) NBR(&JOBNBR) +
                          LOGCLPGM(&LOGCLPGM)

/* Checking parameters */
             IF         COND(&OUTQNAM *NE '*ALL') THEN(DO)
                IF         COND(&OUTQLIB *EQ '  ') THEN(DO)
                   CHGVAR     VAR(&MSGDTA) VALUE('No library name +
                                specified for output queue.')
                   CHGVAR     VAR(&PGMERROR) VALUE('1') /* *ESCAPE */
                   GOTO       CMDLBL(DIAG)
                ENDDO      /* COND(&OUTQLIB *EQ '  ') */
                ELSE       CMD(DO)
                   CHKOBJ     OBJ(&OUTQLIB/&OUTQNAM) OBJTYPE(*OUTQ)
                ENDDO      /* COND(&OUTQLIB *NE '  ') */
             ENDDO      /* COND(&OUTQNAM *NE '*ALL') */

/*** Start of program ***/

/* Retrieving ending date */
             RTVSYSVAL  SYSVAL(QDATETIME) RTNVAR(&CURDATE)

             CALLSUBR   SUBR(RTVENDDAT) RTNVAL(&RTVENDDAT)

             SELECT
                WHEN       COND(&RTVENDDAT *EQ -1) THEN(DO) /* SUBR error */
                   CHGVAR     VAR(&PGMERROR) VALUE('1') /* *ESCAPE */
                   GOTO       CMDLBL(DIAG)
                ENDDO      /* COND(&RTVENDDAT *EQ -1) */
                WHEN       COND(&RTVENDDAT *EQ 0) THEN(DO) /* Ok */
                ENDDO      /* COND(&RTVENDDAT *EQ 0) */
                OTHERWISE  CMD(DO)
                   CHGVAR     VAR(&MSGDTA) VALUE('Invalid return code from +
                                subroutine' *BCAT &SUBRNAME *TCAT '.')
                   CHGVAR     VAR(&PGMERROR) VALUE('1') /* *ESCAPE */
                   CHGVAR     VAR(&DUMP) VALUE('1') /* Dump enabled */
                   GOTO       CMDLBL(DIAG)
                ENDDO      /* OTHERWISE */
             ENDSELECT

/* Retrieving spooled files */
             IF         COND(&PQOUTQ *EQ '*ALL') THEN(DO)
                CHGVAR     VAR(&OUTQ) VALUE(&PQOUTQ)
             ENDDO      /* COND(&PQOUTQ *EQ '*ALL') */
             ELSE       CMD(DO)
                CHGVAR     VAR(&OUTQ) VALUE(&OUTQLIB *TCAT '/' *CAT &OUTQNAM)
             ENDDO      /* COND(&PQOUTQ *NE '*ALL') */
             CHGVAR     VAR(&SQL) VALUE('SELECT SFI.CREATION_TIMESTAMP, +
                          SFI.SPOOLED_FILE_NAME, SFI.SPOOLED_FILE_NUMBER, +
                          SFI.QUALIFIED_JOB_NAME FROM +
                          TABLE(QSYS2.SPOOLED_FILE_INFO(USER_NAME => ''' +
                          *CAT &PUSRNAM *TCAT ''', STATUS => ''*HELD +
                          *READY *SAVED'', OUTPUT_QUEUE => ''' *CAT &OUTQ +
                          *TCAT ''', USER_DATA => ''' *CAT &PUSRDTA *TCAT +
                          ''', ENDING_TIMESTAMP => ''' *CAT &CURTIMSTMP +
                          *TCAT ''')) SFI')
             IF         COND((&PJOBNAM *NE '*ALL') *OR (&PFILNAM *NE +
                          '*ALL')) THEN(DO)
                CHGVAR     VAR(&SQL) VALUE(&SQL *BCAT 'WHERE')
             ENDDO      /* COND((&PJOBNAM *NE '*ALL') *OR (&PFILNAM *NE +
                          '*ALL')) */
             IF         COND(&PJOBNAM *NE '*ALL') THEN(DO)
                CHGVAR     VAR(&SQL) VALUE(&SQL *BCAT 'SFI.JOB_NAME = ''' +
                             *CAT &PJOBNAM *TCAT '''')
             ENDDO      /* COND(&PJOBNAM *NE '*ALL') */
             IF         COND((&PJOBNAM *NE '*ALL') *AND (&PFILNAM *NE +
                          '*ALL')) THEN(DO)
                CHGVAR     VAR(&SQL) VALUE(&SQL *BCAT 'AND')
             ENDDO      /* COND((&PJOBNAM *NE '*ALL') *AND (&PFILNAM *NE +
                          '*ALL')) */
             IF         COND(&PFILNAM *NE '*ALL') THEN(DO)
                CHGVAR     VAR(&SQL) VALUE(&SQL *BCAT +
                             'SFI.SPOOLED_FILE_NAME = ''' *CAT &PFILNAM +
                             *TCAT '''')
             ENDDO      /* COND(&PFILNAM *NE '*ALL') */
             CHGVAR     VAR(&SQL) VALUE(&SQL *BCAT 'ORDER BY +
                          SFI.CREATION_TIMESTAMP')
             CHGVAR     VAR(&MSGDTA) VALUE('Searching for spooled files to +
                          remove...')

             CALLSUBR   SUBR(RUNSQLSEL) RTNVAL(&RUNSQLSEL)

/* Removing spooled files */
             SELECT
                WHEN       COND(&RUNSQLSEL *EQ -1) THEN(DO) /* SUBR error */
                   CHGVAR     VAR(&PGMERROR) VALUE('1') /* *ESCAPE */
                   GOTO       CMDLBL(DIAG)
                ENDDO      /* COND(&RUNSQLSEL *EQ -1) */
                WHEN       COND(&RUNSQLSEL *EQ 0) THEN(DO) /* Ok */
                ENDDO      /* COND(&RUNSQLSEL *EQ 0) */
                OTHERWISE  CMD(DO)
                   CHGVAR     VAR(&MSGDTA) VALUE('Invalid return code from +
                                subroutine' *BCAT &SUBRNAME *TCAT '.')
                   CHGVAR     VAR(&PGMERROR) VALUE('1') /* *ESCAPE */
                   CHGVAR     VAR(&DUMP) VALUE('1') /* Dump enabled */
                   GOTO       CMDLBL(DIAG)
                ENDDO      /* OTHERWISE */
             ENDSELECT

             CALLSUBR   SUBR(RMVSPLFIL) RTNVAL(&RMVSPLFIL)

             SELECT
                WHEN       COND(&RMVSPLFIL *EQ -1) THEN(DO) /* SUBR error */
                   CHGVAR     VAR(&PGMERROR) VALUE('1') /* *ESCAPE */
                   GOTO       CMDLBL(DIAG)
                ENDDO      /* COND(&RMVSPLFIL *EQ -1) */
                WHEN       COND(&RMVSPLFIL *EQ 0) THEN(DO) /* Ok */
                ENDDO      /* COND(&RMVSPLFIL *EQ 0) */
                OTHERWISE  CMD(DO)
                   CHGVAR     VAR(&MSGDTA) VALUE('Invalid return code from +
                                subroutine' *BCAT &SUBRNAME *TCAT '.')
                   CHGVAR     VAR(&PGMERROR) VALUE('1') /* *ESCAPE */
                   CHGVAR     VAR(&DUMP) VALUE('1') /* Dump enabled */
                   GOTO       CMDLBL(DIAG)
                ENDDO      /* OTHERWISE */
             ENDSELECT

/*** End of program ***/

/* Cleaning up */
             CALLSUBR   SUBR(CLEANUP)

/* Exit */
             GOTO       CMDLBL(RETURN)

 DIAG:
/* SUBRs don't have to send diagnostic messages, everything has to be done in +
   MAIN */
/* Send diagnostic messagge for handled errors */
             IF         COND(&ERR_TEXT *NE ' ') THEN(DO)
                SNDPGMMSG  MSGID(CPF9897) MSGF(QCPFMSG) MSGDTA(&ERR_TEXT) +
                             TOPGMQ(*PRV (*)) TOMSGQ(*TOPGMQ) MSGTYPE(*DIAG)
             ENDDO      /* COND(&ERR_TEXT *NE ' ') */

             IF         COND(&MSGID *EQ ' ') THEN(DO)
                CHGVAR     VAR(&MSGID) VALUE('CPF9897')
             ENDDO      /* COND(&MSGID *EQ ' ') */

             SNDPGMMSG  MSGID(&MSGID) MSGF(QCPFMSG) MSGDTA(&MSGDTA) +
                          TOPGMQ(*SAME (*)) TOMSGQ(*TOPGMQ) MSGTYPE(*DIAG)

             IF         COND(&PGMERROR *EQ '-') THEN(DO)
                CHGVAR     VAR(&PGMERROR) VALUE('0') /* *DIAG */
             ENDDO      /* COND(&PGMERROR *EQ '-') */

 ERROR:
             IF         COND(&ERROR *EQ '1') THEN(RETURN)

             CHGVAR     VAR(&ERROR) VALUE('1')

             RCVMSG     PGMQ(*SAME) MSGTYPE(*LAST) RMV(*YES) MSG(&MSG) +
                          MSGLEN(&MSGLEN) SECLVL(&SECLVL) +
                          SECLVLLEN(&SECLVLLEN) MSGDTA(&MSGDTA) +
                          MSGDTALEN(&MSGDTALEN) MSGID(&MSGID) +
                          RTNTYPE(&RTNTYPE) MSGF(&MSGF) SNDMSGFLIB(&MSGFLIB)
/*           MONMSG     MSGID(CPF0000) */

             IF         COND((&PGMERROR *EQ '-') *OR (&DUMP)) THEN(DO) /* +
                          Global MONMSG or forced dump */
                CHGVAR     VAR(&ERR_TEXT) VALUE(' ') /* Handled errors only */
                OVRPRTF    FILE(QPPGMDMP) USRDTA(&PGMNAME) SPLFOWN(*JOB) +
                             OVRSCOPE(*CALLLVL)
                MONMSG     MSGID(CPF0000)
                DMPCLPGM
                MONMSG     MSGID(CPF0000)
                DLTOVR     FILE(QPPGMDMP) LVL(*)
                MONMSG     MSGID(CPF0000)
             ENDDO      /* COND((&PGMERROR *EQ '-') *OR (&DUMP)) */

             CALLSUBR   SUBR(CLEANUP)

/* 02: Diagnostic - 15: Escape (exception already handled at time of RCVMSG) +
                  - 17: Escape (exception not handled at time of RCVMSG) */
             IF         COND((&RTNTYPE *EQ '02') *OR (&RTNTYPE *EQ '15') +
                          *OR (&RTNTYPE *EQ '17')) THEN(DO)

                IF         COND(&PGMERROR *EQ '0') THEN(DO)
/* Set DIAGNOSTIC message */
                   CHGVAR     VAR(&MSGTYPE) VALUE('*DIAG')
                ENDDO      /* COND(&PGMERROR *EQ '0') */
                ELSE       CMD(DO)
/* Set ESCAPE message */
                   CHGVAR     VAR(&MSGTYPE) VALUE('*ESCAPE')
                ENDDO      /* COND(&PGMERROR *NE '0') */

/* Send error message */
                SNDPGMMSG  MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) +
                             MSGDTA(&MSGDTA) TOPGMQ(*PRV (*)) +
                             TOMSGQ(*TOPGMQ) MSGTYPE(&MSGTYPE)
                MONMSG     MSGID(CPF0000)

             ENDDO      /* COND((&RTNTYPE *EQ '02') *OR (&RTNTYPE *EQ +
                          '15') *OR (&RTNTYPE *EQ '17')) */

/* Normal exit */
 RETURN:
             RETURN

/* SUBR(RTVENDDAT): Start *****************************************************/
 RTVENDDAT:  SUBR       SUBR(RTVENDDAT)

                CHGVAR     VAR(&SUBRNAME) VALUE('RTVENDDTA')
                CHGVAR     VAR(&RTVENDDAT) VALUE(0)

                CALLPRC    PRC(CEEDAYS) PARM((&CURYYMD) ('YYYYMMDD') +
                             (&LILDATE) (*OMIT))
                MONMSG     MSGID(CEE0000) EXEC(DO)
                   CHGVAR     VAR(&ERR_PGM) VALUE('CEEDAYS')
                   CHGVAR     VAR(&ERR_FUN) VALUE('PRC')
                   CHGVAR     VAR(&ERR_TEXT) VALUE(&ERR_FUN *TCAT '(' *CAT +
                                &ERR_PGM *TCAT ')@SUBR(' *CAT &SUBRNAME +
                                *TCAT '): An error occurred. Check the job +
                                log.')
                   RTNSUBR    RTNVAL(-1)
                ENDDO      /* MSGID(CEE0000) */

                CHGVAR     VAR(&LILDATE) VALUE(&LILDATE - &PDAYS)

                CALLPRC    PRC(CEEDATE) PARM((&LILDATE) ('YYYY-MM-DD') +
                             (&YYYY_MM_DD) (*OMIT))
                MONMSG     MSGID(CEE0000) EXEC(DO)
                   CHGVAR     VAR(&ERR_PGM) VALUE('CEEDATE')
                   CHGVAR     VAR(&ERR_FUN) VALUE('PRC')
                   CHGVAR     VAR(&ERR_TEXT) VALUE(&ERR_FUN *TCAT '(' *CAT +
                                &ERR_PGM *TCAT ')@SUBR(' *CAT &SUBRNAME +
                                *TCAT '): An error occurred. Check the job +
                                log.')
                   RTNSUBR    RTNVAL(-1)
                ENDDO      /* MSGID(CEE0000) */

                IF         COND(&PDAYS *EQ 0) THEN(DO)
                   CHGVAR     VAR(&HH_MI_SS) VALUE(&CURHH *CAT &TIMSEP +
                                *CAT &CURMI *CAT &TIMSEP *CAT &CURSS)
                   CHGVAR     VAR(&CURTIMSTMP) VALUE(&YYYY_MM_DD *BCAT +
                                &HH_MI_SS *CAT '.' *CAT &CURMICSEC)
                ENDDO
                ELSE       CMD(DO)
                   CHGVAR     VAR(&CURTIMSTMP) VALUE(&YYYY_MM_DD)
                ENDDO

/*              CHGVAR     VAR(&RTVENDDAT) VALUE(0) */

             ENDSUBR    RTNVAL(&RTVENDDAT)
/* SUBR(RTVENDDAT): End *******************************************************/

/* SUBR(RUNSQLSEL): Start *****************************************************/
 RUNSQLSEL:  SUBR       SUBR(RUNSQLSEL)

                CHGVAR     VAR(&SUBRNAME) VALUE('RUNSQLSEL')
                CHGVAR     VAR(&RUNSQLSEL) VALUE(0)

                DLTF       FILE(&SQLLIB/&SQLFIL)
                MONMSG     MSGID(CPF2105) EXEC(DO)
                   RCVMSG     MSGTYPE(*LAST) RMV(*YES)
                ENDDO      /* MSGID(CPF2105) */

                IF         COND(&MSGID *EQ ' ') THEN(DO)
                   CHGVAR     VAR(&MSGID) VALUE('CPI8859')
                ENDDO      /* COND(&MSGID *EQ ' ') */
                IF         COND(&MSGDTA *EQ ' ') THEN(DO)
                   CHGVAR     VAR(&MSGDTA) VALUE('Running SQL statement...')
                ENDDO      /* COND(&MSGDTA *EQ ' ') */
                SNDPGMMSG  MSGID(&MSGID) MSGF(QCPFMSG) MSGDTA(&MSGDTA) +
                             TOPGMQ(*EXT) TOMSGQ(*TOPGMQ) MSGTYPE(*STATUS)

                CHGVAR     VAR(&SQL) VALUE('CREATE TABLE' *BCAT &SQLLIB +
                             *TCAT '/' *CAT &SQLFIL *BCAT 'AS (' *CAT &SQL +
                             *TCAT ') WITH DATA')

                RUNSQL     SQL(&SQL) COMMIT(*NONE) NAMING(*SYS)
                MONMSG     MSGID(SQL0000) EXEC(DO)
                   CHGVAR     VAR(&ERR_PGM) VALUE('RUNSQL')
                   CHGVAR     VAR(&ERR_FUN) VALUE('CMD')
                   CHGVAR     VAR(&ERR_TEXT) VALUE(&ERR_FUN *TCAT '(' *CAT +
                                &ERR_PGM *TCAT ')@SUBR(' *CAT &SUBRNAME +
                                *TCAT '): An error occurred. Check the job +
                                log.')
                   CHGVAR     VAR(&MSGID) VALUE('CPF9897')
                   CHGVAR     VAR(&MSGDTA) VALUE('SQL statement: "' *CAT +
                                &SQL *TCAT '".')
                   SNDPGMMSG  MSGID(&MSGID) MSGF(QCPFMSG) MSGDTA(&MSGDTA) +
                                TOPGMQ(*EXT) TOMSGQ(*TOPGMQ) MSGTYPE(*STATUS)
                   RTNSUBR    RTNVAL(-1)
                ENDDO      /* MSGID(CPF0000) */

/*              CHGVAR     VAR(&RUNSQLSEL) VALUE(0) */

             ENDSUBR    RTNVAL(&RUNSQLSEL)
/* SUBR(RUNSQLSEL): End *******************************************************/

/* SUBR(RMVSPLFIL): Start *****************************************************/
 RMVSPLFIL:  SUBR       SUBR(RMVSPLFIL)

                CHGVAR     VAR(&SUBRNAME) VALUE('RMVSPLFIL')
                CHGVAR     VAR(&RMVSPLFIL) VALUE(0)

                RTVMBRD    FILE(&SQLLIB/&SQLFIL) MBR(&SQLMBR) +
                             NBRCURRCD(&SQLROWS)

                IF         COND(&SQLROWS *GT 0) THEN(DO)

                   CHGVAR     VAR(&MSGID) VALUE('CPI8859')
                   CHGVAR     VAR(&MSGDTA) VALUE('Removing spooled files...')
                   SNDPGMMSG  MSGID(&MSGID) MSGF(QCPFMSG) MSGDTA(&MSGDTA) +
                                TOPGMQ(*EXT) TOMSGQ(*TOPGMQ) MSGTYPE(*STATUS)

                   CHGVAR     VAR(&RCDNUM) VALUE(0)
                   CHGVAR     VAR(&CMD_OK) VALUE(0)
                   CHGVAR     VAR(&CMD_KO) VALUE(0)

                   OVRDBF     FILE(SQLOUT) TOFILE(&SQLLIB/&SQLFIL) +
                                MBR(&SQLMBR) LVLCHK(*NO) OVRSCOPE(*CALLLVL)

                   CHGJOB     LOGCLPGM(*NO)
                   MONMSG     MSGID(CPF0000)

                   DOWHILE    COND(&RCVSQLOUT)

                      RCVF       OPNID(SQLOUT)
                      MONMSG     MSGID(CPF0864) EXEC(LEAVE)

                      CHGVAR     VAR(&RCDNUM) VALUE(&RCDNUM + 1)

                      CHGVAR     VAR(&CMD) VALUE('DLTSPLF FILE(' *CAT +
                                   %SST(&SQLOUT_SPLFNAME 3 10) *TCAT ') +
                                   JOB(' *CAT %SST(&SQLOUT_JOB 3 28) *TCAT +
                                   ') SPLNBR(' *CAT %CHAR(&SQLOUT_SPLFNBR) +
                                   *TCAT ')')

                      IF         COND(&PVERBOSE *EQ '*YES') THEN(DO)
/*                       IF         COND(&RCDNUM *LE 100) THEN(DO) */
                         SNDPGMMSG  MSG('CMD: "' *CAT &CMD *TCAT '"')
/*                       ENDDO         COND(&RCDNUM *LE 100) */
                      ENDDO      /* COND(&PVERBOSE *EQ '*YES') */

                      IF         COND(&PRMV *EQ '*YES') THEN(DO)
                         CALL       PGM(QCMDEXC) PARM(&CMD &CMDLEN)
                         MONMSG     MSGID(CPF0000) EXEC(DO)
                            CHGVAR     VAR(&MSGID) VALUE('CPF9897')
                            CHGVAR     VAR(&MSGDTA) VALUE('A problem +
                                         occurred while deleting FILE(' +
                                         *CAT %SST(&SQLOUT_SPLFNAME 3 10) +
                                         *TCAT ') JOB(' *CAT +
                                         %SST(&SQLOUT_JOB 3 28) *TCAT ') +
                                         SPLNBR(' *CAT +
                                         %CHAR(&SQLOUT_SPLFNBR) *TCAT ').')
                            CHGVAR     VAR(&MSGTYPE) VALUE('*DIAG')
                            SNDPGMMSG  MSGID(&MSGID) MSGF(QCPFMSG) +
                                         MSGDTA(&MSGDTA) TOPGMQ(*PRV) +
                                         TOMSGQ(*TOPGMQ) MSGTYPE(&MSGTYPE)
                            CHGVAR     VAR(&CMD_KO) VALUE(&CMD_KO + 1)
                            ITERATE
                         ENDDO      /* MSGID(CPF0000) */
                      ENDDO      /* COND(&PRMV *EQ '*YES') */

                         CHGVAR     VAR(&CMD_OK) VALUE(&CMD_OK + 1)

                   ENDDO      /* DOWHILE */

                   CHGJOB     LOGCLPGM(&LOGCLPGM)
                   MONMSG     MSGID(CPF0000)

                   CLOSE      OPNID(SQLOUT) /* RVCF */

                   DLTOVR     FILE(SQLOUT) LVL(*) /* OVRDBF */

                   CHGVAR     VAR(&MSGID) VALUE('CPI8859')
                   IF         COND(&PRMV *EQ '*YES') THEN(DO)
                      CHGVAR     VAR(&MSGDTA) VALUE('Spooled file +
                                   selected:' *BCAT %CHAR(&SQLROWS) *BCAT +
                                   '- Removed:' *BCAT %CHAR(&CMD_OK) *BCAT +
                                   '- Failed:' *BCAT %CHAR(&CMD_KO))
                   ENDDO      /* COND(&PRMV *EQ '*YES') */
                   ELSE       CMD(DO)
                      CHGVAR     VAR(&MSGDTA) VALUE('Spooled file +
                                   selected:' *BCAT %CHAR(&SQLROWS))
                   ENDDO      /* COND(&PRMV *NE '*YES') */
                   CHGVAR     VAR(&MSGTYPE) VALUE('*COMP')
                   SNDPGMMSG  MSGID(&MSGID) MSGF(QCPFMSG) MSGDTA(&MSGDTA) +
                                TOPGMQ(*PRV) TOMSGQ(*TOPGMQ) MSGTYPE(&MSGTYPE)

                ENDDO      /* COND(&NBRCURRCD *GT 0) */

                ELSE       CMD(DO)

                   CHGVAR     VAR(&MSGID) VALUE('CPI8859')
                   CHGVAR     VAR(&MSGDTA) VALUE('No spooled files exist +
                                which match the selection criteria.')
                   CHGVAR     VAR(&MSGTYPE) VALUE('*COMP')
                   SNDPGMMSG  MSGID(&MSGID) MSGF(QCPFMSG) MSGDTA(&MSGDTA) +
                                TOPGMQ(*PRV) TOMSGQ(*TOPGMQ) MSGTYPE(&MSGTYPE)

                ENDDO      /* COND(&NBRCURRCD *EQ 0) */

/*              CHGVAR     VAR(&RMVSPLFIL) VALUE(0) */

             ENDSUBR    RTNVAL(&RMVSPLFIL)
/* SUBR(RMVSPLFIL): End *******************************************************/

/* SUBR(CLEANUP): Start *******************************************************/
 CLEANUP:    SUBR       SUBR(CLEANUP)

                CHGVAR     VAR(&SUBRNAME) VALUE('CLEANUP')

                DLTF       FILE(&SQLLIB/&SQLFIL)
                MONMSG     MSGID(CPF2105) EXEC(DO)
                   RCVMSG     MSGTYPE(*LAST) RMV(*YES)
                ENDDO      /* MSGID(CPF2105) */
                MONMSG     MSGID(CPF0000)

             ENDSUBR    RTNVAL(0)
/* SUBR(CLEANUP): End *********************************************************/

 ENDPGM:
             ENDPGM

Esempio di programma CLLE utilizzato per cancellare gli spooled file più vecchi di 7 giorni di alcune tipologie di lavori:

/* Before compiling: +
   CREATE TABLE QTEMP.SQLOUT AS ( +
   SELECT TOTAL_JOBS_IN_SYSTEM, MAXIMUM_JOBS_IN_SYSTEM +
   FROM QSYS2.SYSTEM_STATUS_INFO_BASIC) +
   WITH NO DATA */

             PGM

             DCLF       FILE(SQLOUT) OPNID(SQLOUT) ALWVARLEN(*YES)

             DCL        VAR(&E) TYPE(*LGL) VALUE('0')
             DCL        VAR(&ERROR) TYPE(*LGL) VALUE('0')

/* RUNSQL */
             DCL        VAR(&SQL) TYPE(*CHAR) LEN(5000)
             DCL        VAR(&OUTQ) TYPE(*CHAR) LEN(21)
             DCL        VAR(&SQLLIB) TYPE(*CHAR) LEN(10) VALUE('QTEMP')
             DCL        VAR(&SQLFIL) TYPE(*CHAR) LEN(10) VALUE('SQLOUT')
             DCL        VAR(&SQLMBR) TYPE(*CHAR) LEN(10) VALUE('SQLOUT')

             MONMSG     MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))

             CALLSUBR   SUBR(RTVTOTJOBS)

             RMVOLDSPLF JOBNAME(QP0ZSPWT) USERNAME(*ALL) DAYS(7)
             MONMSG     MSGID(CPF0000) EXEC(CHGVAR VAR(&E) VALUE('1'))

             RMVOLDSPLF JOBNAME(QP0ZSPWP) USERNAME(*ALL) DAYS(7)
             MONMSG     MSGID(CPF0000) EXEC(CHGVAR VAR(&E) VALUE('1'))

             RMVOLDSPLF JOBNAME(QJVACMDSRV) USERNAME(*ALL) DAYS(7)
             MONMSG     MSGID(CPF0000) EXEC(CHGVAR VAR(&E) VALUE('1'))

             RMVOLDSPLF JOBNAME(QZSHSH) USERNAME(*ALL) DAYS(7)
             MONMSG     MSGID(CPF0000) EXEC(CHGVAR VAR(&E) VALUE('1'))

             RMVOLDSPLF JOBNAME(QRWTSRVR) USERNAME(QUSER) DAYS(7)
             MONMSG     MSGID(CPF0000) EXEC(CHGVAR VAR(&E) VALUE('1'))

             RMVOLDSPLF JOBNAME(QPRTJOB) USERNAME(*ALL) USRDTA(QZRCSRVS) +
                          DAYS(7)
             MONMSG     MSGID(CPF0000) EXEC(CHGVAR VAR(&E) VALUE('1'))

             CALLSUBR   SUBR(RTVTOTJOBS)

             GOTO       CMDLBL(ENDCLP)

 ERROR:
             CHGVAR     VAR(&ERROR) VALUE('1')
             MONMSG     MSGID(CPF0000)
             GOTO       CMDLBL(ENDCLP)

 ENDCLP:
             IF         COND(&ERROR) THEN(DO)
             SNDPGMMSG  MSGID(CPF9898) MSGF(QCPFMSG) +
                          MSGDTA('Rilevato errore durante +
                          l''esecuzione della procedura, vedere +
                          messaggi precedenti') MSGTYPE(*ESCAPE)
             MONMSG     MSGID(CPF0000)
             ENDDO
             ELSE       CMD(IF COND(&E) THEN(DO))
             SNDPGMMSG  MSGID(CPF9898) MSGF(QCPFMSG) +
                          MSGDTA('Spooled files rimossi +
                          parzialmente, vedere messaggi +
                          precedenti') MSGTYPE(*ESCAPE)
             MONMSG     MSGID(CPF0000)
             ENDDO

             RETURN

 RTVTOTJOBS: SUBR       SUBR(RTVTOTJOBS)

                DLTF       FILE(&SQLLIB/&SQLFIL)
                MONMSG     MSGID(CPF2105)

                CHGVAR     VAR(&SQL) VALUE('SELECT TOTAL_JOBS_IN_SYSTEM, +
                             MAXIMUM_JOBS_IN_SYSTEM FROM +
                             QSYS2.SYSTEM_STATUS_INFO_BASIC')
                CHGVAR     VAR(&SQL) VALUE('CREATE TABLE' *BCAT &SQLLIB +
                             *TCAT '/' *CAT &SQLFIL *BCAT 'AS (' *CAT &SQL +
                             *TCAT ') WITH DATA')

                RUNSQL     SQL(&SQL) COMMIT(*NONE) NAMING(*SYS)
                MONMSG     MSGID(SQL0000) EXEC(DO)
                ENDDO

                OVRDBF     FILE(SQLOUT) TOFILE(&SQLLIB/&SQLFIL) +
                             MBR(&SQLMBR) LVLCHK(*NO) OVRSCOPE(*CALLLVL)

                RCVF       OPNID(SQLOUT)

                CLOSE      OPNID(SQLOUT) /* RVCF */

                DLTOVR     FILE(SQLOUT) LVL(*) /* OVRDBF */

                SNDPGMMSG  MSGID(CPI8859) MSGF(QCPFMSG) MSGDTA('Jobs in +
                             system:' *BCAT %CHAR(&SQLOUT_TOTAL_JOBS) +
                             *BCAT '/' *BCAT %CHAR(&SQLOUT_MAX_JOBS)) +
                             TOPGMQ(*PRV) TOMSGQ(*TOPGMQ) MSGTYPE(*INFO)

                DLTF       FILE(&SQLLIB/&SQLFIL)
                MONMSG     MSGID(CPF0000)

             ENDSUBR    RTNVAL(0)

 ENDPGM:
             ENDPGM

Riferimenti

  1. In IBM i 7.5 a job log will no longer be produced when there are no messages in the job log, unless the user explicitly requests a job log using the DSPJOBLOG OUTPUT(*PRINT) command, in which case a job log will be spooled, and it will contain a single message, CPF2523, “No job log information.” ↩︎
Related Posts
DB2 for i SQL – Stringhe – POSSTR-LOCATE-LOCATE_IN_STRING (IT)

Introduzione Spesso, nelle nostre applicazioni, abbiamo la necessità di lavorare con le stringhe di testo e l'SQL del DB2 può Read more

DB2 for i & SQL – FAQ & Howto (Part. 1) (IT)

Database DB2 e SQL ... forse lo strumento più potente e completo che abbiamo sulla piattaforma IBM i: ecco una Read more

Annuncio IBM i 7.4

Arriva direttamente con l'uovo di Pasqua questo annuncio IBM per le novità della versione IBM i 7.4, versione iNext secondo Read more

Generated Always Columns – Approfondimenti (IT)

Introduzione "Generated Always Column": sono colonne, campi, di una tabella il cui contenuto è controllato direttamente dal sistema ... e Read more

Recent Posts

VsCode Extension: Bob Cozzi’s RPG IV to RPG Free Conversion

L’estensione “RPG IV to Free Format Conversion” sviluppata da Bob Cozzi (Cozzi Research) è pensata per semplificare la conversione di…

3 mesi ago

IBM i & SQL Tips #010 – Localizzare programmi nella Call Stack con STACK_INFO

Ciao a tutti, oggi voglio segnalarvi un altro interessante contributo di Massimo Duca, parte della sua ormai nota serie IBM…

3 mesi ago

Display file DDS Edit per VsCode, nuova preview.

Incuriosito da alcuni messaggi di Cristian Larsen su Linkedin (New Release - Display File DDS Edit v.0.10.1) ho voluto scaricare…

3 mesi ago

Project Bob: il nuovo strumento AI di IBM per sviluppo COBOL su IBM Z e RPG su IBM i

Ciao a tutti,oggi voglio segnalarvi un annuncio che potrebbe segnare una svolta per lo sviluppo applicativo su ambienti IBM: Project…

4 mesi ago

IBM i & SQL Tips #6: chiamare API REST e analizzare le risposte JSON con SQL

Voglio segnalarvi un nuovo articolo molto interessante di Massimo Duca nella serie IBM i & SQL Tips. In questo sesto…

4 mesi ago

Come funziona il passaggio di parametri a un programma IBM i (RPG / Cobol)

Ciao a tutti, voglio segnalarvi un post molto utile di Marco Riva sul suo sito Markonetools, in cui spiega in…

4 mesi ago