Who Knew You Could Do That With RPG IV - Modern RPG For The Modern Programmer
Who Knew You Could Do That With RPG IV - Modern RPG For The Modern Programmer
Who Knew You Could Do That With RPG IV - Modern RPG For The Modern Programmer
Rich Diedrich
Jim Diephuis
Susan Gantner
Jeff Minette
Jon Paris
Kody Robinson
Tim Rowe
Paul Tuohy
Redbooks
International Technical Support Organization
December 2016
SG24-5402-01
Note: Before using this information and the product it supports, read the information in “Notices” on
page ix.
This edition applies to Version 7, Release 2, Modification 0, Technology Refresh 1 of IBM i (5770-SS1) and
IBM Rational Development Studio for i (5770-WDS).
© Copyright International Business Machines Corporation 2000, 2016. All rights reserved.
Note to U.S. Government Users Restricted Rights -- Use, duplication or disclosure restricted by GSA ADP Schedule
Contract with IBM Corp.
Contents
Notices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix
Trademarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .x
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Now you can become a published author, too! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv
Comments welcome. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv
Stay connected to IBM Redbooks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Chapter 3. Subprocedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.1 Subprocedure terminology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.1.1 ILE modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.1.2 Main procedure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.1.3 Built-in functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.1.4 Subroutines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.2 Advantages of using subprocedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.3 The anatomy of a subprocedure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.3.1 Subprocedure definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
3.3.2 Procedure-interface definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.3.3 Order of coding the source elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.3.4 Calling your subprocedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.4 Moving from subroutines to subprocedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.4.1 Why use subprocedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.4.2 Subroutine example DATESUBR . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.4.3 Transforming a subroutine to a subprocedure . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.4.4 DATEMAIN1 subprocedure example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.5 Using subprocedures efficiently . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.5.1 Using /COPY members for prototypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.5.2 Prototype for the main procedure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.5.3 Subprocedures using subprocedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.5.4 Using an ILE service program. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.6 More on subprocedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
3.6.1 The power of prototyping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
3.6.2 Parameter passing styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
3.6.3 Using procedure pointer calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
iv Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Chapter 4. An ILE guide for the RPG programmer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
4.1 Introduction to ILE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.1.1 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.1.2 Service programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.1.3 Binding: Creating programs and service programs. . . . . . . . . . . . . . . . . . . . . . . . 79
4.1.4 Binding directories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
4.1.5 Service program exports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.1.6 Binder language source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.1.7 Activation groups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.1.8 CL commands used with ILE and RPG. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.2 ILE tips for the RPG programmer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.2.1 Creating programs from modules (binding by copy) . . . . . . . . . . . . . . . . . . . . . . . 83
4.2.2 Binding service programs to programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
4.2.3 Service programs, binder language, and signatures. . . . . . . . . . . . . . . . . . . . . . 104
4.2.4 Using binding directories. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
4.2.5 Activation groups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
4.2.6 Call stack and error handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
4.3 Additional CL commands and useful ILE APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
4.3.1 Additional CL commands . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
4.3.2 Some useful APIs to get information about ILE objects . . . . . . . . . . . . . . . . . . . 142
4.4 More information about ILE and shared open data paths . . . . . . . . . . . . . . . . . . . . . . 144
Contents v
6.3.2 Creating an SQL procedure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
6.3.3 Starting a stored procedure and returning the completion status . . . . . . . . . . . . 193
6.3.4 A stored procedure example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
6.4 DB2 call level interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
6.4.1 Differences between DB2 CLI and embedded SQL . . . . . . . . . . . . . . . . . . . . . . 203
6.4.2 Writing a DB2 CLI application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
6.4.3 Initialization and termination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
6.4.4 Transaction processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
6.4.5 Diagnostic tests. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
6.4.6 Data types and data conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
6.4.7 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
6.4.8 Introduction to a CLI example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
6.5 Trigger programs. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
6.5.1 Adding a trigger program to a file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
6.5.2 Creating a trigger program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
6.6 Commitment control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
6.6.1 File journaling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
6.6.2 Using commitment control with RPG native file operations . . . . . . . . . . . . . . . . 247
6.6.3 Using commitment control with embedded SQL . . . . . . . . . . . . . . . . . . . . . . . . . 248
6.6.4 Using commitment control with the CLI interface . . . . . . . . . . . . . . . . . . . . . . . . 249
6.7 A note about globalization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
6.8 More information about database access with RPG IV. . . . . . . . . . . . . . . . . . . . . . . . 250
vi Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
7.10 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
Contents vii
10.14 Built-in functions that you need to know . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
viii Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Notices
This information was developed for products and services offered in the US. This material might be available
from IBM in other languages. However, you may be required to own a copy of the product or product version
in that language in order to access it.
IBM may not offer the products, services, or features discussed in this document in other countries. Consult
your local IBM representative for information on the products and services currently available in your area.
Any reference to an IBM product, program, or service is not intended to state or imply that only that IBM
product, program, or service may be used. Any functionally equivalent product, program, or service that does
not infringe any IBM intellectual property right may be used instead. However, it is the user's responsibility to
evaluate and verify the operation of any non-IBM product, program, or service.
IBM may have patents or pending patent applications covering subject matter described in this document. The
furnishing of this document does not grant you any license to these patents. You can send license inquiries, in
writing, to:
IBM Director of Licensing, IBM Corporation, North Castle Drive, MD-NC119, Armonk, NY 10504-1785, US
This information could include technical inaccuracies or typographical errors. Changes are periodically made
to the information herein; these changes will be incorporated in new editions of the publication. IBM may make
improvements and/or changes in the product(s) and/or the program(s) described in this publication at any time
without notice.
Any references in this information to non-IBM websites are provided for convenience only and do not in any
manner serve as an endorsement of those websites. The materials at those websites are not part of the
materials for this IBM product and use of those websites is at your own risk.
IBM may use or distribute any of the information you provide in any way it believes appropriate without
incurring any obligation to you.
The performance data and client examples cited are presented for illustrative purposes only. Actual
performance results may vary depending on specific configurations and operating conditions.
Information concerning non-IBM products was obtained from the suppliers of those products, their published
announcements or other publicly available sources. IBM has not tested those products and cannot confirm the
accuracy of performance, compatibility or any other claims related to non-IBM products. Questions on the
capabilities of non-IBM products should be addressed to the suppliers of those products.
Statements regarding IBM's future direction or intent are subject to change or withdrawal without notice, and
represent goals and objectives only.
This information contains examples of data and reports used in daily business operations. To illustrate them
as completely as possible, the examples include the names of individuals, companies, brands, and products.
All of these names are fictitious and any similarity to actual people or business enterprises is entirely
coincidental.
COPYRIGHT LICENSE:
This information contains sample application programs in source language, which illustrate programming
techniques on various operating platforms. You may copy, modify, and distribute these sample programs in
any form without payment to IBM, for the purposes of developing, using, marketing or distributing application
programs conforming to the application programming interface for the operating platform for which the sample
programs are written. These examples have not been thoroughly tested under all conditions. IBM, therefore,
cannot guarantee or imply reliability, serviceability, or function of these programs. The sample programs are
provided “AS IS”, without warranty of any kind. IBM shall not be liable for any damages arising out of your use
of the sample programs.
The following terms are trademarks or registered trademarks of International Business Machines Corporation,
and might also be trademarks or registered trademarks in other countries.
DB2® Language Environment® Redpaper™
developerWorks® OS/400® Redbooks (logo) ®
IBM® Rational® RPG/400®
Integrated Language Environment® Redbooks® System i®
Evolution, and Inc. device are trademarks or registered trademarks of Kenexa, an IBM Company.
Linux is a trademark of Linus Torvalds in the United States, other countries, or both.
Microsoft, Windows, and the Windows logo are trademarks of Microsoft Corporation in the United States,
other countries, or both.
Java, and all Java-based trademarks and logos are trademarks or registered trademarks of Oracle and/or its
affiliates.
Other company, product, or service names may be trademarks or service marks of others.
x Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Preface
Application development is a key part of IBM® i businesses. The IBM i operating system is a
modern, robust platform to create and develop applications. The RPG language has been
around for a long time, but is still being transformed into a modern business language.
This IBM Redbooks® publication is focused on helping the IBM i development community
understand the modern RPG language. The world of application development has been
rapidly changing over the past years. The good news is that IBM i has been changing right
along with it, and has made significant changes to the RPG language. This book is intended
to help developers understand what modern RPG looks like and how to move from older
versions of RPG to a newer, modern version. Additionally, it covers the basics of Integrated
Language Environment® (ILE), interfacing with many other languages, and the best tools for
doing development on IBM i.
Using modern tools, methodologies, and languages is key to continuing to stay relevant in
today’s world. Being able to find the right talent for your company is key to your continued
success. Using the guidelines and principles in this book can help set you up to find that
talent today and into the future.
This publication is the result of work that was done by IBM, industry experts, business
partners, and some of the original authors of the first edition of this book. This information is
important not only for developers, but also business decision makers (CIO for example) to
understand that the IBM i is not an ‘old’ system. IBM i has modern languages and tools. It is a
matter of what you choose to do with the IBM i that defines its age.
Authors
This book was produced by a team of specialists from around the world working with the
International Technical Support Organization, Rochester Center.
Jeff Minette has been part of the Lab Services and Training for
the past 17 years. Over his consulting career, Jeff has assisted
many customers with modernizing their IBM i applications. He
has done so through lab services application modernization
workshops and by working directly with development teams as
they modernize their applications. Although he has been
primarily focused on IBM i native applications and incorporating
new technologies, this focus has expanded through the years
to include both client and server-side web technologies in both
an architectural and developmental role.
xii Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Kody Robinson is a developer for Arkansas Electric
Cooperative Corporation, located in Little Rock, AR. He
develops on the IBM i platform and uses various languages to
deliver applications to his clients. Kody graduated from the
University of Arkansas at Monticello with a bachelor’s degree in
Computer Information Systems. During this time, he worked at
a banking institution where he first worked with AS/400. He has
used his knowledge to help modernize RPG in his work place
by helping implement newer solutions, modernize file usage,
and more. Kody was the recipient of the 2016 IBM/Common
Innovation Award and helps proactively advocate for the
platform. Kody is now finishing his masters and hopes to teach
part-time at colleges to help fill the world with more RPG
programmers.
Barbara Morris
Architect for RPG Compiler, Toronto
Edmund Reihnardt
Architect for Rational Developer for i
Debra Landon
IBM Redbooks Project Leader, Rochester, MN
Preface xiii
Scott Forstie
Business Architect DB2 for i
Paul Jackson
Costco Wholesale Corporation
Find out more about the residency program, browse the residency index, and apply online at:
ibm.com/redbooks/residencies.html
Comments welcome
Your comments are important to us!
We want our books to be as helpful as possible. Send us your comments about this book or
other IBM Redbooks publications in one of the following ways:
Use the online Contact us review Redbooks form found at:
ibm.com/redbooks
Send your comments in an email to:
[email protected]
xiv Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Mail your comments to:
IBM Corporation, International Technical Support Organization
Dept. HYTD Mail Station P099
2455 South Road
Poughkeepsie, NY 12601-5400
Preface xv
xvi Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
1
One of the great things about RPG is the compatibility with an earlier version that is still
supported today. However, you can make the same argument that this is one of the worst
things about the language. The RPG program has never been forced to stop doing ‘old’ things
and move forward. However, there are great new things that you can take advantage of.
Today, if you are leveraging the latest in RPG and IBM i technology, you are in great shape for
a successful future. The new modern RPG language looks and feels natural to the modern
developer. Although most university students are not taught RPG in school (although there
are schools still offering RPG through the IBM Academic Initiative Program), the latest style
and tools are similar to most other languages they have learned. This makes the transition to
RPG much smoother.
The RPG IV language was introduced in Version 3 Release 1 (V3R1) of OS/400® and has
continued to evolve and mature. Even though RPG IV has been around for over 21 years and
the Integrated Language Environment (ILE) has been available since Version 2 Release 3
(V2R3), many RPG programmers are still confused about the relationship. Using RPG IV
does not necessarily require the use of, or the understanding of, the ILE. However, using ILE
is where RPG IV gets its power. Understanding how to use ILE to minimize rewriting code and
maximize code reuse is what continues to make the RPG language relevant today. For more
information about how to take advantage of ILE, see Chapter 4, “An ILE guide for the RPG
programmer” on page 77.
In addition to the many changes to the actual RPG language itself, there have also been
many changes in how applications are structured, how you access data, how data is passed
from application to application, and also how you build applications. Many of these topics are
covered to some level in this book. The rest of this chapter helps you to understand what has
changed, and how you can go about making the transition to modern RPG.
2 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
1.1 Why update this book on RPG IV now?
This publication was first published in February 2000 and updated in April of that year. The
world of programming and IBM i development has changed considerably in the past 16 years.
Many of the examples that were included in the first book are now functions that have been
included in to the RPG language itself. In addition, the RPG language has been transformed
from a language that was built and styled for use with a punch card to a modern business
language.
Much of the content of this book describes the many features that make up modern RPG.
These features range from the style of the language, the methodologies used, how you
access data, to the tooling you use to build and develop applications in today’s modern world.
For the modern developer, Rational Developer for IBM i (RDi) is now the development tool of
choice. PDM and SEU are no longer being maintained.
The system itself has undergone multiple changes both in hardware and names since the
original book was published. It was time to update this document to show people what new
magic can be done with this updated language. RPG is still relevant and a key player in the
newer environments where the main interface is now a web browser. The system is more
about managing a business’ data rather than being the only computer that a business might
have. However, several of the chapters of the previous version of this book are still relevant to
today's programming environment. If you have read the old version, you will still find some of
the same wisdom in this updated version with updated examples.
Is RPG IV a dead language? The answer to this question is no. The RPG language continues
to evolve and change. The IBM i development team continues to listen to the development
community and update RPG with the newest features to help you succeed not just today, but
well into the future.
With each release of the operating system, more features have been added. Over the years,
each release of the RPG language has changed it to “reinvent” it as a modern business
language. These enhancements and changes to the language include free-form calculation
specifications, prototyped procedures, additional data types, more BiFs, and many other
enhancements. The now code looks more like what is shown in Figure 1-3.
With IBM i 7.1 DB2 PTF group SF99701 level 26 for the SQL precompiler, a completely
free-form version of the language is available as shown in Figure 1-4.
Important: Since publication, a corrected version of the free-form code was provided by
the community:
dcl-ds resDS qualified;
res char(5);
resn packed(3:0);
end-Ds;
resDS.res = 'ABC';
resDS.resn = 5;
*inLR = *on;
return;
The free-form and older specifications can be intermixed in the code without the need for the
/free and /end-free. The code must still appear between columns 8 and 80 of the source
member. With the release of the latest RPG updates, the 8-80 column restriction has been
removed. You can specify **FREE in the first column and from that point on, there are no
column restrictions. The right side can go out as wide as you want. The only restriction with
this feature is that you cannot mix old and new styles of RPG. This feature only supports fully
free-form RPG. See Figure 1-8 on page 6 for details.
4 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Note: All of the examples that are shown in this book are given in this free-form version.
This form comes in the initial release of IBM i 7.2.
Figure 1-5 shows a program with a main procedure defined and using a template to define
the data structure that is used in the routine.
Figure 1-5 Main procedure using a template to define the data structure used in the routine
The main() in Figure 1-5 tells the RPG compiler that it does not need to include the
RPG-cycle in the machine level code (note that the RPG-cycle should not be used any
longer). The template keyword makes the variable resT a model that can be used to define
other variables, like resDS in the procedure.
You can turn this program into a procedure that sets the values of the data structure. Put the
template and prototype into a separate source member as shown in Figure 1-6.
Figure 1-6 Procedure that sets the values of the data structure
The latest enhancements to the language allow the code to be totally free. The code can start
in column one and go for as long as the source member is defined (minus the 12 bytes for
sequence number and last changed date). This is part of the enhancements in 7.1 Group
PTF SF99701 level 38 for the SQL precompiler and 7.2 Group PTF SF99702 level 9 for the
SQL precompiler.
Figure 1-8 shows what the code shown in Figure 1-7 could look like in the future.
The **free in the first line of the code in Figure 1-7 tells the compiler there are no column
limitations or old code specifications in this source member. The code in the /copy member
could still be defined within column limitations or even with statement type specifications.
Using this version of RPG IV makes it easier to store your source in the integrated file system
(IFS) or a stream-oriented change management system with fewer concerns about white
space.
6 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
1.2.1 Examples in this book are based on IBM i 7.2
The RPG programs and IBM i database libraries that are used in this book are available for
you to download from the Internet. These examples were developed by using an IBM i system
and the ILE RPG compiler (5770-WDS, option 31) at IBM i version 7.2. See Appendix A,
“Additional material” on page 385 for instructions about how to download the IBM i save file
that contains the RPG examples that are used in this book. IBM i version 7.3 also supports all
references in this book.
Note: Some of the programming examples in this book use functions and features of the
compiler and IBM i that will only compile and run on IBM i. Others might run on IBM i 7.1
without modifications. The full free-form RPG for almost all statement types was not
available before IBM i 7.1 technology refresh (TR) 7. Therefore, most of the examples will
not run on any system before that refresh without modification.
One significant area where changes are likely to continue relates to making it easier to
integrate RPG with newer interfaces. XML is already easier to do with RPG. Several of these
new interfaces are examined in Chapter 8, “Interfacing” on page 315. The ability to integrate
the RPG code that has been reliably serving business application requirements for years is
critical to the smooth implementation of these new technologies.
Although it is possible to call between RPG and Java applications today (for example, by
using the program call from the IBM Toolbox for Java or the JNI support in V4R4), there is still
much room for improvement in making the integrated support between the languages easier
and more robust. Enhancements in this area are important to the future utilization of both
languages, and therefore are a high priority.
It is also important to continue the growth of RPG as a language in its own right. Despite the
many dramatic enhancements in the past releases, there is still room to continue to evolve the
language as programmers evolve in their use of the language.
The RPG development team in the IBM Toronto laboratory is keenly aware of the need for and
the advantage of listening to the RPG programming community to help steer the direction of
their language of choice. RPG programmers will see even more dramatic enhancements in
both the near term and long term that will enhance their productivity and their programming
style options in RPG. Many of these enhancements have been guided by input from RPG
programmers around the world.
1.4 A roadmap
It might be helpful to suggest a roadmap for programmers who are looking to move from
pre-RPG IV environments to taking advantage of today’s RPG language. The following steps
are discussed in this section:
Step 1: RPG IV and RDi
Step 2: Modularization by using ILE
Step 3: Using database features
Step 4: Modernizing the user interface
Besides moving to RPG IV, it is time to move from the 5250 display and PDM/SEU to a
modern integrated development environment (IDE). For the IBM i developer, this is Rational
Developer for i (RDi).
Note: All of the example code that is used in this book was developed using RDi.
To enhance your skills in RPG IV, many books, publications, and classes are offered by IBM
and others. In addition, tutorials and conferences are available that provide help on how to
take advantage of the latest technologies while using RPG IV.
After you are in RPG IV and throughout your journey, adopt a consistent style of coding in
RPG IV. The style guide included in this book (see Chapter 2, “Programming RPG IV with
style” on page 11) provides a good place to start in developing your style for RPG IV coding.
Although it is typically not feasible to rewrite all your existing code to meet the requirements of
your style guide, new code and enhancements can use the style features that you decide
work best for your environment. Some of the conversion tools available on the market also
help in making some basic style adjustments, such as the use of upper and lowercase, the
use of the dcl-s specification for stand-alone work fields, and using free-form operations.
It is important to avoid the pitfall of using RPG IV in the same style as you have used with
previous RPG languages. Keep up with the latest techniques and enhancements of the
compiler and continue to enhance your style and coding standards to fully use all that this
language has to offer.
8 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Convert all fixed-format RPG to the new totally free-format RPG. Several tools in the industry
do a great job of doing this sort of transformation:
ARCAD Transformer: Provided by IBM i strategic partner. It can be purchased either
directly from ARCAD or though the IBM software ordering system:
http://arcadsoftware.com/products/arcad-transformer-ibm-i-refactoring-tools/
Linoma Software RPG Toolbox:
http://www.linomasoftware.com/products/rpg-toolbox
This book helps you to understand many of the basic ILE concepts and the features of
RPG IV that require the use of ILE (and their advantages). In addition, there are other books,
manuals, classes, articles, seminars, and conferences that can help you understand how to
implement ILE in your applications.
Look for opportunities to modularize your application code. Some examples of ways to do this
are included in this book. Modularization, when done well, usually results in higher
productivity for programmers and more reliable application logic. Many developers have also
experienced improvements in application performance because they could tune a procedure
that is now written once and used many times.
Referential integrity constraints, for example, can be implemented in the database more
reliably than in application logic, which depends on all programmers implementing the logic
correctly and consistently. Likewise, application logic could be replaced or made more robust
by adding check constraints to database fields to implement functions such as range or value
list checking. Using SQL instead of native I/O to return the set of data the program really
wants can reduce the size of the code and improve the performance of the application.
Some of the current interfaces and how to use them with RPG IV are discussed in Chapter 8,
“Interfacing” on page 315.
Note: This is the COMMON website for the USA. Click Friends to select your
geography for other worldwide COMMON organizations. If you are interested in
European COMMON activity, you can go directly to the COMMON Europe website:
http://www.comeur.org
IBM i development training, education, articles, and general useful information about
modern development on IBM are available at:
http://systemideveloper.com/
10 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
2
Another source for style guidelines can be found in Appendix D of Programming in ILE RPG,
5th Edition:
http://www.qrpglesrc.com/downloads/ILE_RPG_Style_Guide.pdf
When trying to develop guidelines and standards, one of the major challenges is determining
what is a standard and what is a guideline. For example, code indentation is a standard, but
whether the code is indented by two, three, or four characters is a guideline. One of the
objectives of standards is to ensure that required coding standards are implemented without
impeding the creativity of the programmer.
Even if you are not developing in a multi-language environment, you can still learn from other
languages. For example, use /INCLUDE instead of /COPY and use all uppercase for name
constants, as these are conventions that are common to most (if not all) programming
languages.
IBM Rational Developer for i can help with the implementation of standards. Learn to use
automatic indent, the outline view, content assist, templates, and snippets. All of these
features can help automate standards and styles. For more information about IBM Rational
Developer for i, see Chapter 9, “IBM Rational Developer for IBM i” on page 343.
Even if you decide that existing programs are not going to be converted to free-form RPG,
make any modifications to the programs in free-form only anyway.
Whether writing new programs or modifying existing programs, only code in free-form RPG.
However, avoid creating the situation where the code switches frequently between fixed-form
and free-form code. If you are making several minor changes to a section of fixed-form code,
consider changing that section of fixed-form code to free-form before making your changes.
12 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The first thing to understand about RPG free-form support is that there are multiple types of
free-form. Three types are available, if you do not count the original free-form EVAL type
expression support that was available in the initial release of RPG IV. As a result, you often
have to qualify exactly which type you are talking about.
This section describes the different versions of free-form as it evolved over the years.
RPG was partially liberated in Version 5.1 and this version is often referred to as /Free RPG.
This is the version that many people think of when the term free-form RPG is mentioned.
Here are the main features of this initial release:
The introduction of free-form versions of all supported op-codes. For more information,
see 2.2.3, “Unsupported op-codes” on page 14.
The introduction of the semicolon (;) as a statement terminator.
Operand names were no longer constrained to 14 characters to fit within boxes.
The EVAL and CALLP op-codes are optional in most cases.
A number of op-codes and other features such as resulting indicators and result field
definition were removed.
A number of new BiFs were added, which replaced some existing op-codes and provided
improved functions.
By coding the compiler directive /Free, subsequent RPG logic could be placed anywhere
within columns 8 - 80. There was no need for a “C” in position 6.
A new style of commenting was introduced. The use of a double / allowed for the addition
of comments to the end of calc specs and could be used for full-line comments.
In the following example, key chain filename, simply becomes chain key file name. As you
can see in Example 2-1, every statement is followed by a semicolon. In this way, RPG differs
from some languages, such as C and PHP, where a semicolon is used in most cases but not
all of them. For example, an alternative character such as a { is used in conditionals, such as
IF statements and certain other constructs, such as function definitions. The RPG rule is
much simpler to remember: You must always have a semicolon at the end of every line.
Operations can extend across as many lines as you want with no need for special
continuation characters, unless you are extending a literal. However, you cannot have multiple
operations on a single line.
You can indent the code as you want, which aids greatly in readability, especially where
nested IFs and other statements are used.
BegSr CalcDividend;
TotalSales = %XFoot(MthSales);
Eval(H) Dividend = TotalSales / 100 * DivPerc;
Record_transaction();
EndSr;
Example 2-1 on page 13 does not use many of the new features other than the resequencing
of operators in statements such as the CHAIN. It does use indentation and features such as
the built-in function %EOF. %EOF, and other I/O related BIFs have been available for some
time, but their use was optional because resulting indicators could still be used in previous
versions of RPG.
Within the subroutine, the first line is an EVAL, but the ability to omit the opcode was used.
However, the second line required that the EVAL be coded because the operation extender
(H) for half adjust was required. The final line of the subroutine is also missing an opcode in
this example (it is CALLP). The compiler can easily differentiate between EVAL and CALLP
because EVALs always have an = immediately following the first operand.
Another feature that is demonstrated by this example is the use of the new // style comment
lines, which can be used at the end of the line. These lines can be useful in documenting
operations without needing an extra line. It also makes it obvious that the comment relates to
the line rather than the block of code that follows it.
14 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Op-codes that were replaced by a combination of new and existing support.
An example is the date operations ADDDUR and SUBDUR, which are replaced by a
combination of the new BIFs, such as %Days, %Months, and %Years together with simple
+ and - expression operators.
2.2.4 Op-codes that tend to produce unclear and error-prone source code
Of these, the one that was the most controversial was MOVE. The major reason for its
removal was that when you look at a MOVE operation, you can tell almost nothing about what
is happening unless you know the full definitions of both factors. All that you really know is
that some data is moved from the factor 2 field to the result field. But, without the full
definitions, you do not know whether any of the following items are true:
The date was simply copied byte for byte.
The type of the data was changed (was converted from character to numeric).
The data was truncated.
Two strings were effectively concatenated.
High-order numeric digits were discarded without warning.
Because of these and other issues, MOVE operations often caused misunderstandings and
errors. IBM replaced them with EVAL type operations where the intent of the code was more
obvious.
This release also removed the need for the /Free and /End-Free directives. The compiler now
assumes that the source line is in free-form if columns 6 and 7 are left blank.
F and D spec entries have become declarations. A declaration is introduced by the characters
DCL and then is followed by an indication of the type of declaration. For example, files are
declared by DCL-F, data structures by DCL-DS, and stand-alone fields by DCL-S.
Another change was the introduction of a number of sensible defaults. For example, all files
are assumed to be described externally unless otherwise specified. Similarly, all numeric
fields are assumed to have zero decimal positions unless otherwise specified. The various
defaults are described in the descriptions of the different declarations.
All of the new declarations follow the same basic format. Therefore, it is easy to convert to this
new way of coding. The format consists of these items in the following order:
DCL-xx
The name of the item (file, DS, field, and so on)
Keywords to replace the column-sensitive entries
Any keywords that are in the D and F spec option columns
A semi-colon (;) that terminates the definition
Keywords such as char and packed replace the column-sensitive data type definitions. Unlike
the old method, where you might have omitted the data types (a and p), there are no such
defaults in data declarations. In the current version, it is necessary to close explicitly a DS
with an end-ds.
Declaring files
You do not have to tell the compiler that files are externally described. However, RPG does
permit you to specify *EXT as a parameter to the device type keyword. For example,
DISK(*EXT).
These new defaults mean that you can omit the device type and the compiler assumes that
you are referring to an input DISK file. Similarly, you do not have to specify that a PRINTER
file is used for output because that is the default.
The default file usage (input, output, or both) is based on the device type. You specify it only if
you want a usage other than the default value.
Therefore, if you are using an externally described and non-keyed database file for input only,
the following declarations have the same effect:
Dcl-F Orders;
Dcl-F Orders Disk;
Dcl-F Orders Disk(*Ext) Usage(*Input);
16 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Table 2-1 shows the defaults for the different file types.
PRINTER Output
WORKSTN Input/Output
SPECIAL Input
SEQ Input
Sensible defaults are also used in cases where the default usage is not sufficient for your
task. The compiler takes an incremental approach. For example, to perform updates, the file
must be defined as input capable. However, you do not have to specify that definition. The
compiler adds *INPUT capability whenever *UPDATE is specified.
*Input None
*Output None
Previously, if you specified a “U” to enable a file for update, it was also enabled for delete. As
you can see from Table 2-2, this situation is no longer the case in free-form. Specifying
*DELETE implies *UPDATE, but the reverse is not true.
Similarly, in order for records to be added to a file, *OUTPUT must now be specified.
Previously, file addition was specified by coding an “A” in a specific column, which resulted in
a situation where what appeared to be an input-only file could have new records written to it.
This situation confused a number of RPG programmers who failed to notice the “A” in the
F-spec.
Even if you ignore PRINTER files, you might still need to program describe a file, for example
when writing a generic file processing program.
To define a file as program-described requires that you add a length parameter to the device
keyword, for example, PRINTER(132). Similarly, if you must program-describe a keyed disk file,
add a key type and length parameter to the KEYED keyword: For example:
KEYED( *Char : 5 )
DCL-SUBF Define a DS subfield. This declaration type is only required if field name
matches an RPG op-code name.
Data structures must be terminated by an END-DS directive because they are no longer
terminated by the beginning of another definition.
Table 2-4 lists the fixed-form data types with their corresponding free-form keywords.
N IND Indicator
I INT Integer
18 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Fixed form Free-form Data type
* POINTER Pointer
C UCS2 UCS-2
T TIME Time
Z TIMESTAMP Timestamp
When it comes to defining data structures, you specify only the DCL at the start of the DS.
There is a DCL for subfields as well (DCL-SUBF) but, like EVAL and CALLP, that opcode is
optional in most cases. The only time that you must specify it is if the name of the field you are
defining is the same as an opcode, such as SELECT or READ.
Example 2-4 demonstrates that the new approach allows overlay fields to be indented to
show visually the structure.
Regarding overlays, one difference in the D spec rules is that the OVERLAY keyword can no
longer reference the DS name. Instead, use the new POS keyword to specify the exact
position at which the subfield starts.
This free-form version shows how the changes are needed. Instead of coding
Overlay(daysOfWeek), use Pos(1) to position the overlaying array.
In the original version, the field names were left blank. In the new version, you must use the
marker “*n” to indicate to the compiler that the field is unnamed because it can no longer
determine that from the empty column as shown in Example 2-6.
This new POS keyword also provides a much “cleaner” approach when defining fields that are
in fixed column positions, such as components of the PSDS or numbered indicators in an
INDDS. You can see this approach in the following example, which demonstrates how to
associate names with the conventional *INnn indicators. As you can see, adding the indicator
number to the end of the name makes it easier to associate the numbers that are used in the
DDS with the names that are used in the program.
20 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// Conditioning indicators
D Error_31 N Overlay(DspInd: 31)
D StDtErr_32 N Overlay(DspInd: 32)
D EndDtErr_33 N Overlay(DspInd: 33)
D pIndicators S * Inz(%Addr(*In))
The new version is a lot “cleaner” because there is no need to repeatedly specify the DS
name. As a result, the association to the actual indicator number is clear.
Previously, to ensure that all fields of a specific type (currency for instance) used a common
definition, you could define a basic item definition by using the TEMPLATE keyword, and then
using the LIKE keyword. This method was useful but you could not change the data type of
the cloned field. If your base definition was packed, then all of its clones were also going to be
packed.
The relaxed constant support offers an alternative approach because one place where you
can now use them is in the length definitions for fields as shown in Example 2-9.
Notice that rather than clone the field definition with the LIKE keyword, you can now specify
the data type that you want while still using a common set of length definitions. Should you
need to change the size of these types of field, you can do so simply by changing for example
DIGITS from 7 to 9.
Concerning the LIKE keyword, there is also one small aspect of moving that to a free-form
declaration that might not be immediately obvious. For example, consider the following
definition:
D reportTotal S +2 Like(customerBalance)
To perform similar adjustment to the length of the cloned field in declarations you must code a
second parameter, as in the following:
dcl-S reportTotal2 Like(customerBalance: +2);
Potential pitfalls
This new flexibility in the use of literals does have one potential pitfall. Some keywords that
previously accepted literals without the use of quotation marks (such as DTAARA,
EXTNAME, and EXTFLD) now need quotes.
However, it is not quite that simple. Quotation marks must be added around the literals:
Dcl-Ds myNewExtDs ExtName('PRODUCT') End-ds;
Dcl-S myDataArea Char(20) DtaAra('JONSDTAARA');
Another potential pitfall that you might encounter involves the use of ellipses in data
definitions. If you use long descriptive field names in your code, you might have encountered
situations where you have needed to use these, as in the following example:
D masterAccountDetail...
D DS LikeDS(accountDetail_T)
22 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
When converting this code manually, the most obvious approach would be to replace the
initial D with Dcl-Ds, remove the D and DS from the second line and you would be done. The
resulting code would look like the following:
Dcl-Ds masterAccountDetail...
LikeDS(accountDetail_T);
However, this code will not compile. The reason is quite simple. The ellipses tell RPG to
continue the specification at the first non-blank character of the next line. Therefore, the
compiler sees the LikeDS simply as a continuation of the name and a number of errors will
result. Ellipses can still be used in free-form, but only to continue the name, not the
specification as a whole. In other words, you will only ever need them if you are writing an
essay rather than a field or procedure name.
The different declaration types, together with their associated END-xx definitions, are listed in
Table 2-5.
DCL-PROC Begins a procedure definition (that is it replaces the old B(egin) P-spec).
END-PR Ends the prototype definition. The prototype name can optionally be specified.
END-PI Ends the procedure interface. The procedure name can optionally be specified.
END-PROC Ends the Procedure definition (that is it replaces the old E(nd) P-spec).
Procedure name can optionally be specified.
In some respects, being able to code subprocedures completely in free-form is one of the
nicest parts of the new support. Having to drop out of free-form into fixed form P and D specs
to start a subprocedure, and then going back to fixed form to end it, was annoying.
Based on the number of queries that have popped up on Internet lists as to how to code
subprocedures in free-form, many people have trouble in this area. However, the “rules” are
basically the same as for all the free-form declarations.
Prototypes
The basic building blocks for prototypes are DCL-PR, END-PR, and DCL-PARM. As with
DCL-SUBF in data structures, the DCL-PARM keyword is only needed when someone has
given a parameter the same name as an RPG op-code.
Program prototypes
Example 2-11 shows a fixed form version of a program prototype for QCMDEXC.
The only real difference between the two examples, apart from the free-form, is the addition of
the END-PR directive.
Note that the DCL-PR could also have been coded like this:
dcl-pr qcmdexc ExtPgm;
As you can see, if you code the prototype name to match that of the actual program object,
then only the EXTPGM keyword is needed. The program name does not have to be specified.
The compiler simply converts the prototype name to uppercase and uses that. This is a good
idea as there have been situations where programmers have coded the literal for the program
name on the EXTPGM keyword in lowercase. IBM has also enabled this particular feature in
fixed form.
As with data structures, an end directive is always required. However, the END-PR can be
coded on the same line as the DCL-PR in cases where there are no parameters. For
example, if the program MYPROGRAM takes no parameters, then the prototype could be
coded like this:
dcl-pr MyProgram EXTPGM end-pr;
Note: In this case, there cannot be a semi-colon (;) after the EXTPGM keyword, it can only
be at the end of the line following the END-PR.
24 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Procedure prototypes
Procedure prototypes follow the same basic pattern as those for programs.
One new feature is the introduction of the new keyword *DCLCASE. Those who make a lot of
use of functions and APIs with mixed-case names, such as Qp0lRenameUnlink and
Qp0lRenameKeep, will appreciate this feature. Previously, when prototyping such a procedure,
you had to specify the name as a literal to the EXTPROC keyword because without it the
compiler would have changed the prototype name to uppercase. Using it like that does not
work. Now, specify EXTPROC(*DCLCASE) to tell the compiler to use the procedure name
exactly as you declared it on the DCL-PR.
This example demonstrates how you had to code the EXTPROC keyword previously:
dcl-pr MixedCaseProcName ExtProc('MixedCaseProcName')
end-pr;
This example shows how *DCLCASE can be used to avoid retyping the procedure name as it
was in the original version:
dcl-pr MixedCaseProcName ExtProc(*DclCase) end-pr;
The new procedure interface definition DCL-PI follows the same pattern as that of the
prototype. It uses DCL-PI to begin the definition and END-PI to terminate it. DCL-PARM, as
with the prototypes, is used to define any parameters within the procedure interface whose
names clash with RPG op-codes.
In fixed form, the procedure name could be omitted from the procedure interface definition. In
free-form, the place marker *N must be used if the name of the procedure is to be omitted on
the DCL-PI directive. This requirement is illustrated in Example 2-13.
The example code includes the definition of the data structure that is returned by the
subprocedure. The routine accepts a single date in *MDY format. Specifying the CONST
keyword ensures that any format of date is accepted and formatted for processing. It returns a
data structure that contains the day number, day name, and a fully formatted date string.
In the fixed form version, /Free and /End-Free had to be used to switch between fixed and
free-format. In version 7, these directives could be removed, but the need to switch back and
forward remains.
/end-free
// DateInfo Subprocedure
p DateInfo B Export
d PI LikeDS(dateInfo_T)
d inputDate D DatFmt(*MDY) Const
p DateInfo E
// Free-Form version
// DateInfo Subprocedure
Apart from the obvious advantage of being free-form, there is one other major benefit from
this latest support. File and data declarations can now be intermixed. This change allows you
to define a file together with any related flags, data structures, constants, and so on. In fact,
this technique is even allowed if you stick with the old fixed-format F and D specs.
26 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
With the new support, that is now possible. Example 2-14 shows a simple example based on
the idea that you might want to have a single source, but be able to use it to generate both a
UTF-16 (double byte) version of the program and a conventional single-byte version. Using
the new support, the related variables can be defined as shown in Example 2-14.
When the program is compiled, condition UTF16 is defined, the accountName field is defined
as usc2(40), but if that condition was not defined, the field is defined as char(40).
Note: Currently, the definition terminating semi-colon has to be placed outside of the
conditioned code as shown in this example. In some ways, it would be preferable to be able
to add it to both definitions (that is following the ccsid(1200) and char(40) entries) in the
knowledge that only one will ever be used. Perhaps the compiler will be updated to allow
that in the future.
The rules
These are the basic rules for using the new support:
All source files (including /Copy and /Include files) are assumed to be in the IBM i
version 7.2 style.
To use the full width free-form support, the first line in the source file must begin with
**Free in column 1.
A **Free source file cannot contain any fixed form code. If this is a requirement (for
example, to include O-specs), then it must be done by using a /Copy.
Support for this feature has also been added to the SQL pre-compiler.
Subprocedures can be coded internally in a module or they can be external (in a service
program or another bound module).
Although multiple subprocedures are coded within a module, the approach to writing a
subprocedure should be that the subprocedure is stand-alone. The design of a subprocedure
should use local variables instead of global variables, and all required data that is external to
the subprocedure should passed as parameters or a return value. Think of every
subprocedure as a stand-alone program.
This approach to writing subprocedures means that when you determine that a subprocedure
might be useful in other places, it is a simple process to remove it from its current module and
place it in a service program.
Also, when defining global variables, make sure that you are doing so for a good reason.
2.4 Naming
Before looking at the components of naming conventions, remember that although RPG is a
mixed-case language, it is not a case-sensitive language. In an RPG program, the variable
names customerID, CustomerID, customerid, and customerId all refer to the same variable,
whereas in a PHP or Java program, they are four different variables. However, try to use the
same mixed-case form everywhere that you use the name.
28 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Underscore
Named constants
Naming conventions
When naming variables, arrays, and data structures, think of the name as a noun that simply
states what the item is, for example currentAccountNumber, customerID, and customerList.
When naming subroutines, subprocedures, or prototype names, think of the name as a verb
combined with a noun. There is an action and an item, for example calculate_Pay(),
get_customerData(), and convert_toCelsius();.
Use consistent abbreviations in your names. For example, if you have several procedures that
perform the “convert” action, be consistent in how you name the action (“convert”, “cvt”, or
“conv”, and so on.)
2.4.2 Case
Names (except for named constants) should be mixed case. The usual standard is to use
camel case. Camel case means that a name is made up of compound words where each
word begins with a capital letter. The first word can start with a capital letter or with a
lowercase letter, but all following words start with a capital letter. Some examples are
CurrentAccountNumber and currentAccountNumber.
2.4.4 Underscore
The underscore character can be used to add clarity to a name. There is an inclination to use
underscore to separate compound words in a name, but this is superfluous when camel case
is used. The name currentAccountNumber is as legible as current_Account_Number.
An underscore can be useful when used with subroutine, subprocedure, or prototype names.
The underscore is used to separate the action from the item, such as calculate_Pay() or
get_customerData().
A programmer must check whether salary was being set by a call to the subprocedure
calculatePay() or from element 97 of the array calculatePay. The use of underscore in
subprocedure names adds clarity to the code:
salary = calculate_Pay(97);
Underscore might also be useful when used with named constants and naming conventions.
The convention in most programming languages is that named constants are all uppercase.
Underscore should be used to separate compound words within the name.
If literals are standard throughout an application (for example, status codes), the named
constants should be defined in a copy member and included in programs as required.
For example, a copy member includes the following named constants that are used to identify
message IDs. All of the named constants begin with MSGID_.
dcl-C MSGID_CODE 'ERR0001';
dcl-C MSGID_DESCRIPTION 'ERR0002';
Use a standard prefix or suffix to distinguish template variables and files. For example, use a
prefix such as type_, or typ_, or t_, or use a suffix such as _T, or _typ, or _type.
For the rare occasion where global variables are used in subprocedures, the names of the
global variables begin with the characters g_.
When naming subprocedures and prototyped program calls, it is imperative that the naming
convention is consistent. For example, subprocedures that add information to a database
should start with add_ or write_, not a mixture of the two.
Naming conventions are definitely required for names that are defined through copy members
or globally defined in a program/module. But the use of local variables (in subprocedures) and
qualified data structures minimizes the requirement for naming conventions within
subprocedures.
30 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
2.5 Comments
All programs must be documented. One of the major benefits of free-form RPG and correct
naming conventions is that it reduces the need for detailed documentation because the code
is self-explanatory. Even so, programs must be documented.
2.5.4 Positions 1 - 5
Historically, positions 1 - 5 were used to indicate or flag lines that were changed for a certain
modification. This practice should be avoided. Specifying **FREE on the first line of code
means that positions 1 - 5 on all subsequent lines can now be used for code.
Declarations should be grouped so that related items are defined together. The procedure
interface should be first before any other declarations.
Align definitions so they are easy to read. For example, when defining stand-alone variables,
parameters or data structure subfields, align the data type on each line.
32 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Compare the definition of this data structure with alignment, as shown in the code and then
without alignment in Example 2-15.
If a statement takes more than one line of code, use alignment to make the code more legible,
as shown in the code in Example 2-17.
Use SELECT/WHEN if the choice statements all compare a particular variable to a set of
values. Use IF/ELSEIF if the choice statements have varied types of conditions.
34 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
2.6.4 Embedded SQL
Use indentation and alignment to make SQL statement legible. An SQL formatter is available
in Run SQL Scripts in IBM i Access Clients Solutions, which is included in Rational Developer
for i. Example 2-18 shows an example of well formatted SQL.
A qualified data structure means that all references to the data structure subfields must be
qualified with the data structure name (for example, mailAddress.city and
mailAddress.state). Following this guideline allows subfields with the same name to be
defined in multiple data structures without the possibility of conflict. In addition, the
association between a subfield and its data structure is clear.
Defining a qualified data structure as a template means that the data structure cannot be
used as a data structure. However, it can be used as the template for other data structures
that are defined by using the LIKEDS keyword. In Example 2-19, a reference to the subfield
baseAddress.city is invalid, but a reference to the subfield mailAddress.city is valid.
Templates and qualified data structures provide an excellent means of gathering together
related “work” variables in a program or providing parameters for a call interface. The
definition of the template data structure can be placed in the same copy member as the
prototype definition for a called program or subprocedure.
You can use the LIKEREC keyword to define qualified data structures based on input/output
records for an external described file.
Unfortunately, the %ERROR() built-in function does not allow for a file name parameter.
Always check the %ERROR() function (or the %STATUS() function) directly after an operation
with an error (E) extender.
2.9 Strings
When it comes to string handling, it is better to use varying length (VARCHAR) as opposed to
character (CHAR) fields. The use of varying length fields reduces the requirement for string
functions (such as %TRIM) and makes the code more legible. Strings are used oftern with
RPG code that interacts with web applications.
2.10 Subroutines
Subroutines should not be used for modularization/structure. Use subprocedures instead. But
subroutines can be useful for organizing the logic in a subprocedure.
Define your own indicators when a Boolean condition is required. For example, use if
monthEnd; instead of if *in70;.
If the program externally described display files or print files use numeric conditioning or
resulting indicators, then define an Indicator Data Structure for the display/print file that
remaps the numeric indicators (in the display/print file) to indicators with meaningful names in
the program.
36 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Example 2-20 shows example code to accomplish this task.
It is better to define an array and its corresponding data in a data structure, with the definition
of the data structure overlaying the definition of the data, as shown in Example 2-22.
Example 2-22 Example of defining an array and its data in a data structure
dcl-Ds allMonths;
*N char(9) inz('January');
*N char(9) inz('February');
*N char(9) inz('March');
*N char(9) inz('April');
The definition of an array in the same location as the data makes it easier to modularize code
into subprocedures, when required.
38 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Avoid naming variables that start with SQL. They might conflict with variable names that
are automatically included by the SQL pre-compiler.
Use SET OPTIONS to ensure that the SQL environment is specified correctly at compile
time.
Wherever possible, use multi-row fetch instead of single-row fetch.
If global variables are being defined, they should be qualified (either in a data structure or with
a prefix such as gv_) so they are easily identified as global variables within a subprocedure.
The use of the IMPORT and EXPORT keywords for variables should be used only as a last
resort and be documented when used.
Prototypes are required only for external subprocedure or program calls. They are not
required for subprocedures that are coded and called only within the same module/program.
2.14.1 Parameters
If the value of a parameter is not changed by a subprocedure/program, use the VALUE or
CONST keywords to indicate that such is the case. These stop parameters from being
inadvertently changed by a subprocedure or program.
The argument for always returning a value is that there is always a value to return. For
example, even if a procedure does not calculate and return a specific value, should it return a
value to indicate whether the process worked? As a preferred practice, differentiate between
procedures (that do not return a value) and functions (that do return a value).
The copy members that contain the definition of the prototypes should also contain the
definition of any templates or named constants that refer specifically to the use of the
corresponding prototypes.
Managing the maintenance and usage of the prototype members can be quite a challenge
and (at the moment) there is no single simple solution. Every solution comes with a cost.
The prototype members must be easy to maintain. You do not want a situation where two
programmers need to change prototype definitions in the same member at the same time.
This requirement means that there will usually be a one to one correspondence between
prototype members and modules, that is, a prototype member that contains all prototypes,
templates, and named constants for subprocedures in a corresponding module. The same
one to one correspondence would apply to prototypes for program calls although a “program”
prototype member might contain prototypes for a group of related programs.
The ease of maintenance requirement means that there will be quite a few prototype
members. There is now the challenge of how the programmer knows which members to
include in a program when a call is to be made to a program or an external subprocedure.
This is even more challenging if the prototype members are in a source physical file with a ten
character naming restriction, where there is little chance of having meaningful names. An
alternative is to define prototype members in directories in the integrated file system (IFS),
where meaningful names can be given to the prototype members.
Compare the following directive to include a prototype member from a source file (/include
qrpglesrc,UTILITY01P) with the corresponding directive for a file in the IFS (/include
'/myApp/proto_utility/userSpaceAPIs.rpgle').
The requirement for /INCLUDE directives can be reduced by using nested copies. It is
possible to have a single /INCLUDE directive that would include all prototype members in the
application. Although this approach is appealing (the programmer only needs to know the
name of one copy member), there are a number of considerations:
Someone must be responsible for maintaining the extra copy members that handle the
nesting and grouping of the prototype members.
All prototypes, templates, and named constants (defined in prototype members) will now
be included in the Outline View in Rational Developer for i. If there are many prototypes,
templates, and named constants, the Outline view takes a long time to refresh. This
limitation can be alleviated by using conditional compilation directives (as you will see in a
following example) but that, in turn, leads to even more complicated maintenance of the
extra copy members that handle the nesting and grouping of the prototype members.
Change management systems might have difficulty when a change is made to prototype
member (even as simple a change as adding a comment). The change management
system can determine that, because the member is included in every program and the
member has changed, then every program should be recreated.
The following example shows one approach to using nested copies to minimize the
requirement for multiple /INCLUDE directives:
/include common,baseInfo
40 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The BASEINFO member contains nested include directives, as shown in Example 2-23.
Each of the included members might contain prototype definitions or further nested include
directives. For example, the PUTILITY member contains nested include directives as shown
in Example 2-24.
One of the difficulties with this approach is that many prototypes (and templates) are being
included. Although the number of items has no effect on the size of the program/module when
it is compiled (prototypes and templates are declarative), it can take a long time for the outline
view in Rational Developer for i to refresh.
This situation can be solved by using compiler directives to indicate what should and should
not be included. Changing the definition of the PUTILITY member as follows means that a
definition name must be set in order for the prototypes to be included as shown in
Example 2-25.
The originating program, which includes the BASEINFO member, can set the required
definition names or can define UTILITY, which results in all members being included as
shown in Example 2-26.
The difficulty with this approach is that the programmer must know the required definition
names, so documentation is required. The benefit of this approach is that it documents what
is being included in the program.
In this example, definition names are being used to include single members, but they could as
easily be used to include multiple members (as with CGIPGM in the BASEINFO member).
As stated earlier, there is no simple solution to managing the maintenance and usage of the
prototype members. The challenge is to find which combination of techniques provides the
best balance between ease of maintenance and ease of use.
Unfortunately, the answer to all of these questions is: It depends. The structure of an ILE
application depends on the application and what it does.
42 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
A change management system is something that can have a major impact on the
methodology that you use when developing an ILE application because the change
management system might have a preferred technique for managing service programs,
binder language, and so on.
Regardless of the final development environment, the following sections describe guidelines
for an ILE environment.
This task can be achieved through the diligent use of binding directories and a standard
control specification, which is included in every program through an /INCLUDE directive as
shown in Example 2-27.
Because the content of a service program can be called from multiple places, the
management of a service program requires a bit more care than “normal” programs. The
approach to managing changes to a service program should be along the same lines as the
way changes to a database are managed. As with a database, very few users should be able
to make changes. Any programmer can develop a subprocedure, but not every programmer
can incorporate it in a service program.
Binding directories should be kept to a minimum. A single binding directory should list all
service programs (and modules) that might be required when a standard program is created.
Depending on the complexity and cross referencing between service programs, each service
program might also require a binding directory. If there are service programs and modules
that are common to all of multiple ILE applications, then another binding directory can be
used to list those objects. A list of binding directories can be included in the BNDDIR keyword
on a Control Spec.
There are cases where an application has a binding directory per program. The binding
directory lists the modules and service programs that are required to define the program. This
is a mistake and imposes a maintenance impact that is not required.
Managing service programs with many modules must be done by having a specific binding
directory for the service program, by using something like CL to create the service program,
or by using a generic name for all modules in the service program.
ILE programs should never be run in the default activation group. This situation can be
achieved only if an ILE program is created with an activation group of *CALLER and the
program is called by an OPM program (or from a command line). It is preferable to use the
name of the activation group when creating programs, which is easily achieved by using the
ACTGRP keyword in a standard Control Spec in the programs.
Even worse than having an ILE program in the default activation group is having a service
program in the default activation group. This situation can happen only if an ILE program is
running in the default activation group. Again, ILE programs should never be run in the default
activation group.
Also, avoid using the QILE activation group. This is the activation group that is used when
care has not been taken to choose the activation group. If care was not taken to choose the
activation group, care might not have been taken in other aspects of the application. Your
application might be subject to the activation group ending unexpectedly, or to overrides and
commitment control actions that you do not want.
44 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
3
Chapter 3. Subprocedures
One of the major enhancements of RPG IV in V3R2 and V3R6 was the ability to define
multiple procedures per module. This chapter discusses how to use those procedures
efficiently and also gives a working example on moving from subroutines to subprocedures
easily.
You can think of RPG IV modules as falling into one of three basic categories:
Those containing only a main procedure and no subprocedures
Those containing a main procedure and one or more subprocedures
Those with no main procedure but with one or more subprocedures
However, subprocedures must always be called by a bound call. It is for this reason that
subprocedures can only be used in programs that are created by the Create Program
(CRTPGM) or by the Create Bound RPG Program (CRTBNDRPG) commands.
Subprocedures differ from main procedures in several respects. The main difference is that
subprocedures do not (and cannot) use the RPG cycle while running, even if they are part of
a module where the main source section is using the RPG cycle.
For a full description of other differences, see the Programming IBM Rational Development
Studio for i, ILE RPG Reference manual at:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzasc/sc092507.pdf
3.1.4 Subroutines
Subroutines are different from subprocedures, but in a conceptual way they are often
interchanged because they provide the same basic function in basic structured programming
methodologies. Both provide a way to wrap re-usable code within a container.
46 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
A subroutine is part of the main procedure or any subprocedure (like the *PSSR subroutine)
and can be invoked multiple times from different locations (only in the same procedure).
Subroutines share global variables or local variables within the same procedure. The
subprocedure offers the same base functionality as subroutines, plus many more advantages.
For more information, see 3.2, “Advantages of using subprocedures” on page 47.
You can call the subprocedure from outside the module, if it is exported.
Subprocedures provide multiple entry points within the same RPG module.
Multiple exported subprocedures can be placed into the same module and each
subprocedure can be called from outside or within the same module. This support is
provided with the ILE service program functionality.
Chapter 3. Subprocedures 47
Subprocedures support recursion.
Variables in a subprocedure are by default automatic. A new version of the variable is
created each time that the subprocedure is invoked. Because of this, RPG IV
subprocedures are allowed to recurse, meaning to call themselves directly or indirectly.
This can be useful in certain types of design, for example, when building an inventory
system that contains multiple levels of parts nested to each other (parts explosions).
Note: There might be other specs between the control spec and the beginning of the
subprocedure, such as file specs to define files to be used by the subprocedures.
The following are the basic elements that are used when coding subprocedures in your
programs:
Prototype (see “Prototypes” on page 49 for more information)
Procedure Begin and End specs
Procedure Interface (PI) definition
Definition of local variables (optional)
Calculation section, which incorporates the optional return value
These elements are highlighted in the simple subprocedure that is shown in Example 3-1,
which receives two integers passed as parameters and returns the sum of the two.
end-proc; (6)
48 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The markers (1) through (6) on the left side of Example 3-1 are defined as follows:
1. The procedure spec supplies the name of the subprocedure. Its presence also signals to
the compiler not to flag the subsequent data and calc specs as out of sequence.
2. The Procedure Interface definition identifies the data type (packed decimal) and size
(10 digits with no decimal places) of the return value. It also marks the beginning of the
parameter list. In this sense, it performs a similar function to the *ENTRY PLIST operation.
This example has two parameters: AnInputParm1 and AnInputParm2. The end of the list is
signaled by the appearance of the end-pi;.
3. Definitions of variables, constants, files, and prototypes needed by the subprocedure.
These definitions are local definitions.
4. Any calculation specs that are needed to perform the task of the procedure. The
calculations can refer to both local and global definitions, although the use of global data is
discouraged. If any subroutines were included within the subprocedure, they are local and
cannot be used outside of the subprocedure.
5. If the subprocedure returns a value, supplying the actual value is the task of the RETURN
operation.
Note that RETURN can either use a simple variable, as in this example, or can return an
expression. If you use this option, this example could be simplified by coding the following
line:
Return AnInputParm1 + AnInputParm2;
6. The End procedure spec (end-proc). Note that the name of the subprocedure can be
repeated here, but is not required.
Prototypes
To call a subprocedure, use a prototyped call. You can call any program or procedure that is
written in any language in this way. A prototyped call is one where the call interface (the
number, type, and size of the parameters) is checked at compile time by reference to the
prototype.
The prototype for the sample subprocedure would look something like Example 3-2.
Note: The field names for the two parameters are identical. Normally this is not permitted
in RPG IV, but in this case, it is acceptable because the compiler is going to ignore the
name anyway. The only part that matters to the compiler is the number of parameters and
their data type and size. However, the best practice is not to have identical parameters.
Instead, give a meaningful name to each parameter to document the meaning of the
parameter to other programmers.
Chapter 3. Subprocedures 49
Whether operational descriptors should be passed
The data type of the return value (optional, for subprocedures only)
A prototype must be included in the definition specs of any external program or procedure
that makes the call. The prototype is used by the compiler to call the program or procedure
correctly, and to ensure that the caller passes the correct parameters. For this verification to
occur, the prototype also needs to be included when compiling the module in which the
subprocedure is located, and when compiling any module that wants to use the
subprocedure.
No prototype is required if the subprocedure is used only within the source member it is
defined in. However, However, many consider it a best practice to prototype it anyway. That
way, if you decide to export it later, the prototype already exists.
A procedure interface can also be used for the main procedure, in place of the *ENTRY
PLIST. This interface can be coded anywhere in the definition specs after the prototype
statement. The procedure interface spec should be placed after the prototype definitions and
any global variables that are going to be used in the program. Typically, the procedure
interface should be put as early in the code as possible, ideally right after the H specs and the
/copy file that has the prototype for the main procedure.
50 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Any files and global variables that are required in the main procedure or in the subprocedures
must be defined in the main section of the code.
Figure 3-1 shows the layout of a complete RPG source that contains multiple subprocedures.
*MODULE
Main Procedure
1 H specifications Specify NOMAIN if no Calcs
1 F specfications Global
Scope
2 D specfications PR Prototypes
Main Program Interface def (can
2 D specfications PI
replace *Entry PLIST)
3 D specfications Data items visible throughout module
I specfications
C specfications
O specfications
4 Subprocedure 1
P specifications B Start of procedure 1
D specifications PI Interface definition
Data items visible only to Local
D specfications Scope
Subprocedure 1
C specfications Can access local and global data items
5 Subprocedure n
P specifications B Start of procedure n
D specifications PI Interface definition
Data items visible only to Local
D specfications Scope
Subprocedure n
C specfications Can access local and global data items
ILEmod.prz
Note: The file specs can now be placed at the beginning of the member, just after the
control spec, or inside the procedure. File specs after the control spec can be used
either by the mainline code or by the subprocedures. If the file spec is coded inside a
procedure, it is available only within that procedure.
Chapter 3. Subprocedures 51
2 The prototypes always go before the first procedure in the module. Any global variables
also go before the first procedure.
3 The procedure statement and interface definition for the main procedure should be at the
top of the module. The main program procedure is usually noted by an EXTPROC or
EXTPGM keyword on the prototype. This keyword is used by the compiler and binder to
determine how to handle the module and how other programs can call this one. This
configuration is discussed in more detail in Chapter 4, “An ILE guide for the RPG
programmer” on page 77.
Tip: You should not be coding I specs anymore. There is no free form code for input
specs.
4 After the calculation specs, the procedure is ended with an end procedure statement. The
name of the procedure is optional on this line. However, it is considered good
programming style to include it for subprocedures.
5 Any other subprocedures would follow, each with its own set of procedure, data, and calc
specs.
The procedure-interface definition can be placed anywhere within the definition specs.
However. it is considered a good programming practice that you code it immediately following
the procedure spec. The use of a RETURN opcode is recommended, but does not need to be
coded unless a value is to be returned. The subprocedure automatically returns when it
reaches the end procedure spec.
In RPG, prototyped calls are also known as free-form calls. A free-form call refers to the fact
that the arguments for the call are specified by using free-form syntax, much like the
arguments for built-in functions.
How you call the procedure depends on whether it has a return value or not:
If there is no return value, the code is on its own line in the member. There is no need to
use the CALLP operation.
If there is a return value, the prototyped procedure can be in an expression. It can go
anywhere an RPG built-in function (BiF) can go. If you do not want to use the returned
value, you can code it on a line by itself and the return value is ignored.
Using either type of procedure call, you can call these types of procedures:
A procedure in a separate module within the same ILE program or service program.
A procedure in a separate ILE service program.
A procedure in another program by using a procedure pointer as described in 3.6.3,
“Using procedure pointer calls” on page 72.
52 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The following are some examples of using the subprocedure AFunction that was introduced
in 3.3.1, “Subprocedure definition” on page 48“. Notice that the value returned can be used in
ways other than simply assigning it to a variable. As shown in the second example, it can also
be used directly in an expression.
// Return a result
theResult = AFunction(aParm1 : aParm2);
If the subprocedure does not return a value or you want to ignore the return value, you can
use the following syntax to call it:
// Use with no result returned
AFunction(aParm1 : aParm2);
// Days of the week name table - note field names are required
// You could use *N to avoid giving specific names.
Dcl-ds NameData;
Chapter 3. Subprocedures 53
d1 char(9) Inz('Monday');
d2 char(9) Inz('Tuesday');
d3 char(9) Inz('Wednesday');
d4 char(9) Inz('Thursday');
d5 char(9) Inz('Friday');
d6 char(9) Inz('Saturday');
d7 char(9) Inz('Sunday');
// Program parameters
dcl-pi datesubr;
workdate date(*iso);
end-Pi;
// Terminate Program
*InLR = *On;
// Testing for < 1 allows for the situation where the input date
// is earlier than the base date (AnySunday)
If WorkDay < 1;
WorkDay += 7;
Endif;
Endsr;
Try it yourself: You can try the example shown in Example 3-3 on page 53 by compiling
the code from this section on your IBM i system. Use the following command to create the
program:
CRTBNDRPG PGM(rpgiscool/datesubr) SRCFILE(rpgiscool/subprocsrc)
54 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
3.4.3 Transforming a subroutine to a subprocedure
There are four steps required to transform a subroutine into a subprocedure:
1. Remove the BEGSR/ENDSR instructions, and specify the return value.
2. Add the begin and end procedure specs and the procedure interface.
3. Define the prototype. Prototypes are not required if the procedure is in the same module
and the procedure will not be used outside the module.
4. Replace the EXSR instruction in the main procedure with the subprocedure invocation.
The following modifications can also be made to use the language more efficiently:
Use a procedure interface for the Main procedure’s parameters. For more information, see
3.5.2, “Prototype for the main procedure” on page 60.
Use other subprocedures in your subprocedure. For more information, see 3.5.3,
“Subprocedures using subprocedures” on page 61.
Place your subprocedures in a Service Program. For more information, see 3.5.4, “Using
an ILE service program” on page 63.
Note: In this section, the markers 1 - 5 next to the code snippets in the examples
correspond to the same numbers in the full program listing found in 3.4.4, “DATEMAIN1
subprocedure example” on page 57.
Example 3-4 Code specifying value returned to the caller as simple value
// Testing for < 1 allows for the situation where the input date
// is earlier than the base date (AnySunday)
If WorkDay < 1;
WorkDay += 7;
Endif;
return workday; 1
Chapter 3. Subprocedures 55
Step 2: Defining the interface
This section defines the interface that defines the entry parameters and the return value of
the subprocedure:
1. Add the declare procedure and end procedure statements.
Subprocedures begin with a declare procedure and end with an end procedure spec. The
declare procedure spec names the subprocedure and has a similar layout to the data
spec. Note that the end procedure spec does not require that the name of the procedure
be present as shown in Example 3-6.
Example 3-6 The end procedure does not require the name of the procedure
// SubProcedure: DayOfWeek (Day of the Week)
// The subprocedure accepts a valid date (format *ISO) and returns
// a number (1 digit) representing the day of the week
// (Monday = 1, ... , Sunday = 7)
2. Declare the Procedure Interface. Now, you need a Procedure Interface (PI). The first line
of the PI defines the data type and size of the return value. In this example, this is a single
digit numeric field with no decimal places.
Subsequent lines define the parameters passed to or from the subprocedure. This
example has only one parameter, the field WorkDate, which is an *ISO format date.
The PI is typically the first D spec in the subprocedure and effectively acts as the *ENTRY
PLIST. The end of the parameter list is indicated by an end procedure interface statement
as shown in Example 3-7.
For this example, the field definitions and constants, which are used only by the
subprocedure, were moved into the subprocedure itself. These local data items are now
accessible only within the subprocedure.
Returning values subprocedures: A subprocedure can return, at most, one value. If you
need to return more than one value, you can choose any of the following options:
Return a data structure.
Return a pointer to a data structure. However, it is very error prone to return a pointer to
a data structure. This is an advanced technique that can cause serious problems if it is
misused.
Modify the content of parameters passed to you. This is only possible if the parameter
was passed by reference. For more information, see “Passing by reference” on
page 71.
Use the ability of ILE to share data items between modules by using the IMPORT or
EXPORT keyword. For more information, see Chapter 4, “An ILE guide for the RPG
programmer” on page 77.
56 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Step 3: Defining the prototype
The format of the parameters in the prototype must match the PI. The parameters do not
need to be named. If they are named, the name does not need to match the one specified on
the PI. In fact, the compiler is going to completely ignore the name on the parameter. It is only
interested in its data type and size as shown in Example 3-8.
Most RPG programmers would probably start out by coding as shown in Example 3-9.
However, there is a better way. Remember that subprocedures can appear anywhere in an
expression where a variable of the same type can be used. Here is the code from
Example 3-9 with a cleaner programming style (Example 3-10).
The compiler generates the necessary call to the DayOfWeek procedure and uses the returned
value to supply the array subscript for the Name array.
Chapter 3. Subprocedures 57
end-Pr;
// Days of the week name table - note field names are required
Dcl-ds NameData;
d1 char(9) Inz('Monday');
d2 char(9) Inz('Tuesday');
d3 char(9) Inz('Wednesday');
d4 char(9) Inz('Thursday');
d5 char(9) Inz('Friday');
d6 char(9) Inz('Saturday');
d7 char(9) Inz('Sunday');
Dcl-pi DateMain1;
WorkDate date(*ISO);
end-Pi;
// Using subprocedure DayofWeek, initialize DayName with table Name
DayName = Name(DayOfWeek(WorkDate)); 5
// Terminate Program
*InLR = *On;
return;
// SubProcedure: DayOfWeek (Day of the Week)
// The subprocedure accepts a valid date (format *ISO) and returns
// a number (1 digit) representing the day of the week
// (Monday = 1, ... , Sunday = 7)
dcl-Proc DayOfWeek; 2
// Local variables
Dcl-s WorkNum zoned(7 : 0);
Dcl-s WorkDay zoned(1 : 0);
// Testing for < 1 allows for the situation where the input date
// is earlier than the base date (AnySunday)
58 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
If WorkDay < 1;
WorkDay += 7;
Endif;
return workday; 1
end-Proc DayOfWeek; 2
Try it yourself: You can try Example 3-11 on page 57 by compiling the code from this
section on your IBM i system. Use the following command to create the program:
CRTBNDRPG PGM(rpgiscool/datemain1) SRCFILE(rpgiscool/subprocsrc)
DFTACTGRP(*NO) ACTGRP(*NEW)
These techniques, among others, show the power of using subprocedures in your application
design.
Prototypes for groups of related functions should be placed in a single member, for example,
date routines, validation routines, and so on. You can also choose to group prototypes per
service program.
It is important to note that /COPY, does not mean that you should copy the source lines by
using Source Edit Utility (SEU) (or any other editor for that matter). One of the objectives of
using prototypes is to avoid making mistakes when calling programs and procedures. If the
prototype exists in each individual source member, it can be edited in each member with a
resulting loss of integrity.
Chapter 3. Subprocedures 59
The examples shown in the following sections list a prototype in their specific members and
include them in the main source section when required. The /COPY instruction can be use as
follows:
/COPY library/sourcefile,member
For more information, see the Programming IBM Rational Development Studio for i, ILE RPG
Programmer’s Guide at:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzasc/sc092507.pdf
For the example used in this chapter, the formal *ENTRY PLIST has already been replaced
with a procedure interface. Now use a prototype as well. As we are not going to be using the
RPG cycle in this program, use the main keyword to eliminate the cycle from the generated
code. The resulting prototypes can be used by other programs. Given the nature of this
sample program, this is not likely, but the principle sounds good.
Note: The markers A through H are used throughout the following sections to identify
matching source code examples up to, but not including, 3.6.3, “Using procedure pointer
calls” on page 72“.
For this example, the main procedure’s input parameters would be coded as shown in
Example 3-12.
Of course, you also have to include a prototype with the same information into your main
procedure. In this case, you would use a /COPY member because other programs might want
to use a prototyped call to invoke the main program. The name of the prototype does not
need to match the name of your module if you specify the EXTPGM keyword on the prototype
definition.
One advantage of using prototypes for this type of call is that the actual program or procedure
name can be “overridden” to a more meaningful name as shown in Example 3-13.
60 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Coding considerations: A main procedure is always exported, which means that other
procedures in the program can call the main procedure by using bound calls. The call
interface of a main procedure can be defined in one of two ways:
Using a prototype and procedure interface.
Using an *ENTRY PLIST without a prototype.
However, a prototyped call interface is much more robust because it provides parameter
checking at compile time. If you prototype the main procedure, you also dictate how it is to
be called. This is achieved by specifying either the EXTPROC or EXTPGM keyword on the
prototype definition:
If EXTPGM is specified, then an external program call is used.
If EXTPROC is specified, it is called by using a bound procedure call.
If neither keyword is specified, the compiler defaults to EXTPROC.
For more information about these keywords, see “External naming” on page 70. Note that
is it not necessary that the called program uses a procedure interface for the calling
program to use a prototype. These options can be mixed with more traditional *ENTRY
PLISTS and CALL/PARM operations.
Chapter 3. Subprocedures 61
Example 3-15 shows the prototype that is related to the NameOfDay subprocedure. As noted
previously, this prototype must be included in the calling procedure, and in the subprocedure
itself, unless the subprocedure is part of the same module as the caller.
From the main procedure, the new procedure is invoked by the instruction shown in
Example 3-16.
Example 3-17 Using the two subprocedures in the same EVAL statement
* Using the Day number returned by subprocedure DayOfWeek from the
* WorkDate, retrieve the Day Name from subprocedure NameOfDay
C Eval DayName = NameOfDay(DayOfWeek(WorkDate))
To use the return value of a subprocedure as the input value of another one, specify the input
parameter of the second procedure as passed by value on the same free-form expression
statement.
Example 3-18 shows what the NameOfDay subprocedure would look like using the VALUE
keyword on the parameter definition. Do not forget to change the prototype. More information
about passing parameters to subprocedures can be found in 3.6.2, “Parameter passing
styles” on page 71.
Example 3-18 NameOfDay subprocedure using the VALUE keyword on the parameter definition
// SubProcedure: NameOfDay (Name of the Day)
// The subprocedure accept a day number (monday = 1,..., Sunday = 7)
// and returns a string representing the name of the day
dcl-Proc NameOfDay Export;
// Days of the week name table - note field names are required
Dcl-ds NameData;
d1 char(9) Inz('Monday');
d2 char(9) Inz('Tuesday');
d3 char(9) Inz('Wednesday');
d4 char(9) Inz('Thursday');
62 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
d5 char(9) Inz('Friday');
d6 char(9) Inz('Saturday');
d7 char(9) Inz('Sunday');
The NOMAIN keyword is used on the control option spec because it allows you to take
advantage of the cycleless feature of RPG IV that improves performance (see marker H). In
addition, by coding NOMAIN, the compiler is stopped from complaining that it cannot
determine how the program ends.
NOMAIN: The NOMAIN keyword is not required, but causes slightly smaller modules.
NOMAIN tells the compiler not to include any RPG cycle logic in this module.
The keyword is only allowed if and when there are no calc specs in the source member
before the first subprocedure. In other words, NOMAIN can only be used if there is no main
procedure logic coded in this module.
If you specify NOMAIN, you cannot use the CRTBNDRPG command on the source
member because the CRTBNDRPG command requires that the module contain a program
entry procedure. Only a main procedure can be a program entry procedure.
Similarly, when using the CRTPGM command to create a program, keep in mind that a
NOMAIN module cannot be an entry module because it does not have a program entry
procedure.
A subprocedure can be exported, allowing it to be called from other modules. To indicate that
it is to be exported, specify the keyword EXPORT on the Procedure Begin spec. If EXPORT is
not specified, the subprocedure can only be called from other procedures within the module.
When the subprocedures are moved to a different module, they can no longer see the
prototype definition placed in the main procedure. You must include a copy of the prototype
definition in the subprocedures module. The fact that these prototypes are required in multiple
places is the reason that it is considered a best practice to use the /COPY directive to bring in
the prototype code. This technique ensures the correct prototype is always used.
Using the preceding example, Example 3-19 shows what the new service program module
would look like.
Chapter 3. Subprocedures 63
// Control option spec, specify no main procedure in this code H
ctl-opt Nomain;
// Include prototypes D
/Copy Radredbook/SUBPROCSRC,DATESUBPR2
end-Proc NameOfDay;
// Local variables
Dcl-s WorkNum zoned(7 : 0);
Dcl-s WorkDay zoned(1 : 0);
64 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// Testing for < 1 allows for the situation where the input date
// is earlier than the base date (AnySunday) A
If WorkDay < 1;
return WorkDay + 7;
else;
return workDay;
Endif;
end-Proc DayOfWeek;
In this example, the subprocedure DayOfWeek does not require the EXPORT keyword because
it is only used by the subprocedure NameOfDay, which is located in the same module. However,
we have chosen to export it so that it is available to all programmers. The main procedure
would now look like what is shown in Example 3-20.
ctl-opt main(myMainModule);
// Program start
dcl-proc myMainModule;
// Program input parameter F
Dcl-pi myMainModule;
WorkDate date(*ISO);
end-Pi;
// Terminate Program
return;
end-Proc;
Do not forget to code the prototypes. The code shown in Example 3-21 is for the main
procedure.
Chapter 3. Subprocedures 65
dcl-pr myMainModule extpgm('DATEMAIN2');
workdate date(*iso);
end-Pr;
Try it yourself: To re-create the example shown in this section on your system, you need
to compile the two modules by using the following commands:
For the service program (subprocedures module), use these commands:
CRTRPGMOD MODULE(rpgiscool/datesrvpg2) SRCFILE(rpgiscool/subprocsrc)
CRTSRVPGM SRVPGM(rpgiscool/datesrvpg2)MODULE(rpgiscool/datesrvpg2)
EXPORT(*ALL)
For the main procedure, use these commands:
CRTRPGMOD MODULE(rpgiscool/datemain2) SRCFILE(rpgiscool/subprocsrc)
CRTPGM PGM(rpgiscool/datemain2) MODULE(rpgiscool/datemain2)
BNDSRVPGM(rpgiscool/datesrvpg2)
Example 3-23 Passing an integer when the callee expects a value with decimal places
Dcl-pr OvrDBFile ExtPgm('QCMDEXC');
CmdString char(3000) Options(*Varsize)
66 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Const;
CmdLength packed(15 : 5) Const;
CmdOpt char(3) Options(*NoPass)
Const;
end-Pr;
*InLR = *On;
The use of the CONST keyword allows the compiler to accommodate a mismatch in the
definition of the parameters between the callee and the caller. For example, a mismatch might
happen when the callee expects a packed decimal value of five digits with no decimal places
and the caller wants to pass a three-digit signed numeric. Normally you would have to create
a temporary variable (packed - five digits), move the three-digit number to it, and then pass
the temporary field as the parameter. When you use the CONST keyword, you are specifying
that it is acceptable that the compiler make a copy of the data before sending it, if necessary,
to accommodate these mismatches.
The use of the option *NOPASS on the OPTIONS keyword means the parameter does not
have to be passed on the call. Any parameters following that spec must also have *NOPASS
specified. When the parameter is not passed to a program or procedure, the called program
or procedure simply functions as though the parameter list did not include that parameter.
When parameters are not mandatory, you can also use the option *OMIT, which indicates the
value *OMIT is allowed for that parameter when calling the subprocedure. *OMIT is only
allowed for CONST parameters and parameters that are passed by reference.
Other options on the OPTIONS keyword are *VARSIZE, *STRING, and *RIGHTADJ. For
more information about these keywords, see the Programming IBM Rational Development
Studio for i, ILE RPG Programmer’s Guide at:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzasc/sc092507.pdf
However, suppose that you wanted to use the DayOfWeek procedure from a program that
defines its date fields as having the *USA format. All that you need to do is to modify the date
subprocedures so that they include the CONST keyword on the parameter definitions in the
Chapter 3. Subprocedures 67
procedure interface and prototypes. After you recompile the subprocedures and rebuild the
service program, you can safely call by using the *USA date field.
You also need to modify the PI of those two subroutines to reflect the changes made in the
prototype as shown in Example 3-25.
Note: The full version of the code can be seen in 3.5.4, “Using an ILE service program” on
page 63.
// H
ctl-opt Nomain;
// Include prototypes D
/Copy SUBPROCSRC,DATESUBPR3
68 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Example 3-26 shows the main procedure modified to use a *USA date format.
ctl-opt main(datemain3);
// Program start
dcl-proc dateMain3;
// Progam input parameter F
Dcl-pi DateMain3;
WorkDate date(*USA);
end-Pi;
// Terminate Program
*InLR = *On;
end-Proc;
You also need to modify the prototype of the main procedure as shown in Example 3-27.
Chapter 3. Subprocedures 69
Try it yourself: To re-create this example on your system, you need to compile the two
modules by using the following commands:
For the service program (subprocedures module), use these commands:
CRTRPGMOD MODULE(rpgiscool/datesrvpg3) SRCFILE(rpgiscool/subprocsrc)
CRTSRVPGM SRVPGM(rpgiscool/datesrvpg3) MODULE(rpgiscool/datesrvpg3)
EXPORT(*ALL)
For the main procedure, use these commands:
CRTRPGMOD MODULE(rpgiscool/datemain3) SRCFILE(rpgiscool/subprocsrc)
CRTPGM PGM(rpgiscool/datemain3)MODULE(rpgiscool/datemain3)
BNDSRVPGM(rpgiscool/datesrvpg3)
External naming
Another feature of prototypes is the use of the EXTPGM and EXTPROC keywords. ILE
procedure names can be up to 4096 bytes long and can be of mixed case. Typically, mixed
case names are used only in procedures that are written in C. However, because you can use
prototypes to call C functions, it is important to understand the use of the EXTPROC keyword
to accommodate the mixed case names typical of C functions. To easily use mixed case
procedure names in RPG, use EXTPROC(*DCLCASE) on the prototype (or procedure
interface if there is no prototype). That way, the external name will be have the same case as
the name you use for the prototype.
If you use mixed case names for your own procedures, you will find it easier to understand the
names of your procedures if you encounter them in a job log or when you are looking at the
program stack of your job.
If the keyword EXTPGM or EXTPROC is specified on the prototype definition, calls use the
procedure name given, but the actual procedure or program called is the one specified in the
EXTPGM or EXTPROC keyword. If neither keyword is specified, then the external name is
the prototype name, which is the name specified on the Prototype definition and converted to
uppercase. See Example 3-28.
myProcName(Procparm1);
AProgName(Parm1);
70 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
3.6.2 Parameter passing styles
Program calls, including system API calls, require that parameters be passed by reference.
However, there is no such requirement for procedure calls. ILE RPG allows three methods for
passing and receiving prototyped parameters:
By reference (the default)
By value (keyword VALUE on the parameter definition)
By read-only reference (keyword CONST on the parameter definition)
Important: IBM i program calls can only pass parameters by reference and by read-only
reference, not by value. The keyword CONST should be used with every input parameter
on the IBM i API prototypes definition.
Passing by reference
The default parameter passing style for RPG IV is to pass by reference. When a parameter is
passed by reference, the compiler only passes a (hidden) pointer to the data value, rather
than passing the actual value. Therefore, you do not have to code any keywords on the
parameter definition to pass the parameter in this way. You must pass parameters by
reference to a procedure when you expect the callee to modify the field passed. You might
also want to pass by reference to improve runtime performance, for example, when passing
large character fields.
Note that parameters that are passed on external program calls can only be passed by
reference. It is not possible to pass a parameter by value to a *PGM object.
Chapter 3. Subprocedures 71
When a parameter is passed by value, the called program or procedure can change the value
of the parameter. However, the caller never sees the changed value.
One primary use for passing by value is that it allows for less stringent matching of the
attributes of the passed parameter. For example, if the definition calls for a numeric field of
type packed-decimal and a length of 5 with two decimal positions, you must still pass a
numeric value. It can be any of the following options:
A packed, zoned, or binary constant or variable, with any number of digits and number of
decimal positions
A built-in function returning a numeric value
A subprocedure returning a numeric value
A complex numeric expression, such as:
2 * (Min(Length(First) + Length(Last) + 1): %size(Name))
Passing a parameter by read-only reference has many of the same advantages as passing by
value. In particular, this method allows you to pass literals and expressions. However, it is
important that you know that the parameter is not changed during the call.
When a parameter is passed by read-only reference, the compiler might copy the parameter
to a temporary field and pass the address of the temporary field. This process happens
whenever the parameter passed is not a strict match to the prototype, such as when the
passed parameter is an expression or the passed parameter has a different format.
If the called program or procedure does not or cannot use a prototype, for example an
RPG/400 program, the compiler cannot ensure that the parameter will not be changed. For
this reason, exercise caution when defining prototypes using this parameter-passing
method.
Procedure pointer calls provide a way to call a procedure dynamically. For example, you can
pass a procedure pointer as a parameter to another procedure, which would then run the
procedure that is specified in the passed parameter. You can also manipulate arrays of
procedure names or addresses to dynamically route a procedure call to different procedures.
If the called procedure is in the same activation group, the speed of a procedure pointer call is
almost identical to that of a static procedure call.
72 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
To demonstrate the use of procedure pointer calls, this example revisits the date routines one
more time. This section produces a second version of the NameOfDay subprocedure, which
provides the name of the day in French. We then modify the main program to ask the user if
they want the results displayed in English or in French. Although this might not be the most
practical use of procedure pointer, it helps you understand the basic principles involved.
The new subprocedure NomDuJour has an identical interface to the one for NameOfDay. This
configuration is essential because there will only be a single invocation point. Therefore, the
parameter and return value should be identical. Otherwise you will encounter unusual errors.
NomDuJour is almost identical to NameOfDay. The only significant change, other than the name
of the subprocedure, is the values used for the days of the week. For this reason, do not
include the source here. You can find it with the other sources in SUBPRCSRC, member
name DATESRVPG4.
A new prototype
One problem with using procedure pointers to call a subprocedure is that you cannot use the
same prototype to describe the interface that was used in the subprocedure itself. See the
code shown in Example 3-30.
Chapter 3. Subprocedures 73
// Constants for procedure pointers to NameOfDay and NomDuJour
Dcl-c NomDuJour@ %Paddr('NomDuJour'); 2
Dcl-c NameofDay@ %PAddr('NameOfDay');
Example 3-31 Modifying the main program to pose the English or French question
// DATEMAIN4 from SUBPROCSRC
ctl-opt main(datemain4);
Dcl-pi DateMain4;
WorkDate date(*usa);
end-Pi;
74 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
DayName = Name(WorkDate); // 6
// Terminate Program
return;
end-Proc;
Try it yourself: You can re-create the examples that are shown in this section by using the
following commands:
Create a module for the procedures to be called via the procedure pointer call:
CRTRPGMOD SRCFILE(rpgiscool/subprocsrc) SRCMBR(datesrvpg4)
MODULE(rpgiscool/datesrvpg4)
Create a module for the main program:
CRTRPGMOD SRCFILE(rpgiscool/subprocsrc) SRCMBR(datemainp4)
MODULE(rpgiscool/datemainp4)
CRTPGM PGM(rpgiscool/datemainp4) MODULE(datemainp4 datesrvpg4)
To execute each program, enter the following command:
CALL rpgiscool/datemainp4 parm(’07-02-99’)
Chapter 3. Subprocedures 75
Subprocedure calls
Table 3-1 summarizes the different types of calls and the parameter options available for each
type of call. An “X” in a particular cell indicates that such a combination of a call and
parameter option is allowed. For example, you can use *OMIT as a parameter to a CALLB,
but not to a CALL.
Table 3-1 Calls and the parameter options available for each type of call
CALL CALLB CALLP CALLP Expr.
ExtPgm ExtProc
Dynamic call X X
Static/Bound call X X X
Fixed format X X
Free format X X X
Uses prototype X X X
Return value X
Pass by reference X X X X X
Parms by value X X
CONST X X X
*VARSIZE X X X
*OMIT X X X
*NOPASS X X X
Static call
A static procedure call is a call to an ILE procedure where the name of the procedure is
resolved to an address during binding, therefore, the term static. As a result, runtime
performance using static procedure calls is faster than runtime performance using
conventional dynamic program calls.
Static calls allow operational descriptors and omitted parameters, and they extend the limit (to
399) on the number of parameters that are passed.
76 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
4
To take full advantage of ILE, you should also understand RPG IV subprocedures. These are
also covered in this chapter.
The foundation of ILE and how it relates to RPG are explained in 4.2, “ILE tips for the RPG
programmer” on page 83. Modules, service programs, binding directories, activation groups,
and the CL commands that pull them all together are explained in the following subsections.
4.1.1 Modules
Modules are objects of *MODULE type that are created by the compiler when the Create
RPG Module (CRTRPGMOD) command is used. A module can be composed of a main
procedure, and one or more subprocedures. Some modules, especially those destined to be
in a service program, have no main procedure.
A module is sometimes called a compilation unit because it comes from the compilation of a
source member. Modules are not executable. They only serve as building blocks for creating
programs or service programs.
Service programs are objects of *SRVPGM type. A service program is simply a collection of
modules, especially those containing subprocedures. Service programs cannot be directly
78 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
called. However, the procedures contained in them can be called by ILE programs or other
procedures.
An ILE program can be created from one or more module objects by using the Create
Program (CRTPGM) command. In addition, the CRTPGM command can also bind the
program to one or more service programs. Modules whose code is to be copied into the
bound program (typically only the entry module for the program, but there might be others)
are listed in the Module parameter of CRTPGM. Service programs that are to be referenced
by this program and used at run time (but not copied into the program object) are listed in the
Service program parameter of CRTPGM. The CRTPGM process can be simplified by the use
of binding directories, as described in 4.1.4, “Binding directories”.
Service program objects are created by using the Create Service Program (CRTSRVPGM)
command in a similar fashion to creating programs. Service programs contain code that is
copied from module objects (listed in the Module parameter) and can also reference other
service programs (listed in the Service program parameter).
RPG IV programs can also be created by using the Create Bound RPG Program
(CRTBNDRPG) command. This command can be issued for the source member that
contains the entry module for the program. This command performs both compilation and
binding in a single step. If the program being created this way needs to copy any modules in
addition to the entry module or if it needs to reference any service programs, then a Binding
Directory is required.
Not all items that are contained in the binding directory are necessarily bound. Only those
items that contain procedures that are called (which are called imports) are bound. Modules
and service programs that are listed in a binding directory often contain standard procedures,
such as mathematical functions or other system procedures. Programmers can create and
maintain their own binding directories by using these commands:
Create Binding Directory (CRTBNDDIR)
Add Binding Directory Entry (ADDBNDDIRE)
Work with Binding Directory (WRKBNDDIR)
Work with Binding Directory Entry (WRKBNDDIRE) commands
The use of these commands is covered in more detail in 4.2.4, “Using binding directories” on
page 110.
An RPG IV subprocedure can only be exported if the EXPORT keyword is specified in the
subprocedure definition. If that module becomes part of a service program, then the
subprocedure can then be made available to external programs (or service programs) by
being exported from the service program. In other words, for a subprocedure to be called by
an external program or service program, it must be exported in two ways. It must be exported
from the module (by using the EXPORT keyword on the subprocedure declaration), and then
exported from the service program.
There are two ways to export procedures from a service program. both of which are
controlled by the EXPORT keyword on the CRTSRVPGM command.
If the value of *ALL is specified for that keyword, then all procedures that have been exported
from the service program's modules are automatically exported. The procedures then
become available to be called by programs or other service programs.
If the EXPORT(*SRCFILE) value is specified for the EXPORT keyword, then a Binder
Language source must be created that specifies the procedures to be exported.
The EXPORT(*SRCFILE) value is the default, and requires that a special source member
exists. The source member, called binder language source, contains a list of exported
subprocedure names (and possibly variable names) that the service program makes
available. The other exports of the service program remain inaccessible to external users.
The source member has a BND source type and is placed in a source file (the default is
QSRVSRC). The binder source member is never compiled.
The EXPORT(*ALL) value makes available all exports from the service program. The problem
with using this option is that any time you add new exported procedures to the service
program (or if you take any exported procedures away), then the service program’s signature
changes. The impact of the change in signature is that every program that was previously
bound to the changed service program generates a runtime error unless it has been updated
to pick up the new signature.
The Update Program command (UPDPGM) picks up the changed signature. Because each
program that references the changed service program requires this update process, it can be
cumbersome to do, especially when many programs are using service programs. For that
reason, most ILE developers find it easier to use binder language source to control the
signature value of the service program. You can read more about using binder language in
4.2.3, “Service programs, binder language, and signatures” on page 104.
80 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Tip: Generally, use binder language source and EXPORT(*SRCFILE) instead of
EXPORT(*ALL) to make maintenance easier.
Default activation groups exist automatically and are never deleted. There are two default
activation groups. Many system programs run in default activation group 1. RPG IV programs
that are created with the parameter DFTACTGRP(*YES) of the CRTBNDRPG command run
in default activation group 2.
Note: In this chapter, the name default activation group is used to mean activation
group 2.
The other types of activation groups are specified by the parameter ACTGRP in the program
and service program creation commands CRTPGM and CRTSRVPGM. Therefore, the type of
activation group is determined by the program or service program at creation time.
An activation group is created when the program is started. An activation group can include
these items:
Static and automatic variables
The variables of programs running in the activation group. Static variables are those
defined in a main procedure. They come from external sources such as DDS or SQL
specifications, or are defined as RPG variables (fields, indicators). One more place you
might find static variables is as local variables in subprocedures declared with the STATIC
keyword. Automatic variables are local variables that are defined in subprocedures.
Open data paths (ODPs)
These are temporary objects that represent open files to programs. The data buffer and
pointer to the current record are part of the ODP.
Dynamically allocated storage
These are temporary objects created by the %alloc built-in function (BiF) in the RPG IV
program.
Error handling routines
These are system or user programs (modules) that handle error messages. Programmers
can write their own modules to handle error messages coming from any procedure in the
call stack, no matter in which programming language the procedure is written. Notice that
the “program stack” has been renamed to “call stack”. For more information, see 4.2.6,
“Call stack and error handling” on page 124“.
The following commands help to create binding directories that can be referred to by the
CRTSRVPGM command as a source of suitable exports:
Create Binding Directory (CRTBNDDIR) command
Creates a *BNDDIR object to be specified in the BNDDIR parameter of the CRTSRVPGM
command.
Add Binding Directory Entry (ADDBNDDIRE) command
Adds entries (module or service program names) to the binding directory.
Work with Binding Directory Entries (WRKBNDDIRE) command
Displays binding directory entries (module or service program names) on the user screen
so you can maintain them.
Other commands service binding directories and their entries (delete and display
commands).
82 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
4.2 ILE tips for the RPG programmer
This section illustrates the following ILE concepts when used with RPG IV:
Various methods to create ILE programs from modules and service programs
Export and import of external symbols
Service program signatures and their relation to programs
Activation group creation and deletion, as well as shared open data paths and overrides
ILE-specific error message handling programs
The examples in this section use prototypes and procedure interfaces for all the calls. If you
want to see what other calls you can use, see the Programming IBM Rational Development
Studio for i ILE RPG Programmer's Guide at:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzasc/sc092507.pdf
Module M02
Module M02 is compiled from the source shown in Example 4-1. Module M02 calls the
subprocedure NonDigit in the IF statement as a function that returns a value. The value is
*OFF if no non-digit character in the string is found, or *ON if one exists. The position number
of the first non-digit character is available in the second parameter.
ctl-opt main(mainPgm);
// Data definitions
Dcl-s NonDigTxt char(30) Inz('Non-digit in position');
Dcl-s AllDigits char(30) Inz('All digits in string');
// Main procedure
// Local variables
Dcl-s String varchar(100) Inz('111*1');
Dcl-s Position packed(3 : 0) Inz(1);
If NonDigit(String : Position);
Dsply (NonDigTxt + ' ' + %char(Position));
Else;
Dsply AllDigits;
EndIf;
*InLR = *On;
end-Proc;
Module M02A
Module M02A does not contain a main procedure, so it specifies the NOMAIN keyword. It
contains only the subprocedure NonDigit. Module M02A is compiled from the source shown
in Example 4-2.
ctl-opt nomain;
// Procedure prototype
/COPY ILESRC,CPYM02A
// Procedure definition
//Dump;
Position = %check('0123456789' : String : 1);
if position = 0;
return *off;
else;
return *on;
endIf;
end-Proc NonDigit;
84 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Note: The EXPORT keyword must be specified to make the procedure NonDigit
accessible to the module (main procedure) M02. The function returns not only the
return value (*ON or *OFF), but also passes the Position parameter by reference.
Therefore, the calling module M02 can use it.
Prototype CPYM02A
The prototype CPYM02A is used as a copy member by both modules M02 and M02A. See
Example 4-3.
Try it yourself: Compilation of the two modules M02 and M02A can be done by using the
following commands:
CRTRPGMOD MODULE(RPGISCOOL/M02) SRCFILE(RPGISCOOL/ILESRC)
CRTRPGMOD MODULE(RPGISCOOL/M02A) SRCFILE(RPGISCOOL/ILESRC)
Then, use the following command to bind the two modules together into the program P02:
CRTPGM PGM(RPGISCOOL/P02) MODULE(RPGISCOOL/M02 RPGISCOOL/M02A) ENTMOD(*FIRST)
ACTGRP(QILE)
The ENTMOD parameter says that the first module, M02, gets control when the program
P02 is started.
Module M03
Module M03 calls the procedure NonDi that has the same purpose as the function NonDigit in
the previous example. The call is now accomplished by a call with no return value. It provides
the result of the call in the second parameter Position passed by reference. If the input string
contains a non-digit character, the second parameter contains its position number (nonzero).
Otherwise, it contains zero.
ctl-opt main(mainPgm);
// Procedure prototype
Dcl-pr nonDi;
string varchar(100) Value;
postion packed(3 : 0);
end-Pr;
// Data definitions
Dcl-s NonDigTxt char(30) Inz('Non-digit in position ');
Dcl-s AllDigits char(30) Inz('All digits in string');
// Main procedure
dcl-proc mainPgm;
dcl-pi mainPgm;
end-Pi;
// Local variables
Dcl-s String varchar(100) Inz('111*1');
Dcl-s Position packed(3 : 0) Inz(1);
// Main procedure
NonDi (String : Position);
If Position <> 0;
Dsply (NonDigTxt + %char(Position));
Else;
Dsply AllDigits;
EndIf;
return;
end-Proc mainPgm;
86 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Module M03A
Module M03A does not contain a main procedure, so it specifies the NOMAIN keyword. It
contains only the subprocedure NonDi. Module M03A is compiled from the source shown in
Example 4-5.
Note: The EXPORT keyword must be specified to make the procedure NonDi accessible to
the module (main procedure) M03.
ctl-opt Nomain;
// Procedure prototype
Dcl-pr nonDi;
string varchar(100) Value;
position packed(3 : 0);
end-Pr;
// Procedure start
dcl-Proc NonDi EXPORT;
Try it yourself: Compilation of the two modules can be done by using the following
commands:
CRTRPGMOD MODULE(RPGISCOOL/M03) SRCFILE(RPGISCOOL/ILESRC)
CRTRPGMOD MODULE(RPGISCOOL/M03A) SRCFILE(RPGISCOOL/ILESRC)
Then, use the following command to bind the two modules together into the program P03:
CRTPGM PGM(RPGISCOOL/P03) MODULE(RPGISCOOL/M03 RPGISCOOL/M03A) ENTMOD(*FIRST)
ACTGRP(QILE)
The ENTMOD parameter says that the first module, M03, gets control when the program
P03 is started.
Note: The method of passing data between modules by EXPORT and IMPORT is
presented here only as a supplement to other methods. It is not usually needed. It could be
used, for example, for passing data to a program that is called indirectly through an
intermediate program that needs this data. However, such a requirement indicates poor
program design.
In this example, two separate modules (M04 and M04A) are created from two source
members and bound into a program P04.
Module M04
This time, the first module, M04, does not use any parameters. It exports its two variables
designated as EXPORT, instead, to make them accessible by the other module. The
procedure call is used to call the module M04A. A prototype for M04A with no parameters is
included in the code. Notice that EXPORT is specified in the module where the procedure is
used.
// Data definitions
dcl-pr M04A;
end-Pr;
// Main procedure
M04A();
If Position <> 0;
Dsply (NonDigTxt + %char(Position));
Else;
Dsply AllDigits;
EndIf;
return;
88 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Module M04A
The module M04A accepts two variables exported from the module M04 through the
procedure call and processes them. The variables (String and Position) are designated by
the IMPORT keyword and have the same names in both modules.
This type of passing values between modules should not be used altogether. If still used, take
care in maintenance because it represents “hidden parameters”. Such hidden passing can be
also more difficult to debug than passing regular parameters.
// Data definitions
Dcl-s String varchar(100) import;
Dcl-s Position packed(3 : 0) import;
// Main procedure
Position = %check('0123456789' : String : 1);
return;
Try it yourself: Compilation of the two modules can be done by using the following two
commands:
CRTRPGMOD MODULE(RPGISCOOL/M04) SRCFILE(RPGISCOOL/ILESRC)
CRTRPGMOD MODULE(RPGISCOOL/M04A) SRCFILE(RPGISCOOL/ILESRC)
Then, use the following command to bind the two modules together into the program P04:
CRTPGM PGM(RPGISCOOL/P04) MODULE(RPGISCOOL/M04 RPGISCOOL/M04A) ENTMOD(*FIRST)
ACTGRP(QILE)
The ENTMOD parameter says that the first module, M04, gets control when the program
P04 is started.
The first elementary procedure is DynEdit, which performs dynamic editing. It edits a 15-digit
packed number defined with 0 decimal positions (15 0), as though it had a different number of
decimal positions. This configuration is sometimes needed in application packages where all
numbers in database files are defined as (15 0) and the number of decimal positions is stored
in separate numeric fields in another database file. No check is made if the number of decimal
positions is greater than 15. The procedure is placed in module M11A.
The second elementary procedure is NonDigit, which checks whether a string contains a
valid number. The valid characters are digits and blanks. The procedure is placed in module
M11B.
The third procedure is EdtChrNbr, which edits a character coded number, as though it had the
requested decimal positions. This procedure uses both the elementary procedures in
sequence. The input to the procedure is a string of digit and blank characters. If the string
contains at least one invalid character (other than decimal digit or blank), the result is all
blanks. The procedure is placed in module M11.
All three modules have the NOMAIN keyword. They are used (called) by module M10, which
contains a main procedure and no subprocedures.
Source codes of the four modules that are used in the examples in this section are presented
in “Source codes” on page 91. Then, different ways are shown how the modules can be
combined in various service programs in the following sections:
Creating one service program
Creating two chained service programs
Creating three chained service programs
The decision about grouping the modules into service programs (that is, how many service
programs to create from these modules) is made as part of the application design. Service
programs are simply collections of commonly used modules of code. The kind of logic that is
used for deciding how many IBM i libraries you need and what kinds of objects are grouped
by library is similar to the logic you will use here.
From a performance perspective, the groupings should be made based on how likely it is that
the modules will be referenced together by a group of programs in the same job. Grouping
multiple modules together into a single service program requires fewer connections between
a program and service program to accomplish multiple tasks. Because making these
connections takes time at application run time, minimizing the number of connections
necessary can improve performance.
However, you can go too far in that direction. For example, you would not want to create only
one large service program for use by all programs in your entire shop because the memory
required to activate it in every user job (if all the users are not using all the functions) can
cause too much paging activity on the system. Likewise, the CPU time that is required to
initialize all the storage in the service program modules that are not used by some users
could cause a performance problem.
In addition to the performance impact of service program packaging, consider the effect on
maintaining the applications. Set up the packaging so that the programmers who use the
functions can easily find them to use and maintain them. Some sort of logic in the groupings,
such as grouping similar functions together, is useful in this respect.
90 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
As you can see, the decisions about packaging modules into service programs is a balancing
act. You must try to balance the performance needs with ease of development and
maintenance. Performance needs also require a balance between the desire to minimize the
number of connections between a program and its required service programs. At the same
time, you must minimize the activation and initialization of modules in a service program that
are not used by a group of programs that a user uses within a job.
The good news is that adjusting the service program packaging is a relatively easy task. If the
module objects still exist on the system, no recompile of the source code is required to
change the packaging scheme.
For this particular example of the DynEdit, NonDigit, and EdtCharNbr procedures, it is most
likely that these modules would best be grouped into a single service program (as in the first
example). This is because all three modules are used together by at least one main module
(M10). Grouping them reduces the number of connections required between program and
service programs. In addition, all three procedures are similar in function. They all perform
various types of string handling functions, so there is also a logical reason to group them. It
might have been a good idea to group all three of these subprocedures into a single module
because of the close relationship they have to one another. However, for purposes of
illustrating grouping them in different ways, the three functions are in separate modules.
How the service programs are bound to a program is explained in 4.2.2, “Binding service
programs to programs” on page 102.
Source codes
This section lists the source codes of the four modules used in the examples in this section:
Procedure DynEdit in module M11A
Procedure NonDigit in module M11B
Prototype CPYS11
Procedure EdtChrNbr in module M11
Program (main procedure) in module M10
Prototype CPYS10
Display file description CHRNUMW
ctl-opt nomain;
//----------------------------------------------------------------
// Procedure prototypes
//----------------------------------------------------------------
/Copy ILESRC,CpyS11
//----------------------------------------------------------------
// Procedure interface
//----------------------------------------------------------------
Dcl-pi DynEdit varchar(100);
Number packed(15 : 0) Value;
DecPos packed(3 : 0) Value;
end-Pi;
//----------------------------------------------------------------
// Local data definitions
//----------------------------------------------------------------
Dcl-s EdtNbr varchar(100);
Dcl-s WorkNbr packed(30 : 15);
Dcl-s Correction packed(1 : 0);
Dcl-s Pos packed(3 : 0);
If DecPos = 0;
Correction = -1;
// Edit the work number with edit code 3 and place the result
// in the varying length character variable:
// ' 1111111111111.110000000000000'
EdtNbr = %Subst(EdtNbr : 1 :
%Len(EdtNbr) -15 + DecPos
+ Correction );
92 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// Return the edited number as a varying character variable
Return EdtNbr;
end-proc DynEdit;
ctl-opt Nomain;
//================================================================
// NonDigit - Checks if the input character variable contains
// a non-digit (or nonblank) character.
// If yes, returns error code *On and replaces
// the input variable with all zero characters.
// If not, returns positive code *Off replaces
// all blanks by zeros.
//
//================================================================
//----------------------------------------------------------------
// Procedure prototypes
//----------------------------------------------------------------
/Copy ILESRC,CpyS11
//----------------------------------------------------------------
// Procedure definition
//----------------------------------------------------------------
dcl-Proc NonDigit EXPORT;
//----------------------------------------------------------------
// No local data definitions
//----------------------------------------------------------------
end-Proc NonDigit;
Prototype CPYS11
These prototypes are used by the copy member in modules M11, M11A, and M11B as shown
in Example 4-10.
94 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Procedure EdtChrNbr in module M11
Module M11 is compiled from the source shown in Example 4-11.
ctl-opt Nomain;
//================================================================
// EdtChrNbr - Edit character coded number
//================================================================
//----------------------------------------------------------------
// Procedure prototypes
//----------------------------------------------------------------
/Copy ILESRC,CpyS10
/Copy ILESRC,CpyS11
//----------------------------------------------------------------
// Procedure definition
//----------------------------------------------------------------
dcl-Proc EdtChrNbr EXPORT;
//----------------------------------------------------------------
// Local data definitions
//----------------------------------------------------------------
end-Proc EdtChrNbr;
ctl-opt main(mainPgm);
//================================================================
// File description - Display file
//================================================================
dcl-F CHRNUMW WorkStn;
//================================================================
// Data definitions
//================================================================
Dcl-s EditNbr varchar(100);
Dcl-s RC ind;
Dcl-s CharNbr varchar(100);
//================================================================
// Mainline program
//================================================================
dcl-proc mainPgm;
96 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// Edit the character coded number with requested decimal positions
CharNbr = %trim(CHRNBR);
RC = EdtChrNbr(CharNbr: DecPos: EditNbr);
end-Proc;
Prototype CPYS10
Prototype CPYS10 is used by the copy member in module M10 and M11 as shown in
Example 4-13.
98 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Format CHRNUMWR2 in Figure 4-1 shows the result of editing along with the entered values.
Try it yourself: The following commands can be used to create the modules described
previously. These compilations need to be done before creating the service program
objects described in the upcoming sections:
CRTRPGMOD MODULE(RPGISCOOL/M11A) SRCFILE(RPGISCOOL/ILESRC) SRCMBR(*MODULE)
CRTRPGMOD MODULE(RPGISCOOL/M11B) SRCFILE(RPGISCOOL/ILESRC) SRCMBR(*MODULE)
CRTRPGMOD MODULE(RPGISCOOL/M11) SRCFILE(RPGISCOOL/ILESRC) SRCMBR(*MODULE)
Before creating the module M10, create the display file CHRNUMW by using the following
commands:
CRTDSPF FILE(RPGISCOOL/CHRNUMW) SRCFILE(RPGISCOOL/ILESRC)
ADDLIBLE LIB(RPGISCOOL)
CRTRPGMOD MODULE(RPGISCOOL/M10) SRCFILE(RPGISCOOL/ILESRC) SRCMBR(*MODULE)
Figure 4-2 on page 100 illustrates how the service program is built to combine the three
separate modules into one service program.
The service program is called S123 and made all its exports available to external callers. The
EXPORT(*ALL) parameter on the CRTSRVPGM command specifies this.
Note: The exports are the names of procedures DynEdit, NonDigit, and EdtCharNbr, which
specify the EXPORT keyword. You will see later that this option, although easy to specify,
has some disadvantages related to signatures. A signature is a code that expresses a
version of exports. It is similar to level check values used with files.
Figure 4-3 on page 101 illustrates how the two separate service programs are chained
together to create one entity for the program to resolve.
The two service programs are called S12 and S3S12, and they “export all its exports” by
using the EXPORT(*ALL) parameter of the CRTSRVPGM command.
The imports from the module M11 (procedures DynEdit and NonDigit) were resolved in the
example in “Creating one service program” on page 99 by specifying Bind Service Program
(BNDSRVPGM) S12. A different possible way to resolve them would have been to include
modules M11A and M11B in the same service program as in the previous example with
service program S123.
100 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 4-3 Two chained service programs
Figure 4-4 illustrates how the three separate service programs are chained together to form a
single entity for the program to resolve.
Note: A service program always binds at least one module and an arbitrary number of
service programs.
Important: Recall that programs are objects of the *PGM type and can be run. Modules
are objects of the *MODULE type and cannot be run. Service programs are objects of the
*SRVPGM type and cannot be run without a program.
Figure 4-5 Program consisting of one module and one service program
Try it yourself: Use the following command to create the program object by using the
service program created in “Creating one service program” on page 99:
CRTPGM PGM(RPGISCOOL/PS123) MODULE(RPGISCOOL/M10) ENTMOD(*FIRST)
BNDSRVPGM(S123) ACTGRP(QILE)
102 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Program from one module and a chained service program
Program PS3S12 is created by binding module M10 and the chained service program S3S12.
Figure 4-6 illustrates how the program is built chaining multiple service programs together.
Figure 4-6 Program consisting of one module and two chained service programs
Try it yourself: Use the following command to create the program object using the service
programs created in “Creating two chained service programs” on page 100:
CRTPGM PGM(RPGISCOOL/PS3S12) MODULE(RPGISCOOL/M10) ENTMOD(*FIRST)
BNDSRVPGM(RPGISCOOL/S3S12) ACTGRP(QILE)
Note: A program always binds at least one module. It does not need to bind any service
program at all, or it can bind several service programs.
Figure 4-7 Program consisting of two modules and two service programs
The signature of a service program has a similar function as the level check value used in
files. Whenever a service program is activated by the program, a check is made to see
whether the signature matches the one that is stored in the program since the last binding of
the program. If not, an error message is issued that refers to a signature violation. To correct
this error situation, the developer needs to rebind the service program to all programs that
reference it. Note that this is only a rebind requirement and not a recompile requirement. The
easiest and most common way to accomplish this rebind is by using the Update Program
(UPDPGM) command and specifying the service program that has changed. This way, you do
not need to have all the modules at the correct version to create the program again using the
original CRTPGM command.
When a signature of a service program changes, the update process described above must
be done to every program that references it. Because service programs are often used by
large numbers of programs, this process can be cumbersome. Therefore, most ILE shops find
it better to gain control over the signature by avoiding EXPORT(*ALL) and using binder
language source.
When using binder language source, use the parameter value EXPORT(*SRCFILE) along
with two other parameters designating a source member that contains the binder language
source. The source member type for binder language is BND and its default source file is
QSRVSRC. The specifications in this binder language source are collectively called binder
language.
Most customers prefer maintaining binder language to manage the service program
signatures to rebinding all the programs that use a service program. This preference is
especially true in cases where the only change made to the service program was to add one
or two new procedures. In some shops with relatively simple application structures and only a
single IBM i system, you might find that rebinding by using the UPDPGM command is simpler
than creating and maintaining the binder language source. However, if you have many
systems, particularly if they are in remote locations, you will likely find that maintaining binder
language makes it easier to make relatively small, incremental changes to your service
program structures.
104 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
In addition, by using the binder source, you can control which procedures from the service
program that you actually want to make available to calling programs by controlling which
ones you export. This configuration gives you the flexibility to “hide” some of the procedures in
the service program so that they are callable only from other procedures inside the same
service program. Then, they will not be callable by program modules or other service program
modules.
There are three primary approaches for managing signatures using binder language:
1. The developer lets the system generate multiple signatures based on multiple export lists
in the binder language.
2. The developer supplies multiple export lists, and also supplies multiple hardcoded values
for the signatures rather than letting the system generate the signature values.
3. The developer provides one hardcoded signature value, which stays the same over time,
and one export list, which changes with each new version of the service program.
By default, the signature is generated by the system based on the exports included in the
binder language source block. We will look at that option first after an introduction to the
syntax of binder language.
Example 4-17 shows two blocks of export symbols. In the first block, default values for the
parameters in the STRPGMEXP statement are shown. If you do not specify any parameters,
these values are assumed.
The EXPORT statement has only the SYMBOL parameter, which specifies a procedure name
or a variable name that is to be exported from the service program. The name is sometimes
called export symbol or external symbol. The name can be written in capital and small letters
without apostrophes or quotation marks. In this case, the name is converted into capital
letters. If lowercase letters or special characters are needed in exported symbols,
apostrophes or quotes need to be used.
If a new export symbol is to be added to the list of exports, the current block can be expanded
by adding the new export symbol after the last existing EXPORT statement. It is critical that all
new exports are always added at the end of the exported symbol list.
This is the simplest form of the binder language source. The source member is usually
named the same as the service program that it belongs to as in the S12 example.
The EXPORT, SRCFILE, and SRCMBR parameters are needed to specify the binder
language source. The *SRVPGM value in the SRCMBR parameter tells the binder that the
source member has the same name as the resulting service program. You can specify your
own name instead. However, using the service program name is considered a good practice.
The binder stores signatures for all the export blocks in the binder language source in the
service program so that they are available when a program is bound using one of the earlier
and later called. The current signature is stored in the program when it is created (bound) by
the binder (for example, by the CRTPGM or the CRTBNDRPG command).
106 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
At program activation time, the system checks if the signature stored in the program matches
one of those in the service program. If at least one signature matches, the program can run. If
there is no matching signature, a level check error is reported (unless disabled by
LVLCHK(*NO) in the binder language source). This way, the system ensures that the correct
version of the service program is used with the program. This process is similar to level
checking with files.
The “previous” signatures enable a service program to be used by a program that was bound
when one of the previous export blocks was the current one. The binder language source
causes the previous signatures to be retained by keeping the original block of exports,
changing only the *CURRENT to *PRV for the earlier export blocks. This process allows a
program that has stored the old (current when the program was compiled) service program
signature to use the newly re-created service program.
When adding a third new procedure to the example service program S12 to maintain the older
generated signature along with the new current signature, the binder language should look
like the output shown in Example 4-19.
STRPGMEXP PGMLVL(*PRV)
EXPORT SYMBOL("DYNEDIT")
EXPORT SYMBOL("NONDIGIT")
ENDPGMEXP
When adding more procedures to the service program, the export block that is specified as
*CURRENT becomes *PRV. A new *CURRENT block is created with one or more new
procedures added to the end of the list of EXPORT statements in the most recent *PRV list.
The symbol named "DYNEDIT" must always remain the first export in all export lists and
"NONDIGIT" must always remain the second export in all export lists for the previous
signatures to be used successfully.
All the same rules for exports lists are still in effect here. There are multiple export lists, and
the current export list must retain the positions of all exports from all previous/earlier export
lists. There is still only one currently valid export list, which is the only “real” export list used at
run time. All *PRV export lists are there only for purposes of maintaining the earlier
signatures. The advantage in this case is that the signature values themselves, which are
visible from the objects by using the Display Service Program (DSPSRVPGM) and Display
Program (DSPPGM) commands, are far more meaningful, which can make maintenance and
problem determination easier.
In this scenario, the binder language for the example service program S12 would look like
Example 4-21 the first time that the service program is created.
Example 4-21 The binder language for a single hardcoded signature with a single export list
STRPGMEXP PGMLVL(*PRV) SIGNATURE (‘S12’)
EXPORT SYMBOL("DYNEDIT")
EXPORT SYMBOL("NONDIGIT")
ENDPGMEXP
108 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
When adding a third procedure to service program S12, the binder language for the example
service program S12 is changed to add the new export at the end of the list as shown in
Example 4-22.
Example 4-22 Changes to the binder language for the third procedure
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE (‘S12’)
EXPORT SYMBOL("DYNEDIT")
EXPORT SYMBOL("NONDIGIT")
EXPORT SYMBOL("NEWONE")
ENDPGMEXP
Even though you have only one export list in the binder language source in this scenario, it is
still essential that each “old” export symbol remains in the same sequential position in every
iteration of the binder language. In this example, the symbol named “DYNEDIT” must always
remain the first export and “NONDIGIT” must always remain the second export in all export
lists.
Using this technique, the signature value of the service program never changes. Therefore, all
programs that were previously bound to this service program continue to work throughout
multiple versions of the service program. If a point is reached when the list of or the sequence
of the exported symbols must be changed (for example, when removing an export from a
service program), this system creates a situation where all previously bound programs must
be rebound in order to work reliably. In this case, change the hardcoded signature value in the
binder language and all programs that use the service program must be updated or
re-created to pick up the new signature value.
The advantage of this scenario is that the binder language does not become cluttered with
many old export lists and signatures that need to be cleared out from time to time. The
disadvantage of this approach is that there is little help with problem determination if
something were to go wrong at some point.
For example, if a developer makes a mistake and does not maintain the earlier sequence of
exports, all programs that use S12 could run the wrong procedures at the wrong times. This
kind of error would typically be difficult to track down. While this situation could also occur with
the other two binder language scenarios,*PRV export lists can be used to help identify the
error. In this case, with only one version of the export list, it would be difficult to determine not
only what went wrong, but also how to fix the export list to make it right. When using this
technique, archiving earlier versions of the binder language is advised to help with problem
determination.
For more information about signatures and binder language, see these resources:
Programming ILE Concepts, SC41-5606
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/ilec/sc415606.pdf
“Binder Language and the Signature Debate” by Scott Klement
http://iprodeveloper.com/rpg-programming/binder-language-and-signature-debate
"Writing the Binder Language" by Susan Gantner
http://www.mcpressonline.com/programming/rpg/writing-the-binder-language.html
The program PM11S1S2 is created by using binding directory BM11S1S2. The binding
directory BM11S1S2 is created by the following commands:
CRTBNDDIR BNDDIR(RADREDBOOK/BM11S1S2)
ADDBNDDIRE BNDDIR(RADREDBOOK/BM11S1S2) OBJ((RADREDBOOK/M11 *MODULE))
ADDBNDDIRE BNDDIR(RADREDBOOK/BM11S1S2) OBJ((RADREDBOOK/S1 *SRVPGM))
ADDBNDDIRE BNDDIR(RADREDBOOK/BM11S1S2) OBJ((RADREDBOOK/S2 *SRVPGM))
You can display contents of a binding directory by using the DSPBNDDIR or WRKBNDDIRE
command:
DSPBNDDIR BNDDIR(RADREDBOOK/BM11S1S2) OUTPUT(*PRINT)
Now you can create your program by using this binder command:
CRTPGM PGM(RADREDBOOK/PM11S1S2) MODULE(RADREDBOOK/M10) BNDDIR(RADREDBOOK/BM11S1S2)
ACTGRP(QILE)
This command creates the same binding as the earlier example in “Program from two
modules and two service programs” on page 103. The program still performs the same
function as the previous programs.
Binding directories are used in the operating system to bind system procedures to compiled
modules. The names of system procedures are generated by the compiler as unresolved
imports (references). For example, you can display your module contents by the command:
DSPMOD MODULE(RADREDBOOK/M11B) DETAIL(*IMPORT)
Then, you would get the system procedure names shown in Example 4-24.
110 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
_QRNX_INIT_H
_QRNX_INIT
_QRNX_GET_ASSOC_CCSID
_QRNX_SIGNAL_EXCP
Q LE leDefaultEh2
These unresolved imports are later resolved through system binding directories. The binder
can find them in the service program QRNXIE listed in the binding directory QRNXLE in the
QSYS library.
You can combine modules and service programs in many variations to produce a program.
The specific combination that you choose depends on your preferences or your software
project conventions.
The Create Bound RPG Program (CRTBNDRPG) command has one other option if the
default activation group parameter is *NO (DFTACTGRP):
*STGMDL:
– If the storage model parameter (STGMDL) is single level (*SNGLVL), the program runs
in the QILE activation group.
– If the storage model parameter is teraspace (TERASPACE), the program runs in the
QILETS activation group.
– If the storage model parameter is inherit (*INHERIT), the activation group parameter
must be *CALLER and the program runs in the activation group of the calling program.
An activation group can be single level or teraspace. The first program that is called in an
activation group determines the storage model. The default activation group, where OPM
programs run, can only be single level. If an activation group is started as teraspace, you
cannot use any program or service program that was created as single level storage. If an
activation group is single level, you cannot call any program or procedure that was created as
teraspace.
Programs and service programs can be created to inherit the storage model of the activation
group, but only if the activation group is specified as *CALLER when it is created. Any ILE
program that needs to use teraspace and is called by an OPM program must be created to
run in a different activation group (DFTACTGRP(*NO)).
112 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
An activation group is ended (deleted) according to its type:
– A *NEW activation group is deleted when the program ends abnormally or when it
returns to its caller (even without the LR indicator turned on). This process is important
when calling a program recursively and when you test a new program. You do not need
to sign off or use a Reclaim Activation Group (RCLACTGRP) command to end the
activation group as you would have to with a named activation group.
– A named activation group exists during the life of the active job. It is ended along with
the job end. To end a named activation group while the job is still running, you can use
the RCLACTGRP command or a bound call to the ILE API program, CEETREC. The
CEETREC call has no required parameters. Although the CEETREC call ends the
current activation group immediately, the RCLACTGRP command can specify an
activation group name or *ELIGIBLE. The eligible activation groups are those where no
programs or procedures are currently in the call stack for the job. Only activation
groups that are not in use can be deleted with this command. A named activation group
is deleted automatically if the only program active in it ends abnormally.
– The Default activation group is never ended before the job ends.
The override commands have, among others, two parameters that specify a scope
(boundary) of their influence. The OVRSCOPE parameter tells the system where in the call
stack the override will be valid before the file is open. The OPNSCOPE parameter specifies
where in the call stack the first (full) open operation on the file is valid for subsequent (shared)
open operations on the same file.
For each shared ODP, a single file cursor (record pointer) exists. Therefore, file operations in
separate programs that use that shared ODP affect each other.
As an example, a program positions to a specific record (for example, with SETLL) in a file
with a shared ODP. It then calls a second program, which performs an I/O operation (for
example, READ, CHAIN, or SETLL). The first program is no longer positioned at that record.
It is positioned to whatever record the second program selected. For example, if the first
program reads record 6, it can expect that the next sequential read returns record 7. However,
if a second program that shares the ODP is called and it chains to record 11, the first program
reads record 12.
If a program holds a record lock in a file with a shared ODP and then calls a second program
that performs an I/O operation (for example, READ, READ with no lock, UPDATE, DELETE,
or UNLOCK) on the same file, the first program no longer retains the record lock.
For ILE programs that run in non-default activation groups, shared files are scoped to either
the job level or the activation group level. Shared files that are scoped to the job level can be
shared by any programs that run in any activation group within the job. Shared files that are
scoped to the activation group level can be shared only by the programs that run in the same
activation group.
For programs that run in non-default activation groups, the default scope for shared files is the
activation group. For job-level scope, specify OPNSCOPE(*JOB) on the override command
(often the OVRDBF command).
The RPG IV language offers several enhancements in the area of shared open data paths. If
a program or procedure performs a read operation, another program or procedure can update
the record if SHARE(*YES) is specified for that file. In addition, when using multiple-device
files, if one program acquires a device, any other program that shares the ODP can also use
the acquired device. It is up to the programmer to ensure that all data that is required to
perform the update is available to the called program.
114 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Sharing an open data path improves performance because the system does not have to
create an open data path. However, sharing an open data path can cause problems. For
example, an error is signaled in the following cases:
If a program sharing an open data path attempts file operations other than those specified
by the first open (for example, attempting input operations when the first open specified
only output operations).
If a program sharing an open data path for an externally described file tries to use a record
format that the first program ignored.
If a program sharing an open data path for a program described file specifies a record
length that exceeds the length that is established by the first open.
This situation is more specifically presented in the following examples. Programs P1 and P2
print the contents of the ITEMS database file, which specifies SHARE(*YES) permanently. In
addition, program P2 updates the file by increasing the unit price by one percent. Both
programs open the file for update because they share the same file. If program P1 specified
only input, an error would occur when opening the file in program P2.
Program P1
Program P1 is compiled from the source code that is shown in Example 4-25.
// File descriptions
// prototypes
dcl-pr mainPgm extpgm('P1');
end-Pr;
dcl-pr P2 extpgm('P2');
end-Pr;
// Main procedure
dcl-proc mainPgm;
dcl-pi mainPgm;
end-Pi;
Read(N) ITEMS;
If %EoF; 2
EOFTEXT = 'P1 End of file';
Else;
EOFTEXT = 'P1 Beginning of file';
EndIf;
Write EOFLINE;
P2(); 4
116 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
*inlr = *on;
Return;
end-Proc;
Note the following points, which correspond to the numbers shown in the right side of
Example 4-25:
1. Program P1 runs in activation group AG1. It is created by the CRTBNDRPG command.
2. After the first READ operation, the program tests whether the current record position is
EOF and, according to the test result, prints a line with appropriate text.
3. If the position is not at EOF, the program reads all database file records and prints them.
4. After it reads all records, the program calls the other program, which is P2.
Program P2
Program P2 is compiled from the source code that is shown in Example 4-26.
// File descriptions
dcl-F ITEMS Disk(*ext) usage(*update);
dcl-F REPORT printer;
// prototypes
dcl-pr P2 extpgm('P2');
end-Pr;
// Main procedure
dcl-proc P2;
dcl-pi P2;
end-Pi;
Read ITEMS;
If %EoF; 2
EOFTEXT = 'P2 End of file';
Else;
EOFTEXT = 'P2 Beginning of file';
EndIf;
Write EOFLINE;
Read ITEMS;
EndDo;
*InLR = *On;
Return;
end-Proc;
Note the following points, which correspond to the numbers in Example 4-26 on page 117:
1. Program P2 runs in the same activation group, AG1, as program P1.
2. The record position is still at EOF because the database file is shared, which is true even
if program P1 ended with LR indicator *ON. The program prints an end of file message.
3. The reading cycle is skipped, and program P2 ends and returns to the program P1, which
also ends.
The programs must be compiled by running the CRTBNDRPG command because they
specify the keywords DFTACTGRP and ACTGRP in the control spec. These keywords are not
allowed in the CRTRPGMOD command because the ACTGRP parameter must be specified
with the subsequent CRTPGM command.
File ITEMS
This example uses a physical file member with the definition that is shown in Example 4-27.
The contents of the sample physical file used in these examples is shown in Example 4-28.
118 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Printer file REPORT
The printer file definition that is used in these sample programs is shown in Example 4-29.
Try it yourself: After running the example by calling program P1 from the command line,
you get the following printout from program P1:
P1 Beginning of file
00001 25.00 First item
00002 50.00 Second item
00003 75.00 Third item
Create the programs P1 and P2 by using the following commands. Before you create the
programs, the physical file and printer file must be created:
ADDLIBLE RPGISCOOL
CRTPF FILE(RPGISCOOL/ITEMS) SRCFILE(RPGISCOOL/ILESRC)
CRTPRTF FILE(RPGISCOOL/REPORTS) SRCFILE(RPGISCOOL/ILESRC)
CRTBNDRPG PGM(RPGISCOOL/P1) SRCFILE(RPGISCOOL/ILESRC)
CRTBNDRPG PGM(RPGISCOOL/P2) SRCFILE(RPGISCOOL/ILESRC)
The activation group AG1 remains active because it is a named activation group.
The OVRDBF command is issued when the CL program is called in the default activation
group. In this case, *ACTGRPDFN is the same as *CALLLVL. It means that the open data
path is shared between programs P1 and P2, and the results are the same as before.
If you change the source type to CLLE (ILE CL), you can compile the same CL program with
the Create Bound CL Program (CRTBNDCL) command. Specify the activation group
ACTGRP(AG1) for the CL program. In this case, the OVRDBF command causes the
parameter SHARE(*YES) to be valid for open operations inside the activation group AG1 only.
The results are again the same because programs P1 and P2 are running in the same
activation group AG1.
120 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
This situation is presented in the following examples. Program P1A and P2A perform the
same function as programs P1 and P2.
Program P1A
Program P1A is identical to program P1, except that it calls program P2A and prints slightly
different text, as shown in Example 4-31.
// File descriptions
// prototypes
dcl-pr mainPgm extpgm('P1A');
end-Pr;
// Main procedure
dcl-proc mainPgm;
dcl-pi mainPgm;
end-Pi;
Read(N) ITEMS;
If %EoF;
EOFTEXT = 'P1A End of file';
Else;
EOFTEXT = 'P1A Beginning of file';
EndIf;
Write EOFLINE;
P2A();
*inlr = *on;
Return;
end-Proc;
// File descriptions
dcl-F ITEMS Disk(*ext) usage(*update);
dcl-F REPORT printer;
// prototypes
dcl-pr P2A extpgm('P2A');
end-Pr;
// Main procedure
dcl-proc P2A;
dcl-pi P2A;
end-Pi;
Read ITEMS;
If %EoF;
EOFTEXT = 'P2A End of file';
Else;
EOFTEXT = 'P2A Beginning of file';
EndIf;
Write EOFLINE;
Write ITEMDETAIL;
Read ITEMS;
EndDo;
*InLR = *On;
Return;
end-Proc;
122 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
After running the example by calling program P1A from the command line, you get the
printout shown in Example 4-33 from program P1A.
You also get the printout in Example 4-34 from program P2A.
Program P2A opens its own data path in the newly created activation group AG2 regardless
of whether program P1A already has an ODP. Even though the file specifies SHARE(*YES)
permanently, no sharing occurs between activation groups AG1 and AG2 because the first
open was performed in the activation group AG1. Therefore, all records are printed in both
programs, with the unit price increased by 1% in program P2A. The activation groups AG1
and AG2 remain active because they are named activation groups.
If you want to share the ODP in both activation groups, run the following commands, with the
open scope being the entire job:
OVRDBF FILE(ITEMS) OVRSCOPE(*JOB) SHARE(*YES) OPNSCOPE(*JOB)
CALL PGM(P1A)
The OVRSCOPE parameter says that sharing the ITEMS file lasts for the entire time that the
job is active (unless another override comes in between). The OPNSCOPE parameter says
that subsequent opens of the ITEMS file after the first open follow the attributes that are set
by this command. The attributes are, among others, open options for all types of access
(input, output, update, and delete) and the share option set by the override command just
before.
Try it yourself: You can create the programs P1A and P2A by running the following
commands. Before you create the programs, the physical file and printer file must be
created (if they were not created in the previous section):
ADDLIBLE RPGISCOOL
CRTPF FILE(RPGISCOOL/ITEMS) SRCFILE(RPGISCOOL/ILESRC)
CRTPRTF FILE(RPGISCOOL/REPORTS) SRCFILE(RPGISCOOL/ILESRC)
CRTBNDRPG PGM(RPGISCOOL/P1A) SRCFILE(RPGISCOOL/ILESRC)
CRTBNDRPG PGM(RPGISCOOL/P2A) SRCFILE(RPGISCOOL/ILESRC)
The main procedure of an ILE program has a special name in the call stack,
_QRNP_PEP_programname.
Program entry procedure (PEP) represents the main procedure of a program. The program
name itself follows the PEP in the call stack. The PEP is a piece of code that is generated by
the compiler to initialize (activate) the program. The PEP passes control to the program code
that was written by the programmer.
Service programs and OPM programs have no PEP. Subprocedures and modules that are
called by the bound call also have no PEP.
Entries appear in the call stack and disappear from it, as they are called and ended (in last in,
first out (LIFO) order).
124 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
As an example, the call stack is shown in Figure 4-10 in two views.
Program E01REG is waiting in the EXFMT operation. The top screen shows the entry
_QRNP_PEP_E01REG. The second screen shows that QRNPPEP_E01REG is the next higher control
boundary. If the < sign precedes the name, a shortened name of a system procedure is
displayed. To see the full name, place the cursor in the name and press F22.
The ILE condition handler is an ILE function, and the other functions are also available in
OPM.
For example, if a CHAIN (E) operation was specified and the record to be read is locked by
another job, you would catch the error message by using the %ERROR built-in function.
If you specify a CHAIN operation without the (E) modifier, you can catch the error message by
specifying an INFSR subroutine in the main procedure.
If you did not specify any of the preceding functions, you can catch error messages by using a
condition handler program. You write it as a module with a special interface. Register the
module by calling a special ILE API (CEEHDLR) in the program or the procedure in which you
expect errors. The condition handler receives control whenever an error message arrives in
the program or procedure message queue. You can test for messages that have type
*STATUS, *NOTIFY, *ESCAPE, and function check, which is a special type of an *ESCAPE
message (CPF9999).
The RPG specific functions (1, 3, and 4 from the above list), when applied, mark the message
as handled, and the message is not propagated any further.
The last function, the default RPG exception handler, does not mark the message, but sends
it to the message queue of the next higher entry in the stack. There, the message is
processed again according to the hierarchy described above. This process is called
percolation, and continues until the message is handled or a control boundary is reached.
If the message is not handled by the procedure in the control boundary, the system handles
the *ESCAPE message as follows:
1. The *ESCAPE message is written in the job log, and a function check message
(CPF9999) is generated and written in the job log.
2. The system returns to the point where the *ESCAPE message was originated and repeats
the percolating process with the function check message.
3. At the control boundary, a RNQxxxx inquiry message can be generated and sent to the
*EXT message queue if the control boundary is just below the command line. This action
only applies to main procedures.
4. The CEE9901 application error *ESCAPE message is generated and sent to the message
queue of the call stack entry just above the control boundary.
5. For *STATUS and *NOTIFY error message types, see IBM i ILE Concepts, SC41-5606:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/ilec/sc415606.pdf
126 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
ILE condition handler interface
ILE conditions are OS/400 exception messages that are represented in a manner that is
independent of the system. An ILE condition token is issued to represent an ILE condition.
Condition handling refers to the ILE functions that allow you to handle errors separately from
language-specific error handling. You can use condition handling to handle error messages in
programs that are composed of modules written in different ILE languages.
The condition handler program accepts input parameters (as described in Table 4-1) that
indicate the kind of message it received and from which procedure.
CondToken Incoming message identification Char(12) See Table 4-2 on page 128.
ProcName Name of the procedure that Char(10) Reference to the name (a pointer).
generated the message
RtnAct Return action: What the system Integer(10) See the following note.
should do with the message
Note: RtnAct is a result code that the condition handler sends to the system to take a
specific action. The values for the result code are as follows:
10 Resume at the next instruction, and handle the condition as follows:
– Function Check (severity 4): The message appears in the job log.
– *ESCAPE (severity 2-4): The message appears in the job log.
– *STATUS (severity 1): The message does not appear in the job log.
– *NOTIFY: The default reply is sent and the message appears in the job log.
20 Percolate to the next condition handler.
21 Percolate to the next call stack entry. This action can skip a high-level language
condition handler for this call stack entry.
30 Promote to the next condition handler.
31 Promote to the next call stack entry. This action can skip a high-level language
condition handler for this call stack entry.
The description of the specific contents of the structure is shown in Table 4-2, and applies to
NewToken.
Note: Case, Severity, and Control are binary numbers that are placed in bit fields in a byte:
Case A 2-bit field that defines the format of the Condition_ID portion of the
token. ILE conditions are always case 1.
Severity A 3-bit binary integer that indicates the severity of the condition. The
Severity and MsgSev fields contain the same information. Actual used
numbers are 0 - 4.
Control A 3-bit field that contains flags that describe or control various aspects of
condition handling. The third bit specifies whether the Facility_ID has
been assigned by IBM.
The message type and message number uniquely identify the incoming message.
You can process a message so that you replace it with a different message and send it to the
system. This way of handling messages is called promotion. The result code is 30 or 31 in
this case. Promotion is accomplished by using the ILE API program CEENCOD.
128 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The CEENCOD API program builds a new message according to the parameters that are
listed in Table 4-3.
Table 4-3 Parameters for the CEENCOD API program to promote a message
Variable name Description RPG IV data type Values
A variable name or *OMIT Feedback information from Char(12) or omitted *OMIT or a 12-byte token
CEENCOD
Note: Message file QUSRMSG must be created by running the Create Message File
(CRTMSGF) command to enable promotion of USRxxxx messages. Message descriptions
that are prefixed with USR must be added in the message file by running the ADDMSGD
command. You can change the message file name to QxxxMSG, but the messages must
begin with xxx because you cannot specify the message file name in the parameters.
With this information, the CEENCOD API program builds a new condition token in the
NewToken variable, which is sent (promoted) to the caller as a new message by the condition
handler.
//================================================================
// Data definitions
//================================================================
// Constants 3
Dcl-c ResumeNextMI Const(10);
Dcl-c PercolCallStk Const(20);
Dcl-c PercolNextHnd Const(21);
Dcl-c PromoteCallStk Const(30);
Dcl-c PromoteNextHnd Const(31);
Note the following points, which correspond to the numbers shown in the right side of
Example 4-35 on page 129:
1. Parameters for the CEENCOD ILE API program are specified as a prototype in the /COPY
member file ILEERRPR. The last parameter (the feedback data structure) is omitted.
2. The condition token data structure is shown.
3. Result codes are defined as constants.
4. Parameter data for the CEENCOD API program is initialized. The MsgNumber and
FacilityId variables are overwritten dynamically.
5. Parameters for the condition handler are given here and in the ILEERRPR copy member.
130 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The procedural part of the ILEERRHDL program tests for error messages and takes
appropriate actions, as shown in Example 4-36.
CondTokenPtr = %addr(CondToken);
Select;
// Error MCH1211 3
// Attempt made to divide by zero for fixed point operation.
When MsgType = 'MCH' and MsgNum = X'1211';
RtnAct = ResumeNextMI;
Other; 4
RtnAct = PercolNextHnd;
EndSl;
Return;
end-Proc;
Note the following points, which correspond to the numbers shown in the right side of
Example 4-36:
1. If the incoming message is RNX1218 (a locked record), the condition handler decides to
change it into a new message USR1218 and promote it to the next call stack entry. The
new message is created by calling the CEENCOD ILE API program with parameters.
2. If the incoming message is RNX1021 (duplicate record), the condition handler changes
the message into USR1021 and promotes it to the next call stack entry.
3. If the incoming message is MCH1211 (divide by zero), the condition handler tells the
system to resume processing at the next instruction.
4. All the other messages are percolated to the next call stack entry handler.
This module is used in “Creating a program that uses the condition handler” on page 136.
132 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Before the condition handler is used, it must be registered. This action is done by calling the
CEEHDLR ILE API program with certain parameters.
When the condition handler is no longer needed, it can be unregistered by the CEEHDLU ILE
API program. Only parts of the E01REG module are shown.
Note: The variable CondHdlr@ that is used in the E01REG module breaks the style guide
rule found in 2.4.3, “Special characters” on page 29: Avoid using the “@” symbol to
indicate the variable is a pointer. A good method is to use the “Hungarian Notation,” where
the first character of the variable name indicates the data type of the variable, such as
pCustNbr or cActStsCde.
The data definition part of the E01REG module is shown in Example 4-38.
ctl-opt main(e01reg);
//================================================================
// File descriptions
//================================================================
dcl-F STOCK disk(*ext) keyed usage(*update : *output : *delete);
// Program prototypes
/COPY ILESRC,ILEERRPR 3
/COPY ILESRC,e0itemspr
//================================================================
// Data definitions
//================================================================
To unregister the condition handler, only the CondHdlr@ pointer is needed. The prototype
parameters can be found in the /COPY member in “ILE condition handler API prototypes” on
page 132.
The relevant parts of the procedural part of the E01REG module are shown in Example 4-39.
// local variables
dcl-s RC ind;
134 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Note the following points, which correspond to the numbers shown in the right side of
Example 4-39 on page 134:
1. The E01REG module registers the condition handler.
2. After some processing, a check for the item number is made by calling the findItems
procedure in the E01ITEMS module. In the module, a divide by zero error occurs (see the
source for E01ITEMS in Example 4-40). The condition handler ignores this message.
3. After some more processing, a record is read from the STOCK file for updating. If another
job has locked the same record, the RNX1218 error message is generated by the system
and sent to the program message queue. The condition handler handles it as required. It
replaces the message by the USR1218 message, which has a different text.
4. The condition handler is unregistered.
//================================================================
// Data definitions
//================================================================
Dcl-s ReturnValue ind;
Dcl-s Divisor packed(9 : 2);
Dcl-s unitPrice like(unitpr);
Dcl-s localItemNumber like(ITEMNBR);
exec sql
select unitpr into :unitPrice
from items
where ITEMNBR = :itemNumber;
if sqlcode <> 0;
ReturnValue = *on;
else;
// Generate an artificial error (divide by zero)
Return ReturnValue;
end-Proc findItem;
Try it yourself: You can create these two modules by using the two following commands.
The two physical files and the display file that are used in this example must be created
before creating the modules.
CRTRPGMOD MODULE(RPGISCOOL/E01REG) SRCFILE(RPGISCOOL/ILESRC) SRCMBR(*MODULE)
CRTRPGMOD MODULE(RPGISCOOL/E01ITEMS) SRCFILE(RPGISCOOL/ILESRC) SRCMBR(*MODULE)
These modules are used in “Creating a program that uses the condition handler” on
page 136.
However, in this example, the condition handler is fairly generic so that this same handler can
be useful for many different programs. Therefore, it makes more sense to place the condition
handler in a service program. In a real application environment, you would most likely place it
in a service program together with other modules, perhaps even other ILE condition handling
modules. However, for purposes of simplicity in this example, the condition handler module is
placed in a service program by itself. This service program is then bound by reference to the
application program. The application program contains the application function modules
bound together by copy.
Example 4-41 Commands to create a program that uses the condition handler
CRTSRVPGM SRVPGM(RPGISCOOL/SILEERRHDL) MODULE(RPGISCOOL/ILEERRHDL) EXPORT(*ALL)
ACTGRP(*CALLER)
Example 4-42 An error message after running a program with a condition handle
Application error. USR1218 unmonitored by E01REG at statement *N,
instruction X'0000'.
136 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
If you display the job log, the user screen shown in Example 4-43 opens.
Example 4-43 Job log messages resulting from a program call with errors
> call e01reg
Attempt made to divide by zero for fixed point operation. 1
Record 4 in use by job or transaction 093172/DIEPHUIS/QPADEV0008.
? C
Record 4 in use by job or transaction 093172/DIEPHUIS/QPADEV0008. 2
> s C
Another program holds the same record. Wait please, or call the system
administrator to find out who is it. 3
Application error. USR1218 unmonitored by E01REG at statement *N, 4
instruction X'0000'.
Note the following points that correspond to the numbers shown on the right side of
Example 4-43:
1. The divide by zero was generated by the FINDITEM procedure in module E01ITEMS in
the E01REG program, as shown in Figure 4-12.
To program . . . . . . . . . . : E01REG
To library . . . . . . . . . : RADREDBOOK
To module . . . . . . . . . : E01ITEMS
To procedure . . . . . . . . : FINDITEM
To statement . . . . . . . . : 134
Figure 4-12 Details of the divide by zero message
To program . . . . . . . . . . : E01REG
To library . . . . . . . . . : RADREDBOOK
To module . . . . . . . . . : E01REG
To procedure . . . . . . . . : E01REG
To statement . . . . . . . . : 177
Figure 4-13 Details of the record in use message
3. The message Another job holds the same record... is the message USR1218 that
replaced the message RNX1218 (which does not appear in the job log). Details are shown
in Figure 4-14.
To program . . . . . . . . . . : E01REG
To library . . . . . . . . . : RADREDBOOK
To module . . . . . . . . . : E01REG
To procedure . . . . . . . . : _QRNP_PEP_E01REG
To statement . . . . . . . . : *N
Figure 4-14 Details of the USR1218 message
138 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
4. The message Application error... is the special *ESCAPE message sent by the
system to the calling procedure QUOCMD above the control boundary of the QILE
activation group. Figure 4-15 shows the message details.
To program . . . . . . . . . . : QCMD
To library . . . . . . . . . : QSYS
Instruction . . . . . . . . : 01C8
Figure 4-15 Details of the CEE9901 message
Because no error was unhandled by the condition handler, no CPF9999 function check
message appears.
If you specified result code 10 (ResumeNextMI) for “other” messages in the condition handler,
the job log is what is shown in Figure 4-16, for example:
RtnAct = ResumeNextMI
Now, the function check escape messages are processed by the condition handler according
to the rules in “Error handling in ILE” on page 125.
Press Enter. On another session, enter the same commands and the same data. After
some time (60 seconds is default), the second call ends with a message on the command
line, as shown in Example 4-42 on page 136.
140 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Display file STOCKW
Example 4-45 shows the STOCK inventory entry display file.
* Key field
A K ITEMNBR
Try it yourself: Create the two physical files and the display file by running the following
commands:
CRTPF FILE(RPGISCOOL/STOCK) SRCFILE(RPGISCOOL/ILESRC) SRCMBR(*FILE)
CRTPF FILE(RPGISCOOL/ITEMS) SRCFILE(RPGISCOOL/ILESRC) SRCMBR(*FILE)
CRTPRTF FILE(RPGISCOOL/REPORTS) SRCFILE(RPGISCOOL/ILESRC)
These files are used by the two programs that are described in “Creating a program that
uses the condition handler” on page 136. The STOCK file can contain data that is specified
in “Physical file content: STOCK” on page 140. An example of the contents of file ITEMS is
listed in “File ITEMS” on page 118.
For more information about updating programs and service programs, and unresolved
references, see the IBM i Programming ILE Concepts, SC41-5606:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/ilec/sc415606.pdf
142 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The following API programs might be especially useful:
The List Module Information (QBNLMODI) API lists information about modules. The
information is placed in a user space that is specified by you. This API is similar to the
Display Module (DSPMOD) command. You can use the QBNLMODI API to do the
following tasks:
– List the defined symbols that can be exported to other modules.
– List the symbols that are defined externally to the module.
– List procedure names and their type.
– List objects that are referenced when the module is bound into an ILE program or
service program.
– List copyright information.
The List Service Program Information (QBNLSPGM) API gives information about service
programs, similar to the Display Service Program (DSPSRVPGM) command. The
information is placed in a user space specified by you. You can use the QBNLSPGM API
to do the following tasks:
– List modules that are bound into a service program.
– List service programs that are bound to a service program.
– List data items that are exported to the activation group.
– List data item imports that are resolved by weak exports that were exported to the
activation group.
– List copyrights of a service program.
– List the procedure export information of a service program.
– List data export information of a service program.
– List signatures of a service program.
The List ILE Program Information (QBNLPGMI) API gives information about ILE programs,
similar to the Display Program (DSPPGM) command. The information is placed in a user
space that is specified by you. You can use the QBNLPGMI API to do the following tasks:
– List modules that are bound into an ILE program.
– List service programs that are bound to an ILE program.
– List data items that are exported to the activation group.
– List data item imports that are resolved by weak exports that were exported to the
activation group.
– List the copyrights of an ILE program.
You can, for example, list signatures of service programs that are bound in a program by
using the QBNLSPGM API to get the “old” signatures. You can also list all “new”
signatures of these service programs by using the QBNLPGMI API and compare the two
lists to see whether they match. If there is a mismatch, you can trigger a new binding of
the program by using the CRTPGM command.
Be prepared to inspect lists of lists in some cases because the information that is retrieved by
these APIs is organized hierarchically.
144 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
5
The following link is for the Application programming interfaces section of the IBM i 7.2
Knowledge Center:
http://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/apiref/api.htm
Figure 5-1 Application programming interfaces topic in the IBM i 7.2 Knowledge Center
There are various ways to find the specific API that you need. If you can make a reasonable
guess as to how IBM classified the API, drilling down through the APIs by category can be
useful. The API finder can be used if you know the API name or keywords that describe the
API.
146 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
After you have found the API that you need, you must interpret the API description and either
find or create the prototypes and possibly the data structures that are required to use the API.
The C library and POSIX functions generally use C type prototypes and data structures. The
IBM i system interfaces are documented in a more language neutral way.
The QSYSINC library contains RPG IV source members that contain data structures and
some prototypes that can be used in some cases. However, the data structures do not
generally follow current RPG IV preferred practices, such as being qualified or using
templates. In addition, prototypes for the actual procedure or program are not generally
present. Fortunately, it is not difficult to create your own collection of source members after
you understand how to map either the documentation or C include members to RPG IV.
5.2 C functions
The C programming language runtime environment includes many useful functions, including
mathematical functions, and character and string handling. To use C functions from RPG IV,
you must create prototypes for the C functions that you use. Some C functions also use
specialized data structures for parameters, so you might need to create those as well.
Although RPG IV and C support the same data types, they are specified differently, as shown
in Table 5-1.
short int(5)
float float(4)
double float(8)
void * pointer
char char(1)
char[n] char(n)
The QRPGLESRC file in the QSYSINC library contains member SYSTYPES, which contains
a useful set of definitions that can be used to simplify the mapping of data types between
RPG IV and C. For example, i_int is defined to be int(10), so a field can be defined as
like(i_int) instead of using Table 5-1 for conversion.
RPG IV and C also have different parameter passing conventions. The RPG IV default is to
pass parameters by reference, and C passes parameters by value. To pass a parameter by
value in RPG IV, the value keyword must be coded on the prototype. The RPG IV options
keyword can also be used to help map parameter formats between RPG IV and C.
const char *parm3 parm3 pointer value Because the C parameter is defined as a
options(*string:*trim) constant, use option(*string:*trim) to trim a
character field and convert it to a C string
automatically.
C math function
double sin ( double );
RPG IV prototype
dcl-pr sin float(8) extproc(*cwiden:*dclcase);
x float(8) value;
end-pr;
Result
DSPLY +5.003474302699141E-001
Notes:
The C prototype can be found in QSYSINC/H(MATH). Although that prototype does not
name the parameter, and you can use *n instead of a name in the RPG IV prototype,
naming the parameters generally makes prototypes more easily understood.
Although the double type does not require widening, using the *cwiden option in
prototypes for C functions reduces the risk of missing it when it is necessary.
The *dclcase option tells the compiler that the actual name of the C function sin is in
lowercase instead of using the RPG IV default of converting function names to
uppercase. The actual function name of 'sin' in single quotation marks can be used as
well.
148 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
5.2.2 A more complex prototype mapping example
This example uses the C regular expression functions to parse an email address from a
character buffer.
/copy QSYSINC/QRPGLESRC,SYSTYPES
// regcomp() cflags
// regexec() eflags
150 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
preg likeds(C_regex_t) const;
errbuf char(256) options(*varsize);
errbuf_size like(size_t) value;
end-pr;
/copy cregexpr
// Main routine
dcl-proc main;
dcl-pi main extpgm('XMPREGEX');
end-pi;
return matchval;
end-proc;
Result
Figure 5-2 shows the result of the XMPREGEX example program.
Note: The original C prototypes are in QSYSINC/H(REGEX). The include member uses
the system provided type mappings from QSYSINC/QRPGLESRC(SYSTYPES) for the
mapping for the simple C types.
/include QSYSINC/QRPGLESRC,IFS
dcl-proc main;
dcl-pi main extpgm('IFSXMP');
end-pi;
152 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dcl-s rc like(i_int);
filename = '/richd/newfile.txt';
line = 'Test line' + STREAM_LINE_FEED; // Add line feed to text
rc = unlink(filename); // Delete existing file
fd = open(filename:
// Create an output file with a specified CCSID and
// translate the characters when they are written
O_CREAT + O_WRONLY + O_TEXTDATA + O_CCSID + O_TEXT_CREAT:
// Permissions for output file
S_IRWXU + S_IRGRP + S_IROTH:
// Output file will be UTF-8, but we will be writing data
// in the job CCSID
UTF8_CCSID:JOB_CCSID);
// When writing from a varchar buffer, we need the address of
// the data
rc = write(fd:%addr(line:*data):%len(line));
rc = close(fd);
end-proc;
Char(n) char(n)
Binary(4) int(10)
Binary(2) int(5)
Packed(m,na) packed(m:n)
PTR(SPP) pointer
a. ‘n’ needs to be large enough to contain the expected value. If it refers to a data structure, the
structure is described in the documentation.
Although the QSYSINC library contains data structure definitions for many of the system
interfaces, they might not be usable when the structures contain fields of undefined length
and they do not follow current RPG IV programming standards.
Dec Hex
Although the actual size of the exception data field is not defined, anything beyond the first
32,767 characters is not used. In practice, you can define the data structure to be much
smaller than that.
Note: The exception data (Data field) is defined to be 48 characters in this template. This
amount is large enough in most cases.
List APIs
Because many system interfaces must return arbitrary length lists of information, a standard
technique is to have the API return output data in a user space object that can be extended
up to a little less than 16 MB. These APIs use a standard format for header data in the user
space and specific data structures for list entries. The header data includes information about
where the output data is in the user space, the number of list entries, and the size of each
entry. You can use this information to move through the list by using data structures based on
pointer values.
The header structures are documented in the API Concepts topic of the IBM i 7.2 Knowledge
Center:
http://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/apiref/usfcommonstructure.
htm
154 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Example 5-5 shows a prototype template for generic header format 0100.
Example 5-6 shows a prototype for Create User Space (QUSCRTUS), Retrieve Pointer to
User Space (QUSPTRUS), and Delete User Space (QUSDLTUS).
For more information, see the List Objects (QUSLOBJ) API topic in the IBM i 7.2 Knowledge
Center:
http://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/apis/quslobj.htm
This website contains detailed information about the API parameters, all the formats that are
available, and descriptions of the data that is contained in each field of the data structures.
156 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Note: Fields marked as Reserved in the documentation do not need names, so you can
use the special value of *n instead of coming up with names for multiple reserved fields.
Note: Parameters that are documented as Char(*) generally include the *varsize option,
and optional parameters should include the *nopass option.
For this example, use the system List Objects API to create a simple procedure that returns a
list of objects with their types and descriptions that meet some user-specified selection
criteria. The procedure is designed so that it can be easily used by the Integrated Web
Services for IBM i tools.
When you create an API, you should create a source member that contains the data
structures and prototypes that needed by a user of the interface.
Note: The input fields are declared as const rather than value because you want this API
to be usable by the Integrated Web Services for IBM i tools, which do not support value
parameters that are not integers.
158 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Example 5-11 shows the module source.
After you write the code, create the module and the service program. Because you want your
API to be exported from the service program, specify the procedure name in your export
source member.
After you have your service program, add it to a binding directory so that it can be found and
automatically bound into calling programs:
CRTBNDDIR BNDDIR(RADREDBOOK/MYAPI)
ADDBNDDIRE BNDDIR(RADREDBOOK/MYAPI) OBJ((LISTOBJ *SRVPGM))
To use the API, users must include the prototypes and use the binding directory, as shown in
Example 5-13.
// Main procedure
dcl-proc main;
160 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dcl-pi main extpgm('LISTOBJTST');
end-pi;
The following website contains the documentation about how to use the wizards that are
provided by Integrated Web Services for IBM i to deploy web services:
http://www.ibm.com/systems/power/software/i/iws/
For this example, see the information that is specific to your interface.
Because you defined the first three parameters as const, the wizard sets them as input
parameters. The last two parameters default to input/output because there is no way to
identify them as output only in your RPG IV prototype. Change them to output only and tell
the wizard that the RETURNCOUNT parameter specifies the number of entries in the
OBJECTS parameter, as shown in Figure 5-4.
You navigate through a few more pages to finish deploying the service and test it.
162 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 5-5 shows the needed input parameters.
164 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
6
Over the years, a number of different ways to access databases on a wide variety of systems
have evolved. The IBM i system is no different. This chapter uses RPG IV as the common
denominator as you explore the database access methods.
While this chapter does provide an overview of what is possible for accessing the database
from RPG, for more information see SQL Procedures, Triggers, and Functions on IBM DB2
for i, SG24-8326, which is available at:
http://www.redbooks.ibm.com/abstracts/sg248326.html
Note: The idea of externalizing your input and output (I/O) to allow your RPG IV
applications to adapt quickly to new database requirements has been around since the first
edition of this book. This edition retains the section on externalizing from the first edition
(6.1, “Externalizing input and output” on page 166). This edition updates the examples to
be free form, but otherwise has only small changes in that section.
This section explains externalizing I/O and shows you some of the benefits of taking this
approach. Some of this information was described in the Chapter 2, “Programming RPG IV
with style” on page 11.
Why externalize
Why would you want to externalize? The following example provides good reasons.
Suppose that during discussions with your users it becomes apparent to you that business
needs have changed. After studying the new requirements for a while, you realize that you
must redesign the database to accommodate these changes.
If you chose the second option, it might be because your knowledge of your applications tells
you that the database I/O is spread liberally throughout the programs. This situation makes
the impact of a database change difficult to estimate and equally difficult to implement. Do not
feel embarrassed if you pick the second option. If you are entirely honest, this is the answer
that 90% or more of all RPG users would give.
Many of the applications that you use today have evolved from an original System/36 base.
Sometimes their history goes even further back in time to a System/3 or System/34. Even
applications that are designed for the System/38 (the IBM i system's predecessor) were often
forced to balance design and performance. If your application is relatively new, it was
probably created by duplicating portions of existing applications. Unfortunately, the benefits of
this approach (mainly speed of development) tend to be offset by the disadvantages
(perpetuation of problems). It is easy to overlook the disadvantages when you are pressed for
time to produce as quickly as possible, and you can always redo it later.
166 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Although many people have successfully implemented such schemes in RPG/400, doing so
requires that you make program calls to the I/O routines. By using the RPG IV
subprocedures, you can now provide this interface in a much more natural language fashion.
For example, a routine to read a record from the Customer Master file might be called
ReadCustomer and can be designed to either read a record by key or simply return the next
record in the sequence. It might look like the code that is shown in Example 6-1.
Alternatively, you might choose to use different names for the two functions, for example,
ReadCustKey and ReadNextCust. For background information and details about the
subprocedures, see Chapter 2, “Programming RPG IV with style” on page 11.
Customer: 12345
F3=Exit
Figure 6-1 CUSTDISP display file
To use the program, enter a 5-character account number and the program attempts to retrieve
the corresponding business address record. If the mailing address flag in the record indicates
that there is a separate mailing address, and that the record is retrieved and displayed. If
either record is missing, an appropriate error message appears.
The text Original Customer file that appears on the sample display is supplied by the
program. Each of the samples displays a different value.
Example 6-2 shows the source code for the display file.
168 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
A CUST# R B 6 16
A 8 16'Business Address'
A 8 48'Mailing Address'
A 10 5'Detail:'
A B_CUSNAME R O 10 16REFFLD(CUSNAME)
A B_ADDRESS1R O 11 16REFFLD(ADDRESS1)
A B_ADDRESS2R O 12 16REFFLD(ADDRESS2)
A B_CITY R O 13 16REFFLD(CITY)
A B_PROVINCER O 14 16REFFLD(PROVINCE)
A B_POSTCODER O 14 26REFFLD(POSTCODE)
A M_CUSNAME R O 10 48REFFLD(CUSNAME)
A M_ADDRESS1R O 11 48REFFLD(ADDRESS1)
A M_ADDRESS2R O 12 48REFFLD(ADDRESS2)
A M_CITY R O 13 48REFFLD(CITY)
A M_PROVINCER O 14 48REFFLD(PROVINCE)
A M_POSTCODER O 14 58REFFLD(POSTCODE)
A 16 5'F3=Exit'
The file contains two distinct record types: Business address records and Mailing address
records. The Business address records are indicated by a “B” in the field ADDRFLAG. The
Mailing address records are indicated by an “M” in the same field.
There is a Business address record for each customer. Mailing address records are optional.
If one exists, its presence is indicated by the appearance of an “M” in the MAILFLAG field of
the corresponding Business address record.
Dcl-ds D_Indicators; 1
// Response Indicators
Exit ind pos(3);
end-ds;
DoU Exit;
170 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Chain %kds(CustKey) Customer;
If %Found(Customer);
MailAddr = CustAddr;
Else;
// Error - no mailing record found - display error text
MailAddr = *Blanks;
M_CusName = MailAddrErr;
EndIf;
EndIf;
Else;
// Requested Customer not found - display error text
BusAddr = *Blanks;
MailAddr = *Blanks;
B_CusName = NotFound;
EndIf;
// Display results and accept next request
ExFmt DispRec;
EndDo;
*InLR = *On;
return;
Note the following points, which correspond to the numbers shown on the right side of
Example 6-4 on page 170:
1. This example takes advantage of the Indicator Data Structure (INDDS) facility. By using
INDDS, the programmer can assign names to response and conditioning indicators. The
From/To notation method must be used to indicate which indicator is being defined. In this
example, display file indicator 03 is associated with the name Exit.
The DDS keyword INDARA had to be specified on the display CUSTDISP to use this
facility.
2. To simplify the moving of data from the file to the panel, the Customer file record is
specified as an externally described data structure CustAddr. Data structures are also
defined for the Business address (BusAddr) and the Mailing address (MailAddr) by using
the same external definition, but using the Prefix keyword to generate different field
names. A quick check of the display file shows you that these names are the ones that are
used to display the data. This process allows the file to be read and all relevant fields to be
populated by simply moving the entire CustAddr DS to either BusAddr or MailAddr as
appropriate.
The database I/O functions are moved to a new service program that is named GETCUST2.
As you see later, after the separation is made, it becomes far easier to accommodate
changes in the database design.
Example 6-5 shows the source code for the subprocedure GetCust.
172 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// customer responsibility and depends on the customer's ability to
// evaluate and integrate them into the customer's operational
// environment. See Special Notices in redbook SG24-5402 for more.
//---------------------------------------------------------------------
/Copy RPGisCool/ExtIOSrc,GetCustPr 1
// key structure
dcl-ds custKey likerec(custdet : *key);
Else;
AddrFlag = Mailing;
Chain %kds(CustKey) Customer;
If %Found(Customer);
MailAddr = CustAddr;
EndIf;
EndIf;
// We have retrieved at least the Business address so return found
Return Found; 3
Else;
// Requested Customer not found - set up error text and return
BusAddr = *Blanks;
MailAddr = *Blanks;
B_CusName = NotFound;
Return NotFound; 3
EndIf;
end-proc GetCust;
Note the following points, which correspond to the numbers shown on the right side of
Example 6-5 on page 172:
1. You produce the prototype for the new subprocedure and added a /Copy statement to
bring that source member into the program.
2. You add the procedure interface. If you read Chapter 4, “An ILE guide for the RPG
programmer” on page 77, this task should be familiar. If not, consider reading that chapter
before continuing because these features are not described here.
3. You added the Return op-code. In this example, it returns a named indicator (data type ind)
to notify the caller of the success or failure of the read operation.
4. Because the purpose of the subprocedure is to retrieve the Customer information on
behalf of its caller, you find some way to accomplish this task. There are many different
methods that you can use, but in this example, use the ILE Import/Export capability. The
data that is read from the file is “exported” so that it can be “imported” by the main
program. This process allows the two procedures to share the data without it needing to
be passed as parameters. Some programmers do not like this approach, but in this limited
context, it seems to be the method to use. This chapter later describes alternative
methods that might be used.
In this example, the Import/Export data structures use the definitions of the underlying
databases to simplify the example. In practice, you might choose to develop a separate
composite definition for the express purpose of passing back the result set.
174 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// environment. See Special Notices in redbook SG24-5402 for more.
//---------------------------------------------------------------------
/Copy RPGisCool/ExtIOSrc,GetCustPr 2
// Response Indicators
Dcl-ds D_Indicators;
Exit ind pos(3);
end-ds;
ExFmt DispRec;
DoU Exit;
ExFmt DispRec;
EndDo;
*InLR = True;
return;
Note the following points, which correspond to the numbers shown on the right side of
Example 6-6 on page 174:
1. The first modification was to remove the file I/O logic and replace it with an invocation of
the GetCust subprocedure.
2. The next step was the addition of a /COPY statement to incorporate the prototype for
GetCust that was developed earlier.
3. To allow the program to access the data retrieved by GetCust, the Import keyword was
added to the definitions for the data structures BusAddr and MailAddr. Whenever GetCust
places data into these structures, it is “visible” to the program.
Database changes
For the sake of simplicity, the two new databases retain the original field names. The RPG IV
PREFIX keyword allows you to rename the fields at the program level. Each of the new files is
keyed only on the Customer number (CUST#).
As shown in Example 6-7, the source code for the CUSTOMERB file is almost identical to the
original CUSTOMER file (see “The initial database design” on page 169) with the exception
that the record format was renamed to CUSTDETB.
176 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The Mailing address file is similar. However, the Separate mailing address flag was removed
and its record format changed to CUSTDETM. Example 6-8 shows the source code.
Changes to GetCust
GETCUST3 contains a modified version of the original subprocedure GetCust. It now
retrieves the customer data by accessing the two separate databases.
/Copy RPGisCool/ExtIOSrc,GetCustPr
Else;
Chain CustNum CustomerM; 5
If %Found(CustomerM);
Else;
// Error - no mailing record found - set up error text
MailAddr = *Blanks;
M_CusName = MailAddrErr;
EndIf;
EndIf;
// We have retrieved at least the Business address so return found
Return Found;
Else;
// Requested Customer not found - set up error text and return
BusAddr = *Blanks;
MailAddr = *Blanks;
B_CusName = NotFound;
Return NotFound;
EndIf;
end-proc GetCust;
Note the following points, which correspond to the numbers shown on the right side of
Example 6-9 on page 177:
1. The first change was to modify the F specs to remove the old customer file and introduce
the new Business and Mailing address files. The same Prefix entry is used later for the
data structures BusAddr and MailAddr so that the data from each file is read directly into its
associated structure 3.
2. The definition of CustNum in the procedure interface parameter list was modified to
reference the field B_Cust# because the field Cust# that it referenced in GETCUST2 does
not exist in this version.
178 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Note: You might be tempted to go back and use B_Cust# as the reference field in
GETCUST2 to avoid this change. That would have been cheating. Looking back to
solving your problem is always easier. You will undoubtedly encounter similar “why
didn’t I think of that” situations in your own efforts.
3. The names of the new files are used to supply the external descriptions and the Prefix is
used to match the field names to the files.
4. The CHAIN operation was modified to use CustNum as the key because there is no longer
a need for a key list. The key list and the EVAL that set up the ADDRFLAG field were
removed. The customer records are now being read directly into their data structures, so
there is no need to move the data after the read.
5. Similar changes were made to the code handling the read of the mailing address.
Changes to SHOWCUST
The time has come to test the claim that having externalized the I/O minimizes the impact of
the changes to the calling programs even though you have only one calling program.
The only change that is required to produce SHOWCUST3 from the previous version was that
the external definitions for BusAddr and MailAddr were changed to use the new database
formats. That is the total extent of the changes as shown in Example 6-10.
If you used a different way of passing the information to the program, such as passing the
business address and mailing address structures as parameters on the procedure originally,
you cannot make any changes to the calling program.
Try it yourself: To re-create this example on your system, you must compile the
subprocedure and the main program by running the following commands:
CRTRPGMOD MODULE(RPGISCOOL/GETCUST3)
CRTRPGMOD MODULE(RPGISCOOL/SHOWCUST3)
CRTPGM PGM(RPGISCOOL/SHOWCUST3) MODULE(RPGISCOOL/SHOWCUST3 RPGISCOOL/GETCUST3)
CALL RPGISCOOL/SHOWCUST3
This book does not provide a solution to this particular problem, so consider it as an exercise
for you. However, when you create the Business address database, use a new name. Retain
the existing definition so that it can still be used to describe the Import/Export data structure
BusAddr. Handle the new Mailing address database the same way.
For example, with the I/O operations spread throughout the code, you probably never need to
add additional logic to your programs to determine whether the record being requested
already was read and locked by the previous request. If all I/O for the file is in one place, such
a change is trivial, which can offer significant performance benefits, particularly in a batch
environment.
Source code that contains embedded SQL statements must first be processed by an SQL
preprocessor. Its job is to replace SQL statements with calls to corresponding SQL function
programs. This preprocessor is a part of the IBM licensed product DB2 Query Manager and
SQL Development Kit for IBM i (5770ST1), which must be available during application
development. The runtime support is included in the operating system.
The request for additional chargeable software could be a reason for not using embedded
SQL. In that case, you can try to use Call Level Interface APIs, described in 6.4.2, “Writing a
DB2 CLI application” on page 204. These system APIs allow the use of SQL statements in
RPG IV program without needing an SQL preprocessor.
180 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Example 6-11 shows an example of an embedded SQL UPDATE statement.
The source member that contains the RPG IV program with embedded SQL statements must
be of type SQLRPGLE. Rational Developer for i gives you the option to call the CRTSQLRPGI
CL command, which is required to call the SQL preprocessor.
By default, the precompiler calls the host language compiler by using either the Create Bound
RPG Program (CRTBNDRPG) or Create RPG Module (CRTRPGMOD) command,
depending on the object value that is specified on the compile type parameter (OBJTYPE) of
the create SQL ILE RPG object command (CRTSQLRPGI).
The SQLCA, included in ILE RPG program, contains the fields shown in Example 6-12.
The SQLCOD and SQLSTT values are set by the database manager after each SQL
statement is run. A program should check the SQLCOD or SQLSTT value to determine
whether the last SQL statement was successful:
If the SQL encounters an error while processing the statement, SQLCOD is a negative
number, and the first two characters of the SQLSTT are not 00, 01, or 02.
If SQL encounters a warning but a valid condition while processing your statement,
SQLCOD is a positive number and the first two characters of SQLSTT are 01.
If the SQL statement is processed without encountering an error or warning condition, the
SQLCOD returned is 0 and SQLSTT is 00000.
The commonly used condition No record found returns the value SQLCOD = +100 or SQLSTT =
02000.
The Communication Area contains many fields with specific information related to the
executed SQL statement.
182 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The WHENEVER statement
As an alternative to checking the SQLCOD or SQLSTT values, a programmer can use the
SQL statement WHENEVER.
The WHENEVER statement causes SQL to check the SQLSTT and SQLCOD variable
values. It continues processing your program or branch to another area in your program if an
error, exception, or warning exists as a result of running an SQL statement. An exception
condition handling subroutine can then examine the SQLCOD or SQLSTT field to take an
action specific to the error or exception situation.
You can also specify the action that you want for a specific condition:
CONTINUE Program continues to the next statement.
GO TO label Program branches to a label (TAG) in the program.
According to this behavior, a SELECT statement puts all selected records in the result table.
Usually, a program has to transfer all these records from an SQL result table to a subfile so
the user can see them. To access a result table, SQL provides a technique called a cursor. A
cursor is used within an SQL program to maintain a position in the result table. SQL uses a
cursor to work with the rows in the result table and to make them available to the program. A
program can have several cursors, although each must have a unique name.
Exec Sql
Open C1;
Exec Sql
Fetch C1 Into :PartNo, :PartDs, :PartQy, :PartPr, :PartDt;
Exec Sql
Close C1;
SQL supports two types of cursors: Serial and scrollable. The type of cursor determines the
positioning methods that can be used with the cursor.
Serial cursor
A serial cursor is defined by default if the keyword SCROLL is not used. With a serial cursor,
each row of the result table can be fetched only once per OPEN of the cursor. When the
cursor is opened, it is positioned before the first row in the result table. With each FETCH
statement, the cursor is moved to the next row in the result table, which becomes the current
row. If host variables are specified (with the INTO clause on the FETCH statement), SQL
moves the current row's contents into the program's host variables.
This sequence is repeated each time a FETCH statement is issued until the end-of-data
(SQLCOD = 100) is reached. When you reach the end-of-data condition, close the cursor. You
cannot access any rows in the result table after you reach the end-of-data condition. To use
the cursor again, you must first close the cursor and then reissue the OPEN statement.
Scrollable cursor
With a scrollable cursor, the rows of the result table can be fetched many times. The cursor is
moved through the result table based on the position option that is specified on the FETCH
statement. When the cursor is opened, it is positioned before the first row in the result table.
With a FETCH statement, the cursor is positioned to the row in the result table that is
specified by the position option. That row becomes the current row. The following scroll
options, relative to the current cursor location in the result table, are used to position the
cursor when issuing a FETCH statement:
NEXT Positions the cursor on the next row. The default if no position is
specified.
PRIOR Positions the cursor on the previous row.
FIRST Positions the cursor on the first row.
LAST Positions the cursor on the last row.
BEFORE Positions the cursor before the first row.
AFTER Positions the cursor after the last row.
CURRENT Does not change the cursor position.
RELATIVE n Positions the cursor for n rows relative to the current position.
184 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
6.2.5 An embedded SQL program example
To illustrate the coding of embedded SQL statements in RPG IV program, this section shows
a simple example of a file maintenance program, where you can implement different SQL
statements (SELECT, INSERT, UPDATE, and DELETE).
The program uses the display file DSPFIL1 as an interface with a terminal user and handles
records in the database file PARTS. To help you understand the logic of a program, this
example provides the DDS definitions for both the database and display file.
The database file PARTS (Example 6-15) contains five fields, with one of them used as a key.
The display file DSPFIL1 (Example 6-16) contains two records and a subfile.
The keyword INDARA defined at the file level allows you to create an indicator data structure
in the program and to avoid the use of numeric indicators.
The subfile is used to display all records from the PARTS file and is populated by using the
FETCH statement.
Based on the function key that is pressed, different SQL statements in the program are run.
186 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
//--------------------------------------------------------------------
Dcl-s RecNum packed(3 : 0);
//--------------------------------------------------------------------
Exfmt DspRec1;
// Loop begin
DoW Not (Exit Or Cancel);
InvalidRec = *Off;
Select;
Exec Sql 4
Insert Into Parts
Values (:HostStr);
If SqlStt = '23505';
InvalidRec = *On;
Endif;
Exec Sql 8
Declare C1 Cursor For
Select * From Parts
Order by PartNum
For Fetch Only;
Exec Sql
Open C1; 9
Exec Sql
Fetch C1 Into :HostStr; 10
Exec Sql
Fetch C1 Into :HostStr; 10
EndDo;
Exec Sql
Close C1; 11
Exfmt SflRec2;
Exec Sql
Select * Into :HostStr 7
From Parts
Where PartNum = :PartNo;
If SqlStt = '02000';
InvalidRec = *On;
Else;
Exfmt DspRec2;
Select;
When Exit Or Cancel;
Leave;
Exec Sql
Update Parts 5
Set PartDes = :DspDes,
188 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
PartQty = :DspQty,
PartPrc = :DspPrc,
PartDat = :DspDat
Where PartNum = :DspNum;
Exec Sql
Delete From Parts 6
Where PartNum = :DspNum;
EndSl;
EndIf;
EndSl;
Exfmt DspRec1;
EndDo;
// Loop end
*INLR = *On;
This is a simple example to make it easier for you to understand how to use SQL in an RPG
program. To make this a better program, you could separate the SQL into a service program
with procedures for each of the functions. To fill the subfile, the procedure called would have
returned a result set that the main program processed, rather than doing one record at a time.
Then, the service program would have been available to other interfaces, such as a web
process, and a 5250 emulation display. The procedures of the service program could even be
defined as stored procedures and still be used by multiple interfaces.
One of the advantages of using stored procedures is for distributed applications. The
execution of one CALL statement on the application requester or client can perform any
amount of work on the application server. This configuration can reduce the data transfer
between the client and server and improve the performance of the distributed application.
190 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
You can define a stored procedure in two ways:
External procedure: An external procedure can be any supported high-level language
program (including ILE RPG) or a REXX procedure. The procedure does not need to
contain SQL statements, but it might contain SQL statements.
SQL procedure: An SQL procedure is defined entirely in SQL and can contain SQL
statements that include SQL control statements.
You must understand the following concepts when creating and calling stored procedures:
Stored procedure definition through the CREATE PROCEDURE statement
Stored procedure invocation through the CALL statement
Parameter passing conventions
Methods for returning a completion status to the program starting the procedure
Parameters can be defined as type IN, OUT, or INOUT. The parameter type determines when
the values for the parameters get passed to and from the procedure:
Indicates that the procedure is written in RPGLE. The language is important because it
affects the types of parameters that can be passed.
Indicates that the procedure is an external program that modifies SQL data.
Names the program that is called when the procedure is invoked on a CALL statement.
Example 6-18 uses as input the employee number and a rating value. The procedure uses a
CASE statement based on a rating value to determine the appropriate increase and bonus for
the update.
192 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
6.3.3 Starting a stored procedure and returning the completion status
To start a stored procedure, use the SQL CALL statement. This statement contains the name
of the stored procedure and any arguments passed to it. Arguments can be constants, special
registers, or host variables. Example 6-19 is an example of how to call a stored procedure
and pass two arguments.
The easiest way to return a completion status to the SQL programs that issue the CALL
statement is to code an extra INOUT type parameter and set it before returning it from the
procedure.
All these stored procedures are called from the program SPRCRUN.
Note: Example 6-20 is divided into several figures to fit into this book. However, these
figures should be considered one flow.
//--------------------------------------------------------------------
// Stored Procedures SPrcSel written with embedded SQL
//
exec sql
set option datfmt = *iso; 4
Exec Sql
Create or replace Procedure SprcSel 5
(InOut Action Char(1), InOut PartNum Numeric(5 , 0),
Out PartDes Char(25), Out PartQty Numeric(5 , 0),
Out PartPrc Numeric(6 , 2), Out PartDat Date)
Language RPGLE
Modifies SQL Data
Program type sub
External Name Progparts(progSel);
194 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
In PartPrc Numeric(6 , 2), In PartDat Date)
Language RPGLE
Modifies SQL Data
Program type sub
External Name progparts(ProgUpd);
Exfmt DspRec1;
// Loop begin
DoW Not (Exit Or Cancel);
InvalidRec = *Off;
Select;
Exec Sql
Call SprcUpd 8
(:Action, :PartNo, :DspDes, :DspQty, :DspPrc, :DspDat);
If Action = Error;
InvalidRec = *On;
Endif;
Exec Sql
Call SprcSel 12
(:Action, :DspNum, :DspDes, :DspQty, :DspPrc, :DspDat);
Exfmt SflRec2;
Exec Sql
Call SprcSel 11
(:Action, :DspNum, :DspDes, :DspQty, :DspPrc, :DspDat);
If Action = Error;
InvalidRec = *On;
Else;
Exfmt DspRec2;
Select;
When Exit Or Cancel;
Leave;
Exec Sql
Call SprcUpd 9
(:Action, :PartNo, :DspDes, :DspQty, :DspPrc, :DspDat);
EndSl;
EndIf;
EndSl;
Exfmt DspRec1;
EndDo;
// Loop end
*INLR = *On;
196 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
SPRCRUN program notes
The notes in this section refer to the numbers shown on the right side of Example 6-20 on
page 193:
1. On the file specifications, only the display file must be defined. The database file PARTS is
accessed through stored procedures and is not defined in the program.
2. On the data specifications, this example uses the indicator data structure DISPIND, where
all display file response and conditioning indicators are defined with meaningful names.
3. The parameter ACTION is used for communication with stored procedures. Defined
constants provide meaningful names for all of its values to improve the readability of the
program. The actions are defined in a common copy member so all the programs using
the procedures have access to the same list.
4. The SET OPTION SQL statement is used so that the date fields are defined as ISO
standard dates. Without this option, the CREATE PROCEDURE statements define the
date parameters to be whatever the date format of the job that is running the program is.
5. The stored procedure SPRCSEL should be created before it is used. It requires two
input/output parameters and four output parameters, and relates to the RPG IV procedure
PROGSEL in service program PROGPARTS. As you see later, this procedure contains the
embedded SQL statements SELECT, FETCH, OPEN, and CLOSE cursor to read data
from the PARTS file.
6. Another stored procedure, SPRCUPD, is created. It requires one input/output parameter
and five input parameters, and relates to the RPG IV procedure PROGUPD, also found in
the PROGPARTS service program. This procedure contains native file operations CHAIN,
WRITE, and UPDATE to show you that any procedure or program can be declared and
used as a stored procedure.
7. The stored procedure SPRCDEL is created. It has only one input parameter and is written
as a single SQL statement.
All these stored procedures can be created outside of the program by using interactive
SQL (STRSQL) or any other SQL interface. Created stored procedures are cataloged in
the SQL catalog, and can be used from any program by using the SQL CALL statement.
Notice that all of them use the CREATE OR REPLACE PROCEDURE statement, so you
can run this program multiple times without getting a procedure already exists SQL
error.
8. Stored procedure SPRCUPD is called to insert a record.
9. Stored procedure SPRCUPD is called to update a record.
10.Stored procedure SPRCDEL is called to delete a record.
11.Stored procedure SPRCSEL is called to read a single record.
12.Stored procedure SPRCSEL is called to read the first record.
13.Stored procedure SPRCSEL is called to read the next record.
Bridgeport notes
The notes in this section refer to the numbers shown on the right side of Example 6-21 on
page 198:
1. The allowed values of the ACTION parameter are given here. Now, everyone that uses
these prototypes can use the same constants for setting the action and checking the
return.
198 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
2. The prototype for the preadapt procedure with its parameters. As all but the first parameter
is used as input only, you might have used the CONST keyword with them. This example
uses the procedures as SQL procedures, so the prototypes are not needed by the calling
program. Having the prototypes means that you can use these procedures as either an
SQL procedure or as an RPG procedure.
3. The Pergolas procedure with its parameters, which are used as output only by the SQL
procedure.
Example 6-22 shows the code for the PROGPARTS service program.
/copy dbsrc,progpartpr 2
//*************************************************************************
// Procedure progSel
// Uses SQL to select a record from the parts table.
//*************************************************************************
dcl-proc progSel export; 3
dcl-pi ProgSel;
Action char(1);
PartNo zoned(5 : 0);
PartDs char(25);
PartQy zoned(5 : 0);
PartPr zoned(6 : 2);
PartDt date;
end-Pi;
//--------------------------------------------------------------------
Select;
When Action = SingleRec;
If SqlStt = '02000';
Action = Error;
Endif;
Exec Sql 5
Declare C1 Cursor For
Select * From Parts
Order by PartNum
For Fetch Only;
Exec Sql
Open C1; 6
If SqlStt = '02000';
Action = EndOfFile;
Exec Sql
Close C1; 8
Endif;
EndSl;
Return;
end-proc progSel;
//*************************************************************************
// Procedure progUpd
// Native I/O used to update or write a row into the parts table
//*************************************************************************
dcl-proc Progupd export; 9
dcl-pi ProgUpd;
Action char(1);
PartNo zoned(5 : 0);
PartDs char(25);
PartQy zoned(5 : 0);
PartPr zoned(6 : 2);
PartDt date;
end-pi;
200 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dcl-f Parts Disk(*ext) usage(*update: *output) Keyed qualified; 10
//--------------------------------------------------------------------
// Read a record from file Parts
Chain PartNo parts partsDS; 12
partsDS.PartNum = PartNo;
partsDS.PartDes = PartDs;
partsDS.PartQty = PartQy;
partsDS.PartPrc = PartPr;
partsDS.PartDat = PartDt;
Select;
When Action = UpdRecord And %FOUND(Parts); 13
// Update a record in file Parts
Update parts.partr partsDS; 14
When Action = AddRecord And Not %FOUND(Parts);
// Write a record into file Parts
Write parts.partr partsDS; 15
Other;
// Return error in other cases
Action = Error;
EndSl;
Return;
end-proc progUpd;
Try it yourself: You can try this example by compiling the code from this section on your
IBM i system. To create the programs, run the following commands:
CRTSQLRPGI OBJ(RPGISCOOL/SPRCRUN) SRCFILE(RPGISCOOL/DBSRC) +
COMMIT(*NONE) DATFMT(*ISO)
CRTSQLRPGI OBJ(RPGISCOOL/PROGPARTS) SRCFILE(RPGISCOOL/DBSRC) COMMIT(*NONE)
OBJTYPE(*MODULE)
CRTSRVPGM SRVPGM(RPGISCOOL/PROGPARTS) EXPORT(*ALL)
DB2 CLI is an alternative to embedded dynamic SQL. The important difference between
embedded dynamic SQL and DB2 CLI lies in how the SQL statements are invoked. On the
IBM i, this interface is available to any of the ILE languages, including RPG IV.
DB2 CLI also provides full Level 1 Microsoft Open Database Connectivity (ODBC) support,
plus many Level 2 functions. ODBC is based on the emerging X/Open and SQL Access
Group Call Level Interface specification.
The X/Open company and the SQL Access Group (SAG) are jointly developing a standard
specification for a callable SQL interface that is referred to as X/Open CLI or SAG CLI. The
goal of this interface is to increase the portability of applications by enabling them to become
independent of any one database server.
Microsoft developed a callable SQL interface called ODBC for Microsoft Windows based on a
preliminary draft of X/Open CLI. ODBC has expanded X/Open CLI and provides extended
functions that support additional capability. ODBC provides a Driver Manager for Windows,
which offers a central point of control for each ODBC.
202 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
6.4.1 Differences between DB2 CLI and embedded SQL
An application that uses an embedded SQL interface requires a precompiler to convert the
SQL statements into code. This code is then compiled, bound to the database, and run. In
contrast, a DB2 CLI application does not require precompilation or binding. Instead, it uses a
standard set of functions to run SQL statements and related services at run time.
DB2 CLI can run any SQL statement that can be prepared dynamically in embedded SQL.
This compatibility is ensured because DB2 CLI does not actually run the SQL statement itself,
but passes it to the DBMS for dynamic execution.
DB2 CLI is ideally suited for query-based applications that require portability and do not
require the APIs or utilities that are offered by a particular DBMS (for example, catalog
database, backup, and restore). This limitation does not mean that DBMS-specific APIs
cannot be called from an application by using DB2 CLI, but rather that the application no
longer is as portable.
In most cases, the choice between either interface is open to personal preference. Your
previous experience might make one alternative seem more intuitive than the other.
The functions must be called in the sequence that is shown or an error is returned. The tasks
of the application are explained here:
Initialization This task allocates and initializes some resources in
preparation for the main Transaction Processing task.
Transaction Processing This is the main task of the application. SQL statements are
passed to DB2 CLI to query and modify the data.
Termination This task frees allocated resources. The resources generally
consist of data areas that are identified by unique handles.
After the resources are freed, these handles can be used by
other tasks.
In addition to the three tasks listed here, there are general tasks, such as handling diagnostic
messages, that occur throughout an application.
204 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
6.4.3 Initialization and termination
Figure 6-3 shows the function call sequences for both the initialization and termination tasks.
The transaction processing task in the middle of the diagram is shown in Figure 6-4 on
page 206.
The initialization task allocates and initializes environment and connection handles. The
termination task frees them.
A handle is a variable that refers to a data object that is controlled by DB2 CLI. Using handles
frees the application from having to allocate and manage global variables or data structures,
such as the SQLDA or SQLCA, that are used in embedded SQL interfaces for IBM DBMS. An
application then passes the appropriate handle when it calls other DB2 CLI functions.
206 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Preparation and running of SQL statements
After a statement handle is allocated, you can use these methods of specifying and running
SQL statements:
Prepare and then run them in order:
a. Call SQLPrepare() with an SQL statement as an argument.
b. Call SQLBindParamete(), if the SQL statement contains parameter markers.
c. Call SQLExecute().
Run directly:
a. Call SQLBindParameter() if the SQL statement contains parameter markers.
b. Call SQLExecDirect() with an SQL statement as an argument.
The “prepare and run in order” method splits the preparation of the statement from the
execution. This method is used in the following situations:
The statement is run repeatedly (usually with different parameter values), which means
that you do not have to prepare the same statement more than once.
The application requires information about the columns in the result set before statement
execution.
The “run directly” method combines the preparation step and the execution step into one. This
method is used in the following situations:
The statement is run once, which means that you do not have to call two functions to run
the statement.
The application does not require information about the columns in the result set before the
statement is run.
Both methods can use parameter markers in place of an expression (or a host variable in
embedded SQL) in an SQL statement.
Parameter markers are represented by the “?” (question mark) character and indicate the
position in the SQL statement where the contents of application variables are substituted
when the statement is run. The markers are referenced sequentially, from left to right,
starting at 1.
The application variable is called a deferred argument because only the pointer is passed
when SQLBindParameter() is called. No data is read from the variable until the statement is
run, which applies to both buffer arguments and arguments that indicate the length of the data
in the buffer. You can use deferred arguments with the application to modify the contents of
the bound parameter variables and repeat the execution of the statement with the new values.
For Version 7 Release 1 and Version 7 Release 2, the SQLBindParameter() function allows
you to bind a variable of a different type from that required by the SQL statement. In this case,
DB2 CLI converts the contents of the bound variable to the correct type. The types must be
compatible or an error is returned. For more information, see DB2 for IBM i SQL Call Level
Interface, SC41-5806.
208 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Freeing statement handles
To end processing for a particular statement handle, call SQLFreeStmt(). This function can be
used to perform one or more of the following actions:
Unbind all columns.
Unbind all parameters.
Close any cursors and discard the results.
Drop the statement handle and release all associated resources.
When using DB2 CLI, transactions are started implicitly with the first access to the database
by using SQLPrepare(), SQLExecDirect(), or SQLGetTypeInfo(). The transaction ends when
you use SQLTransact() to either roll back or commit the transaction.In this case, any SQL
statements that are run between them are treated as one unit of work.
Return codes
Each function gives a return code to inform the program about possible errors or exceptions.
Table 6-1 describes all DB2 CLI function return codes.
To help address this concern, DB2 CLI provides symbolic names for the various data types,
and manages the transfer of data between the DBMS and the application. It also performs
data conversion if required. To accomplish this task, DB2 CLI must know both the source and
target data type, which requires the application to identify both data types by using symbolic
names.
The symbolic names are used in functions SQLBindParam(), SQLBindCol(), and SQLGetData()
to indicate the data types of the arguments.
SQL symbolic names are defined as integer values and should be declared in an include file
that is available to all applications.
The code snippet that is shown in Example 6-23 illustrates how symbolic names can be
defined.
210 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Dcl-c SQL_DOUBLE 8;
Dcl-c SQL_DATTIM 9;
Dcl-c SQL_VARCH 12;
Dcl-c SQL_BLOB 13;
Dcl-c SQL_CLOB 14;
Dcl-c SQL_DBCLOB 15;
Dcl-c SQL_DATALINK 16;
Dcl-c SQL_WCHAR 17;
Dcl-c SQL_WVARCHAR 18;
Dcl-c SQL_BIGINT 19;
Dcl-c SQL_BLOB_LOCATOR 20;
Dcl-c SQL_CLOB_LOCATOR 21;
Dcl-c SQL_DBCLOB_LOC 22;
Dcl-c SQL_UTF8_CHAR 23;
Dcl-c SQL_GRAPHIC 95;
Dcl-c SQL_VARGRAPHIC 96;
Dcl-c SQL_BINARY -2;
Dcl-c SQL_VARBINARY -3;
Dcl-c SQL_DATE 91;
Dcl-c SQL_TIME 92;
Dcl-c SQL_TIMESTAMP 93;
Dcl-c SQL_CODE_DATE 1;
Dcl-c SQL_CODE_TIME 2;
Dcl-c SQL_CODD_TIMESTMP 3;
Dcl-c SQL_ALLTYPES 0;
Dcl-c SQL_DECFLOAT -360;
Dcl-c SQL_XML -370;
6.4.7 Functions
All DB2 CLI functions are available as procedures in the service program QSQCLI in the
library QSYS. IBM i version7, Release 2 has 81 functions. They are described in the IBM
Knowledge Center at:
https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_73/cli/rzadpkickoff.htm
Note: All functions that are needed in this example are described in this section.
The output connection handle is used by DB2 CLI to reference all information that is related to
the connection, including general status information, the transaction state, and error
information.
212 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Argument Description Use RPG data type C data type
You can define various connection characteristics (options) in the application by using
SQLSetConnectOption().
The database name must already be defined on the system for the connection to work. On an
IBM i system, you can use the Work with Relational Database Directory Entries
(WRKRDBDIRE) command to determine which data sources are already defined and to
optionally define more data sources.
All connection and statement options that are set through SQLSetConnectOption() persist
until SQLFreeConnect() is called or the next SQLSetConnectOption()call.
DB2 CLI uses each statement handle to relate all the descriptors, result values, cursor
information, and status information to the SQL statement processed. Although each SQL
statement must have a statement handle, you can reuse the handles for different statements.
If the statement handle is used with a SELECT statement, SQLFreeStmt() must be called to
close the cursor before calling SQLPrepare().
A prepared statement can be run once or multiple times by calling SQLExecute(). The SQL
statement remains associated with the statement handle until the handle is used with
SQLExecDirect(), SQLColumns(), SQLSpecialColumns(), SQLStatistics(), SQLTables(), or
another SQLPrepare().
The SQL statement string can contain parameter markers. A parameter marker is
represented by a “?” (question mark) character and indicates a position in the statement
where the value of an application variable is substituted when SQLExecute() is called.
SQLBindParam() is used to bind (or associate) an application variable to each parameter
214 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
marker and to indicate whether any data conversion should be performed at the time the data
is transferred.
This function is also used to specify any data conversion that is required. It is called once for
each column in the result set that the application must retrieve.
SQLBindCol() must be called before SQLFetch() to transfer data to the storage buffers that are
specified by this call.
For this function, both rgbValue and pcbValue are deferred outputs, meaning that the storage
locations to which these pointers point are not updated until SQLFetch() is called. The
locations that are referred to by these pointers must remain valid until SQLFetch() is called.
The application calls SQLBindCol() once for each column in the result set that it wants to
retrieve. When SQLFetch() is called, the data in each of these bound columns is placed in the
assigned location (given by the pointers rgbValue and pcbValue). Columns are identified by a
number that is assigned sequentially from left to right, starting at 1.
For example, the size of variable for a numeric field, declared as (6,2), calculated by using this
formula, is:
6 * 256 + 2 = 1538
The application can query the attributes (such as data type and length) of the column by first
calling SQLDescribeCol() or SQLColAttributes(). This information can then be used to
specify the correct data type of the storage locations or to indicate data conversion to other
data types.
rgbValue Buffer with actual data for the Input or Pointer SQLPOINTER
parameter Output
216 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Argument Description Use RPG data type C data type
The SQL statement string can contain parameter markers. A parameter marker is
represented by a “?” (question mark) character and indicates a position in the statement
where the value of an application variable is substituted when SQLExecute() is called.
SQLBindParam() is used to bind (or associate) an application variable to each parameter
marker and to indicate whether any data conversion should be performed at the time the data
is transferred.
All parameters must be bound before calling SQLExecute(). After the application processes
the results from the SQLExecute() call, it can run the statement again with new (or the same)
values in the application variables.
The SQL statement string can contain parameter markers. A parameter marker is
represented by a “?” (question mark) character, and indicates a position in the statement
where the value of an application variable is substituted when SQLExecDirect() is called.
SQLBindParam() binds (or associates) an application variable to each parameter marker to
indicate whether any data conversion should be performed at the time the data is transferred.
All parameters must be bound before calling SQLExecDirect().
SQLFetch() can be used to receive the data directly into variables that you specify with
SQLBindCol(), or the columns can be received individually after the fetch by calling
SQLGetData(). Data conversion is also performed when SQLFetch() is called, if conversion
was indicated when the column was bound.
SQLFetch() can be called only if the most recently run statement on hstmt was a SELECT.
The number of application variables that are bound with SQLBindCol() must not exceed the
number of columns in the result set, or SQLFetch() fails.
If SQLBindCol() is not called to bind any columns, then SQLFetch() does not return data to the
application, but advances the cursor. In this case, SQLGetData() can be called to obtain all of
the columns individually. Data in unbound columns is discarded when SQLFetch() advances
the cursor to the next row.
When all the rows are retrieved from the result set, or the remaining rows are not needed, call
SQLFreeStmt() to close the cursor and discard the remaining data and associated resources.
218 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
SQLTransact(): Transaction management
SQLTransact() commits or rolls back the current transaction in the connection. All changes to
the database that are performed on the connection since the connect time or the previous call
to SQLTransact() (whichever is most recent) are committed or rolled back.
If diagnostic information that is generated by one DB2 CLI function is not retrieved before a
function other than SQLError() is called with the same handle, the information for the previous
function call is lost. This is true whether diagnostic information is generated for the second
DB2 CLI function call.
SQLFreeStmt() is called after running an SQL statement and processing the results.
220 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Here is the C syntax:
SQLRETURN SQLFreeStmt (SQLHSTMT hstmt,
SQLSMALLINT fOption);
After a successful SQLDisconnect() call, the application can reuse hdbc to make another
SQLConnect() request.
If this function is called when a connection still exists, SQL_ERROR is returned, and the
connection handle remains valid.
This function is the last DB2 CLI step that an application needs before terminating.
If this function is called when there is still a valid connection handle, SQL_ERROR is
returned, and the environment handle remains valid.
222 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
6.4.8 Introduction to a CLI example
To illustrate the use of CLI interface, this section uses a modified version of the example that
is shown in 6.2.6, “Source code for the SQLEMBED program” on page 186. Embedded SQL
statements are replaced with subprocedures that call CLI functions.
All subprocedure prototypes that are required for this example are made available through the
include file CLIPROTO. This file contains only selected functions that are used in the
example. It also contains definitions of constants for CLI return codes, connect attributes,
SQL data types, and other options used by CLI functions.
Example 6-24 shows the source code for the SQLCLI program.
//--------------------------------------------------------------------
Dcl-s RecNum zoned(3 : 0);
//--------------------------------------------------------------------
// Begin of CLI initialization tasks
//
MsgLenMax=SQL_MAXMSG+1; 3
// Allocate environment handle
SQL_RC=SQLAlcEnv(henv); 4
// Allocate connection handle
224 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
SQL_RC=SQLAlcCon(henv:hdbc); 5
// Set connection options
ConnOpt=SQL_ISOLVL; 6
IslLvl=SQL_NONE;
SQL_RC=SQLSetCnOp(hdbc:ConnOpt:IslLvlP);
// Connect to database
DBName='dbname'; 7
DBName=(%TRIM(DBName))+Zerobin;
DBUser='username';
DBUser=(%TRIM(DBUser))+Zerobin;
DBPwd='password';
DBPwd=(%TRIM(DBPwd))+Zerobin;
SQL_RC=SQLConnect(hdbc:DBNameP:SQL_NTS:
DBUserP:SQL_NTS:DBPwdP:SQL_NTS);
Exfmt DspRec1;
// Loop begin
DoW Not (Exit Or Cancel);
InvalidRec=*Off;
Select;
// * * Insert record
When InsertRec;
Clear DspDes;
Clear DspQty;
Clear DspPrc;
Clear DspDat;
DspNum=PartNo;
Exfmt DspRec2;
// Allocate statement handle for INSERT
SQL_RC=SQLAlcStmt(hdbc:hstmt); 9
// Bind value for 1. parameter marker PartNum
Param1P=%ADDR(DspNum); 10
ParamLen=5;
ParamDec=0;
SQL_RC=SQLBindPar(hstmt:1:SQL_DECIM:
SQL_DECIM:ParamLen:ParamDec:Param1P:
pcbValue);
// Bind value for 2. parameter marker PartDes
Param2P=%ADDR(DspDes); 10
ParamLen=25;
ParamDec=0;
SQL_RC=SQLBindPar(hstmt:2:SQL_CHAR:
SQL_CHAR:ParamLen:ParamDec:Param2P:
pcbValue);
// Bind value for 3. parameter marker PartQty
226 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// Bind 3. column to program variable DspQty
Param3P=%ADDR(DspQty); 12
ParamLen=1280;
SQL_RC=SQLBindCol(hstmt:3:SQL_DECIM:
Param3P:ParamLen:pcbValue);
// Bind 4. column to program variable DspPrc
Param4P=%ADDR(DspPrc); 12
ParamLen=1538;
SQL_RC=SQLBindCol(hstmt:4:SQL_DECIM:
Param4P:ParamLen:pcbValue);
// Bind 5. column to program variable DspDat
Param5P=%ADDR(DspDat);
ParamLen=10;
SQL_RC=SQLBindCol(hstmt:5:SQL_DATE:
Param5P:ParamLen:pcbValue);
// Execute Fetch in loop to read all records
DoU SQL_RC=SQL_NODATA;
SQL_RC=SQLFetch(hstmt); 13
If SQL_RC=SQL_NODATA;
Leave;
EndIf;
RecNum=RecNum+1;
Write SflRec1;
EndDo;
// Free statement handle for SELECT all records
SQL_RC=SQLFreeStm(hstmt:SQL_DROP); 14
Exfmt SflRec2;
// * * Display single record
Other;
// Allocate statement handle for SELECT single record
SQL_RC=SQLAlcStmt(hdbc:hstmt); 9
// Bind value for 1. parameter marker PartNum
Param1P=%ADDR(PartNo); 10
ParamLen=5;
ParamDec=0;
SQL_RC=SQLBindPar(hstmt:1:SQL_DECIM:
SQL_DECIM:ParamLen:ParamDec:Param1P:
pcbValue);
// Define and execute SELECT statement
SQLStm='SELECT PARTDES, PARTQTY, ' + 11
'PARTPRC, PARTDAT FROM RPGISCOOL.PARTS '
+ 'WHERE PARTNUM=?';
SQLStm=(%TRIM(SQLStm))+Zerobin;
SQL_RC=SQLExecDir(hstmt:SQLStmP:SQL_NTS);
// Bind 1. column to program variable DspDes
Param2P=%ADDR(DspDes); 12
ParamLen=25;
SQL_RC=SQLBindCol(hstmt:1:SQL_CHAR:
Param2P:ParamLen:pcbValue);
// Bind 2. column to program variable DspQty
Param3P=%ADDR(DspQty); 12
ParamLen=1280;
SQL_RC=SQLBindCol(hstmt:2:SQL_DECIM:
Param3P:ParamLen:pcbValue);
// Bind 3. column to program variable DspPrc
228 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// Bind value for 5. parameter marker PartNum
Param5P=%ADDR(DspNum); 10
ParamLen=5;
ParamDec=0;
SQL_RC=SQLBindPar(hstmt:5:SQL_DECIM:
SQL_DECIM:ParamLen:ParamDec:Param5P:
pcbValue);
// Define and execute UPDATE statement
SQLStm='UPDATE RPGISCOOL.PARTS SET ' 11
+ 'PARTDES=?, PARTQTY=?, PARTPRC=?, '
+ 'PARTDAT=? WHERE PARTNUM=?';
SQLStm=(%TRIM(SQLStm))+Zerobin;
SQL_RC=SQLExecDir(hstmt:SQLStmP:SQL_NTS);
// Free statement handle for UPDATE
SQL_RC=SQLFreeStm(hstmt:SQL_DROP); 14
// * * Delete record
When DeleteRec;
// Allocate statement handle for DELETE
SQL_RC=SQLAlcStmt(hdbc:hstmt); 9
// Bind value for 1. parameter marker PartNum
Param1P=%ADDR(PartNo); 10
ParamLen=5;
ParamDec=0;
SQL_RC=SQLBindPar(hstmt:1:SQL_DECIM:
SQL_DECIM:ParamLen:ParamDec:Param1P:
pcbValue);
// Define and execute DELETE statement
SQLStm='DELETE FROM RPGISCOOL.PARTS ' 11
+ 'WHERE PARTNUM=?';
SQLStm=(%TRIM(SQLStm))+Zerobin;
SQL_RC=SQLExecDir(hstmt:SQLStmP:SQL_NTS);
// Free statement handle for DELETE
SQL_RC=SQLFreeStm(hstmt:SQL_DROP); 14
EndSl;
EndIf;
EndSl;
Exfmt DspRec1;
// Loop end
EndDo;
//--------------------------------------------------------------------
// Begin of CLI Termination tasks
// Disconnect form database
SQL_RC=SQLDisconn(hdbc); 15
// Free connection handle
SQL_RC=SQLFreeCon(hdbc); 16
// Free environment handle
SQL_RC=SQLFreeEnv(henv); 17
// End of CLI Termination tasks
//--------------------------------------------------------------------
*INLR = *On;
230 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Source code for CLIPROTO prototypes
This source member is copied into the previous SQLCLI program. It contains subprocedure
prototypes for CLI functions that are used by this program.
Example 6-25 shows the source code for the CLIPROTO prototypes.
//********************************************************************
// Function Prototype ALLOCATE CONNECTION HANDLE
//
// SQLRETURN SQLAllocConnect (SQLHENV henv,
// SQLHDBC *phdbc);
//********************************************************************
// Return value = 0 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLAlcCon int(10) ExtProc('SQLAllocConnect');
// Environmental handle
envHndle int(10) value;
// Connection handle
connHndle int(10);
end-Pr;
//********************************************************************
// Function Prototype CONNECTION TO A DATABASE
//
// SQLRETURN SQLConnect (SQLHDBC hdbc,
// SQLCHAR *szDSN,
// SQLSMALLINT cbDSN,
// SQLCHAR *szUID,
// SQLSMALLINT cbUID,
// SQLCHAR *szAuthStr,
// SQLSMALLINT cbAuthStr);
//********************************************************************
// Function Prototype SET CONNECTION OPTION
//
// SQLRETURN SQLSetConnectOption (SQLHDBC hdbc,
// SQLSMALLINT fOption,
// SQLPOINTER vParam);
//********************************************************************
// Return value = 0 or 1 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLSetCnOp int(10) ExtProc('SQLSetConnectOption');
// Connection handle
connHndle int(10) value;
// Connect option
connectOption int(5) value;
// Pointer to the field containing the value of the connect option
option pointer value;
end-Pr;
//********************************************************************
// Function Prototype ALLOCATE STATEMENT HANDLE
//
// SQLRETURN SQLAllocStmt (SQLHDBC hdbc,
// SQLHSTMT *phstmt);
//********************************************************************
// Return value = 0 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLAlcStmt int(10) ExtProc('SQLAllocStmt');
// Connection handle
connHndle int(10) value;
// Handle of the SQL statement
SQLHndle int(10);
end-Pr;
//********************************************************************
// Function Prototype PREPARE SQL STATEMENT
//
// SQLRETURN SQLPrepare (SQLHSTMT hstmt,
// SQLCHAR *szSqlStr,
232 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// SQLINTEGER cbSqlStr);
//********************************************************************
// Return value = 0 or 1 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLPrepare int(10) ExtProc('SQLPrepare');
// Handle of the SQL statement
SQLHndle int(10) value;
// Pointer to the field containing the SQL statement
SQLStmt pointer value;
// Length of the SQL statement
SQLStmtLength int(5) value;
end-Pr;
//********************************************************************
// Function Prototype BIND BUFFER TO A PARAMETER MARKER
//
// SQLRETURN SQLBindParameter(SQLHSTMT hstmt,
// SQLSMALLINT iparm,
// SQLSMALLINT iType,
// SQLSMALLINT vType,
// SQLSMALLINT pType,
// SQLINTEGER pLen,
// SQLSMALLINT pScale,
// SQLPOINTER pData,
// SQLINTEGER BLen,
// SQLINTEGER *pcbValue);
//********************************************************************
// Return value = 0 or 1 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLBindPar int(10) ExtProc('SQLBindParameter');
// Handle of the SQL statement
SQLHndle int(10) value;
// Sequential parameter marker number
parmMarker int(5) value;
// Input/output type of the parameter (application)
appDataType int(5) value;
// Value type of the parameter (SQL)
SQLValueType int(5) value;
// Data type of the parameter (SQL)
SQLDataType int(5) value;
// Length of the parameter
parmLength int(10) value;
// Decimal number of the parameter
parmDecimal int(5) value;
// Pointer to the buffer containing the parameter
parmBuffer pointer value;
// Length of the buffer
bufferLength int(10);
// Length of the parameter (se alfanumerico) or 0
parmLength int(10);
end-Pr;
//********************************************************************
// Function Prototype BIND A COLUMN TO APPLICATION VARIABLE
//
// SQLRETURN SQLBindCol (SQLHSTMT hstmt,
// SQLSMALLINT icol,
//********************************************************************
// Function Prototype EXECUTION STATEMENT PREPARED USING SQLPREPARE
//
// SQLRETURN SQLExecute (SQLHSTMT hstmt);
//********************************************************************
// Return value = 0 or 1 (OK) or 100 (no data found)
// or -1 (error) or -2 (invalid handle)
Dcl-pr SQLExecute int(10) ExtProc('SQLExecute');
// Handle of the SQL statement
SQLHndle int(10) value;
end-Pr;
//********************************************************************
// Function Prototype EXECUTION DIRECT SQL STATEMENT
//
// SQLRETURN SQLExecDirect (SQLHSTMT hstmt,
// SQLCHAR *szSqlStr,
// SQLINTEGER cbSqlStr);
//********************************************************************
// Return value = 0 or 1 (OK) or 100 (no data found)
// or -1 (error) or -2 (invalid handle)
Dcl-pr SQLExecDir int(10) ExtProc('SQLExecDirect');
// Handle of the SQL statement
SQLHndle int(10) value;
// Pointer of the field containing the SQL statement
SQLStmt pointer value;
// Length of the SQL statement
SQLStmtlength int(5) value;
end-Pr;
//********************************************************************
// Function Prototype FETCH NEXT ROW
//
// SQLRETURN SQLFetch (SQLHSTMT hstmt)
234 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
//********************************************************************
// Return value = 0 or 1 (OK) or 100 (no data found)
// or -1 (error) or -2 (invalid handle)
Dcl-pr SQLFetch int(10) ExtProc('SQLFetch');
// Handle of the SQL statement
SQLHndle int(10) value;
end-Pr;
//********************************************************************
// Function Prototype LAST TRANSACTION
//
// SQLRETURN SQLTransact (SQLHENV henv,
// SQLHDBC hdbc,
// SQLSMALLINT fType);
//********************************************************************
// Return value = 0 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLTrans int(10) ExtProc('SQLTransact');
// Environmental handle
envHndle int(10) value;
// Connection handle
connHndle int(10) value;
// Action of last transaction: 0=COMMIT, 1=ROLLBACK
action int(5) value;
end-Pr;
//********************************************************************
// Function Prototype RETRIEVE ERROR INFORMATION
//
// SQLRETURN SQLError (SQLHENV henv,
// SQLHDBC hdbc,
// SQLHSTMT hstmt,
// SQLCHAR *szSqlState,
// SQLINTEGER *pfNativeError,
// SQLCHAR *szErrorMsg,
// SQLSMALLINT cbErrorMsgMax,
// SQLSMALLINT *pcbErrorMsg);
//********************************************************************
// Return value = 0 or 1 (OK) or 100 (no data found)
// or -1 (error) or -2 (invalid handle)
Dcl-pr SQLError int(10) ExtProc('SQLError');
// Environmental handle
envHndle int(10) value;
// Connection handle
connHndle int(10) value;
// Handle of the SQL statement
SQLHndle int(10) value;
// Pointer to the field that must contain the SQLSTATE
retSQLSTATE pointer value;
// SQLCODE returned from the database
retSQLCODE int(10);
// Pointer to the field that must contain the error message
errorMsg pointer value;
// Maximum length of the error message
errMaxLength int(5) value;
// Total length of the error message
//********************************************************************
// Function Prototype DEALLOCATION HANDLE OF THE SQL STATEMENT
//
// SQLRETURN SQLFreeStmt (SQLHSTMT hstmt,
// SQLSMALLINT fOption)
//********************************************************************
// Return value = 0 or 1 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLFreeStm int(10) ExtProc('SQLFreeStmt');
// Handle of the SQL statement
SQLHndle int(10) value;
// Mode of deallocation
deallocMode int(5) value;
end-Pr;
//********************************************************************
// Function Prototype DISCONNECTION OF A DATABASE
//
// SQLRETURN SQLDisconnect (SQLHDBC hdbc);
//
//********************************************************************
// Return value = 0 or 1 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLDisconn int(10) ExtProc('SQLDisconnect');
// Connection handle
connHndle int(10) value;
end-Pr;
//********************************************************************
// Function Prototype CONNECTION DEALLOCATION HANDLE
//
// SQLRETURN SQLFreeConnect (SQLHDBC hdbc);
//********************************************************************
// Return value = 0 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLFreeCon int(10) ExtProc('SQLFreeConnect');
// Connection handle
connHndle int(10) value;
end-Pr;
//********************************************************************
// Function Prototype DEALLOCATION ENVIRONMENTAL HANDLE
//
// SQLRETURN SQLFreeEnv (SQLHENV henv);
//********************************************************************
// Return value = 0 (OK) or -1 (error) or -2 (invalid handle)
Dcl-pr SQLFreeEnv int(10) ExtProc('SQLFreeEnv');
// Environmental handle
envHndle int(10) value;
end-Pr;
//********************************************************************
// RETCODE values
//********************************************************************
Dcl-c SQL_OK 0;
236 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Dcl-c SQL_OK_INF 1;
Dcl-c SQL_NODATA 100;
Dcl-c SQL_NEEDAT 99;
Dcl-c SQL_ERROR -1;
Dcl-c SQL_INVHAN -2;
//********************************************************************
// Valid values for connect attribute
//********************************************************************
Dcl-c SQL_AUTIPD 1001;
Dcl-c SQL_ISOLVL 0;
Dcl-c SQL_NONE 1;
Dcl-c SQL_CHANGE 2;
Dcl-c SQL_CS 3;
Dcl-c SQL_ALL 4;
Dcl-c SQL_RR 5;
//********************************************************************
// SQLFreeStmt option values
//********************************************************************
Dcl-c SQL_CLOSE 0;
Dcl-c SQL_DROP 1;
Dcl-c SQL_UNBIND 2;
Dcl-c SQL_RESET 3;
//********************************************************************
// SQLTransact option values
//********************************************************************
Dcl-c SQL_COMMIT 0;
Dcl-c SQL_ROLLBK 1;
Dcl-c SQL_COMMIT_HOLD 2;
Dcl-c SQL_ROLLBK_HOLD 3;
Dcl-c SQL_SVPT_NAME_RLS 4;
Dcl-c SQL_SVPT_NAME_RB 5;
//********************************************************************
// Standard SQL data types
//********************************************************************
Dcl-c SQL_CHAR 1;
Dcl-c SQL_NUMER 2;
Dcl-c SQL_DECIM 3;
Dcl-c SQL_INTEG 4;
Dcl-c SQL_SMINT 5;
Dcl-c SQL_FLOAT 6;
Dcl-c SQL_REAL 7;
Dcl-c SQL_DOUBLE 8;
Dcl-c SQL_DATTIM 9;
Dcl-c SQL_VARCH 12;
Dcl-c SQL_BLOB 13;
Dcl-c SQL_CLOB 14;
Dcl-c SQL_DBCLOB 15;
Dcl-c SQL_DATALINK 16;
Dcl-c SQL_WCHAR 17;
Dcl-c SQL_WVARCHAR 18;
Dcl-c SQL_BIGINT 19;
//********************************************************************
// C data type to SQL data type mapping
//********************************************************************
Dcl-c SQL_C_CHAR 1;
Dcl-c SQL_C_LONG 4;
Dcl-c SQL_C_SHRT 5;
Dcl-c SQL_C_FLOT 7;
Dcl-c SQL_C_DOUB 8;
Dcl-c SQL_C_DTTM 9;
//********************************************************************
// Generally useful constants
//********************************************************************
// Null Terminated String
Dcl-c SQL_NTS -3;
Dcl-c SQL_MAXMSG 70;
Dcl-c SQL_SQLSTS 5;
Try it yourself: You can try this example by compiling the code from this section on your
IBM i system. Change the DBName, DBUser, and DBPwd values in the SQLCLI source code to
valid values for your system, and then run the following commands to create the program:
CRTRPGMOD MODULE(RPGISCOOL/SQLCLI) SRCFILE(RPGISCOOL/DBSRC)
CRTPGM PGM(RPGISCOOL/SQLCLI) BNDSRVPGM(QSQCLI)
238 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Triggers can be used for different purposes:
Enforce business rules
Validate input data
Generate a unique value for a newly inserted row on a different file
Write to other files for audit trail purposes
Query from other files for cross-referencing purposes
Access system functions (for example, print an exception message when a rule is
violated)
Replicate data to different files to achieve data consistency
To use trigger support on the IBM i system, you must create a trigger program and add it to a
physical file.
A well-modernized application and database rarely need a trigger because the business rules
are enforced by the database and changes to the rules should be isolated to a few programs
or tables.
You also can create SQL triggers by running the CREATE TRIGGER SQL statement. SQL
allows more flexibility in the types of triggers and the options for when to use a trigger. For
information about how to use SQL triggers, see the DB2 for i SQL reference topic in the IBM i
7.2 Knowledge Center:
https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_72/db2/rbafzintro.htm
When adding an external trigger to a physical file, use the Add Physical File Trigger
(ADDPFTRG) command to associate a trigger program with a specific physical file. After the
association exists, the system automatically calls this trigger program when the specified
operation (the trigger event, described below) is initiated against these items:
A record in the physical file
A member of the physical file
Any logical file that is created over the physical file
You can associate up to 300 triggers with one file. Each external trigger has an associated
trigger event which can be insert, update, delete or read. In addition, each trigger has an
associated trigger time, which can be either after the event or before the event. Triggers
written in SQL have an additional trigger time available which is “instead of” the event.
Because this section only covers with writing external trigger programs, you might want to
read up on the use of “instead of” triggers in SQL Procedures, Triggers, and Functions on
IBM DB2 for i, SG24-8326, or in this article:
http://iprodeveloper.com/database/instead-triggers
The choice of the trigger time can be important, especially if the trigger program logic should
prevent the insert, update or delete from happening. Each event can call a trigger before the
change operation occurs, after it occurs, or both. A before trigger may be used to validate the
database rules and prevent a change from occurring. If the validation fails, the trigger signals
an exception informing the system that an error occurred in the trigger program. The system
then informs the application that the operation cannot be completed due to an error. Before
triggers are also often used to transform/correct data prior to inserting or updating the record.
To remove the association between trigger program and a file, use the Remove Physical File
Trigger (RMVPFTRG) command. After you remove the association, no action is taken if a
change is made to the physical file. There is also a Change Physical File Trigger
(CHGPFTRG) command and an ALTER TRIGGER SQL statement in IBM i 7.2 and later that
can be used to DISABLE and ENABLE external triggers.
For those who prefer a graphical interface, System i Navigator's Database Schemas contains
trigger support that can be used as an alternative to the CL commands mentioned here.
The Display File Description (DSPFD) command provides a list of the triggers that are
associated with a file. Specify TYPE(*TRG) or TYPE(*ALL) to get this list. Alternatively, the
database catalog table SYSTRIGGERS in library QSYS2 contains details of triggers on the
system. SQL or any query tool can also be used to produce a list of triggers for any file or
combination of files.
The change operation passes two parameters to the trigger program. From these inputs, the
trigger program can reference images of both the original and new record as needed. The
trigger program must be coded to accept these parameters.
240 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The static section contains information about the trigger such as the physical file name,
member name, trigger event, trigger time, and other details as shown in Table 6-19. It also
contains information about the location of the parts of the variable section. This information is
provided in the form of the number of bytes offset from the beginning of the trigger buffer.
The location of the variable section information can only be reliably determined by using the
offset information from the static portion of the trigger buffer. An example of doing this is
shown below.
In this discussion, the term “original record” typically refers to the values in the database
before the trigger event that fired the trigger (for example, an application update) while “new
record” refers to the values that were the result of the trigger event. However, because
multiple triggers may exist for the same event, it is possible that either or both the “original
record” and the “new record” could have been changed by previous trigger programs.
34 36 Char(3) Reserved
44 48 Char(4) Reserved
81 * Char(*) Reserved
The record null byte map contains the Null value information for each field of the record. Each
byte represents one field. The possible values for each byte are represented by the character
representation of zero and one:
0 Not Null
1 Null
End-Ds;
242 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// Before and After Record Images
Dcl-C Fields 5; // 4
*INLR = *On;
The notes in this section refer to the numbers in the right side of Example 6-26 on page 242:
1. The Procedure Interface parameter list defines two parameters: The trigger buffer and
trigger buffer length. Note the use of the LIKEDS keyword to define the trigger buffer.
Using LIKEDS makes the data structure implicitly qualified. Therefore, the first subfield in
the trigger buffer parameter must be referred to as TrigBuffer.TFileName.
2. The trigger buffer is defined as a data structure with the TEMPLATE keyword. The
TEMPLATE keyword ensures that this DS can be used only for reference, such as in the
LIKEDS above. The benefit of this coding technique is that any reference to any of the
subfields here without qualification (e.g., TFileName) results in a compile error because the
subfields of a template DS cannot be referenced. This technique prevents potential errors
by developers who forget to qualify the field names from the trigger buffer.
3. Two separate data structures are defined to hold the original (before) image of the record
format and the new (after) image of the record format. Depending on the type and timing
of the trigger, one of these might not be relevant or populated. For example, an insert
trigger will not have an original image and a delete trigger will not have an after image.
Both of these data structures are declared as externally described by using the EXTNAME
keyword so that a current and accurate format of the record format will be included at
compile time. They are also defined by using the BASED keyword so that they can be
mapped to the appropriate location in the trigger buffer using the offset values included
there to set the address of the pointer specified in the BASED keyword. Note that the
pointers are defined implicitly by use of the BASED keyword so there is no need to declare
them explicitly.
4. If the table or physical file involved with this trigger does not contain any null-capable
columns or fields, then the two data structures (OrgNullMap and NewNullMap) and the
constant (Fields) may be omitted. If there are null capable fields, then the constant should
contain the number of columns (the total number, not just the null-capable ones). This
configuration allows the program to access the information about whether a field in either
the original or new image is null or contains a value.
5. At the beginning of the logic, the before and after image data structures are mapped to the
appropriate location. Each of the basing pointers for those data structures is set to the
address that matches the address of the trigger buffer parameter plus the offset values
taken from the trigger buffer itself. TrigBuffer.TOrgOffSet contains the offset to the
original data image and TrigBuffer.TNewOffSet is the offset to the new data image.
Remember that some changes made to the layout of the trigger buffer information in V5R1
mean that using the offset values from the trigger buffer as done in this example is the only
reliable way to ensure correct mapping of both the original and new images of the data.
The locations of the start of those images should never use a hard-coded value.
6. If there are any null capable fields, then similar mapping for the original and new null
indicator maps needs to be done as shown in this example.
7. This example simply displays both the original and new values of four of the columns and
one null indicator in the record format after an update. This output can provide an easy
way to test that your record images are mapped correctly before adding the trigger logic.
After the basing pointers are set, for any field in the record, the program can access the
before (original) and after (new) value. Because back at step 3 we defined both data
structures to be QUALIFIED, the field/column names must use the DS qualification.
244 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
In this example, the original value of column name PARTNUM can be found in
ORGIMAGE.PARTNUM and the new/updated value can be found in
NEWIMAGE.PARTNUM. Any logic that needs to be performed in the trigger program can
now be written after setting the pointer addresses.
This example program can be used as a template for an external trigger program for any file.
Because it uses externally described data structures, there are very few changes necessary
to make this trigger work for a different file. For example, if you need an update trigger
program for the ORDERS file, only the following changes would be required:
Change the file name on the two EXTNAME parameters on the OrgImage and NewImage
data structure declarations.
Change the number of fields constant value to contain the number of fields in the
ORDERS file (if there are null capable fields in the file).
In addition, the name of the program changes in the PI (Procedure Interface) and the logic of
the trigger will be unique for each trigger program except for the first few statements. The
statements that set the basing pointers for the data structures containing the before and after
record images and, if needed, the data structures containing the null indicator maps should
remain in place for any trigger programs. After that, any necessary logic for the trigger can be
added.
Try it yourself: You can try this example by compiling the code from this section on your
IBM i system. You can use the following commands to create the program:
CRTBNDRPG PGM(RPGISCOOL/RPGTRIG) SRCFILE(RPGISCOOL/TRIGSRC) SRCMBR(RPGTRIG)
Because this is an update trigger program, the best way to test it is pick an easy way to
update data in the PARTS file. One simple way to accomplish this is to use DFU to change
the value of one of the fields being displayed in the trigger logic (PARTNUM, PARTQTY, or
PARTDAT) in a record. To do this, you can use the following command:
UPDDTA FILE(RPGISCOOL/PARTS)
For example, if you changed the PARTQTY from 123 to 122 in the record where PARTNUM
= 12345, you should see messages displayed that look something like this:
DSPLY Org PartNum 12345
DSPLY Org PartQty 123
DSPLY Org PartDat 1999-05-01
DSPLY Org PartDat Null? 0
DSPLY New PartNum 12345
DSPLY New PartQty 122
DSPLY New PartDat 1999-05-01
DSPLY New PartDat Null? 0
A logical unit of work (LUW) is defined as a group of individual changes to objects on the
system that should appear as a single atomic change to the user. Users and application
programmers call this concept a transaction.
Commitment control ensures that either the entire group of individual changes occurs on all
systems that participate or that none of the changes occur.
The typical example of changes that can be grouped is the transfer of funds from a savings to
a checking account. To the user, this is a single transaction. However, more than one change
occurs to the database because both savings and checking accounts are updated.
Commitment control can be used to design an application so that it can be restarted if a job,
an activation group within a job, or the system ends abnormally. The application can be
started again with assurance that no partial updates are in the database due to incomplete
LUWs from a prior failure.
Commitment control uses a journal to write entries that identify the beginning and ending of
commitment control, the start of a commit cycle, or a commit and rollback operation. It also
uses the before record images when a rollback operation is performed.
If only the after images are being journaled for a database file, when that file is opened under
commitment control, the system automatically starts journaling both the before and after
images. The before images are written only for changes to the file that occur under
commitment control.
When using SQL, a journal and journal receiver are automatically created as a part of the
SQL collection. Any SQL table that is created inside the SQL collection is automatically
journaled.
If the database file is created by using a native interface (CRTPF), you must take care to
activate journaling. Three steps are required:
1. Run the Create Journal Receiver (CRTJRNRCV) command to create a journal receiver
object.
2. Run the Create Journal (CRTJRN) command to create a journal object and associate it
with the journal receiver.
3. Use the Start Journal Physical File (STRJRNPF) command to start journaling for selected
files.
246 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
6.6.2 Using commitment control with RPG native file operations
To use commitment control in an ILE RPG program with native file operations, complete the
following steps:
1. On the system, complete the following steps:
a. Prepare for using commitment control to start journaling for all files that will be used
under commitment control.
b. Notify the system when to start and end commitment control. Use the CL commands
Start Commitment Control (STRCMTCTL) and End Commitment Control
(ENDCMTCTL).
2. In the ILE RPG program, complete the following steps:
a. Specify commitment control (COMMIT) on the file-description specifications of the files
that you want under commitment control.
b. Use the COMMIT operation code to apply a group of changes to files under
commitment control, or use the ROLBK (Roll Back) operation code to eliminate the
pending group of changes to files under commitment control in the current transaction.
When a program specifies commitment control for a file, the specification applies only to the
input and output operations that are made by this program for this file. Other programs that
use the same file without commitment control are not affected.
The COMMIT keyword has an optional parameter that allows conditional use of commitment
control. The ILE RPG compiler implicitly defines a 1-byte character field with the same name
as the one specified in the parameter. If the parameter is set to 1, the file runs under
commitment control.
On a calculation line, you can use two operation codes at the end of a related group of
changes to denote the end of a transaction:
The COMMIT operation tells the system that a group of changes to the files under
commitment control is completed.
The ROLBK operation eliminates the current group of changes to the files under
commitment control.
If the system fails, it implicitly issues a ROLBK operation. You can check the identity of the last
successfully completed group of changes by using the label that you specify in factor 1 of the
COMMIT operation code and the notify object that you specify on the Start Commitment
Control (STRCMTCTL) command.
Example 6-27 illustrates the use of commitment control in a program with native file
operations.
DCL-PI cmtExample;
ComitFlag ind;
END-PI ;
Update(E) PartR;
Update(E) TranR;
If ComitFlag = *on;
If %Error;
Rolbk;
Else;
Commit;
EndIf;
248 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Starting commitment control
The commitment control environment for ILE RPG programs with embedded SQL statements
is started automatically. DB2 for IBM i implicitly calls the CL command Start Commitment
Control (STRCMTCTL) and specifies the requested parameters NFYOBJ(*NONE) and
CMTSCOPE(*ACTGRP). The LCKLVL parameter is specified according to the lock level on
the COMMIT parameter of the Create SQL ILE RPG Object (CRTSQLRPGI) command,
which is used to create the program.
Both statements have an optional HOLD parameter, which can be useful when using the
cursor technique to fetch rows from a result table. If HOLD is specified, currently open cursors
are not closed, and all resources that are acquired during the unit of work are held. Locks on
specific rows and objects that are implicitly acquired during the unit of work are released.
If HOLD is omitted, cursors opened within this unit of work are closed unless the cursors were
declared with the WITH HOLD clause.
SQL_ATTR_COMMIT is a numeric constant with a value of 0 and signals to the CLI that you
want to define an option for commitment control. SQL_COMMIT_CHG is another numeric
constant with a value of 2 that defines the change isolation level.
The numeric constant SQL_COMMIT with value 0 defines a commit operation. The constant
SQL_ROLLBACK, with value 1, represents a rollback operation.
For more information about the globalization of your application, see the IBM i globalization
topic in the IBM i 7.2 Knowledge Center:
http://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_72/nls/rbagsglobalmain.htm
250 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
For programming samples and frequently asked questions (FAQs) and their answers, see the
following websites:
DB2 for i Frequently Asked Questions
http://www.ibm.com/common/ssi/cgi-bin/ssialias?subtype=ST&infotype=SA&htmlfid=P
OQ12365USEN&attachment=POQ12365USEN.PDF
IBM DB2 for i: Tips
http://www.ibm.com/systems/power/software/i/db2/support/tips/clifaq.html
IBM DB2 for i tips and techniques
http://www.ibm.com/systems/power/software/i/db2/support/tips/
IBM DB2 for i website
http://www.ibm.com/systems/power/software/i/db2/
For these reasons, this chapter handles the topic of exception and error handling in a more
comprehensive manner.
This chapter was originally published as an IBM Redpaper™ publication. The code samples
are updated for free-form RPG and a section about placing embedded SQL for producing
exceptions and errors is included. This chapter provides an overview of the traditional original
programming model (OPM) style methods of implementing exception/error handling and how
they change in the ILE environment. It also describes how you can use “pure” ILE methods to
replace some of the more traditional methods.
The following exception and error handling topics are described in this chapter:
Introduction to exception and error handling
What is an exception/error
Trapping at the program level
Trapping at the operation level
Subprocedures and exception/errors
ILE CEE APIs
Priority of handlers
The embedded SQL problem
Using percolation: Try, throw, and catch
Conclusion
The most common indication that you have an unexpected exception/error is that a program
fails with a CPF message. This situation can be disastrous if the program fails in a production
environment and a user receives the CPF message. The user either have a protracted
conversation with the help desk or simply press Enter and, by default, cancel the program.
Users should never, ever see the “two-line screen of death” that is caused by an unhandled
CPF message. Exception/error handling provides all the tools that you need to ensure that,
should a program fail, it terminates in an orderly fashion, displays a friendly message to the
user, notifies the help desk of the problem, and provides enough documentation for a
programmer to determine what caused the problem.
You might also want to trap exception/errors for a specific operation or group of operations
and handle them in the program. This situation applies especially to file operations where it is
reasonable for a program to handle a duplicate record condition on a WRITE operation or a
constraint violation on a DELETE operation.
If you do not want the RPG default error handler to handle exception/errors, RPG provides a
number of alternative methods:
Trap exception/errors for a complete program by using a *PSSR subroutine
Trap exception/errors for individual files by using an INFSR subroutine
Trap exception/errors for a single operation by using an E extender
Trap exception/errors for a group of operations by using a MONITOR group
Throughout this chapter, you will see how all of these alternative methods are handled,
starting with handling unexpected exception/errors at a program level. The examples illustrate
a simple method to consistently provide an orderly shutdown of the program.
254 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
7.3 Trapping at the program level
Start with the traditional methods of handling exception/errors in RPG programs that run in
OPM style (that is, that run in the default activation group). In 7.5, “Subprocedures and
exception/errors” on page 270, you see how most of these traditional methods can be
replaced in an ILE environment.
RPG differentiates between program exception/errors (divide by zero, array index error, and
so on) and file exception/errors (record lock, duplicate record, and so on).
The sample program ERROR01 that is shown in Example 7-1 demonstrates two conditions
that can cause an exception/error.
c = a/b;
*inLR = *on;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR01) SRCFILE(REDBOOK/EXCEPTSRC)
The first exception/error is on the divide operation, which results in an RNQ0102 Attempt to
divide by zero message being issued because b has a value of 0.
The second exception/error occurs on the CHAIN operation, which because an attempt is
made to retrieve a record before opening the file, results in an RNQ1211 I/O operation was
applied to closed file PRODUCT1 message being issued.
When you run this program, you see only the divide by zero error because after the error
occurs, you are presented only with options to cancel the program, continue at *GETIN (that is,
the start of the mainline), or obtain a dump.
c = a/b;
*inLR = *On;
begSR *PSSR;
(2) dump(A);
(3) if not PSSRDone;
PSSRDone = *On;
fatalProgram();
endIf;
(4) endSR '*CANCL';
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR02) SRCFILE(REDBOOK/EXCEPTSRC)
Later, you see how the *PSSR routine can be placed in a copy member. For now, the main
points to note about the inclusion of the *PSSR routine. The numbers on the left side of
Example 7-2 correspond to the following items:
1. The PSSRDone indicator and the prototype for FatalProgram usually are included in a
standard copy member as with prototypes and standard field definitions. The code for
FatalProgram is shown in Example 7-4 on page 257.
2. The (A) extender on the DUMP operation means that the dump A (always) takes place,
regardless of whether the program was compiled with the DEBUG option.
3. The reason for the PSSRDone indicator is in case there is an exception/error in the *PSSR
subroutine itself, which causes the *PSSR subroutine to be run, which fails and causes the
*PSSR subroutine to be run, which fails until the number of dump listings cause a serious
problem on the system.
4. One of the issues with a *PSSR is that when it is invoked by an error, it does not provide
an option to return to the statement in error. Therefore, it should be used as a means of
performing an orderly exit from the program. In theory, the EndSR for a *PSSR subroutine
can contain a return-point instruction, but most of them apply only if you use the RPG
cycle (which should never be used), and even then they are of little use. Here, the value of
*CANCL indicates that the program should cancel when it reaches the end of the
256 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
subroutine. However, this will not happen because the fatal program never returns from
the call.
Now, the divide by zero error results in the *PSSR subroutine being run as opposed to the
RNQ0102 message being issued.
FATALPGM (Example 7-4) is a CL program that gets called when a program fails with an
unexpected exception/error.
DspJobLog Output(*Print)
SndMsg ('It has all just gone horribly wrong!') ToMsgQ(QSysOpr)
Note: You can create this program by running the following commands:
CRTDSPF FILE(REDBOOK/FATALDSP) SRCFILE(REDBOOK/EXCEPTSRC)
CRTBNDCL PGM(REDBOOK/FATALPGM) SRCFILE(REDBOOK/EXCEPTSRC)
The program prints a job log, sends a message to the system operator, and either displays a
meaningful/helpful panel for the user (if running interactively) or sends an inquiry message to
the system operator (if running in batch). The panel is displayed or the message is sent
continuously until the job is canceled.
The complexity of the program that is called from the *PSSR subroutine is up to you. It does
not have to be a CL program. It could, for example, be another RPG program. You might
decide to have the program perform some sort of logging. You might pass an information data
structure or status codes as parameters. It all depends on the level of detail that you want.
With these changes, if a program fails in a production environment, the user is shown a
helpful panel and you have a program dump and a job log to help you identify the problem.
Another example of the type of functions that can be performed by a program is shown in
7.5.3, “Identifying a percolated message” on page 277.
Using a separate subroutine per file made a lot of sense when using the RPG cycle, but is of
little use within a full procedural environment. Each subroutine must provide an orderly means
of exiting the program and you already have that in your *PSSR routine. Therefore, one way of
handling file errors is to simply identify the *PSSR routine on the INFSR keyword for each file
in your program, as shown by the line with a “1” next to it in the program ERROR03 shown in
Example 7-5.
c = a/b;
*inLR = *On;
begSR *PSSR;
dump(A);
258 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
if not PSSRDone;
PSSRDone = *On;
fatalProgram();
endIf;
endSR '*CANCL';
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR03) SRCFILE(REDBOOK/EXCEPTSRC)
Now, with one exception, if there is an exception/error on a file operation, the *PSSR
subroutine is run.
The one exception is with the opening of files. Normally, the files are automatically opened by
the RPG cycle. But, this process occurs before any user code is started, so it cannot run
*PSSR if there is a problem during file open (for example, level check).
To trap file open errors, you must perform an implicit open of the file by specifying a
conditional open for the file (USROPN keyword on F Spec) and use the OPEN operation to
open the file (usually coded in the *INZSR subroutine).
The coding requirement for basic exception/error handling is to define an INFSR(*PSSR) for
every file and a copy directive to include the *PSSR subroutine.
In 7.5.2, “Trapping percolated errors” on page 275, you see how a *PSSR routine needs to be
coded in only one program when programs are running in an ILE environment.
Exception/errors can be trapped at the operation level by using error extenders or MONITOR
groups.
c = a/b;
*inLR = *On;
/include EXCEPTSRC,PSSRSTD
Note: You can create this program by running the following CL command:
CRTBNDRPG PGM(REDBOOK/ERROR04) SRCFILE(REDBOOK/EXCEPTSRC)
Note the following points in Example 7-6 on page 260 as indicated by the numbers on the left
side of the example:
1. The error extender on the CHAIN operation means that the program continues to run with
the next operation as opposed to running the subroutine that is specified on the INFSR
(that is, *PSSR) or failing with an error message.
2. You check for an error by using the %ERROR built-in function (BiF). In this example, the
DSPLY operation is used to display a message if there was an exception/error on the
CHAIN operation.
3. Processing continues with the next CHAIN operation which, as before, results in the
running of the *PSSR subroutine.
Using the error extender does not apply to all operation codes, but it does apply to all file and
“external related” operation codes (CALL, IN, and OUT). See 7.4.3, “Monitor groups” on
page 264 to see how you can use MONITOR for the other operation codes.
260 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Table 7-1 lists the operation codes that are eligible for an error extender or error indicator.
Table 7-1 Operation codes that are eligible for an error extender
ACQ CLOSE OCCUR REL TEST
Status codes correspond to RPG runtime messages in the message file QRNXMSG (for
example, Message RNQ0100 = Status Code 00100). You can view the messages by running
the following command:
DSPMSGD RANGE(*FIRST *LAST) MSGF(QRNXMSG) DETAIL(*FULL)
Table 7-2 lists some of the more commonly used status codes.
Code Description
01021 Tried to write a record that already exists (file being used has unique keys and key is
duplicate, or attempted to write duplicate relative record number to a subfile).
For a full list of status codes, see the RPG IV File and Program Exception/Errors topic in the
IBM i 7.2 Knowledge Center:
http://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_72/rzasd/flpgxpt.htm#flpgxpt
It is useful to define a copy member that contains the definition of named constants that
correspond to the status codes. This definition provides for “self-documenting” code. For
example, the copy member ERRSTATUS contains the definitions that are shown in
Example 7-7 that correspond to the status codes that are shown in Table 7-2 on page 261.
262 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The program ERROR04 simply assumed that the error being trapped was for the status 1211
(File not open). Program ERROR05, shown in Example 7-8, shows how to ensure that only a
status of 1211 is being trapped.
c = a/b;
*inLR = *On;
/include EXCEPTSRC,PSSRSTD
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR05) SRCFILE(REDBOOK/EXCEPTSRC)
Note the following points in Example 7-8 as indicated by the numbers on the left side of the
example:
1. A /COPY directive includes the copy member (ERRSTATUS) containing the named
constants for the status codes.
2. The IF condition for the %ERROR BiF now uses the %STATUS BiF to check whether the
status of the file indicates a file not open condition. You should always specify the file
name with the %STATUS BiF to enable you to differentiate between the current statuses of
different files. It is a preferred practice to do this even if you only have one file coded in the
program because you might need other files later.
3. Otherwise, the program falls back on the *PSSR routine.
RPG contains a MONITOR group, which you can use to monitor a number of statements for
potential errors, as opposed to checking them one at a time. In concept, it is similar to a
SELECT group. It has the structure shown in Example 7-9.
264 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dcl-S PSSRDone ind;
dcl-S dummyCode like(prodCd);
dcl-S a int(10) inz(20);
dcl-S b int(10);
dcl-S c int(10);
/include EXCEPTSRC,ERRSTATUS
(1) monitor;
c = a/b;
*inLR = *on;
/include EXCEPTSRC,PSSRSTD
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR06) SRCFILE(REDBOOK/EXCEPTSRC)
A number of changes have been made to the sample program, which are indicated by the
numbers on the left side of Example 7-10 on page 264:
1. The code for which you want to trap exceptions errors is placed in a MONITOR group. If an
exception/error is issued for any of the operations (between the MONITOR and the first
ON-ERROR operation), control passes to the first ON-ERROR statement.
2. The Error Extender is removed from the first CHAIN operation because any error on the
CHAIN is now caught by the MONITOR.
3. If the exception/error is File not open, a message is displayed and processing continues
at the ENDMON operation.
4. If the exception/error is “Divide by zero”, “Array index error” or “Decimal data error”,
a message is displayed and processing continues at the ENDMON operation.
A MONITOR group also applies to code that is run in called subroutines. The MONITOR
group in ERROR07 (Example 7-11) works in the exact same way as the MONITOR group in
ERROR06.
/include EXCEPTSRC,ERRSTATUS
monitor;
c = a/b;
on-Error ERR_NOT_OPEN;
dsply 'The file has not been opened';
on-Error ERR_DIVIDE_BY_ZERO:
ERR_ARRAY_INDEX:
ERR_DECIMAL_DATA;
dsply 'You got a number wrong!';
on-Error *FILE;
dsply 'You are doing something weird to a file';
on-Error *ALL;
dsply 'Who knows what happened?';
exSR *PSSR;
endMon;
*inLR = *On;
/include EXCEPTSRC,PSSRSTD
266 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR07) SRCFILE(REDBOOK/EXCEPTSRC)
The file processing is placed in the subroutine SubMonitor (noted by the number 2 in
Example 7-11 on page 266). Although the code of the subroutine is not physically in the
MONITOR group, the fact that the EXSR is in the MONITOR group (noted by the number 1 in
Example 7-11 on page 266) means that all of the code in the subroutine is monitored. This
monitoring also applies to any subroutines that were run from the called subroutine, as shown
in Example 7-12.
/include EXCEPTSRC,ERRSTATUS
monitor;
c = a/b;
exSR subMonitor1;
on-Error ERR_NOT_OPEN;
dsply 'The file has not been opened';
on-Error ERR_DIVIDE_BY_ZERO:
ERR_ARRAY_INDEX:
ERR_DECIMAL_DATA;
dsply 'You got a number wrong!';
on-Error *FILE;
dsply 'You are doing something weird to a file';
on-Error *ALL;
dsply 'Who knows what happened?';
exSR *PSSR;
endMon;
*inLR = *on;
begSR subMonitor1;
c = a/b;
exSR subMonitor2;
endSR;
begSR subMonitor2;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR08) SRCFILE(REDBOOK/EXCEPTSRC)
Although a MONITOR group can trap exception/errors from a subroutine that is run from
within the group, the same cannot be said for a subprocedure that is called from within the
group. A call to a subprocedure is akin to a call to a program, which means that it results in a
new entry in the call stack. If the code in a subprocedure fails, you can trap the error on the
call to the subprocedure by checking for a status of 00202 “Called program or procedure
failed”. You can see how exception/error handling works with subprocedures in 7.5,
“Subprocedures and exception/errors” on page 270.
The PSDS is identified by an SDS definition (number 8 in Example 7-13) and there can only
be one per program. An INFDS (number 1 in Example 7-13) is associated with a specific file
by using the INFDS keyword on the F Spec. The INFDS must be unique for a file, so you
cannot share a file information data structure between two files. These data structures
contain an immense amount of information about the program and files and not just
information about exception/errors.
For a complete list of the contents of the data structures, see the RPG IV Concepts → File
and Program Exception/Errors section in the IBM i Version 7.2 Programming IBM Rational
Development Studio for i ILE RPG Reference guide:
http://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_72/rzasd/sc092508.pdf?view=kc
Example 7-13 shows an example of a program status and a file information data structure.
268 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
(2) dcl-Ds DBFileInfDS noOpt qualified;
(3) status *status;
(6) lockedRecords int(5) pos(377);
(6) RRN int(5) pos(397);
end-Ds;
Note the following points in Example 7-13 on page 268 as indicated by the numbers on the
left side of the example:
1. The INFDS keyword identifies the data structure to be used as an INFDS.
2. The information data structure should normally be defined by using the NOOPT keyword,
which ensures that, when a program is optimized, the latest values are applied in the data
structure. The optimizer can keep some values in registers and restore them to storage
only at predefined points during normal program execution. The use of NOOPT ensures
that the values are always current. Although the use of NOOPT probably makes no
difference for programs or modules that are compiled by using default optimization, specify
it for file or program information data structures in case the program or module is ever
re-created for a higher optimization level. For more information about optimization, see the
OPTIMIZE parameter on the CRTBNDRPG and CRTRPGMOD CL commands. The
INFDS is qualified to ensure that there are not any name conflicts with subfields in the
data structure.
3. Some of the definitions in the data structures are no longer required because they were
replaced by BiFs, for example, the status code for a file or program can be determined by
using the %STATUS BiF.
4. The MSGID fields identify the relevant CPF or MCH error message that is received by the
program. Note the use of the OVERLAY keyword to avoid the need to use From/To
positioning on the D-spec.
5. Parts of the file information data structures are different depending on the type of the file.
For a display file, the CURSORROW field identifies the row on the panel at which the
cursor was placed when the panel was input. The CURSORCOL field identifies the
column on the panel at which the cursor was placed when the panel was input. The
MIN_RRN field identifies the RRN of the subfile record at the top of the panel when it was
input. This process is a lot more dependable than using the SFLCSRRRN keyword
in DDS.
6. For a database file, the LOCKRECORDS field identifies the number of records currently
locked. The RRN field identifies the relative record number of the current record.
7. SDS identifies the data structure as being a program status data structure.
8. PROCEDURENAME identifies the name of the program. Keywords can be used in place
of the length and overlay positions for certain information, such as *PROC for the procedure
name.
Program and file information data structures are another item that lend themselves to
standard definitions in a copy member. Your first inclination might be to include the complete
definition of the data structures, but this is not a preferred practice. The standard definitions
should contain only the minimum information that you require. Information in the data
structures take time to obtain, so you should not define it unless you might use it.
The other main consideration is that there is a major difference in the way exception/error
handling works in the ILE runtime environment as opposed to OPM. In an ILE environment,
exception/errors are “percolated” up the call stack.
7.5.1 Percolation
An overview of percolation is provided in 4.2.6, “Call stack and error handling” on page 124,
This section provides a brief explanation of it from the point of view of handling
exception/errors in an application.
In an OPM environment, a program that is down the call stack receives an exception/error. If
the program does not have any exception/error handling, the RPG default handler issues a
function check message.
In an ILE environment, a subprocedure or a program that is down the call stack receives an
exception/error. If the subprocedure or program does not have any exception/error handling
specified, the message is sent back up the call stack to the calling procedure. If the calling
procedure does not have any exception/error handling defined, the message is again sent
back up the call stack to the calling procedure, and so on, until the activation group control
boundary is reached.
Messages are percolated to an activation group control boundary, which can be the program
that originally initiated the AG. For more information, see the Control Boundaries section in
the ILE Advanced Concepts manual:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/ilec/ileac.htm
If none of the subprocedures or programs in the call stack have exception/error handling
defined, the message is resignaled as an exception. Control then returns to the subprocedure
or program that originally received the message and an attempt is made to call the RPG
default handler. However, subprocedures do not have a default RPG handler, which means
that the call to the subprocedure causes an exception/error that, in turn, is percolated up the
call stack. This process continues until a default handler can be called. This process means
that a function check cannot be issued until an entry in the call stack is a mainline program
(which has a default error handler).
270 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The following examples show how exception/errors are percolated when none of the
subprocedures or programs are using exception/error handlers (*PSSR, Error Extender, or
Monitor Group).
Example 1
ERROR10 is another version of the sample program that is used throughout this chapter, and
is shown in Example 7-14.
dcl-Pr ProgramProc;
end-Pr;
dcl-Pr FileProc;
end-Pr;
(2) programProc();
fileProc();
*inLR = *on;
dcl-Proc programProc;
dcl-Pi programProc;
end-Pi;
(3) c = a/b;
end-Proc;
dcl-Proc fileProc;
dcl-Pi fileProc;
end-Pi;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR10) SRCFILE(REDBOOK/EXCEPTSRC)
When you run this program, you might expect it to fail with the “RNQ0102 Attempt to divide
by zero” message, but it does not. Instead, the program fails with a “RNQ0202 The call to
PROGRAMPRO ended in error (C G D F)” message, noted by 2 in Example 7-14 on page 271.
You see the information shown in Example 7-15 in the job log.
The divide by zero message was percolated up the call stack. Because there is no
exception/error handler and the ProgramProc subprocedure does not have a default RPG
exception/error handler, the call to ProgramProc ends in error. This process causes another
exception/error, which results in a function check because the call was issued from the
mainline.
Example 2
ERROR11 (Example 7-16) is similar to ERROR10. The only difference is that the
subprocedure ProgramProc is called from the subprocedure Nest2, which is called from the
subprocedure Nest1.
(1) nest1();
fileProc();
*inLR = *on;
dcl-Proc nest1;
dcl-Pi nest1 end-Pi;
nest2();
end-Proc;
dcl-Proc nest2;
dcl-Pi nest2 end-Pi;
programProc();
end-Proc;
dcl-Proc programProc;
272 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dcl-Pi programProc end-Pi;
c = a/b;
end-Proc;
dcl-Proc fileProc;
dcl-Pi fileProc end-Pi;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR11) SRCFILE(REDBOOK/EXCEPTSRC)
This time the program fails with a RNQ0202 The call to NEST1 ended in error (C G D F)
message, as noted by 1 in Example 7-16 on page 272. You see the information shown in
Example 7-17 in the job log.
The process here is the same as it was in ERROR10. All that was introduced is more levels in
the call stack. Even though there are more levels involved, the result is the same and the
function check is issued for the mainline call to NEST1.
Example 3
The third example splits the sample program into two programs. ERROR15 (Example 7-18)
starts an activation group and makes a dynamic call.
(3) nest1();
*inLR = *on;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR15) SRCFILE(REDBOOK/EXCEPTSRC)
Example 7-19 shows the ERROR16 program that is called from ERROR15.
(2) nest2();
fileProc();
*inLR = *on;
dcl-Proc nest2;
dcl-Pi nest2 end-Pi;
programProc();
end-Proc;
dcl-Proc programProc;
dcl-Pi programProc end-Pi;
c = a/b;
end-Proc;
dcl-Proc fileProc;
dcl-Pi fileProc end-Pi;
274 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR16) SRCFILE(REDBOOK/EXCEPTSRC)
Note the following points in Example 7-19 on page 274 as indicated by the numbers on the
left side of the example:
1. The program is created with an activation group of *CALLER, which means this program
runs in the same activation group (TEST3 in this example) as the procedure that called it.
2. The program follows the same broad logic as before. The mainline issues procedure calls
to Nest2 and FileProc. Nest2 calls ProgramProc.
When the first program is called, the second program fails with a RNQ0202 The call to NEST2
ended in error (C G D F) message, as noted by 2 in Example 7-19 on page 274. You see
the information in Example 7-20 in the job log.
Example 7-20 Job log entry for call to Nest2 ended in error
Attempt made to divide by zero for fixed point operation.
Function check. MCH1211 unmonitored by ERROR16 at statement 0000003300,
instruction X'0000'.
The call to NEST2 ended in error (C G D F).
The failure point is in the second program because there is a default RPG exception/error
handler that handles the error in the mainline.
You want your default exception/error handler to provide an orderly means of aborting a
process. Because of percolation, the only programs or procedures that require an
exception/error handler are those that mark the control boundary of an AG.
In “Example 3” on page 273, simply adding the standard *PSSR subroutine to the mainline of
the first program means that the *PSSR subroutine handles any unhandled exception/error
that is received by any program or subprocedure further down the call stack. ERROR17,
shown in Example 7-21, is the same as ERROR15, but with the standard *PSSR subroutine
added.
nest1();
*inLR = *on;
/include EXCEPTSRC,PSSRSTD
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR17) SRCFILE(REDBOOK/EXCEPTSRC)
The divide by zero error generates the messages shown in Example 7-22 in the job log.
The file error generates the following messages in the job log:
I/O operation was applied to closed file PRODUCT1.
RPG status 00202 caused procedure ERROR17 in program REDBOOK/ERROR17 to
stop.
276 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
7.5.3 Identifying a percolated message
The message details for any message in the job log show details of the program, module,
procedure, and line number that received the message. For example, the message details for
the RNX1211 message show the details shown in Example 7-23.
A job log contains a filtered view of the messages that were sent to program/procedure
message queues in the job. This configuration means that you can write a subprocedure that
retrieves the percolated message from the program/procedure message queue of the
program that starts the activation group.
This section describes a slightly more complex exception/error reporting program to work with
the CL FATALPGM written earlier. The RPGLE program FATALPGMA uses the Receive
Program Message (QMHRCVPM) API to retrieve the percolated message and some of the
Dynamic Screen Management APIs to capture an image of the current panel that is
displayed. A report containing details of the error message and an image of the panel is
produced.
The member STDMSGINFO contains the standard prototype and data structure definitions
that are shown in Example 7-24.
//---------------------------------------------------------------
// Message APIs
//---------------------------------------------------------------
//---------------------------------------------------------------
// ILE CEE APIs
//---------------------------------------------------------------
// Normal End of AG
dcl-Pr endAG extProc('CEETREC');
language_RC int(10) const options(*omit);
user_RC int(10) const options(*omit);
end-Pr;
278 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
// Register Activation Group Exit Procedure
dcl-Pr registerCancelAG extProc('CEE4RAGE');
pCancelProc pointer(*proc) const;
feedback char(12) options(*omit);
end-Pr;
//---------------------------------------------------------------
(4) // Dynamic Screen Manager APIs
//---------------------------------------------------------------
// Read Screen
dcl-Pr ReadScreen int(10) extProc( 'QsnReadScr' );
bytesRead int(10) options(*omit);
bufferHandle int(10) const options(*omit);
cmdBufferhandle int(10) const options(*omit);
environmentHandle int(10) options(*omit);
error like(APIError) options(*omit);
end-Pr;
//---------------------------------------------------------------
// Throw/Catch Procedures
//---------------------------------------------------------------
280 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
names char(4000);
end-Ds;
Note the following points in Example 7-24 on page 277 as indicated by the numbers on the
left side of the example:
1. The standard API error structure is described in detail inChapter 10, “Modern RPG
comparison as viewed by a young developer” on page 369.
2. ReceiveMsg is the prototype for the Receive Program Message API (QMHRCVPM).
3. Message information is returned in the MsgInfo parameter in the format that is specified in
the FormatName parameter. The required format of MsgInfo is defined by the RCVM0300 data
structure (see notes 6 - 9).
4. Prototypes are defined for the Dynamic Screen Management APIs that are used to
retrieve an image of the current panel: Create Input Buffer (QsnCrtInpBuf), Read Screen
(QsnReadScr), and Retrieve Data Pointer (QsnRtvDta).
5. Definitions of the structures that are required by the QMHRCVPM API are based on a
pointer that is never set. This configuration means that although included in a program, the
data structures never occupy any memory. Programs that require these formats can define
corresponding data structures by using the LIKEDS keyword.
6. The RCVM0300 data structure defines the format of the data that is returned by the
QMHRCVPM API for the RCVM0300 format name.
7. The MsgId field is the message ID of the received message.
8. The sum of LenReplace1, LenMsgReturn, and LenHelpReturn provide the offset to the
position of the sender information in the MsgData parameter.
9. The MsgData field contains details of message data (the number of characters that is
defined by LenReplace1) followed by the first-level message text (the number of characters
that is defined by LenMsgReturn) followed by the second-level message text (the number of
characters that is defined by LenHelpReturn) followed by the sender/receiver data (the
number of characters that is defined by LenSenderReturn). The sender/receiver data is
further defined as a structure (next item).
10.The RCVM0300SenderReceiverInfo data structure defines the structure of the
sender/receiver information that is returned in a portion of the MsgData field in the
RCVM0300 data structure.
FatalError (program FATALPGMA) produces a report that provides the exception/error details.
Example 7-25 shows the DDS for the ERRORLST print file.
282 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
A +10'----------'
A SPACEA(1)
A ROW8001 80 1SPACEA(1)
A ROW8002 80 1SPACEA(1)
A ROW8003 80 1SPACEA(1)
A ROW8004 80 1SPACEA(1)
A ROW8005 80 1SPACEA(1)
A ROW8006 80 1SPACEA(1)
A ROW8007 80 1SPACEA(1)
A ROW8008 80 1SPACEA(1)
A ROW8009 80 1SPACEA(1)
A ROW8010 80 1SPACEA(1)
A ROW8011 80 1SPACEA(1)
A ROW8012 80 1SPACEA(1)
A ROW8013 80 1SPACEA(1)
A ROW8014 80 1SPACEA(1)
A ROW8015 80 1SPACEA(1)
A ROW8016 80 1SPACEA(1)
A ROW8017 80 1SPACEA(1)
A ROW8018 80 1SPACEA(1)
A ROW8019 80 1SPACEA(1)
A ROW8020 80 1SPACEA(1)
A ROW8021 80 1SPACEA(1)
A ROW8022 80 1SPACEA(1)
A ROW8023 80 1SPACEA(1)
A ROW8024 80 1SPACEA(1)
A 1'----------'
A +10'----------'
A +10'----------'
A +10'----------'
A R SCREEN132 SPACEB(2)
A 1'----------'
A +10'----------'
A +10'----------'
A +10'----------'
A +10'----------'
A +10'----------'
A +10'----------'
A +10'----------'
A SPACEA(1)
A ROW13201 132 1SPACEA(1)
A ROW13202 132 1SPACEA(1)
A ROW13203 132 1SPACEA(1)
A ROW13204 132 1SPACEA(1)
A ROW13205 132 1SPACEA(1)
A ROW13206 132 1SPACEA(1)
A ROW13207 132 1SPACEA(1)
A ROW13208 132 1SPACEA(1)
A ROW13209 132 1SPACEA(1)
A ROW13210 132 1SPACEA(1)
A ROW13211 132 1SPACEA(1)
A ROW13212 132 1SPACEA(1)
A ROW13213 132 1SPACEA(1)
A ROW13214 132 1SPACEA(1)
A ROW13215 132 1SPACEA(1)
Note: You can create this print file by running the following command:
CRTPRTF FILE(REDBOOK/ERRORLST) SRCFILE(REDBOOK/EXCEPTSRC)
The exception/error report simply lists the message details along with details of the
program/procedure that received the message and, if the program is running interactively, a
copy of the panel.
dcl-Pi fatalError;
end-Pi;
284 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
hlpText03;
hlpText04;
hlpText05;
hlpText06;
hlpText07;
hlpText08;
hlpText09;
hlpText10;
end-Ds;
dcl-Ds screenIn;
screenInData Char(3564);
end-Ds;
dcl-S i int(10);
dcl-S setMsgKey char(4) inz(*ALLx'00');
dcl-S bufferHandle int(10);
dcl-S bytesReturned int(10);
dcl-S dataPtr pointer;
Note the following points in Example 7-26 on page 284 as indicated by the numbers on the
left side of the example:
1. The H spec indicates that the program should run in the activation group of the caller.
2. The F spec for the ERRORLST print file uses the EXTFILE keyword to ensure that the
print file can be found (in this case, it is better not to depend on the library list to locate the
print file).
3. The STDMSGINFO member containing prototypes and standard definitions is included.
4. The prototype for the original FATALPGM is included. FATALPGM is called after the error
report is produced.
5. Data structures are used to map the print fields (from ERRORLST) to fields that are
retrieved from APIs. The HlpText data structure is used to redefine the retrieved
second-level message text for printing. The ScreenIn data structure is used to redefine the
retrieved screen image (either 24 by 80 or 27 by 132). Lengths do not need to be defined
for the subfields because they are externally defined in the ERRORLST print file.
6. A program status data structure is used to identify the job.
7. MsgBack is the data structure that receives the exception/error message. The LIKEDS
keyword is used to define it like the standard RCVM0300 data structure in STDMGINFO.
286 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
8. MsgInfo is the data structure that contains the sender/receiver information from the
message data field in the MsgBack data structure. The LIKEDS keyword is used to define it
like the standard RCVM0300SndRcvInfo data structure in STDMGINFO.
The program starts by retrieving and reporting the error, as shown in Example 7-27.
endIf;
Note the following points in Example 7-27 as indicated by the numbers on the left side of the
example:
1. The FatalError subprocedure starts by opening the ERRORLST print file and printing the
headings, which identifies the failing job.
2. The QMHRCVPM API is called to retrieve the last message in the program/procedure
message queue of the calling program/procedure. The call stack counter is 2 because the
calling procedure/program is two entries back in the call stack. A dynamic call is made to
The program continues by capturing the current panel image (if it is an interactive job), as
shown in Example 7-28.
Note the following points in Example 7-28 as indicated by the numbers on the left side of the
example:
1. The Create Input Buffer API is called to get a buffer handle for the panel.
2. Call the remainder of the DSM APIs only if a buffer handle was successfully created.
288 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
3. Read the panel and determine the number of bytes returned, which will be 1920 for a
24 by 80 screen or 3564 for a 27 by 132 panel. This API captures an image of all
characters on the panel.
4. Get a pointer to the buffer for the panel that was just read. The CatchScreen data structure
is based on this pointer.
5. Copy the screen buffer (CatchScreen) to the ScreenIn data structure so that the print fields
are populated.
6. In this example, any attribute bytes in the captured panel image are replaced with blanks.
You can replace any attribute byte with the identifying character that you want, for
example, to mark the beginning of entry fields.
7. Print either the 24 by 80 or 27 by 132 image of the panel, depending on the number of
characters that are retrieved.
The program finishes by printing the footer, closing the print file, and calling FatalProgram to
notify the system of the error and provide a meaningful message to the user, as shown in
Example 7-29.
fatalProgram();
*inLR = *on;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/FATALPGMA) SRCFILE(REDBOOK/EXCEPTSRC)
Using the FatalError program means that the *PSSR subroutine must change. Instead of
calling the FatalPgm program, the *PSSR subroutine calls the FatalError program.
ERROR18 (Example 7-30) is an altered version of ERROR17 with a new *PSSR coded.
nest1();
*InLR = *On;
Note the following points in Example 7-30 on page 289 as indicated by the numbers on the
left side of the example:
1. The FatalError prototype identifies a dynamic call to the FATALPGMA program. The
program name is qualified to remove any dependency on a library list.
2. The *PSSR subroutine is a much simpler version of the previous one in that all it does is
call FatalError. All code in the *PSSR subroutine is placed in a MONITOR group to
ensure that there are not unrequested repeated calls to *PSSR.
Any unhandled exception/error that is detected in any program or procedure in the call stack
results in the *PSSR routine above being run and the subsequent call to FatalError.
Example 7-31 shows an example of the resulting error exception report that is produced
when the file error is detected in the FileProc procedure in ERROR16.
Selection or command
===> call error18
290 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
F23=Set initial menu
You have seen how you can extend the functions of the program that is called when an
unexpected exception/error occurs. This program can be extended even further to incorporate
the automatic generation of a log number, recording the incident to a database file,
communicating with the operator, or whatever else you want.
For more information about the ILE CEE APIs, see the ILE CEE APIs topic in the IBM i 7.2
Knowledge Center:
http://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/apis/ile1a1.htm
One of the most useful features of an ILE condition handler is that it can determine whether it
handles the exception/error, whether processing should continue, or whether the
exception/error should be percolated up the call stack.
The STDMSGINFO copy member contains the prototypes for the Register a Condition
Handler (CEEHDLR) and Un-Register a Condition Handler (CEEHDLU) APIs and the base
definition of a condition token, as shown in Example 7-32.
ERROR21 (Example 7-33) is an amended version of ERROR18 that includes the registering
and unregistering of an ILE condition handler along with the coding of the condition handler
itself.
/include EXCEPTSRC,STDMSGINFO
nest1();
begSR *PSSR;
monitor;
fatalError();
on-Error;
dsply 'Agggghhhhhh!';
endMon;
endSR '*CANCL';
292 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
pCommArea pointer const;
action int(10);
tokenOut likeDS(base_ConditionToken);
end-Pi;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/ERROR21) SRCFILE(REDBOOK/EXCEPTSRC)
Note the following points in Example 7-33 on page 292 as indicated by the numbers on the
left side of the example:
1. The inclusion of the bindable ILE CEE APIs does not require any special binding directory.
2. ILEHandler is the procedure pointer to the ILE condition handler that is registered in the
program.
3. The ILEHandler subprocedure is registered as an ILE condition handler. It now applies to
all further entries on the call stack unless superseded by another handler (Error Extender,
MONITOR group, or another ILE condition handler). In this example, a communications
area is not used, so the second parameter is null.
4. The ILE condition handler is unregistered at the end of the program.
5. The ILE condition handler indicates that all divide by zero errors should be ignored by
resuming. It is not the usual course of action that is preferred because the resulting value
is unpredictable, but it is applicable in the simple example you see here.
6. All other errors are percolated.
The result of registering the ILEHandler condition handler is that the divide by zero
exception/error in the ProgramProc subprocedure in ERROR16 is ignored, and any other
exception/error results in the “normal” default exception/error process.
The following are some of the more commonly used activation group and control flow APIs:
CEERTX: Register Call Stack Entry Termination user exit procedure
CEEUTX: Un-Register Call Stack Entry Termination user exit procedure
CEETREC: Normal End (of activation group)
CEE4AGE: Register Activation Group exit procedure
CEETREC
CEETREC can be used to end an activation group “normally.” It can be called from any
program or procedure within the activation group. The activation group is immediately ended
much in the same way as the Reclaim Activation Group (RCLACTGRP) command can end an
activation group. The main difference between using CEETREC and RCLACTGRP is that
RCLACTGRP cannot reclaim the activation group in which the command is run.
In RPG, CEETREC provides the same functionality as the exit() function in C and the STOP
RUN operation in COBOL.
The parameters to CEETREC are usually omitted. Both parameters are optional “return
codes” that are input to CEETREC.
Although control is not returned to any of the entries in the call stack, any clean-up
procedures registered for call stack entries are called before the activation group ends.
Multiple exit procedures can be registered for a call stack entry. They are called in reverse
order of registration (LIFO). Example 7-35 shows the prototypes for CEERTX and CEEUTX.
294 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Here are the parameters:
The first parameter for both APIs is a procedure pointer to the procedure being registered
or unregistered.
The second parameter to CEERTX is a pointer to a communications area. Because you
have no means of defining your own parameters with this API, the communications area
provides a means of passing application-specific data to the procedure.
Both APIs have an optional feedback area.
The procedure being registered requires a prototype with the parameters that are shown in
Example 7-36.
The single parameter is the pointer to the communications area that is specified when the
procedure was registered.
CEE4AGE
You can use CEE4AGE to register a procedure that is called when an activation group ends.
The procedure can perform any type of cleanup that you might deem necessary before the
activation group is reclaimed, for example, write information to a log file, or clean up work files
or user spaces.
A multiple exit point procedure can be registered. They are called in reverse order of
registration (LIFO).
The first parameter is a procedure pointer to the procedure being registered. The second
parameter is an omissible feedback area.
The procedure being registered is passed four parameters by the system, so it requires a
prototype with the parameters that are shown in Example 7-38.
10 Do not perform any pending error requests. This code is used if a previous exit
procedure specified a result code of 20 and a subsequent procedure recovers from the
error. The message CEE9901, indicating an application error, is not sent.
20 Send message CEE9901 to the caller of the control boundary after the remaining exit
procedures are called.
21 Send message CEE9901 to the caller of the control boundary. The remaining exit
procedures that are registered by the CEE4RAGE API are not called. This code is used
if an unrecoverable error occurs in the exit procedure requesting this action.
The fourth parameter is a user result code that is passed between activation group Exit
procedures. Set this parameter as you see fit.
Example
This section shows an example of the Activation Group and Control Flow APIs in action. The
member CANCEL01 contains global definitions, a mainline, and a subprocedure.
Example 7-39 shows the global definitions and mainline.
/include EXCEPTSRC,STDMSGINFO
dcl-S x int(10);
(3) x = x + 1;
dsply x;
(4) if x > 2;
subProc01();
endIf;
return;
296 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
begSR *inzSR;
(5) registerCancelAG(%pAddr('AGCANCEL'): *omit);
endSr;
Note the following points in Example 7-39 on page 296 as indicated by the numbers on the
left side of the example:
1. The program runs in a named activation group called CANCEL, which means that the
activation group remains in the job when this program is exited.
2. The prototypes for the CEE APIs are placed in the copy member STDMSGINFO.
3. The program adds 1 to x and displays the value.
4. When x is greater than 2 (which happens when the program is three times in succession),
a dynamic call is made to the program CANCEL02.
5. The subprocedure AGCancel is registered as an exit procedure for the activation group.
This subprocedure is performed in the *INZSR subroutine to ensure that the procedure is
registered only once. If it were in the mainline, the procedure would be registered on every
call and results in the procedure being called multiple times when the activation group
ends.
The member CANCEL01 also contains the definition of the AGCancel subprocedure, as
shown in Example 7-40. The subprocedure does not have to be placed in the same module
that registers it. It can be placed in a module in a service program.
reason0 = reason;
(3) if (%bitAnd(reason3:END_NORMAL) = HEX_00);
dsply 'AG ended normally';
else;
dsply 'AG ended abnormally';
endIf;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/CANCEL01) SRCFILE(REDBOOK/EXCEPTSRC)
Note the following points in Example 7-40 on page 297 as indicated by the numbers on the
left side of the example:
1. Even though the reason code is passed as an integer, you must decipher bit settings in the
four bytes to determine why and how the activation group is ending. One way of achieving
this task is to remap the 4-byte integer to four separate bytes and use the %BITAND BiF to
determine whether the relevant bits are set.
Table 7-4 is taken from the description of the CEE4RAGE API and provides the common
reason codes for ending activation groups and call stack entries, with byte 3 (bits 16 - 23)
being the most relevant.
Table 7-4 Common reason codes for ending activation groups and call stack entries
Bit Meaning
Bits 0 Reserved.
Bits 1 Call stack entry is canceled because an exception message was sent.
Bit 20 Initiated by an exit verb, for example exit() in C, or the CEETREC API.
Bit 22 Call stack entry canceled because of an out-of-scope jump. For example, longjmp()
in C.
2. Named constants are used to identify the bit settings to be tested, for example, a value of
128 indicates that bit 0 of a byte is on or a value of 32 indicates that bit 2 of a byte is on.
3. The %BITAND BiF is used to determine whether the relevant bits are set in the third byte
of the reason code and to condition the relevant message being displayed.
298 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
You are now ready for the first test. Call the program CANCEL01 and the value of x is
displayed:
DSPLY 1
Now issue the command RCLACTGRP CANCEL (do not call CANCEL01 for a third time) and
the following information is displayed:
DSPLY AG ended normally
DSPLY AG ended by RCLACTGRP
Now, add some additional complexity. The member CANCEL02 contains global definitions, a
mainline, and a number of subprocedures. Example 7-41 shows the global definitions and
mainline.
/include EXCEPTSRC,STDMSGINFO
(2) subProc02();
return;
begSR *inzSR;
(3) registerCancelAG(%pAddr('AGCANCELNEXT'): *omit);
endSr;
Note the following points in Example 7-41 as indicated by the numbers on the left side of the
example:
1. The program runs in the activation group of the calling program or procedure. In this
instance, this program runs in the CANCEL activation group.
2. The program simply calls the subprocedure SubProc02.
3. The subprocedure AGCancelNext is registered as an exit procedure for the activation
group. This is the second exit procedure that is registered for the activation group (the first
being in CANCEL01).
Example 7-42 shows the AGCancelNext subprocedure. It is a simplified version of the exit
procedure in CANCEL01. All that it does is display a message. This is a second exit
procedure that is registered for the AG.
Example 7-43 shows the CancelStack subprocedure. It displays a message based on the
pointer that is passed as the communication area. This program uses the same exit
procedure for multiple call stack entries (because all it does is display a message), but you
can have as many different exit procedures as required.
Example 7-44 shows the SubProc02 subprocedure, which is called from the mainline of
CANCEL02. It registers CancelStack as an exit procedure with a pointer to the relevant
message as the second parameter. It then issues a call to SubProc03.
Example 7-45 shows the SubProc03 subprocedure, which is called from the SubProc02
subprocedure.
300 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
(4) subProc05(0);
(5) subProc05(1);
end-Proc;
Note the following points in Example 7-45 on page 300 as indicated by the numbers on the
left side of the example:
1. It registers CancelStack as an exit procedure with a pointer to the relevant message as the
second parameter.
2. It unregisters the exit procedure after some code runs. The exit procedure would have
been called if any of the operations between registering and unregistering caused the
subprocedure to cancel.
3. It issues a call to SubProc04. The call is in a MONITOR group because the call is going to
fail and you do not want the error to be percolated back up the call stack.
4. It issues a call to SubProc05 with a parameter value of 0.
5. It issues a call to SubProc05 with a parameter value of 1.
Example 7-46 shows the SubProc04 subprocedure, which is called from the SubProc03
subprocedure. It registers CancelStack as an exit procedure with a pointer to the relevant
message as the second parameter. It then performs an invalid divide by zero that causes the
call to the subprocedure to end in error. This error in turn causes the exit procedure for the
call stack entry to be called.
Example 7-47 shows the SubProc05 subprocedure, which is called twice from the SubProc03
subprocedure. It registers CancelStack twice as an exit procedure with a pointer to the
relevant message as the second parameter. The subprocedure ends the activation group if
the value of the passed parameter is 1.
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/CANCEL02) SRCFILE(REDBOOK/EXCEPTSRC)
Call the program CANCEL01 three times and the output in Example 7-48 is displayed.
The final call to SubProc05 results in the AG being ended and all exit procedures being called.
starting with the call stack procedures (working back up the call stack) and ending with the
AG exit procedures.
You can also see the ILE Programming Guide, specifically the Calling Programs and
Procedures/Using Bindable APIs and Debugging and Exception Handling/Handling
Exceptions sections:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzasc/rzascmain.htm
302 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
7.7 Priority of handlers
When an exception/error occurs in a program or a procedure, this is the priority order of the
error handlers in RPG IV:
1. ‘E’ Operation extender (or Error Indicator)
2. MONITOR group
3. ILE condition handler
4. File exception/error subroutine or program exception/error subroutine (*PSSR)
5. RPG default error handler (which does not apply to subprocedures)
Exceptions/errors are percolated back up the call stack in an ILE environment. Ensure that
you are not inadvertently trapping an unexpected exception/error.
With a little coding, you can get embedded SQL to send a “fail” message to a program so that
normal RPG exception/error handling (*PSSR, monitor group, and so on) can handle
exception/errors. All that is needed is a call to a subprocedure after every SQL statement.
Example 7-49 shows how the check_SQLState() subprocedure is called after a statement.
The call to check_SQLState() after the second insert statement causes the program to fail
because the insert is attempting to insert a duplicate key. The call to check_SQLState() after
the Fetch shows how the subprocedure can be used to detect an end of file/row not found
condition.
Because there is no exception/error in the program, this output shown in Example 7-50 is the
resulting job log after a call to check_SQLState() fails:
The message that causes the program to fail is that the call to check_SQLState() ended in
error. However, an earlier message shows that a duplicate key value is specified, followed by
the same message that is preceded by the SQLSTATE value. This message is sent by the
check_SQLState() subprocedure.
So what exactly does the check_SQLState() subprocedure do? Based on the value of
SQLSTATE, it performs one of the following actions:
Do nothing (because everything is OK).
Send a diagnostic message (if there is a warning).
Send an escape message if there is a serious error message. The process of sending an
escape message means that the subprocedure fails, which means that the caller receives
the message that check_SQLS() ended in error.
Example 7-51 shows the prototype for the check_SQLState() subprocedure, which is coded in
the STDMSGINFO member. The subprocedure has no parameters, and returns a true
indicator if an EOF status is detected.
/include EXCEPTSRC,STDMSGINFO
// Work fields
dcl-S messageKey char(4);
dcl-S messageType char(10);
dcl-S messageText char(1024);
dcl-S status ind;
// Constants
dcl-C W_DIAGNOSTIC '*DIAG';
304 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dcl-C W_EOF '02';
dcl-C W_ESCAPE '*ESCAPE';
dcl-C W_MSGF 'QCPFMSG *LIBL';
dcl-C W_MSGID 'CPF9897';
dcl-C W_STACK_ENTRY '*';
dcl-C W_STACK_COUNT1 1;
dcl-C W_SUCCESS '00';
dcl-C W_WARNING '01';
endIf;
return status;
end-Proc;
Note the following points in Example 7-52 on page 304 as indicated by the numbers on the
left sides of the examples:
1. A data structure is used to make it easier to extract the first two characters of SQLSTATE.
2. GET DIAGNOSTICS is used to retrieve the last SQLSTATE. The retrieved value
determines what the subprocedure does.
3. If the status indicates a Row Not Found condition, then change the return value to on.
4. If the status indicates a warning message, set the condition to send a diagnostic message.
5. Otherwise, set the condition to send an escape message.
6. If required, send a diagnostic or an escape message.
The MONITOR group offers the ability to run operations and catch any exception/errors that
occur. With a couple of simple subprocedures, you can use MONITOR groups as a means of
catching and identifying application errors and exception/errors.
306 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dcl-S buy packed(11:2);
dcl-S sell packed(11:2);
dcl-S msg char(7);
msg = validCosts(buy:sell);
if (msg <> *blanks);
dsply msg;
endIf;
*inLR = *on;
begSR *PSSR;
monitor;
fatalError();
on-Error;
dsply 'Agggghhhhhh!';
endMon;
endSR '*CANCL';
dcl-Proc validCosts;
dcl-Pi validCosts char(7);
buyPrice packed(11:2) const;
sellPrice packed(11:2) const;
end-Pi;
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/TRYCATCH01) SRCFILE(REDBOOK/EXCEPTSRC)
The program inputs and validates a Buying Price and a Selling Price. The ValidCosts
subprocedure performs two validations between the two values that are passed as
parameters. It returns a seven-character message ID indicating the error. The calling
procedure checks whether the returned value is blanks and, if not, processes any error.
Although this is a workable method of notifying errors, it depends on the calling procedure
checking the return value from the subprocedure.
Call the program a few times, providing valid and invalid values for the buying and selling
prices. Note how the standard exception/error handling is invoked if you provide a value of
zero for the buying price. It causes a divide by zero error in the ValidCosts subprocedure.
SndPgmMsg is a prototype for the Send Program Message (QMHSNDPM) API. QMHSNDPM is
called to send an escape message up the call stack.
Throw is a prototype for a subprocedure that calls the QMHSNDPM API to send a requested
message up the call stack. The Throw subprocedure is coded in the source member
MSGPROCS as shown in Example 7-55.
/include EXCEPTSRC,STDMSGINFO
308 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
endIf;
Note the following points in Example 7-55 on page 308 as indicated by the numbers on the
left side of the example:
1. The NOMAIN keyword is specified on the H spec because MSGPROCS contains only
subprocedures.
2. The default message file is ERRORS in the library REDBOOK (this file can be overridden
by the third parameter passed). This message file contains the messages ERR0001 and
ERR0002.
3. The subprocedure determines whether optional parameters were passed and overrides
work fields.
4. The SndPgmMsg procedure is called to send the requested error message. The message is
sent as an escape message and it is sent to the previous entry in the call stack.
7.9.3 Catch
RPG standard exception/error handling now traps any message that you send (using the
Throw procedure) as a 202 status error (Called program or procedure failed). You need
another subprocedure to catch the error so you can determine what it is.
Again, this action requires a couple of additions to the STDMSGINFO copy member, as
shown in Example 7-56.
Base_CaughtMessage defines the data structure, which is passed as a parameter to Catch. The
data structure contains subfields defining the message ID, message file, first-level message
text, and message data for the message retrieved by the Catch subprocedure.
The catch subprocedure is also coded in the source member MSGPROCS, as shown in
Example 7-57.
clear caughtMessage;
receiveMsg( msgBack
: %size(msgBack)
: 'RCVM0300'
: '*'
: 1
: '*PRV'
: setMsgKey
: 0
: '*REMOVE'
: APIError);
310 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
endIf;
endIf;
end-Proc;
Note: You can create the MSGPROCS service program by running these commands:
CRTRPGMOD MODULE(REDBOOK/MSGPROCS) SRCFILE(REDBOOK/EXCEPTSRC)
CRTSRVPGM SRVPGM(REDBOOK/MSGPROCS) EXPORT(*ALL)
The binding directory ERROR contains an entry for the MSGPROCS service program.
Catch uses the ReceiveMsg procedure (QMHRCVPM) to retrieve the last message in the
program message queue of the calling program or procedure and places the retrieved
information in the parameter data structure.
(4) monitor;
validCosts(buy:sell);
on-Error;
(5) catch(caughtMessage);
dsply caughtMessage.msgId;
endMon;
*inLR = *on;
begSR *PSSR;
monitor;
fatalError();
on-Error;
dsply 'Agggghhhhhh!';
endMon;
endSR '*CANCL';
Note: You can create this program by running the following command:
CRTBNDRPG PGM(REDBOOK/TRYCATCH02) SRCFILE(REDBOOK/EXCEPTSRC)
Note the following points in Example 7-58 on page 311 as indicated by the numbers on the
left side of the example:
1. The ERROR binding directory is specified on the H spec to bind to the Throw and Catch
subprocedures in the MSGPROCS service program.
2. The STDMSGINFO copy member is included for the required prototypes.
3. CaughtMessage is used as the parameter for the Caught subprocedure.
4. The call to ValidCosts is placed in a MONITOR group. Any exception/error causes control
to pass to the ON-ERROR. ValidCosts no longer returns a message ID.
5. The catch procedure is called to determine what the error was.
6. When there is an error, the throw procedure is called to send the relevant error message
and signal an exception/error to the calling program or procedure.
The process of calling the throw subprocedure immediately ends the subprocedure that
issues the call. Consider whether the ValidateCosts subprocedure was coded as shown in
Example 7-59.
The second validation is not performed if the first validation caused ERR0001 to be thrown.
The fact that the throw procedure sends an escape message means that the call to the throw
procedure ends in error. Because this error is not trapped, the procedure ends immediately.
312 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
When you call TRYCATCH02 and provide a value of 0, the divide by zero error is now trapped
by the MONITOR. Control passes to the ON-ERROR operation as opposed to the
exception/error being percolated up the call stack.
You can try to handle these exception/errors after the catch, but it is probably easier to handle
them in the catch subprocedure.
The catch subprocedure can check the message ID that is retrieved, and if it is not an
application error (starts with ERR), the message is rethrown. The code is then added to the
catch subprocedure after the contents of the parameter data structure are set. See
Example 7-60.
The problem with rethrowing the error is that the catch subprocedure is now identified as the
subprocedure that received the exception/error.
Another alternative is to have the catch subprocedure call the FatalError procedure. This
method requires two changes:
The message action parameter on the call to ReceiveMsg in the catch subprocedure
should be *SAME as opposed to *REMOVE.
The Call Stack Counter parameter on the call to ReceiveMsg in the FATALPGMA program
should be 3 as opposed to 2 (you want it to retrieve the message from the program
message queue of the program or procedure that called catch).
7.10 Conclusion
The implementation of exception/error handling in your programs is straightforward.
In an environment where you are not using ILE features, such as subprocedures (that is, the
programs are running in the default activation group), use a combination of the following
methods:
Write a standard *PSSR subroutine and place it in a copy member.
Write a “Fatal Error” program that is called from the *PSSR subroutine.
Include the *PSSR subroutine in programs by using a /COPY directive.
Include an INFSR(*PSSR) keyword for every file.
Implicitly open files.
Use Error Extenders and Monitor groups to handle specific exceptions errors.
In an environment where you are running programs in ILE activation groups (that is, the
programs are not running in the default activation group), you can also write and register
your own condition handlers and exit procedures.
Chapter 8. Interfacing
One significant area where changes are likely to occur in your RPG programs relate to
making it easier to integrate RPG with newer interfaces. XML is already easier to do with
RPG. Several of these new interfaces including Java, Python, and PHP are examined in this
chapter. The ability to integrate the RPG code that has been reliably serving business
application requirements for years is critical to the smooth implementation of these new
technologies.
The following interfaces with RPG topics are described in this chapter:
Interfacing with Java
Python
PHP
The IBM Toolbox for Java contains classes that can be used to call programs or service
programs. It also supports the use of Program Call Markup Language (PCML) to describe the
program parameters. Because Java also includes support for calling web services, Integrated
Web Services (IWS) support can be used to call an RPG IV program or procedure.
Two other techniques that can be used are messaging and stored procedure calls. Because
messaging is the recommended technique for calling Java from RPG IV, this topic is
described in “LISTOBJSP” on page 316.
Also, because stored procedures are supported by a standard interface and can return lists of
data, they are a flexible technique for calling RPG IV from Java. See “Java stored procedure
call” on page 317 for more details.
LISTOBJSP
A stored procedure can return values as parameters or as one or more result sets. Because
the sample ListObjects API returns a list of values, you can create a simple wrapper
program that calls the API and returns the object information in a result set as shown in
Example 8-1.
After you have the program, you can expose it as a stored procedure by using an SQL
CREATE PROCEDURE call, as described in the LISTOBJSP program comments.
// Since the SQL precompiler needs to see the data structure in the
// listobjpr copy, we need to specify the RPGPPOPT(*LVL1) option
// on the CRTSQLRPGI command
// The SQL to create the stored procedure (using system naming) is:
//
// create procedure radredbook/listobjsp(
// in objname char(10), in objlib char(10), in objtype char(10))
// result sets 1 language rpgle deterministic contains sql
// external name radredbook/listobjsp parameter style general
// Main procedure
dcl-proc main;
dcl-pi main extpgm('LISTOBJSP');
316 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Name char(10) const;
Library char(10) const;
Type char(10) const;
end-pi;
rc = ListObjects(Name:Library:Type:count:ObjectInfo);
if rc = 0;
exec sql set result sets for return to client array :ObjectInfo
for :count rows;
endif;
end-proc;
A preferred approach is to start a server in a JVM that can process requests in different
threads and send messages to that server. This approach scales much better because you
have only one JVM running. The messaging can be done with data queues, IBM MQ, or
sockets.
SocketDispatcher
Java has a relatively high-level socket interface, so it is not difficult to create a flexible
multi-threaded socket server as shown in Example 8-4. The server listens on a specified port
on the TCP loopback address so that it is available only to local client connections. When a
socket connection request is accepted, the socket is passed to a processor that is specific to
the application.
import java.io.IOException;
import java.net.*;
/**
*
* Simple Java socket server to handle message based requests.
* The server will only listen for connections on the loopback address.
*
*/
public class SocketDispatcher
{
private int port;
private int backlog = 50;
private Class<? extends SocketProcessor> processclass;
/**
* Create a dispatcher on a specified port for a specified processor class.
*
* @param port the port number for this service
* @param processclass a subclass of SocketProcessor
318 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
*/
public SocketDispatcher(int port, Class<? extends SocketProcessor> processclass)
{
this.port = port;
this.processclass = processclass;
}
/**
* Main processing loop
*
*/
public void run()
{
ServerSocket ss = null;
try
{
ss = new ServerSocket(port, backlog, InetAddress.getLoopbackAddress());
for (;;)
{
Socket s = ss.accept();
try
{
SocketProcessor processor = processclass.newInstance();
processor.setSocket(s);
new Thread(processor).start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
try
{
if (ss != null)
ss.close();
}
catch (Exception e)
{
}
}
}
/**
* Set the maximum server backlog.
* The default value is 50.
*
* @param backlog maximum number of queued requests.
*/
SocketProcessor
The SocketProcessor class provides the code that is required to receive a request message,
call the processRequest method, and send a response message, as shown in Example 8-5.
Because the actual request processing depends on the specific application, the
processRequest method is not implemented in this class, but is implemented in an
application-specific subclass.
import java.io.*;
import java.net.Socket;
/**
*
* Abstract class for processing socket messages
*
*/
public abstract class SocketProcessor implements Runnable
{
private Socket s = null;
private final String FS = "\u001F";
/**
*
* Set the socket for request and response messages.
*
* @param s the connected socket.
*/
public void setSocket(Socket s)
{
this.s = s;
}
/**
*
* The actual request processing method
*
* @param request array of strings making up request and parameters
* @return array of response strings
* @throws Exception
*/
abstract public String[] processRequest(String[] request) throws Exception;
320 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
try
{
in = new DataInputStream(s.getInputStream());
out = new DataOutputStream(s.getOutputStream());
for (;;)
{
byte[] request = new byte[in.readInt()];
in.readFully(request);
String[] respvalues = processRequest(splitMessage(new String(request,
"UTF8")));
if (respvalues != null)
{
byte[] response = prefixMessage("Response", buildMessage(respvalues))
.getBytes("UTF8");
out.writeInt(response.length);
out.write(response);
}
}
}
catch (EOFException e)
{
}
catch (Exception e)
{
try
{
byte[] error = prefixMessage("Error", e.toString()).getBytes("UTF8");
out.writeInt(error.length);
out.write(error);
}
catch (Exception e1)
{
}
}
finally
{
try
{
s.close();
}
catch (Exception e)
{
}
}
}
/**
* Prepend a prefix to a message string.
*
* @param prefix value to prepend.
* @param message original message string.
* @return concatenated strings.
*/
private String prefixMessage(String prefix, String message)
{
/**
* Join an array of strings into a single string using a Unicode delimiter
character.
* @param in array of strings to be joined.
* @return delimited string.
*/
private String buildMessage(String[] in)
{
StringBuilder builder = new StringBuilder();
for (String s : in)
{
builder.append(s);
builder.append(FS);
}
if (builder.length() > 0)
builder.setLength(builder.length() - 1);
return builder.toString();
}
/**
* Split a string using a Unicode delimiter character.
* @param in delimited string.
* @return array of string components.
*/
private String[] splitMessage(String in)
{
return in.split(FS);
}
}
SpreadsheetSocketProcessor
The SpreadsheetSocketProcessor class contains the processRequest method that is needed
to implement an application-specific set of functions. This class uses the Apache Software
Foundation POI support to retrieve some information from an .xlsx spreadsheet as shown in
Example 8-6.
import java.io.IOException;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/**
*
* SocketProcessor for retrieving information from a spreadsheet in IFS
*
322 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
* The requests are:
*
* sheetnames: return a list of the sheet names from a spreadsheet
* getcolumn: return a list of string column values from a spreadsheet
*
*/
public class SpreadsheetSocketProcessor extends SocketProcessor
{
private String filename = null;
private XSSFWorkbook workbook = null;
/**
* Read a file from IFS into a workbook for processing.
* @param filename IFS path to spreadsheet file.
* @throws IOException
*/
private void readFile(String filename) throws IOException
{
if (filename.equalsIgnoreCase(this.filename) && this.workbook != null)
return;
this.filename = filename;
SpreadSheetServer
The SpreadSheetServer class is used to start a SocketDispatcher on the appropriate port
with the application-specific processing class as shown in Example 8-7.
SOCKPR
Just as the example Java code was built in layers with the application-specific code running
on top of general-purpose routines, the example RPG IV code is also built in layers.
Although the QSYSINC library contains RPG IV members for some of the structures and
prototypes that are needed, it does not currently contain a full set of the prototypes needed for
socket communication. The SOCKPR file contains the definitions, as shown in Example 8-8.
/include QSYSINC/QRPGLESRC,SYSTYPES
/include QSYSINC/QRPGLESRC,ERRNO
//
// System socket definitions and prototypes
//
324 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
end-ds;
JAVAMSGPR
The first example module contains the procedures that are needed for communicating with
the Java SocketDispatcher and SocketProcessor classes. It does not contain the procedures
specific to the application. The module is built from two source members. The first contains
the declarations that are used by procedures that use the interface, and the second contains
the actual implementation of the procedures.
326 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The declarations are shown in Example 8-9.
// Receive a message
//
// Input parameters:
// connection - an open socket connection
// message - buffer to receive message
//
// Returns:
// Return code - 0 or a value indicating the error
dcl-pr recvMessage int(10);
connection int(10) value;
message like(Message_t);
end-pr;
// Send a message
//
// Input parameters:
// connection - an open socket connection
// message - message to be sent
//
// Returns:
// Return code - 0 or a value indicating the error
dcl-pr sendMessage int(10);
connection int(10) value;
message like(Message_t) value ccsid(*UTF8);
end-pr;
ctl-opt nomain;
/include javamsgpr
328 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
/include sockpr
dcl-s rc int(10);
dcl-s length int(10);
// Loopback address
address = *allx'00';
address.sin_family = AF_INET;
address.sin_port = port;
address.sin_addr.s_addr = inet_addr('127.0.0.1');
// Connect
if connect(connection:%addr(address):%size(address)) = -1;
rc = close(connection);
return -1;
endif;
return connection;
end-proc;
330 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dow splitsfound < maxsplit;
splitsfound += 1;
if pos < %len(instring);
pos = %scan(c:instring:pos + 1);
if pos > 0;
splits(splitsfound) = pos;
else;
splits(splitsfound) = %len(instring) + 1;
leave;
endif;
else;
splits(splitsfound) = %len(instring) + 1;
leave;
endif;
enddo;
if splitsfound > 0 and splits(splitsfound) < %len(instring);
return -1;
endif;
return 0;
end-proc;
// A single socket receive may not always return the full length
// requested
// This routine will loop until full buffer size is received
dcl-proc recvFully;
dcl-pi recvFully int(10);
connection int(10) value;
XLSXUTILPR
After you have the procedures that are required to communicate with the Java code, you can
create the procedures specific to the example application. Again, this module is built from an
interface source member and an implementation source member.
Example 8-11 shows the code for the interface source member.
332 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
available int(10);
names varchar(100) dim(100) options(*varsize);
curconnection int(10) value options(*nopass);
end-pr;
XLSXUTIL
Example 8-12 shows the code for the interface source member.
/include xlsxutilpr
/include javamsgpr
334 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
endif;
for i = 1 to splitcount;
names(i) = getSplit(i + 1:response:splits);
endfor;
else; // If error,
lasterror = getSplit(2:response:splits); // Store error message
rc = -2;
endif;
else;
lasterror = %char(errno()); // Store socket error
endif;
else;
lasterror = %char(errno());
endif;
if %parms < %parmnum(curconnection); // If new connection, close it
disconnectJVM(connection);
endif;
return rc;
end-proc;
TESTXLSX
Now that you have the example application-specific interface written, you can use it in an
application. Example 8-13 shows a test program for the spreadsheet Java service.
/include xlsxutilpr
dcl-proc main;
dcl-pi main extpgm('TESTXLSX');
end-pi;
SingleRequest();
MultipleRequest();
end-proc;
336 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
dcl-s sheetnames varchar(100) dim(20);
dcl-s rc int(10);
dcl-s available int(10);
dcl-s i int(10);
display('Single Request');
rc = getXLSXSheets('/radredbook/test1.xlsx':
%elem(sheetnames):available:sheetnames);
if rc = 0;
for i = 1 to available;
display(sheetnames(i));
endfor;
else;
display(getXLSXError());
endif;
end-proc;
display('Multiple Request');
connection = openXLSXConnection(); // Open connection for requests
rc = getXLSXSheets('/radredbook/test1.xlsx':
%elem(sheetnames):available:sheetnames:
connection); // Use open connection
if rc = 0;
// Show name of first sheet
display(sheetnames(1));
// Get data from second column of first sheet
rc = getXLSXColumn('/radredbook/test1.xlsx':1:2:
%elem(customers):available:customers:
connection); // Use open connection
if rc = 0;
for i = 1 to available;
display(customers(i));
endfor;
else;
display(getXLSXError());
endif;
else;
display(getXLSXError());
endif;
closeXLSXConnection(connection); // We need to close the connection
end-proc;
338 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
8.1.3 Things to remember
Remember the following important points:
Java calling RPG IV:
– IBM Toolbox for Java classes:
• Call programs or procedures.
• PCML describes the interfaces.
– Web Services:
• Can call Integrated Web Services interfaces.
• WSDL describes the interface.
– Messaging:
• Data queue.
• Socket.
• MQ.
• Messages must be defined.
– Stored procedure:
• Standard interface.
• Might require a wrapper program.
• An SQL CREATE PROCEDURE is required.
• Can return lists of results.
The system handles most of the interface details.
RPG IV calling Java:
– Using RPG IV direct call support is not recommended.
– Messaging is a useful scalable technique:
• The message-format processing should be designed for reuse.
• The Java server process should be multi-threaded to handle simultaneous
requests.
• The Java server process must be running.
8.2 Python
Just as with Java, there various methods that can be used to call RPG IV from Python. In
addition to the standard stored procedure and web services calls, the Toolkit for IBM i works
with the XMLSERVICE library to provide access to IBM i resources.
Example 8-16 shows calling the ListObject API directly leveraging the toolkit.
Example 8-16 Source for calling ListObject API with the toolkit
# Import from itoolkit modules
from itoolkit.lib.ilibcall import *
from itoolkit import *
340 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
# Input parameters
iSrvPgm('listobjects', 'LISTOBJ', 'LISTOBJECTS')
.addParm(iData('objectname', '10a', object))
.addParm(iData('library', '10a', library))
.addParm(iData('objecttype', '10a', objtype))
# Output parameters - returncount contains number of array elements with data
.addParm(iData('returncount', '10i0', '', {'enddo':'rtncount'}))
.addParm(
iDS('objectinfo', {'dim':'1000', 'dou':'rtncount'})
.addData(iData('NAME', '10a', ''))
.addData(iData('LIBRARY', '10a', ''))
.addData(iData('TYPE', '10a', ''))
.addData(iData('ATTRIBUTE', '10a'v ''))
.addData(iData('DESCRIPTION', '50a'v ''))
)
.addRet(iData('rc', '10i0', ''))
)
8.3 PHP
The same techniques that were used with Python can also be used with PHP.
There is also a PHP toolkit for IBM i that simplifies the accessing of IBM i ILE objects,
programs, and data. Because PHP has been available on IBM i for nearly a decade now,
many examples and helps are available from many sources that include books, articles, and
other open source material. If programs are written as procedures that represent stateless
callable routines, it is easy to call them in any language.
For more information about PHP including several examples that take advantage of RPG, see
Modernizing IBM i Applications from the Database up to the User Interface and Everything in
Between, SG24-8185.
Also, Zend is a strategic partner when it comes to PHP. They have many articles and
resources that can help you become successful with PHP on IBM i. See the Zend IBM i
Solutions website at:
http://www.zend.com/en/solutions/modernize-ibm-i
342 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
9
Figure 9-1 shows a window full of errors returned by SEU for valid modern RPG syntax.
344 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 9-2 shows modern RPG syntax with RDi support.
Because RDi is the only environment that understands the latest in RPG syntax, it is no
longer advisable to use the 5250 emulation (“green screen”) tools of Programming
Development Manager (PDM) and SEU. It has been well over a decade since any
enhancements have been added to these development tools.
The fact that new language functions are not supported in the 5250 emulation editors is not
the only reason to move to RDi. In fact, it is not even the best reason. The best reasons are
the enhancements to productivity. This section highlights a few of the biggest
productivity-related differences between RDi and PDM/SEU.
This section covers some of the productivity features in RDi compared to using PDM and
SEU. It is targeted primarily to those who are not already using RDi. Section 9.3, “What is
new in Rational Developer for IBM i” on page 356 covers some more advanced features, such
as the use of plug-ins, and some of the more recent enhancements to RDi. Section 9.4,
“Using the RDi debugger” on page 364 describes debugging RPG programs with RDi i.
The integration of the error feedback with the editor is considerably faster than browsing a
spooled file compile listing to find lines of code in error.
This feature leads to less need for opening other source members to find what parameters
must be passed to a program or procedure, or to use commands such as Display File Field
Descriptions (DSFFD) to get details about externally defined data. Much of the data that is
available through the outline is also available in other, even more convenient ways. For
example, when you position your cursor on the name of a variable within the source, a
definition of it appears as hover text on the user screen.
346 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
The outline is integrated with the editor, which means that you can navigate directly to the
place in the program where a variable, subroutine, or other item is defined or coded. Simply
click the line in the outline view and the code area is moved to that line of code. With the later
versions of RDi, the outline view is ‘live.’ The integration with the editor means as you add new
items in the edit view, the outline is immediately updated with the latest information.
In addition, there is a cross-reference in the outline that shows where each item is referenced
in the source member. Those cross-reference lines are also connected to the editor.
For variables, the outline also indicates which lines of code modify the value of the variable
versus those that simply reference it. For example, using the outline view, you can navigate to
a specific subroutine or procedure or navigate directly to every line of code in the member
that calls that subroutine or procedure.
If your outline is large, the ability to filter by the name of variables, procedures, or subroutines
is helpful. The ability to hide unreferenced items in the outline is helpful when many
prototypes have been copied into the program from a single source member. This feature can
reduce the prototype list to only those that are used in this program.
Figure 9-4 shows the use of the outline view with an RPG IV program.
Figure 9-5 shows RDi with the declarations and the code logic in two views.
Figure 9-5 RDi showing code declarations in one view and code logic in another
RDi allows various possibilities when it comes to the screens that can be displayed at one
time. Figure 9-6 shows another example with the source code on one side and the DDS
screen editor on the other.
348 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
9.1.4 Modern editor capabilities
Freed from the 5250 emulation text-based size limitations, the editor in RDi offers many
features that could not be implemented in the SEU interface:
More code visible at a time
When editing source code, you can typically see two to three times the number of lines of
code compared to the SEU editor. When using SEU, the number of lines that is visible is
fixed, regardless of the size of monitor that is used. With the RDi editor, the number of
source lines visible varies based on the monitor size and shape, and the font size that is
selected. Seeing more code makes it easier and faster to follow the flow of logic.
Undo and redo
During an edit session, you have unlimited levels of both undo and redo of individual
changes, even after a save and compile, while the source member is still open in the
editor. You can open a file, make changes, save, and then compile, repeat this sequence
multiple times, and still undo individual changes all the way back to when you opened that
file. In the SEU editor, the only option to undo changes is to undo all the changes in the
entire editing session by closing the editor without saving the member.
Color coding
With RDi, you can assign separate colors to many features of the RPG language. This
feature allows you to instantly recognize something without ever having to think about it.
By default, RDi sets comments to the color teal, numbers blue, and logic (like ‘IF, ELSE) is
assigned purple. This simple color coding improves your accuracy as you write code
because the editor instantly assigns color to help you eliminate mistakes.
350 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Content assist
Content assist in RDi can make suggestions on how to finish code after you start it by
taking the context of the cursor position into account. You can start content assist by
pressing Ctrl+Space bar.
If you need help remembering the name of an RPG built-in function, type % and press
Ctrl+Space bar to open a list of built in functions.
Content assist can provide a list of variable names that are of the appropriate type for a
parameter for the built-in function or even for a program or procedure call. If the parameter
must be a character, the list contains only character variable names. If you type the first
few letters, the list is filtered to show only the appropriate variables that begin with those
letters, which is convenient for qualified data structure subfields.
Having trouble remembering what all those new keywords are in the new free-format
declarations? After DCL-F and the file name, for example, Ctrl+Space bar brings up a list
of file declaration keywords.
Figure 9-8 shows the use of content assist with DCL-F.
Many resources are available that can be used to help in the learning process. A number of
companies offer onsite training in person. In addition, the following list is a small sampling of
the online resources that can prove useful:
System i Developer's RSE Quick Start Guide
http://systemideveloper.com/downloadRSEQuickStartGuide.html
IBM Rational Developer for i website for downloading a trial version
https://www.ibm.com/developerworks/downloads/r/rdi/
An advantage of the Eclipse base is that many other tools are also built on that same base.
The skills that you use to edit your RPG and CL code become transferable to coding in other
environments, such as PHP or Java. Or better yet, developers who have worked with Java,
PHP, or other modern languages can easily transition to working with modern RPG and the
RDi tools, and become comfortable with them quickly.
An even more significant advantage is that the Eclipse IDE explicitly enables plug-ins to be
built by anyone. Although IBM has written a set of plug-ins that is packaged as RDi, other
companies and individuals are able to develop extra plug-ins that help support IBM i
development environments. Most source control and change management software vendors
with products in the IBM i marketplace, for example, provide plug-ins to interact with their
software.
Some resourceful RDi users create more generic tools that are unrelated to a specific product
on the host to make their own development easier and more productive. There are many
examples of tools that integrate with RDi. One of the most prolific groups of RDi plug-in
developers is the creators of iSphere, which is an open source project with a group of
extensions to RDi.
352 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 9-9 show the search capabilities.
An iSphere enhancement to the base search capability is that the results from multiple
searches can be retained. In addition, the search results can be saved into a spreadsheet,
or the members in the search results can be used to create a member filter.
To navigate through the search results, select a source member in the left column. Each
statement that contains the search strings appears in the right pane. Double-click a
statement to open the source member by using your choice of edit or browse mode.
Each tab that is shown in Figure 9-11 contains the search results from previous searches.
354 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Source/object text decorators
In the base RDi package, if you want to see the text that is associated with a source
member or an object, you must use the Table view. iSphere offers a function called
decorators that shows the text in the Remote Systems view.
Figure 9-12 shows an example of text decorations.
iSphere has many more features than these, and more features are added frequently. For
more information about some of the iSphere facilities, including how to download and install
iSphere, see the following articles:
A Closer Look at iSphere
http://www.ibmsystemsmag.com/ibmi/developer/rpg/isphere-details/
iSphere Plug-in Expands RSE/RDi Toolset
http://www.itjungle.com/fhg/fhg061615-story01.html
More iSphere Goodies
http://www.itjungle.com/fhg/fhg092915-story03.html
Looking For Stuff With iSphere
http://www.itjungle.com/fhg/fhg070715-story01.html
iSphere Plug-in on SourceForge
http://isphere.sourceforge.net/
356 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Code Coverage first appeared in RDi Version 9.1 and has been enhanced in several
subsequent releases. Originally, Code Coverage worked only with programs that ran in
batch. However, support for batch and interactive is now available as well as monitoring
triggered by using service entry points.
Code coverage uses the debugger. To use code coverage, you must first compile the program
to be tested with the source debug option of DBGVIEW(*ALL), DBGVIEW(*SOURCE), or
DBGVIEW(*LIST), if it does not already have one of those options specified.
Compiler optimization can affect the report of the lines that are covered. For the most
accurate results, compile with OPTIMIZE(*NONE), which is the default for a system that is
shipped from IBM.
Note: If portions of the application to be tested do not have debug information, only those
portions of the application with debug information are included in the code coverage report.
When you run the RDi code coverage support, you get several options returned. Figure 9-13
shows the consolidated code coverage summary for a full program.
In addition to seeing an overall report, you can drill down to see the individual lines that have
been covered and which lines were not executed by the test case.
For more information about using the RDi code coverage monitor, see the Running code
coverage topic in the IBM Rational Developer for i Knowledge Center:
http://www.ibm.com/support/knowledgecenter/SSAE4W_9.5.0/com.ibm.debug.pdt.codecove
rage.i.doc/topics/tcc_run_about.html?cp=SSAE4W_9.5.0
A hands-on lab is available from IBM DeveloperWorks on code coverage at the following web
page:
https://www.ibm.com/developerworks/community/blogs/49773f8f-a20d-4816-86f2-44a2d86
2dbc1/entry/New_Code_Coverage_hands_on_lab_available?lang=en
358 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
An emulation session opens in the editor area of your RDi workbench, as shown in
Figure 9-15. This emulation session is a separate job that does not interact directly with your
Remote Systems connection.
In addition, you can start a Run SQL Scripts session, which can be helpful for testing SQL
statements that you want to embed into your RPG code. Because the powerful Visual Explain
support is also part of Run SQL Scripts, you have an easy way to analyze the SQL optimizer's
plan for running the SQL statement, and index advice from the optimizer.
The Run SQL Scripts window opens where you can run the selected SQL. You must update
the source manually to replace program variables with actual values.
360 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 9-17 shows the Run SQL Scripts interface. With the latest support in RDi 9.5.1, a new
Visual Explain link is available that launches the Visual Explain function.
For example, you might have a FOR or DOx loop that has a SELECT/WHEN block nested
inside it and an IF/ELSE block nested inside that. Now, you must add another level of nesting
with another IF/ELSE block that falls between the FOR/DOx and the SELECT block. One of the
benefits of free-form logic is the ability to indent code to show the levels of nesting, which
makes it easier for the next programmer to follow the logic. Therefore, it is critical that the
indentation is accurate.
Even if the original code was indented correctly, introducing a new level in the middle requires
shifting blocks of code to get the indentation to reflect correctly the new nesting levels. You
can do this task manually in RDi using various methods, but these methods can be
error-prone and time-consuming.
You can use the support that is now built into RDi to select a block of code and use the format
option to indent the entire block based on the nesting levels of logic. You can use preferences
to set up how many spaces to indent each level. The formatter supports all the free-format
RPG statements, both logic and declarations.
You can use the built-in formatter for RDi on a specific block of code, or if no block is selected,
the entire member is formatted. Pressing Ctrl+Shift+F starts the formatter, or you can start it
by using the Source menu by right-clicking and selecting Source.
There are some good options that plug into RDi that do a better job at converting fixed format
to free-format RPG, including logic and declarations.
The RPG Toolbox from Linoma Software has an option that can convert either an entire
member or a block of selected code to free format. It also has a reformat option that is similar
to the one that is built into RDi, but it has some slightly different options for the reformat
behavior.
Likewise, Arcad Software also offers ARCAD-Transformer, which converts entire source
members from fixed-format RPG to free format and plugs it into RDi. Figure 9-18 shows the
before and after of some code that was reformatted by using the formatter function. You can
launch this support by pressing Ctrl+Shift+F keys. The formatting support can also be
customized to allow you to specify how much you want to indent after certain code constructs.
Figure 9-18 Before and after formatting from fixed format to free-form format
362 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
You can input some text in the filter box, for example, a full or partial name of a variable or
routine, and the outline view then filters everything except items that contain that text. This
feature is helpful for finding items quickly in an outline view for a large program or one with
many definitions.
Unreferenced items can now be hidden from the outline. This process is helpful when the
code copies in many items that a particular program does not need, such as copying many
unreferenced prototypes into a source member.
There are some limitations in the support for OS X and some differences in behavior, at least
in the initial release of the OS X support at the time of writing (Version 9.5.1). Most notably,
syntax checking and the program verifier are not supported at the time of writing. Some
keyboard shortcuts might be different from the ones that are used in Windows. Bidirectional
text is not currently supported and the integrated 5250 emulator is not supported, although
the ACS emulator is supported through the Remote Systems view.
Even with the current limitations, the ability to run RDi without requiring a Windows virtual
machine is an advantage to those IBM i developers who use Mac workstations.
Commenting can be done by using the shortcut (Ctrl+/) or by selecting Source → Comment
from the menu. Uncommenting is done with Ctrl+\. Both can either be applied to a single line
(where the cursor is positioned) or to a selected block of code.
Continued use of the Alt+Left and Alt+Right keyboard shortcuts can navigate all the way
back through all your edits to the file.
This is a similar kind of navigation to the subroutine or internal subprocedure link that
happened by using F3 when positioned on the routine name. The Alt+Left shortcut also
returns you to the original reference to the routine. In earlier releases, the Alt+Q shortcut was
used to return to the original position. This shortcut was changed in more recent versions to
be more consistent with the extended use of the Alt+Left and Alt+Right navigation through
various edits.
To set a service entry point, find the program (or service program) that you want to debug in
the Remote Systems view. Right-click the program or service program and select Debug or
Code Coverage (Service Entry) → Set Service Entry Point.
It can take several seconds for anything to happen, but eventually you see a confirmation
message and the Service Entry Points view opens in the workbench. Your program name
appears in the list of service entry points. The default settings include “All modules in the
program,” “All procedures in the program,” and your user ID. Those details can be changed
from the Service Entry Point view.
364 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 9-19 shows the Services Entry Point view.
Setting a service entry point starts a monitor on the host. Whenever the specified program is
started by the specified user profile (by default, this is your user profile) anywhere on your
IBM i host system, the host debugger puts the job where the program is running into debug
mode and then notifies RDi on your workstation.
It does not matter whether the program runs in a 5250 emulator, a batch job, or a server job.
The system is monitoring for any job that runs the specified program under the specified user
profile. When that happens, the following steps occur:
1. The Debug perspective in RDi opens (if it was not already open).
2. The specified program is put into debug mode.
3. The source for the program is opened in the editor (if it was not already open).
4. The debugger waits before the first executable statement for your interaction.
Invoke the service entry point program using the specified user profile from any job in any
environment to cause all of those actions to occur. It might mean calling the program from an
interactive job or submitting a batch job, or even invoking the program by using a web server
or database server job.
Your debug session is started and waits for you to tell it how to proceed. You might want to set
some breakpoints or step through the code, which is described in 9.4.4, “RDi debug activities”
on page 366.
Note: You do not need to do anything special in the job to be debugged. The system
detects that the requested program has been started by the appropriate user profile and
puts that job into debug mode automatically for you.
However, that does not mean that non-ILE language programs (for example, RPG or CLP)
cannot be debugged in a session that was started with a service entry point. It simply means
that a non-ILE program cannot be the program that starts the debug session. One solution is
to create a simple RPGLE or CLLE program that calls the non-ILE-language program so that
you can set a service entry point on it. You can then step into the non-ILE program. However,
sometimes that might not be practical to do, in that case, you can still debug those programs
by using a debug configuration.
One other situation where you cannot use service entry points is when the program is already
running, such as in a never-ending job. A debug configuration works for those situations.
The simplest way to create a job debug configuration is to find the job to be debugged in the
Jobs subsystem in the Remote Systems view in RDi. The Jobs subsystem by default is below
the Objects, Commands, and IBM i Contexts subsystems, and above the IFS Files
subsystem. Several built-in filters are available to help find the job to be debugged, and you
can also create your own job filters to make the search faster and easier.
Tip: If you are looking to debug a job with your own user profile, for example, you might find
the built-in My active jobs filter useful.
After finding the appropriate job, right-click it and select Debug (Prompt) to open a window
where you can supply the qualified name of one or more programs (or service programs that
you want to debug.
If you need to use a different debug configuration type, you can create any other type of IBM i
debug configuration from the Debug Configurations option from the Run menu. Six different
IBM i specific configuration types are available, including specific ones for batch,
multi-threaded, and incoming remote debug sessions.
The debug server runs on the IBM i host system and needs to be running before you can
debug. If it is not running, a notification message is displayed in RDi. The message offers an
option to start the debug server. If you do not have sufficient authority to start the debug
server on the host, you might need to have a system administrator start it using the Start
Debug Server (STRDBGSVR) CL command. Ideally that command could be included as part
of the system start routine that automatically runs during the IPL process.
An IBM i Debug preference page is available in RDi. If you normally need to specify *Yes for
Update Production Files when using the Start Debug (STRDBG) command, then you should
be sure to select Update Production Files in the Preferences. It is possible to specify that
feature when using a configuration to start your debug session, but not when using service
entry points. It is advised that if you need to have the Update Production Files set to *Yes
normally, go ahead and set that on your RDi IBM i Debug preference page.
Breakpoints work the same in the RDi debugger as they do in the host-based debugger. The
easiest way to set a watch breakpoint typically is to select the variable name in the source
and select Add Watch Breakpoint from its menu. In the resulting window, a value of 0 in the
Number of bytes to watch field means to watch the entire variable value.
366 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Similarly, conditional breakpoints can also be used. After setting a line breakpoint, right-click
the breakpoint, either in the source view or in the Breakpoints view, and select Edit
Breakpoint from the menu. Click Next on the first window to get to the ability to enter the
conditional expression entry box on the second window.
Stepping through the code can be done by using the various step icons in the Debug
Perspective toolbar. Or you can use options under the Run menu or by using function keys
that are shown next to the Run menu Step options. The Step and Step Into options are there
and work as they did in the host-based debugger.
You can look at and modify the values of variables while debugging. To look at a variable's
value, you can hover over the variable name in the source view. In order to monitor the values
of specific variables while stepping through the code or while going from one breakpoint to
the next, the debugger Monitors view is helpful.
To add a variable to the Monitors view, select the variable name by selecting it in the editor
and select Monitor Expression from the menu of the selected variable. Alternatively, a
variable can be added by name by using the menu or the plus sign in the Monitors view.
Figure 9-20 illustrates the use of some of the capabilities of viewing the value of a variable
during debug. Note that any variable value that changed since the last time the logic was
halted (a breakpoint or a step) turns red to make it easier to tell what actions happened
recently to the variable’s values. A variable's value can also be changed from the Monitors
view by clicking the value and a window appears to allow entry of a new value.
368 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
10
Kody Robinson is a younger developer. He is two years out of university and has been
working as an RPG programmer these past two years. His company has been embracing
modern RPG, the tools, and methodologies that have been discussed in this book. In the
following sections, he is going to break down some of his favorite code concepts and look at
the difference between the old and new. This chapter is not intended to be a tutorial or
reference manual to show all the possible differences. Rather, it looks at modern RPG from
the eyes of a young developer who has been changing from old to new. This chapter has
good pointers for all levels of developer.
Consider a new developer looking at a problem in the program that is shown in Figure 10-1.
Syntax is easy to learn if you have any programming knowledge at all, so assume that the
developer knows that SETLL is an operation code (line 0040.00).
Look one line down at line 0041.00. The developer will want to know what the random number
means to the far right. The number 30, in this instance, means if a record is found or not in the
logical file ARSHSTL1. However, not every company uses the number 30 for the same thing.
Jumping down a few lines more, you see *IN30. So now you not only have one random
number in your code that doesn’t give you any insight into what it does or is used for. A new
developer will have a hard time understanding this code.
Compare the code in Figure 10-1 and that shown in Figure 10-2 on page 371. Free-format
code make many changes to the way that the code looks:
More colors than green and black
The *INXX (indicator) is no longer there
You can use more than 6 - 10 characters when naming a field
The code looks like every other modern language
370 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 10-2 Same RPG ILE code transformed to free-format
The rest of this chapter does a few comparisons between some fixed-form RPG and its
counterpart, free-form. Most of this is basic knowledge, but this should help developers who
have not yet made the the switch to modern RPG.
10.2 File-Specs
Figure 10-3 shows a few differences between fixed-form file specs and free-format file specs.
Probably the most noticeable difference is being able to tell the compiler what you want to be
done with your files (for example, update, delete, or output). The compiler also assumes it is
disk and external, so keying those specifications is not necessary. Look at INVMASTR. In the
free-format example, all you have to put is “keyed.” That is because the compiler assumes
that the usage is input if no usage is declared.
10.4 Parameters
Parameters are how you pass information between programs, CLs, and many other things.
The old way of passing parameters is easy. Make a PLIST and add some values. However,
this method is not that descriptive and not efficient because you are still limited to your field
lengths. Also, there is no way to exactly tell which program those parameters are coming
from. Do these parameters come in from just one program? Or are these parameters a part of
a bigger array of company programs? With free-format parameters, you can label which
parameters deal with what programs or CLs.
Another great comparison (and one of the reasons of why you should use free-format) is you
have much more descriptive declarations at your disposal. You can state that the parameter is
character, decimal, and so on.
You might either think that MKB104 is either a structured program name or one that is not
descriptive at all. If you name programs like the ones pictured (which you have to with limited
program name lengths), then you probably want to use this free-form style anyways. Not
everyone (especially a developer) is going to know what MKB104 does. Pgm_MKB104 can
be called descriptive names like “SpecialOrderEntry,” “PrintAdjustedSales,” “EndOfDay,” or
any other descriptive thing you want to say. This is just another example of how much more
effective and productive you can be with free-format.
372 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 10-5 shows how you can define “like” in a much easier, more descriptive way
Previously you would have to define a distinctive KLIST and put the fields that make up that
key. In free-format, follow the Chain command with the fields you want (shown in Figure 10-6
on page 374 as PRMPO# and PRMRLS) and then the file that you want to chain to. It is that
simple. It does not require any extra code that can confuse a new developer. It is just the code
that you need to get the data that you want.
Instead of using the operation code SETON, you just “turn on” the indicators (if you choose to
still use traditional indicators) as shown in the bottom portion of Figure 10-7 on page 375. You
can either use *INXX = *On or use the traditional “1” for on and “0” for off. You now have the
%TLookup code that functions just like the traditional operation code, so no adjustment is
required.
374 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Figure 10-7 shows setting *in89 on if it is given an error from the array. From here on out, it
functions the same. The “08” is the spot in the array that we want to pull from, then we are
moving it all the way to TABOUT, which will be moved to WERR. The Update SFLRCD is
written the exact same, as well as the leave and else.
Even though there is a little more work to call a program in free-format, it is often worth it
because the end result is a lot better. As discussed in 10.4, “Parameters” on page 372, the
same rules apply here. The program that is shown in Figure 10-8 on page 376 passes the
parameters MBMORD, WHS, and PKSEQ into MKB142CL. At the top of the program, a
prototype is defined just like before. However, the naming standard is Pgm_XXXXX. You can
make the name as descriptive as you want if you define the ExtPgm as your actual program
name. Then, you define your parameters and their attributes underneath.
376 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
10.8 Read commands
Read commands are one of the more commonly used commands because you really cannot
do too much with files without them. This has not changed much in free-format. As a
developer, you can still use READ, READE, READP, and so on. The only difference is instead
of leading with factor 1, you start the line with your READ, then your fields, then your file.
Figure 10-9 shows a couple of examples of using READ in both old and new styled RPG.
Much like the %TLookup command, the CHECK command now functions in about the same
way, meaning that you condition it with a “%.” Instead of leading with the %Check command
(green circle in Figure 10-10 on page 378), you lead with the result field. In this case, it is the
NUM field. Essentially it is the same process, except that your factors are swapped around.
Your result field goes in the front, followed by the %Check command, then conditioned within
parentheses are your factor 1 and factor 2.
378 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
In Figure 10-11, the user is keying a value into the OTYPE field. Whatever that value is
determines what subprocedure is called. You can combine a number of Ifs, Whens, and so on
within the select.
Concatenates in RPG free-format are even easier and are more intuitive. Figure 10-13 shows
using concatenate in free-format. You do not need to add a “%” at the beginning of it. You use
the “+” sign. Your compiler knows whether it is a character field or not, so it does not try to add
it like it does on numeric.
380 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
You can still use the TAG command. Use the TAG command as a field to tell you when you
need to skip over some code. In Figure 10-14, the ATag command is used. In the fixed-format
way, the tag returned is named TSCR. In fact, the Go-To command is turned into a large DoU
(Or DoW) loop. After a condition is met that you want to jump over some code (or Go-To some
code), change the value of the tag that you made, and it works the exact same way as a
traditional Go-To command. It might be a little more work to do, but it is worth it to make your
code more readable and modular.
Next, execute your SQL statement (Exec SQL) and declare your cursor. You can be as
descriptive as you want throughout, but for this example Cursor1 and Cursor2 are used. If you
want to be able to fetch all throughout your cursor (not just forward), include the scroll
keyword. This commands lets you get data from various ways. You have to know a little bit
about SQL to write your statement, but it is easy to learn and fits in with free-format RPG.
Fetch Prior Positions the cursor on the previous row of the result table relative to the current
cursor position.
Fetch First Positions the cursor on the first row of the result table.
Fetch Last Positions the cursor on the last row of the result table.
Fetch Before Positions the cursor before the first row of the result table.
Fetch After Positions the cursor after the last row of the result table.
Fetch Current Does not reposition the cursor, but maintains the current cursor position. If the
cursor has been declared as DYNAMIC SCROLL and the current row has been
updated so its place within the sort order of the result table is changed, an error
is returned.
Fetch Relative Evaluates host-variable or integer to an integral value k. RELATIVE positions the
cursor in the row in the result table that is either k rows after the current row if
k>0, or k rows before the current row if k<0. If a host-variable is specified, it must
be a numeric variable with zero scale and it must not include an indicator
variable.
Fetch From This keyword is provided for clarity only. If a scroll position option is specified,
then this keyword is required. If no scrolling option is specified, then the FROM
keyword is optional.
After you run your SQL statement, open your cursor (Open Cursor1). Then, you continue
fetching your data as needed. You might find it easier if you check your SQL commands
against the SQL codes. If they pass those conditions, then go onto calculations and fetch the
next record that you need.
382 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
You do not want to have to read all the records again if you want to do something special with
your data. You can still check lower limits in SQL. It just requires an extra step and no
keyword. Make another cursor (for example, Cursor 2) and then pass in your last record as a
parameter (pictured to the right in Figure 10-15). This process keeps your run time down and
allows you to not start over each time you do different calculations.
The rest is simple and follows basic RPG guidelines. After you get the hang of embedded
SQL, you will see how easy it is to get the data you need with minimal work, especially for
reports.
%CHAR Graphic, date, time (or time stamp), numeric Get value in character type.
expression
%CHECK Compare value : data-to-search (:StartPOS) First position in data that contains a character
not in the list of characters in the compare
value.
%DATE(DateFo (Value (Date Code if Needed) Converting value to specified data format.
rmatCode) System date is defaulted if not value is entered.
%TLOOKUPxx Search-data : searched table : alternate table *ON if successful. Looks up data in table.
%REPLACE Replacement string : source string (:startPos String produced by inserting replacement
(:SourceLengthToReplace) string into source string.
%SCAN Search argument: string to be searched First position of searched argument in string or
(:startPOS) zero (if not found).
384 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
A
Select the Additional materials and open the directory that corresponds with the IBM
Redbooks form number, SG245402.
The publications listed in this section are considered particularly suitable for a more detailed
discussion of the topics covered in this book.
IBM Redbooks
The following IBM Redbooks publications provide additional information about the topic in this
document. Note that some publications referenced in this list might be available in softcopy
only.
Modernizing IBM i Applications from the Database up to the User Interface and Everything
in Between, SG24-8185
You can search for, view, download or order these documents and other Redbooks,
Redpapers, Web Docs, draft and additional materials, at the following website:
ibm.com/redbooks
Other publications
These publications are also relevant as further information sources:
ILE RPG Style Guide
http://www.qrpglesrc.com/downloads/ILE_RPG_Style_Guide.pdf
Programming IBM Rational Development Studio for i, ILE RPG Reference
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_73/rzasc/sc092507.pdf
Programming in ILE RPG, by Brian Meyers and Jim Buck
http://www.mc-store.com
Free-Format RPG IV, by Jim Martin
http://www.mc-store.com
Programming IBM Rational Development Studio for i ILE RPG Programmer's Guide:
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/rzasc/sc092507.pdf
Programming ILE Concepts, SC41-5606
https://www.ibm.com/support/knowledgecenter/ssw_ibm_i_72/ilec/sc415606.pdf
DB2 for i SQL Reference
https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_72/db2/rbafzintro.htm
388 Who Knew You Could Do That with RPG IV? Modern RPG for the Modern Programmer
Looking For Stuff With iSphere
http://www.itjungle.com/fhg/fhg070715-story01.html
iSphere Plug-in on SourceForge
http://isphere.sourceforge.net/
SG24-5402-01
ISBN 0738442100
Printed in U.S.A.
®
ibm.com/redbooks