An RPG program can contain SQL directly in the code. The source takes member type
SQLRPGLE, a precompiler turns the EXEC SQL statements into calls before the compiler
sees the code, and the result is an ordinary program object.
CRTSQLRPGI OBJ(PRODLIB/INVOICES) SRCFILE(PRODLIB/QRPGLESRC) +
COMMIT(*NONE) DBGVIEW(*SOURCE)
How it is written
**FREE
dcl-s total packed(11:2);
dcl-s customer char(10) inz('C0001');
exec sql
SELECT SUM(ORDAMT)
INTO :total
FROM ORDERS
WHERE ORDCUS = :customer
AND ORDDAT >= CURRENT DATE - 1 YEAR;
Program variables are used inside the SQL prefixed with a colon: they are called host
variables, and they are the bridge between the two worlds. They work both inbound (like
:customer above) and outbound, with INTO.
More than one row needs a cursor:
exec sql
DECLARE C1 CURSOR FOR
SELECT ORDNUM, ORDAMT
FROM ORDERS
WHERE ORDCUS = :customer
ORDER BY ORDDAT DESC;
exec sql OPEN C1;
dow sqlstate < '02000';
exec sql FETCH C1 INTO :number, :amount;
if sqlstate < '02000';
// process
endif;
enddo;
exec sql CLOSE C1;
How the outcome is checked
And this is where people go wrong most often. After every SQL statement the system fills two variables, which the precompiler declares by itself:
SQLCODE: numeric:0fine,100no rows found, negative an error.SQLSTATE: five characters:'00000'fine,'02000'no rows, everything else worth a look.
Warning
IBM defines SQLCODE like this: negative is an error, positive an
exceptional but valid condition, that is a warning (+100 is the best known), zero
no error and no exceptional condition. A check like if sqlcode >= 0 treats warnings as
successes, and the program carries on with data that may be wrong.
On SQLSTATE the classes are read from the first two characters: '00' success, '01'
warning, '02' no rows, anything else an error. IBM advises checking SQLSTATE, which
stays the same across Db2 products while SQLCODE does not. The convention
if sqlstate < '02000' covers success and warnings by treating them alike, and is the
form most often seen: fine for a read loop, not for a write.
On an UPDATE or DELETE, moreover, "no error" does not mean "it did something": the
number of rows affected is in SQLERRD(3) (or ROW_COUNT with GET DIAGNOSTICS), and a change that found nothing to change is a
case to handle, not a success.
The options that matter
exec sql SET OPTION
commit = *none,
closqlcsr = *endmod,
datfmt = *iso,
naming = *sys;
Four lines that settle four categories of surprise:
commit: with no commitment control,*none. The default is*CHG: a program compiled without saying so runs under commitment control, and the rows it changes stay locked until somebody issuesCOMMITorROLLBACK.closqlcsr: when cursors and prepared statements close by themselves. For ILE the default is*ENDACTGRP, at the end of the activation group;*endmodcloses them when the module is exited. It is a choice against the default, giving every call a clean state, and IBM advises keeping the default in general until an analysis has been done.datfmt: the date format.*isoremoves the ambiguity between day and month, which otherwise depends on the job's settings.naming:*sysusesLIBRARY/OBJECTand the library list;*sqlusesSCHEMA.OBJECT. Choosing beats inheriting: where the program looks for its files depends on this.
Dynamic SQL, and injection
When the query is composed at run time you need dynamic SQL, and here the risk is exactly that of any other platform:
// Wrong: the contents of :filter end up inside the statement
sql = 'SELECT * FROM ORDERS WHERE ORDCUS = ''' + filter + '''';
exec sql PREPARE S1 FROM :sql;
// Right: the value stays a value
sql = 'SELECT * FROM ORDERS WHERE ORDCUS = ?';
exec sql PREPARE S1 FROM :sql;
exec sql DECLARE C2 CURSOR FOR S1;
exec sql OPEN C2 USING :filter;
Important
the idea that SQL injection is a web problem, and therefore not one here,
has been wrong ever since interfaces started bringing outside data into the application: a
web service, a procedure called from another system, a file imported from a supplier. The
? marker the value stays a value, and cannot become part of the statement.
When it is worth it and when not
Embedded SQL does not replace native reads: it sits alongside them, and the choice is made case by case.
It is right for totals and groupings, for reading from several files at once, for
filters spanning many columns, and for every set operation: an UPDATE touching ten
thousand rows is one line of SQL against a whole loop.
It is not right for fetching one specific record whose key you know. There CHAIN is
more direct and more readable.
Tip
this is the modernisation you can do one program at a time without asking anyone's permission. It does not change the files, does not change the interfaces, does not force a recompile of the rest of the application: you change how a single program reads its data, and you measure.
Comments
No comments yet. Be the first to comment!
You need an account to comment. Log in · Sign up