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
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.*endmodli chiude alla fine del modulo invece che alla fine del programma, che è quello che serve in un service program.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. 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.
Commenti
Nessun commento ancora. Sii il primo a commentare!
Devi avere un account per commentare. Accedi · Registrati