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:0va bene,100nessuna riga trovata, negativo errore.SQLSTATE: cinque caratteri:'00000'va bene,'02000'nessuna riga, il resto è da guardare.
Avvertenza
IBM definisce SQLCODE così: negativo è un errore, positivo una
condizione eccezionale ma valida, cioè un avviso (+100 è il caso più noto), zero
nessun errore e nessuna eccezione. Un controllo come if sqlcode >= 0 tratta gli avvisi
come successi, e il programma continua con un dato che può essere sbagliato.
Su SQLSTATE le classi si leggono dai primi due caratteri: '00' riuscito, '01'
avviso, '02' nessuna riga, il resto errore. IBM consiglia di controllare SQLSTATE,
che resta uguale fra i prodotti Db2 mentre SQLCODE no. 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 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) (o ROW_COUNT con GET DIAGNOSTICS), 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. Il valore di serie è*CHG: un programma compilato senza indicarlo lavora sotto controllo di commit, e le righe che modifica restano bloccate finché qualcuno non faCOMMIToROLLBACK.closqlcsr: quando cursori e istruzioni preparate si chiudono da soli. Per ILE il valore di serie è*ENDACTGRP, alla fine del gruppo di attivazione;*endmodli chiude all'uscita dal modulo. È una scelta contro il valore di serie, che garantisce a ogni chiamata uno stato pulito, e IBM consiglia in generale di tenere il valore predefinito finché non si è fatta un'analisi.datfmt: il formato delle date.*isotoglie di mezzo l'ambiguità fra giorno e mese, che dipende altrimenti dalle impostazioni del job.naming:*sysusaLIBRERIA/OGGETTOe la lista librerie;*sqlusaSCHEMA.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. Con il segnaposto ? il valore resta un valore, e non può
diventare parte dell'istruzione.
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 e più leggibile.
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.
Commenti
Nessun commento ancora. Sii il primo a commentare!
Devi avere un account per commentare. Accedi · Registrati