Fixed-format RPG programs can declare externally-described keyed DISK files using a fixed-format F-spec:
FINVMAST IF E K DISK
rpgc supports externally-described keyed DISK files declared with an F-spec. The file's field structure can be resolved from either a local DDS physical-file source file (.pf) or a live Db2 for i catalog via JDBC (verify mode).
In free-format, the equivalent declaration is DCL-F. The device keyword is optional for an externally-described file - when omitted it defaults to DISK, so all three of these declare the same keyed DISK file:
DCL-F INVMAST DISK KEYED USAGE(*INPUT);
DCL-F INVMAST KEYED USAGE(*INPUT); // device omitted - defaults to DISK
DCL-F INVMAST; // device and usage omitted
PRINTER and WORKSTN files must still name their device explicitly; only the DISK default is implicit.
A program-described DISK file - one whose record layout is defined by the program rather than by an external file object - is declared by giving its record length as the device keyword's argument:
DCL-F ARRTBL DISK(10) USAGE(*INPUT); // program-described, 10-byte records
A device keyword with no argument (DISK) is externally-described - the default; a device keyword with a record-length argument (DISK(10)) is program-described. This is the free-format spelling of the fixed-format F-spec record-length columns, so the same file declares identically in either format. PRINTER(132) carries a print-line width and WORKSTN(200) a program-described record length the same way.
Program-described record I/O
READ file ds, WRITE file ds and UPDATE file ds move the record as a byte
image through the named data structure. The image is a plain concatenation of each
column's storage image, in column order, with no padding or alignment - a record of
CHAR(4), NUMERIC(5,0) and DECIMAL(5,2) occupies 4 + 5 + 3 = 12 bytes, with
the character column held as characters, the zoned column as its numeric
characters, and the packed column as BCD:
DCL-F MCOL DISK(12) USAGE(*UPDATE);
DCL-DS rec;
sc1 CHAR(4) POS(1);
sn1 ZONED(5:0) POS(5);
sp1 PACKED(5:2) POS(10);
END-DS;
READ MCOL rec;
sc1 = 'WXYZ';
UPDATE MCOL rec;
Declare the structure's subfields at whatever positions or lengths match the file's column layout; the compiler reads that layout from the file's external description without making the column names visible to the program.
A negative zoned or packed value is carried the way IBM i carries it - the sign
is overpunched into the existing image rather than occupying a byte of its own, so
the field's width is the same either way. A ZONED(5:0) holding -12345 is
x'F1F2F3F4D5' (the zone of the last byte becomes D), and a PACKED(5:2) holding
-3.14 is x'00314D' (the low nibble becomes D).
Keyed access in fixed format
A fixed-format F-spec declares keyed access in a different column depending on how the file is described, and rpgc honours both spellings:
| Description | Column | Entry |
|---|---|---|
| Externally described | 34 (record address type) | K |
| Program described | 35 (file organization) | I - with the key's length in columns 29-33 |
FKLPF IF F 20 5AIDISK KEYLOC(6)
Here column 22 F marks the file program-described, columns 23-27 give the record
length, columns 29-33 the key length, column 34 A the key's type, and column 35
I the indexed organization. A file declared this way reads in key sequence; one
without the I reads in arrival sequence.
KEYLOC(n) is accepted and does not affect which record an operation retrieves.
Search arguments come from factor 1, and an operation that takes its key from the
current record - READE with no factor 1 - uses the key the file was positioned
on. This matches IBM i, which also does not require KEYLOC to agree with the
file's actual key.
Supported column types
Character, zoned, packed, DATE, TIME and TIMESTAMP columns are supported.
The three date/time types occupy their *ISO text form in the record image, at the
widths below - the form is *ISO regardless of the job's date format, and the
separators are those *ISO uses:
| Column type | Bytes | Image |
|---|---|---|
DATE |
10 | yyyy-mm-dd |
TIME |
8 | hh.mm.ss (dots) |
TIMESTAMP |
26 | yyyy-mm-dd-hh.mm.ss.ffffff (six fractional places) |
Declare the receiving subfield as D, T or Z at the matching position, or as a
character subfield of that width if the program wants the text.
A column of any other type - binary and float among them - has no byte-image
representation here and is rejected with
TRN1060, as is an operation
that names no data structure at all. That rejection is deliberate: a column whose
image is not defined would otherwise reach the program as
blank bytes, which reads as a wrong value rather than as an error. Read such a file
as externally described instead, which supports the full type set. A
program-described PRINTER file is a separate case: writing a data structure to one
renders it as a print line.
The stated record length
DISK(n) states how much data each operation transfers, and it must be at least the
summed storage widths of the columns. A longer length is accepted - the surplus
bytes simply go unused. A shorter one is rejected with
TRN1062: it is not a smaller
read, it leaves the trailing subfields never receiving data at all, which on IBM i
surfaces as a decimal-data error at run time rather than at compile time.
DDS Physical File Source
Place the DDS physical-file source alongside the RPG source or in the directory specified by --dds-path. The file must be named <FILENAME>.pf (case-insensitive). Example:
A R INVMASTR
A WHSE 4A TEXT('Warehouse code')
A PARTNO 10A TEXT('Part number')
A QTYOH 7P 0 TEXT('Quantity on hand')
A K WHSE
A K PARTNO
At compile time, the compiler reads the .pf file and automatically injects the field declarations (WHSE, PARTNO, QTYOH, etc.) as program variables. No explicit D-spec declarations are needed for DDS fields.
A DDS A (character) field is fixed-length: it holds exactly its declared length, blank-padded, so %LEN returns the column width and bracketed concatenation includes the trailing blanks - matching IBM i. Add the VARLEN keyword to a character field to make it variable-length instead.
SQL DDL (CREATE TABLE) Source
When a file's layout is defined in SQL DDL rather than DDS - a CREATE TABLE statement, as produced by qsqlsrc/*.table members - the compiler can resolve the external description directly from that source, with no hand-written mirror .pf. Place the DDL alongside the RPG source or in the --dds-path directory, named <FILENAME>.table (case-insensitive). Example:
CREATE OR REPLACE TABLE EMPLOYEE
(EMPNO CHAR(6) NOT NULL,
FIRSTNME VARCHAR(12) NOT NULL,
EDLEVEL SMALLINT NOT NULL,
SALARY DECIMAL(9,2) ,
PRIMARY KEY (EMPNO));
Both a fixed-format E DS/F-spec and a free-format DCL-DS ... EXTNAME('EMPLOYEE') / DCL-F resolve their fields from this table exactly as they would from a .pf. -- line comments and /* */ block comments, CREATE OR REPLACE, a schema-qualified name, the PRIMARY KEY clause, and trailing ALTER TABLE / constraint statements are all handled; the PRIMARY KEY columns become the file's key fields.
Each SQL column maps to the RPG field type that native ILE RPG externally-describes it as:
| SQL column type | RPG field |
|---|---|
CHAR(n) |
fixed character, length n |
VARCHAR(n) / CHAR VARYING(n) |
varying character, length n |
SMALLINT |
binary, 4 digits (4B 0) |
INTEGER / INT |
binary, 9 digits (9B 0) |
BIGINT |
integer, 20 digits (20I 0) |
DECIMAL(p,s) |
packed decimal |
NUMERIC(p,s) |
zoned decimal |
DATE / TIME / TIMESTAMP |
date / time / timestamp |
REAL |
4-byte float |
DOUBLE / FLOAT |
8-byte float |
GRAPHIC(n) / VARGRAPHIC(n) |
graphic (UCS-2 when the column CCSID is 13488 or 1200) |
SMALLINT and INTEGER map to binary fields, not integer fields - this is how those columns are externally described. A SQL column with a long name (over 10 characters) is imported under its generated system name, with the long name available as its ALIAS; a FOR COLUMN clause supplies the system name explicitly. Any other SQL column type (DECFLOAT, BLOB/CLOB/DBCLOB, XML, ROWID, BINARY/VARBINARY, BOOLEAN) has no faithful RPG external field type and is rejected with TRN4003 rather than silently mistyped.
Resolution order is .pf DDS first, then .table SQL DDL, then verify mode - a .pf of the same name takes precedence over a .table.
Verify Mode (Catalog Resolution)
When no local .pf file is available, the compiler can resolve externally-described file layouts by querying the Db2 for i system catalog at compile time. This is called verify mode and is enabled by passing a JDBC URL:
JDBC_USER=MYUSER JDBC_PASSWORD=MYPASS \
rpgc --verify-jdbc-url "jdbc:as400://myhost/MYLIB" \
--verify-jdbc-driver com.ibm.as400.access.AS400JDBCDriver \
--verify-library APPLIB \
--verify-library QGPL \
MYPROG.rpgle
The catalog user and password are read from the environment (by default JDBC_USER / JDBC_PASSWORD), never passed on the command line where they would be visible in the OS process table.
Verify mode reads the system catalog for field definitions and key columns, so the build user needs read authority to it. Libraries specified with --verify-library are searched in order until the table is found.
Verify mode only runs for externally-described DISK files that could not be resolved from a local .pf file. If a .pf file exists, it takes precedence.
When no --verify-library is given, the table is resolved by name alone. If the same table name exists in more than one schema, the reference is ambiguous and the compile is rejected with an error naming the conflicting schemas - qualify the intended one with --verify-library rather than letting the compiler guess. With a single matching schema, name-only resolution succeeds.
| Flag | Description |
|---|---|
--verify-jdbc-url <url> |
JDBC URL for catalog queries (enables verify mode) |
--verify-jdbc-user-env <name> |
Env var holding the catalog username (default: JDBC_USER) |
--verify-jdbc-password-env <name> |
Env var holding the catalog password (default: JDBC_PASSWORD) |
--verify-jdbc-driver <class> |
JDBC driver class name |
--verify-library <lib> |
Library to search (repeatable; searched in order) |
Note: These are compile-time settings only. They are not related to the runtime JDBC settings (--jdbc-url-env, etc.) which are baked into the generated program for EXEC SQL and file I/O at runtime.
Supported Operations
| Opcode | Behaviour |
|---|---|
| SETLL | Positions the file cursor at the first record with key >= Factor1. The EQ resulting indicator is set on when an exact-match record exists. |
| READE | Reads the next record whose first key field equals Factor1. The EQ resulting indicator is set on at end-of-file (no more matching records). After a successful read, all DDS field variables are updated from the current row. |
| CHAIN | Random-by-key lookup against the file's first key field. The HI resulting indicator (fixed-format cols 71-72) is set on when no matching record is found, off when a record is fetched. After a successful CHAIN, all DDS field variables are updated from the current row; after a miss the accessors return type defaults. An error indicator in the LO position (fixed-format cols 73-74) traps an I/O error (e.g. a CHAIN to a closed file, status 1211): the indicator is set on, %STATUS is set, execution continues, and the file's INFSR is not run - the same precedence as the (E) extender. A successful or not-found CHAIN sets the error indicator off. |
| UPDATE | Writes the current field values back to the exact record last read by CHAIN/READ/READE, identified by its relative record number - not by key value. On a duplicate-key file (a keyed file with no UNIQUE constraint) only the one record the cursor is positioned on is updated, never every row that shares the key. UPDATE with no record read for update raises file status 1221 (see below). |
| DELETE | With a search argument (Factor 1, or the free-form DELETE key file / DELETE (k1:k2) file / DELETE %KDS(ds) file form), deletes the record whose key equals it - no prior read required - and sets %FOUND (off when no record matches); on a duplicate-key file only the first matching record is removed. With no search argument, removes the exact record last read by its relative record number (as with UPDATE, only the one positioned record on a duplicate-key file). A keyless DELETE with no record read for update raises file status 1221 (see below). |
| OPEN | Explicitly opens a file for processing. Required when the file is declared with USROPN. Opening an already-open file raises status 1215. Supports the (E) extender. |
| CLOSE | Closes a file and releases its cursor resources. CLOSE on an already-closed file is a silent no-op. Factor 2 may be a file name or *ALL to close all files. Supports the (E) extender. |
| FEOD | Forces logical end-of-data on a file without closing it. Sets %EOF ON and invalidates current cursors; subsequent sequential READ returns EOF. The file remains open and can be repositioned (SETLL/SETGT) or closed/reopened. FEOD on a closed file raises status 1211. Supports the (E) extender. |
Record Locking
An input operation against an update-capable file - one declared
USAGE(*UPDATE), or fixed-format file type U or C - reads the record for
update and locks it, as on IBM i. CHAIN, READ, READE, READP and
READPE all take the lock. An input-only file never locks.
While a record is locked, another job's read for update of the same record
waits for the file's WAITRCD and then fails with file status 1218. The
record is not returned. Trap it with an error indicator, the (E) extender, or
a MONITOR block:
CHAIN(E) custKey CUSTFILE;
IF %ERROR AND %STATUS = 1218;
// another job is holding this record
ENDIF;
The lock is a database row lock, so it is honoured by - and honours - other
programs reading the same file, whether or not they were compiled by Triton RPG.
It holds from the read until the record is given back, and it does so whether or
not the file is under commitment control: the CHAIN-then-UPDATE idiom is
protected against a concurrent updater without any transaction management on
your part.
Holding a record therefore opens a database transaction that stays open until
the record is released. Without commitment control that transaction is committed
the moment the record is released - by the UPDATE or DELETE, by UNLOCK, by
the next read, or by closing the file - so each operation is permanent as it
completes, and a record is never held for longer than the program holds it.
Under commitment control the lock joins the unit of work already in progress,
and COMMIT or ROLBK ends it.
Reading without a lock - the (N) extender. Appending (N) to CHAIN,
READ, READE, READP or READPE reads the record for input instead of for
update: no lock is taken, and an existing lock held by another job does not
block the read. The extender applies to the one operation it is written on.
(N) combines with (E) in either order, as one run of letters:
CHAIN(N) custKey CUSTFILE; // read for input, take no lock
CHAIN(EN) custKey CUSTFILE; // ...and trap errors through %ERROR/%STATUS
C custKey CHAIN(N) CUSTFILE
Releasing the lock. A held lock is released by UPDATE, DELETE, the next
input operation on the same file, SETLL, SETGT, UNLOCK, or closing the
file. UNLOCK releases it without writing anything and without repositioning
the file; afterwards no record is held for update, so an UPDATE or DELETE
issued after an UNLOCK raises status 1221 exactly as one issued with no
prior read (see below).
When the file is under commitment control (the COMMIT keyword), the
connection is not in autocommit mode and locks are held until COMMIT or
ROLBK.
UPDATE / DELETE Without a Prior Read
UPDATE and a keyless DELETE operate on the record last read for update.
Issuing either against an open file with no such record - no preceding
CHAIN/READ/READE/READP/READPE that read a record, or one whose record has since
been given back by UNLOCK, SETLL or SETGT - raises file status 1221
("update operation attempted without a prior read"). This is the same whether
the file is keyed or keyless: a keyless file is updated by relative record
number, so the absence of key fields does not change the behaviour.
A keyed DELETE (one with a search argument) is exempt: it locates the
record by key and needs no prior read, so it never raises 1221 - instead it
sets %FOUND to report whether a matching record was deleted.
Trap it like any other file I/O error - with an error indicator in the LO
position (fixed-format cols 73-74), the (E) extender, or a MONITOR block:
C UPDATE(E) MYFILER
C IF %ERROR
C EVAL st = %STATUS
C ENDIF
Left untrapped, the error propagates and ends the program, rather than silently reporting success.
Relative Record Number - RECNO
RECNO(field) on a file declaration names a field the file operations write to: after a record is retrieved, it holds that record's relative record number.
FCUSTMAST IF E DISK RECNO(Rrn)
D Rrn S 5P 0
C READ CUSTMASTR
* Rrn now holds the relative record number of the record just read
and in free-format:
**FREE
DCL-F CustMast DISK USAGE(*INPUT) RECNO(Rrn);
DCL-S Rrn PACKED(5:0);
READ CustMast;
The field must be a numeric field with no decimal places - packed, zoned, or an integer type. Each retrieval replaces it, so it always describes the record currently in the input fields; before the first successful read it is zero.
RECNO does not make CHAIN by relative record number work - that is driven by the number you supply, and works whether or not the file declares RECNO:
C EVAL Rrn = 2
C Rrn CHAIN CUSTMASTR
RECNO's purpose is the other direction: capturing the number of a record you reached some other way, so you can return to it, store it, or update it later.
Null-Capable Fields - ALWNULL and %NULLIND
Database columns that allow SQL NULL (DDS ALWNULL fields, or nullable SQL
columns) are handled according to the ALWNULL keyword on CTL-OPT (or the
H-spec), exactly as on IBM i:
| Value | Behaviour |
|---|---|
*NO (default) |
Reading a record that contains a NULL value fails with the data-mapping I/O error, %STATUS 01299; no record data is made available. Records without NULLs read normally. |
*INPUTONLY / *YES |
NULL columns read as their type default (blanks / zero) with no error. %NULLIND is not maintained, and writes always store concrete values. |
*USRCTL |
Full null support. On every read, %NULLIND(field) is set on for each column that was SQL NULL (the field itself receives its type default). On WRITE and UPDATE, a field whose %NULLIND is on is stored as SQL NULL - the field's own value is not written and is left unchanged in the program. |
**FREE
CTL-OPT ALWNULL(*USRCTL);
DCL-F CUSTFILE DISK KEYED USAGE(*INPUT:*UPDATE);
CHAIN custKey CUSTFILE;
IF %FOUND(CUSTFILE) AND %NULLIND(CREDLIM);
// CREDLIM was SQL NULL in the record just read
ENDIF;
%NULLIND(CREDLIM) = *ON; // store NULL on the next update
UPDATE CUSTREC;
Under the default ALWNULL(*NO), trap the 01299 error with the (E)
extender, an error indicator, or a MONITOR block - the same as any other
file I/O error.
Runtime Requirement
Disk file I/O uses the same JDBC connection as EXEC SQL statements. Set the same environment variables (JDBC_URL, JDBC_DRIVER, etc.) as for SQL programs. The schema/library is determined by the JDBC connection's default schema.
File Control - OPEN / CLOSE / FEOD
The OPEN opcode explicitly opens a file for processing, and CLOSE closes it. When a file is declared with the USROPN keyword, it is not automatically opened at program initialization - an explicit OPEN must be issued before any I/O operation.
FMYFILE UF E K DISK USROPN
C OPEN MYFILE
C READ MYFILER
C CLOSE MYFILE
In free-format:
**FREE
dcl-f MYFILE disk keyed usropn;
open MYFILE;
read MYFILE;
close MYFILE;
- OPEN on an already-open file raises file status 1215. Use the
(E)extender to trap this with%ERROR/%STATUS. - CLOSE on an already-closed file is a silent no-op.
- CLOSE *ALL closes all globally declared files in the program.
- A file closed by
CLOSEcan be reopened byOPENwithout theUSROPNkeyword.
The FEOD opcode forces a logical end-of-data on a file without closing it. Unlike CLOSE, the file remains open after FEOD and can be repositioned or closed later.
C FEOD MYFILE
In free-format:
**FREE
feod MYFILE;
- FEOD sets
%EOFON for the file and invalidates current cursors. - Subsequent sequential
READreturns EOF until the file is repositioned. - The file can be repositioned with
SETLL/SETGTafterFEOD. - The file can be closed with
CLOSEand reopened withOPENafterFEOD. - FEOD on a closed file raises file status 1211.
Extension Specification (E-spec) - Compile-Time Arrays
The E-spec (column 6 = E) defines compile-time arrays whose data is loaded
from ** or **CTDATA records at the end of the source. It declares the same
array the free-format DCL-S ... DIM(...) CTDATA form declares.
E-spec column layout (RPG/400):
| Columns | Field |
|---|---|
| 7-26 | From/to filenames (blank for compile-time) |
| 27-32 | Array name (6 chars) |
| 33-35 | Entries per record (PERRCD) |
| 36-39 | Total entries (DIM) |
| 40-42 | Entry length |
| 43 | Format (P = packed, blank = character) |
| 44 | Decimal positions (0-9) |
| 45 | Sequence (A = ascending, D = descending) |
Example:
E NAMES 1 5 10
**CTDATA NAMES
ALICE
BOB
CHARLIE
DAVE
EVE
Open Access - HANDLER Keyword
The HANDLER keyword on a DCL-F routes I/O operations to a user-written
handler procedure instead of the database. The handler receives a
QrnOpenAccess communication area (modeled after IBM's QrnOpenAccess_T)
with the requested operation code, key data, and record buffers.
dcl-f CURRATE DISK keyed usage(*input) handler('CURRTHND');
The handler procedure takes a single LIKEDS parameter matching the communication-area template:
dcl-proc CURRTHND;
dcl-pi CURRTHND;
info likeds(qrnOpenAccess_t);
end-pi;
// info.rpgOperation contains the operation code (CHAIN=9, READ=4, etc.)
// info.key contains the key bytes for keyed operations
// info.inputBuffer is where the handler writes record data
// info.found / info.eof are set by the handler as feedback
end-proc;
Operations are dispatched with the rpgOperation codes IBM defines for
QrnOperation_*:
| Operation | rpgOperation |
Operation | rpgOperation |
|---|---|---|---|
| OPEN | 1 | SETLL (keyed) | 12 |
SETLL *START |
2 | UNLOCK | 13 |
SETLL *END |
3 | UPDATE | 14 |
| READ | 4 | WRITE | 15 |
| READC | 5 | DELETE (keyed) | 16 |
| READE (keyed) | 6 | FEOD | 17 |
| READP | 7 | CLOSE | 18 |
| READPE (keyed) | 8 | DELETE (no key) | 19 |
| CHAIN | 9 | READE (no key) | 20 |
| EXFMT | 10 | READPE (no key) | 21 |
| SETGT | 11 |
The code depends on the opcode and its operand, matching IBM i. SETLL,
READE, READPE, and DELETE each dispatch a different code depending on
whether a search argument (or *START/*END) is present:
SETLL *START→ 2,SETLL *END→ 3, a keyedSETLL→ 12.READE/READPEwith a search argument → 6 / 8; without one (read the next record equal to the current key) → 20 / 21 (the_CURRENTforms).DELETEwith a search argument (delete by key) → 16; without one (delete the current record) → 19.
A handler must switch on the exact code, since 16-vs-19 and 20/21-vs-6/8 are different record targets.
A plain sequential READ dispatches rpgOperation=4 (QrnOperation_READ); the
handler advances to the next record and either provides input data or sets the
eof feedback flag, which the runtime surfaces through %EOF and the READ's EOF
resulting indicator. %FOUND, %EOF, and %EQUAL read feedback from the
handler's communication area.
EXFMT (10) and READC (5) dispatch when the handler is declared on a WORKSTN
file. READC reads the next changed subfile record: the handler sets its eof
feedback flag when there are no more changed records, which the runtime surfaces
through %EOF(file) exactly as for a database read.
When the handler is told the file is closing
A handler usually owns the resource behind the file - a socket, an HTTP session,
a buffered writer - and CLOSE (rpgOperation=18) is its only signal to flush
and release it. A compiled program dispatches CLOSE:
- on an explicit
CLOSEof the file, and onCLOSE *ALL; - implicitly, when the program ends with
*INLRon, for every handler file still open.
A program that returns with *INLR off stays activated and its files stay
open, so no CLOSE is dispatched - the handler keeps its resource for the next
call. A file that was already closed explicitly is not closed a second time when
the program later ends, so the handler always receives exactly one CLOSE per
open. Reopening a closed file with OPEN dispatches rpgOperation=1 again and
puts it back under the implicit close.
In the default (buffer) mode, the record buffers use the same byte layout as an
externally-described data structure (*INPUT for info.inputBuffer, *OUTPUT
for info.outputBuffer): each field appears in its native record form - packed
decimal as packed BCD, zoned as zoned, binary/integer as two's-complement,
date/time/timestamp in the field's format - not as character text. A handler
decodes info.inputBuffer and populates info.outputBuffer using that layout.
The handler can set useNamesValues = *ON during the OPEN call to opt into
the name/value interface. In this mode, WRITE and UPDATE operations populate a
QrnNamesValues array with per-field metadata (name, data type, length) and
values instead of raw byte buffers. For input operations (CHAIN, READE, etc.),
the handler writes field values into the array and the runtime reads them back.
namesValues is a pointer, exactly as IBM declares it. A handler reaches
the array by basing a QrnNamesValues_T data structure over that pointer - the same declaration it uses on IBM i, so a handler written against IBM's
QRNOPENACC copy member compiles and runs here unchanged:
dcl-ds nv likeds(QrnNamesValues_T) based(info.namesValues);
dcl-s valPtr pointer;
dcl-s valTxt char(64) based(valPtr);
for i = 1 to nv.num;
if %trim(nv.field(i).externalName) = 'AMOUNT';
valPtr = nv.field(i).value;
valTxt = '77.77';
nv.field(i).valueLenBytes = 5;
endif;
endfor;
The array is shared with the program, not copied to the handler, so what the handler writes is what the program reads.
Each field's value is likewise a pointer, addressing storage the entry owns.
Its capacity is in valueMaxLenBytes; do not write past it.
valueLenBytes is how much of that storage is meaningful, and both sides must
set it.
- On input (CHAIN, READ, READE and friends) the handler sets it for each field it supplies. A field left at zero is treated as not supplied and the program field keeps its previous value - a handler that fills in only some of the record's fields is the normal case, and leaving the rest at zero is how it says so.
- On output (WRITE, UPDATE) the runtime sets it to the length of the value it published, so the handler knows where the value ends without having to guess or trim.
Each field's value is conveyed as text - the same string the %CHAR built-in
produces for that field's type (a decimal as 123.45, an integer as -5, a
float in its 23-character exponential form, a date in its external format). A
numeric field is therefore written to and read from the array as that %CHAR
string, not as raw bytes; the runtime converts it back to the field's declared
type on input.
For a keyed file, the search argument reaches the handler the same way through
keynamesValues, a second array of the same shape holding one entry per key
column in key order, with the key's value and valueLenBytes filled in for
every keyed operation:
dcl-ds kv likeds(QrnNamesValues_T) based(info.keynamesValues);
An unkeyed file leaves keynamesValues set to *NULL.
Each field's dataType uses the QrnDatatype_* codes IBM assigns: character
= 1, packed/zoned/binary = 8 (Decimal), integer = 9, unsigned = 10, float =
11, indicator = 7, date = 12, time = 13, timestamp = 14. A binary (DDS type B)
field is reported as Decimal (8), the same as packed and zoned - not as
character.
Program-Described WORKSTN Files
Program-described WORKSTN files (file format F instead of E in the F-spec)
are accepted without requiring a .dspf DDS source file. The record length is
extracted from the F-spec. The file declaration compiles, but screen I/O
operations on a program-described WORKSTN file are not supported.
Not supported
- Program-described WORKSTN I/O operations (WRITE/READ with O-specs/I-specs) are not supported.
- PASS(*NOIND) is accepted but has no runtime effect.
Open Access HANDLER is supported on WORKSTN and PRINTER files. On a WORKSTN
file it dispatches EXFMT (rpgOperation=10), READC (5), WRITE (15) and READ (4)
to the handler, passing the record's field data through info.outputBuffer and
info.inputBuffer.