RPG Data Structures in Depth: LIKEDS, Qualified DS, Template Data Structures, DS Arrays, EXTNAME, and LIKEREC in ILE RPG on IBM i

The previous post covered the IBM i PASE runtime in depth — AIX binary compatibility, PASE vs QShell differences, environment variables, PASE process management with ps and nohup, shared library loading with LIBPATH, yum package management for open-source tools, and how PASE integrates with the IBM i job structure and ILE programs. This post covers RPG data structures in depth: the dcl-ds keyword, qualified data structures with dot notation, LIKEDS for cloning a structure, template data structures, data structure arrays with DIM, external data structures from DB2 file definitions using EXTNAME and LIKEREC, nested data structures, and passing data structures to subprocedures.

Data Structures in ILE RPG — Overview

A data structure (DS) in ILE RPG is a named grouping of fields that occupy contiguous storage. Data structures serve several purposes: organising related fields into a logical unit, mapping memory overlays (a DS and its subfields share the same storage), passing structured data between procedures, and defining the layout of an externally described database record.

In older fixed-format RPG, data structures were defined with a D-spec line that had DS in columns 24–25 and subfields beneath it. In free-format RPG IV, the dcl-ds keyword replaces D-spec DS definitions. Both syntaxes are valid in ILE RPG, but free-format is cleaner and all new code should use it.

Basic dcl-ds Syntax

A simple free-format data structure definition:

**FREE

// Basic data structure — a customer record layout
dcl-ds CustomerDS;
  CustNo     packed(9:0);
  CustName   char(40);
  CustCity   char(30);
  CustSts    char(1);
  CrdLimit   packed(13:2);
end-ds;

// Initialise individual subfields
CustNo   = 100001;
CustName = 'ACME Corporation';
CustSts  = 'A';

// Clear all subfields to their default values
clear CustomerDS;

The clear operation resets all subfields: numeric fields to 0, character fields to blanks, date/time fields to their default values. reset restores subfields to their compile-time initial values (if any were specified with INZ).

Qualified Data Structures

A qualified data structure requires its subfields to be accessed using dot notation: dsname.subfieldname. Without QUALIFIED, subfield names are in the global namespace and can conflict with other field names. QUALIFIED is the recommended default for all data structures in modern ILE RPG.

**FREE

// Qualified DS — subfields accessed as OrderDS.OrderNo, etc.
dcl-ds OrderDS qualified;
  OrderNo    packed(9:0);
  CustNo     packed(9:0);
  OrderDt    date;
  OrderAmt   packed(13:2);
  OrderSts   char(2);
end-ds;

// Access via dot notation — no ambiguity
OrderDS.OrderNo  = 20001;
OrderDS.CustNo   = 100001;
OrderDS.OrderDt  = %date();
OrderDS.OrderAmt = 1250.00;
OrderDS.OrderSts = 'OP';

// Two qualified DS can have subfields with the same name — no conflict
dcl-ds LineDS qualified;
  OrderNo    packed(9:0);   // Same name as OrderDS.OrderNo — no problem
  LineNo     packed(3:0);
  ItemNo     char(10);
  Qty        packed(7:0);
  UnitPrice  packed(11:2);
end-ds;

LineDS.OrderNo = OrderDS.OrderNo;
LineDS.LineNo  = 1;

LIKEDS — Cloning a Data Structure

The LIKEDS keyword creates a new data structure with the same subfield layout as an existing one. This is the RPG equivalent of a struct typedef in C — define the shape once, create as many instances as needed.

**FREE

// Define a "template" layout using a standalone DS
dcl-ds t_Order qualified template;
  OrderNo    packed(9:0);
  CustNo     packed(9:0);
  OrderDt    date;
  OrderAmt   packed(13:2);
  OrderSts   char(2);
end-ds;

// Create two separate instances with the same layout using LIKEDS
dcl-ds CurrentOrder  likeds(t_Order);
dcl-ds PreviousOrder likeds(t_Order);

// Both have their own storage — changing one does not affect the other
CurrentOrder.OrderNo  = 20001;
PreviousOrder.OrderNo = 19998;

// Copy one DS to another of the same LIKEDS type — a single assignment
PreviousOrder = CurrentOrder;

// LIKEDS also works for procedure parameters — strongly typed
dcl-proc ProcessOrder;
  dcl-pi *n;
    pOrder  likeds(t_Order) const;
  end-pi;
  // pOrder.OrderNo, pOrder.CustNo etc. are available here
end-proc;

Template Data Structures

The TEMPLATE keyword marks a data structure as a template — it defines a layout but allocates no storage at compile time. Template DS are only used as prototypes for LIKEDS references or for procedure parameter definitions; they cannot be read from or written to directly.

**FREE

// Template DS — no storage allocated; used only as a LIKEDS source
dcl-ds t_Address qualified template;
  Street   char(50);
  City     char(30);
  State    char(2);
  PostCode char(10);
  Country  char(3);
end-ds;

// Use the template to define actual variables
dcl-ds ShipAddress  likeds(t_Address);
dcl-ds BillAddress  likeds(t_Address);

// Template DS used as a procedure parameter type
dcl-pr FormatAddress varchar(200) extproc('FormatAddress');
  pAddr  likeds(t_Address) const;
end-pr;

// Calling the procedure
ShipAddress.Street   = '123 Main St';
ShipAddress.City     = 'Springfield';
ShipAddress.State    = 'IL';
ShipAddress.PostCode = '62701';
ShipAddress.Country  = 'USA';
wFormattedAddr = FormatAddress(ShipAddress);

Data Structure Arrays

Adding the DIM keyword to a data structure makes it an array of data structures. Each element is a complete instance of the DS, accessed by index. DS arrays are the RPG equivalent of a struct array in C or an array of objects in Java.

**FREE

// DS array — an array of order line items
dcl-ds OrderLines qualified dim(100);
  LineNo     packed(3:0);
  ItemNo     char(10);
  Qty        packed(7:0);
  UnitPrice  packed(11:2);
  LineAmt    packed(13:2);
end-ds;

dcl-s wLineCount  int(5)   inz(0);
dcl-s wIndex      int(5);
dcl-s wTotalAmt   packed(13:2) inz(0);

// Populate the DS array
wLineCount += 1;
OrderLines(wLineCount).LineNo    = wLineCount;
OrderLines(wLineCount).ItemNo    = 'WIDGET-001';
OrderLines(wLineCount).Qty       = 5;
OrderLines(wLineCount).UnitPrice = 24.99;
OrderLines(wLineCount).LineAmt   = OrderLines(wLineCount).Qty *
                                   OrderLines(wLineCount).UnitPrice;

// Iterate over all populated elements
for wIndex = 1 to wLineCount;
  wTotalAmt += OrderLines(wIndex).LineAmt;
endfor;

// Clear all elements
clear OrderLines;

// %ELEM returns the declared size of the DS array (100)
dcl-s wMax int(5);
wMax = %elem(OrderLines);

External Data Structures with EXTNAME

The EXTNAME keyword defines a data structure whose subfields are taken directly from a DB2 for i file (physical file or logical file) or from a SQL table definition using the EXTFLD or PREFIX keywords. The compiler reads the file definition at compile time and generates the subfields automatically — no need to manually define each column.

**FREE

// External DS — subfields come from CUSTMST file definition
// Compiler reads CUSTMST from the library list at compile time
dcl-ds CustomerRec extname('CUSTMST') qualified;
end-ds;

// The subfields match the CUSTMST column names exactly
// (field names are as defined in the DDS or CREATE TABLE)
CustomerRec.CUSNO  = 100001;
CustomerRec.CUSNM  = 'ACME Corporation';
CustomerRec.CUSST  = 'A';

// Use with an SQL cursor — populate the DS from a SELECT
exec sql
  SELECT CUSNO, CUSNM, CUSST, CRDLMT
  INTO   :CustomerRec
  FROM   APPLIB.CUSTMST
  WHERE  CUSNO = :wSearchCustNo;

if sqlcode = 0;
  // CustomerRec.CUSNO, CUSNO, CUSNM etc. are populated
endif;

// EXTNAME with a specific record format (for files with multiple formats)
dcl-ds OrderHdrRec extname('ORDMST': 'ORDHDRF') qualified;
end-ds;

// PREFIX renames subfields to avoid conflicts
dcl-ds OrderLinRec extname('ORDLIN') prefix('LIN_') qualified;
end-ds;
// Subfields become LIN_LINNO, LIN_ITMNO, LIN_QTY, etc.

LIKEREC — Data Structure from a Record Format

LIKEREC is similar to EXTNAME but references a record format within a file that is already declared in the program (via an F-spec or a dcl-f). LIKEREC is particularly useful when working with files declared in the program and you need a DS that matches the full record layout for read/write operations.

**FREE

// File declaration
dcl-f ORDMST usage(*input) keyed;

// DS based on the ORDHDRF record format of ORDMST
dcl-ds OrderHeader likerec(ORDMST.ORDHDRF) qualified;
end-ds;

// Read a record into the DS
chain (wOrderNo) ORDMST OrderHeader;
if %found(ORDMST);
  // OrderHeader subfields are populated from the record
  wOrderAmt = OrderHeader.ORDAMT;
  wOrderSts = OrderHeader.ORDSTS;
endif;

// LIKEREC with *OUTPUT — only output-capable fields included
dcl-ds WriteOrder likerec(ORDMST.ORDHDRF: *output) qualified;
end-ds;

// LIKEREC with *KEY — only key fields included
dcl-ds OrderKey likerec(ORDMST.ORDHDRF: *key) qualified;
end-ds;

Nested Data Structures

Data structure subfields can themselves be data structures, creating nested (multi-level) structures. This is expressed in free-format RPG by embedding a dcl-ds within another dcl-ds:

**FREE

// Nested DS — an order with embedded address and line items
dcl-ds t_ShipAddress qualified template;
  Street   char(50);
  City     char(30);
  State    char(2);
  PostCode char(10);
end-ds;

dcl-ds t_OrderFull qualified template;
  OrderNo     packed(9:0);
  CustNo      packed(9:0);
  OrderDt     date;
  ShipAddr    likeds(t_ShipAddress);  // Nested DS
  TotalAmt    packed(13:2);
end-ds;

dcl-ds CurrentOrder likeds(t_OrderFull);

// Access nested subfields with chained dot notation
CurrentOrder.OrderNo             = 20001;
CurrentOrder.ShipAddr.Street     = '456 Oak Ave';
CurrentOrder.ShipAddr.City       = 'Chicago';
CurrentOrder.ShipAddr.State      = 'IL';
CurrentOrder.ShipAddr.PostCode   = '60601';
CurrentOrder.TotalAmt            = 350.00;

Passing Data Structures to Subprocedures

The standard patterns for passing data structures as procedure parameters:

**FREE

dcl-ds t_Order qualified template;
  OrderNo  packed(9:0);
  CustNo   packed(9:0);
  OrderAmt packed(13:2);
  OrderSts char(2);
end-ds;

// Prototype: pass DS by read-only reference (CONST — cannot be modified)
dcl-pr ValidateOrder ind extproc('ValidateOrder');
  pOrder   likeds(t_Order) const;
  pErrMsg  varchar(200);              // Output — passed by reference (default)
end-pr;

// Prototype: pass DS by reference (can modify)
dcl-pr EnrichOrder extproc('EnrichOrder');
  pOrder   likeds(t_Order);           // No CONST — caller's DS is modified
end-pr;

// Implementation of ValidateOrder
dcl-proc ValidateOrder export;
  dcl-pi *n ind;
    pOrder   likeds(t_Order) const;
    pErrMsg  varchar(200);
  end-pi;

  if pOrder.OrderAmt <= 0;
    pErrMsg = 'Order amount must be greater than zero';
    return *off;
  endif;

  if pOrder.CustNo = 0;
    pErrMsg = 'Customer number is required';
    return *off;
  endif;

  pErrMsg = '';
  return *on;
end-proc;

// Calling code
dcl-ds MyOrder likeds(t_Order);
dcl-s  wMsg   varchar(200);

MyOrder.OrderNo  = 20001;
MyOrder.CustNo   = 100001;
MyOrder.OrderAmt = 500.00;

if ValidateOrder(MyOrder: wMsg);
  // Process the order
else;
  // wMsg contains the validation error
  dsply wMsg;
endif;

Always use CONST for DS parameters that the procedure should not modify — it documents intent and the compiler enforces it. For output parameters, pass without CONST (by reference). Avoid passing DS by value (VALUE keyword) unless the DS is small, because DS by value copies the entire structure on each call.

Next post: CL Error Handling and Exception Management on IBM i — IBM i message types, MONMSG command patterns, program message queues, SNDPGMMSG for custom errors, RCVMSG, condition handlers in CL procedures, DMPJOB diagnostic dumps, and complete error-handling templates for production CL programs.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top