Programmare su IBM i: sorgenti, ILE, RPG e SQL Capitolo 6 di 9

SQL dentro i programmi

Rivista il Verificata su IBM i 7.5

Accedi per salvare

Un programma RPG può contenere SQL direttamente nel codice. Il sorgente prende il tipo di membro SQLRPGLE, un precompilatore traduce le istruzioni EXEC SQL in chiamate prima che il compilatore veda il codice, e il risultato è un normale oggetto programma.

CRTSQLRPGI OBJ(PRODLIB/FATTURE) SRCFILE(PRODLIB/QRPGLESRC) +
           COMMIT(*NONE) DBGVIEW(*SOURCE)

Come si scrive

**FREE
dcl-s totale packed(11:2);
dcl-s cliente char(10) inz('C0001');

exec sql
  SELECT SUM(ORDIMP)
    INTO :totale
    FROM ORDINI
   WHERE ORDCLI = :cliente
     AND ORDDAT >= CURRENT DATE - 1 YEAR;

Le variabili del programma si usano dentro l'SQL precedute dai due punti: si chiamano variabili host, e sono il ponte fra i due mondi. Funzionano sia in ingresso — come :cliente qui sopra — sia in uscita, con INTO.

Per più di una riga serve un cursore:

exec sql
  DECLARE C1 CURSOR FOR
  SELECT ORDNUM, ORDIMP
    FROM ORDINI
   WHERE ORDCLI = :cliente
   ORDER BY ORDDAT DESC;

exec sql OPEN C1;

dow sqlstate < '02000';
  exec sql FETCH C1 INTO :numero, :importo;
  if sqlstate < '02000';
    // elabora
  endif;
enddo;

exec sql CLOSE C1;

Come si controlla l'esito

Ed è qui che si sbaglia più spesso. Dopo ogni istruzione SQL il sistema riempie due variabili, che il precompilatore dichiara da solo:

  • SQLCODE — numerico: 0 va bene, 100 nessuna riga trovata, negativo errore.
  • SQLSTATE — cinque caratteri: '00000' va bene, '02000' nessuna riga, il resto è da guardare.

Avvertenza

SQLCODE = 0 non significa che è andato tutto bene. Significa che non c'è stato un errore. Gli avvisi — un troncamento in una variabile troppo corta, una conversione approssimata — passano con SQLCODE positivo o zero, e il programma continua con un dato sbagliato senza che nessuno se ne accorga.

Il controllo che regge è su SQLSTATE, che distingue i tre casi: '00000' riuscito, '02000' nessuna riga, classe '01' avviso, tutto il resto errore. La convenzione if sqlstate < '02000' copre riuscito e avvisi trattandoli allo stesso modo, ed è la forma che si trova più spesso: va bene per un ciclo di lettura, non va bene per una scrittura.

Su un UPDATE o un DELETE, poi, "nessun errore" non vuol dire "ha fatto qualcosa": il numero di righe toccate sta in SQLERRD(3), e una modifica che non ha trovato nulla da modificare è un caso da gestire, non un successo.

Le opzioni che contano

exec sql SET OPTION
  commit    = *none,
  closqlcsr = *endmod,
  datfmt    = *iso,
  naming    = *sys;

Quattro righe che risolvono quattro categorie di sorprese:

  • commit — senza controllo di commit, *none. Se resta il valore predefinito, il programma apre transazioni che nessuno chiude, e i record restano bloccati.
  • closqlcsr — quando chiudere i cursori. *endmod li chiude alla fine del modulo invece che alla fine del programma, che è quello che serve in un service program.
  • datfmt — il formato delle date. *iso toglie di mezzo l'ambiguità fra giorno e mese, che dipende altrimenti dalle impostazioni del job.
  • naming*sys usa LIBRERIA/OGGETTO e la lista librerie; *sql usa SCHEMA.OGGETTO. Scegliere è meglio che ereditare: da questo dipende dove il programma va a cercare gli archivi.

L'SQL dinamico, e l'iniezione

Quando la query si compone a tempo di esecuzione serve l'SQL dinamico, e qui vale esattamente il rischio di qualunque altra piattaforma:

// Sbagliato: il contenuto di :filtro finisce dentro l'istruzione
sql = 'SELECT * FROM ORDINI WHERE ORDCLI = ''' + filtro + '''';
exec sql PREPARE S1 FROM :sql;
// Giusto: il valore resta un valore
sql = 'SELECT * FROM ORDINI WHERE ORDCLI = ?';
exec sql PREPARE S1 FROM :sql;
exec sql DECLARE C2 CURSOR FOR S1;
exec sql OPEN C2 USING :filtro;

Importante

l'idea che l'iniezione SQL sia un problema del web, e quindi non di qui, è sbagliata da quando esistono le interfacce che portano dati esterni dentro l'applicativo: un servizio web, una procedura chiamata da un gestionale, un file importato da un fornitore. Il segnaposto ? costa una riga in più ed elimina la classe di problemi per intero, oltre a far riusare al database il piano di accesso invece di ricalcolarlo a ogni chiamata.

Quando serve e quando no

L'SQL incorporato non sostituisce la lettura nativa: la affianca, e le due si scelgono caso per caso.

Va bene per totali e raggruppamenti, per leggere da più file insieme, per filtri che riguardano molte colonne, e per tutte le operazioni su insiemi — un UPDATE che tocca diecimila righe è una riga di SQL contro un ciclo intero.

Non va bene per prendere un record preciso di cui si conosce la chiave. Lì CHAIN è più diretto, più leggibile e non più lento.

Consiglio

è la modernizzazione che si può fare un programma alla volta senza chiedere permesso a nessuno. Non cambia gli archivi, non cambia le interfacce, non obbliga a ricompilare il resto dell'applicativo: si cambia il modo in cui un singolo programma legge i dati, e si misura.

Torna all'indice della guida

← Torna al blog

Commenti

Nessun commento ancora. Sii il primo a commentare!

Devi avere un account per commentare. Accedi · Registrati