Intermediate

I/O Operations in RPG

Learn input and output in RPG - console messages, interactive input, database file access, printer reports, and embedded SQL on IBM i

Input and output are where RPG shows its heritage most clearly. The language was born as the Report Program Generator - a tool for reading business records and producing reports - so file and record I/O are not add-on libraries but core parts of the language itself. Where a scripting language reaches for a print function and a file handle, RPG declares files with dcl-f, reads records with read and chain, and writes output records with write.

Because RPG is a procedural, statically-typed business language, its I/O model is built around records rather than raw byte streams. A physical file (a database table on IBM i) is described externally, and its fields become variables in your program automatically. This tight coupling between the program and the database is one of RPG’s defining strengths, and it is why embedded SQL feels so natural in the language.

In this tutorial you will learn how to display and format console output, read interactive input, read and search database files, generate a printer report (RPG’s namesake), and perform database I/O with embedded SQL. Since RPG runs exclusively on IBM i, there is no Docker image - the examples explain the code and show how to compile and run it on an IBM i system.

Note: RPG has no open-source compiler and no Docker image. The docker_image front matter is set to none. All examples must be compiled and run on an IBM i (formerly AS/400) system.

Console Output and Formatting

The dsply operation is the simplest form of output. Beyond plain text, RPG formats numbers with built-in functions (BIFs) like %char for simple conversion and %editc for business-style editing with commas and decimals.

Create a file named io_console.rpgle:

**FREE
dcl-s amount packed(11:2) inz(1234.56);
dcl-s qty    int(10) inz(5);
dcl-s total  packed(13:2);
dcl-s msg    char(52);

// Plain text output
msg = 'Order Report';
dsply msg;

// Numeric to character with %CHAR
msg = 'Quantity: ' + %char(qty);
dsply msg;

// Formatted decimal with %EDITC (edit code 1 adds commas)
msg = 'Unit price: ' + %trim(%editc(amount:'1'));
dsply msg;

// Compute and display a total
total = qty * amount;
msg = 'Total: ' + %trim(%editc(total:'1'));
dsply msg;

*inlr = *on;

Key points:

  • dsply sends a message to the program message queue; on a 5250 terminal each message waits for Enter, and in batch it goes to the job log.
  • %char converts a numeric value to its plain character form (5, 6172.80).
  • %editc applies an edit code - here '1' inserts thousands separators and a decimal point (1,234.56) while suppressing leading zeros.
  • %trim removes the blanks that zero-suppression leaves in front of an edited number so concatenation looks clean.

Interactive Input

The dsply operation can also read a value. When you supply a response field as its third operand, it prompts the user and returns what they type. The character input is then converted to the type you need with a BIF such as %int.

Create a file named io_input.rpgle:

**FREE
dcl-s custName char(20);
dcl-s ageChar  char(3);
dcl-s age      int(5);
dcl-s msg      char(52);

// DSPLY with a response field prompts for and reads input
dsply 'Enter your name:' '' custName;
dsply 'Enter your age:' '' ageChar;

// Convert the character input to a number
age = %int(ageChar);

msg = 'Hello, ' + %trim(custName) + '!';
dsply msg;

if age >= 18;
  msg = 'Status: adult';
else;
  msg = 'Status: minor';
endif;
dsply msg;

*inlr = *on;

Key points:

  • The first operand is the prompt message, the second ('') is the message queue (blank uses the default external message queue), and the third is the response field that receives the typed value.
  • Input always arrives as character data - use %int, %dec, or %date to convert it to the type you need.
  • For real interactive screens, production RPG typically uses display files (DDS-described 5250 screens) with read/write, but dsply is perfect for simple prompts and learning.

Reading Database Files

Database file access is the heart of RPG. A physical file (table) is externally described: you declare it with dcl-f, and its fields automatically become program variables. Records are read sequentially with read or retrieved by key with chain.

Create a file named io_readfile.rpgle:

**FREE
// CUSTOMER is an externally described, keyed physical file with fields:
//   CUSTID   packed(7:0)   - key
//   CUSTNAME char(30)
//   BALANCE  packed(11:2)
dcl-f customer usage(*input) keyed;

dcl-s msg char(60);

// Sequential read of every record until end of file
read customer;
dow not %eof(customer);
  msg = %char(custid) + ' ' + %trim(custname);
  dsply msg;
  read customer;
enddow;

// Random access by key with CHAIN
chain 1001 customer;
if %found(customer);
  msg = 'Found: ' + %trim(custname);
else;
  msg = 'Customer 1001 not found';
endif;
dsply msg;

*inlr = *on;

Key points:

  • dcl-f customer usage(*input) keyed declares an input-only keyed file. The record’s fields (custid, custname, balance) exist as program variables with no extra declaration.
  • read fetches the next record; %eof returns *on when there are no more.
  • chain does a keyed random read - chain 1001 customer looks up the record whose key is 1001. %found reports whether it succeeded.
  • dow (do-while) with not %eof(...) is the idiomatic read loop in free-form RPG.

Writing a Printer Report

RPG earned its name generating printed reports. On IBM i, report output goes to a printer file whose layout is described in DDS with named record formats (a heading format, a detail format). The program moves data into the format’s fields and issues write, with an overflow indicator handling page breaks.

Create a file named io_report.rpgle:

**FREE
// SALESRPT is an externally described printer file (defined in DDS)
// with record formats HEADING and DETAIL. The DETAIL format contains
// the output fields PRODUCT (char 20), QTY (int) and AMOUNT (packed 9,2).
dcl-f salesrpt printer oflind(*in90);

dcl-s names  char(20) dim(3);
dcl-s units  int(10)  dim(3);
dcl-s prices packed(9:2) dim(3);
dcl-s i      int(10);

names(1) = 'Widget'; units(1) = 100; prices(1) = 4.99;
names(2) = 'Gadget'; units(2) = 50;  prices(2) = 9.99;
names(3) = 'Gizmo';  units(3) = 25;  prices(3) = 14.99;

// Write the page heading
write heading;

// Write one detail line per product
for i = 1 to 3;
  // Reprint the heading if the page overflowed
  if *in90;
    write heading;
    *in90 = *off;
  endif;
  product = names(i);   // fields defined in the DETAIL DDS format
  qty     = units(i);
  amount  = prices(i);
  write detail;
endfor;

*inlr = *on;

Key points:

  • dcl-f salesrpt printer oflind(*in90) declares an externally described printer file and names *in90 as the overflow indicator, which turns on when the page fills.
  • write heading and write detail emit records using DDS-defined formats; the program only supplies the field values.
  • Output does not go to the screen - it becomes a spooled file on the output queue, viewable with the WRKSPLF command.
  • This record-and-format model is exactly how RPG has produced business reports since 1959.

Database I/O with Embedded SQL

Modern RPG mixes native record I/O with embedded SQL for DB2 for i. SQL statements are written directly in the program between exec sql and a semicolon. RPG variables become host variables by prefixing them with a colon, and sqlcode reports the result of each statement.

Create a file named io_sql.rpgle:

**FREE
ctl-opt dftactgrp(*no);

dcl-s custName varchar(30);
dcl-s custBal  packed(11:2);
dcl-s totalBal packed(13:2);
dcl-s msg      char(52);

// Single-row read with SELECT INTO
exec sql
  SELECT custname, balance INTO :custName, :custBal
  FROM customer
  WHERE custid = 1001;

if sqlcode = 0;
  msg = %trim(custName) + ': ' + %trim(%editc(custBal:'1'));
  dsply msg;
elseif sqlcode = 100;
  dsply 'Customer 1001 not found';
endif;

// Aggregate query
exec sql
  SELECT SUM(balance) INTO :totalBal
  FROM customer;
msg = 'Total balance: ' + %trim(%editc(totalBal:'1'));
dsply msg;

// Read many rows with a cursor
exec sql DECLARE c1 CURSOR FOR
  SELECT custname FROM customer ORDER BY custname;
exec sql OPEN c1;
exec sql FETCH c1 INTO :custName;
dow sqlcode = 0;
  dsply custName;
  exec sql FETCH c1 INTO :custName;
enddow;
exec sql CLOSE c1;

// Write a new row with INSERT
exec sql
  INSERT INTO customer (custid, custname, balance)
  VALUES (2001, 'New Customer', 0);
if sqlcode = 0;
  dsply 'Row inserted';
endif;

*inlr = *on;

Key points:

  • exec sql ... ; embeds a SQL statement; host variables are RPG fields prefixed with :.
  • sqlcode is 0 on success, 100 when a SELECT/FETCH finds no (more) rows, and negative on error.
  • A cursor (DECLARE, OPEN, FETCH, CLOSE) reads a result set row by row - the SQL equivalent of the native read loop.
  • Because DB2 is built into IBM i, INSERT, UPDATE, and DELETE are as natural in RPG as native write and update.

Running on IBM i

RPG requires an IBM i system - there is no open-source compiler for Linux, macOS, or Windows, and no Docker image is available. The database examples also require the CUSTOMER file (and, for the report, the SALESRPT printer file) to exist first.

Create the sample table with SQL from an interactive session or a script:

1
2
3
4
5
6
7
CREATE TABLE customer (
  custid   DECIMAL(7,0) PRIMARY KEY,
  custname CHAR(30),
  balance  DECIMAL(11,2)
);
INSERT INTO customer VALUES (1001, 'Acme Corp',  2500.00);
INSERT INTO customer VALUES (1002, 'Globex Inc', 1750.50);

Compile and run a plain RPG program (no embedded SQL) with CL commands:

1
2
CRTBNDRPG PGM(MYLIB/IOCONSOLE) SRCSTMF('/home/user/io_console.rpgle')
CALL PGM(MYLIB/IOCONSOLE)

For programs that contain embedded SQL, use the SQL precompiler instead:

1
2
CRTSQLRPGI OBJ(MYLIB/IOSQL) SRCSTMF('/home/user/io_sql.rpgle') COMMIT(*NONE)
CALL PGM(MYLIB/IOSQL)

You can get IBM i access through PUB400.COM (a free public learning system), IBM Power Virtual Server, or IBM i Access Client Solutions.

Expected Output

Console output from io_console.rpgle:

Order Report
Quantity: 5
Unit price: 1,234.56
Total: 6,172.80

Interactive session for io_input.rpgle (assuming the user types Alice and 30):

Enter your name: Alice
Enter your age: 30
Hello, Alice!
Status: adult

Reading the sample CUSTOMER file with io_readfile.rpgle:

1001 Acme Corp
1002 Globex Inc
Found: Acme Corp

The spooled report from io_report.rpgle (viewed with WRKSPLF):

SALES REPORT
Product              Qty      Amount
Widget               100        4.99
Gadget                50        9.99
Gizmo                 25       14.99

Embedded SQL output from io_sql.rpgle:

Acme Corp: 2,500.00
Total balance: 4,250.50
Acme Corp
Globex Inc
Row inserted

On IBM i, DSPLY messages appear in the job log prefixed with DSPLY (for example DSPLY Order Report). Interactively they pause for Enter.

Key Concepts

  • RPG I/O is record-based - files are described externally and their fields become program variables, reflecting RPG’s origins as the Report Program Generator.
  • dsply handles simple console output and, with a response field, simple input; production screens use DDS display files.
  • read / chain read database records sequentially or by key, with %eof and %found reporting the result.
  • write emits output records, including printer-file formats that produce spooled reports.
  • Built-in functions format output: %char for plain conversion, %editc for business editing (commas and decimals), %trim to clean up blanks.
  • Embedded SQL (exec sql) integrates DB2 for i directly into RPG, using colon-prefixed host variables and sqlcode for status.
  • Cursors (DECLARE/OPEN/FETCH/CLOSE) read multi-row result sets, the SQL counterpart of the native read loop.
  • Everything runs only on IBM i - compile with CRTBNDRPG, or CRTSQLRPGI when embedded SQL is present.

Running Today

All examples can be run using Docker:

docker pull none
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining