使用 Rational Open Access: RPG Edition 分离 RPG 数据库 IO

RPG、ILE 和 SQL 的全新起点

从 DDS 迁移到 DB2 for i 上的 SQL 数据库无需更改一行程序代码,也不需要重新编译程序。本文主要描述如何使用 Rational Open Access: RPG Edition ,使 RPG 程序获得高级数据中心编程技术所带来的好处,而这些好处原本只能通过 SQL 编程获取。

Daniel R. Cruikshank, 高级顾问, IBM

Cruikshank 的照片Dan 拥有超过 39 年 IT 从业经验,主要从事 IBM Rochester 系统和 RPG。Dan 还拥有超过 20 年的 SQL 经验。他目前负责教授 DB2 for i Optimization 和利用嵌入式 SQL 的 Data Centric Programming 技术。Dan 擅长应用程序性能故障诊断和数据库再造。



2011 年 12 月 08 日

如今,全世界 IBM i 客户都在使用 SQL 定义和访问数据。IBM 发布的每个 IBM i 新版本都继续提供丰富的新函数和功能,但它们只能通过 SQL 使用。DB2 Modernization 白皮书 列示并描述了这些增强功能。

这些基于 SQL 的新应用程序正在利用现代数据中心开发概念,某些概念强调将更多业务规则和逻辑推到数据库中的重要性。

许多客户仍然依赖以 IBM 的 RPG 编程语言编写的传统应用程序。一些 PRG 程序也能从利用现代数据中心技术中受益。这些现有 RPG 应用程序与新的 SQL 应用程序包含相同的业务规则,但它们是许多年前开发的,所使用的“一次一条记录” 的传统数据访问方法比较低效。重复的规则和逻辑会(其实已经)导致昂贵的双重维护和多个事实版本的存在。

Rational Open Access: RPG Edition(或 ROA)支持(自 IBM i 7.1 中引入且回溯支持 6.1)拦截 RPG IO 操作并将其转换为 SQL,从而消除了双重维护的巨大成本和 / 或昂贵的程序重写。现在可以将一个新关键字 (HANDLER) 添加到现有 PRG 文件规范中。HANDLER 关键字现在可以指定 IBM i 操作系统调用的程序或服务程序入口点。这个新支持能够极大地减少重写历史遗留代码的工作量,如果没有它,则需要转换现有程序来共享公共模块。清单 1 展示了一个 HANDLER 编码示例。

清单 1. HANDLER 关键字编码示例
     FEMPADDRSL1UF   E           K DISK 
      //-------------------Rational Open Access Addition ---------------------- 
      // The handler keyword specifies the program/service program which will 
      // now process the IO operations for this file 
     F                                     handler('UPDHANDLER')

在本文中,我将描述如何创建一个基于格式的 HANDLER,支持多个程序使用一个公共格式和唯一键更新同一个表,以便利用单个通用 SQL UPDATE 语句,该语句利用扩展指示符变量支持。

我将使用的方法如下所示:

  • 构建一个基于格式的 Handler 程序来拦截传统的 “一次一条记录” 数据库 IO 操作。这个 Handlder 程序将把拦截的 IO 操作转换为对等的 SQL 语句。
  • 添加一行代码,允许一个现有的非 SQL RPG ILE 程序使用这个 Handlder 程序。
  • 创建并注册一个外部存储过程,它允许任何支持存储过程调用的接口(Java、PHP 等)访问这个 Handler 程序。

我选择了 RPG IV 自由格式样式作为我的基于主机的编程语言。这个选择的主要原因是我相信使用 Rational Open Access: RPG Edition 对 RPG 程序员的吸引力更大。但是,重要的是要注意,Handler 程序和 和 SQL 程序可以使用 IBM i 上支持的任何基于主机的语言编写。

我在代码示例中也进行了一些变通:合并过程,移动和删除代码,等等。这可能会导致代码示例不能按原样编译。另外,有些代码只有在安装 Rational Open Access 产品后才能工作。

构建一个基于记录格式的 Handler 程序

可以通过两种方法使用 Rational Open Access 在 RPG 程序和 Handlder 程序之间传递数据。第一种方法是基于结构的,其中数据和键值通过缓冲区传递。这些缓冲区可以是程序描述的,也可以是外部描述的。第二种方法是基于列的,其中数据和键值作为独立项目传递。每个项目都包含数据或键字段的属性。采用的方法与需要编写的 Handler 程序的数量有直接关系。无论采用哪种方法,都需要使用动态嵌入式 SQL 来最小化编码量和 Handler 程序的总数。

动态 SQL 是创建通用数据库 Handler 程序的理想选择。可以部署三种类型的通用数据库 Handler 程序,您可以根据您的 ILE 和 SQL 专业技术水平进行选择。这三种类型是基于记录格式、语句和过程的 Handler 程序。基于格式的 Handler 程序非常适合固定列表样式的动态 SQL。基于语句的 Handler 程序更适合变动列表样式的动态 SQL。基于语句的 Handler 程序最适合使用结果集或 MERGE 之类的 “一气呵成”型 SQL 语句。

固定列表动态 SQL 通常用于以下情况:应用程序提供动态更改行选择(通过 WHERE 子句)或列排序(通过 ORDER BY 子句)的功能,尽管计算出的结果集是静态的。在 Handler 应用程序中,固定列表动态 SQL 将用于基于结构的方法。使用固定列表动态 SQL 时不需要 SQL 描述符。

当应用程序从头开始构建整个 SQL 语句时,通常会用到变动列表动态 SQL。在这种情况下,需要使用 SQL 描述符来描述 SQL 语句、结果集中计算出的列以及任何输入参数。有两种描述符类型:一种通过 SQL ALLOCATE DESCRIPTOR 创建,另一种基于 SQLDA 结构定义。要详细了解动态 SQL,请在线参阅 SQL 编程指南:http://ibm.com/systems/i/db2/books.html。

对于本文,我将结合使用固定列表动态 SQL 和静态嵌入式 SQL(用于 UPDATE 语句)来创建一个基于格式的 Handlder 程序。在将来的文章中,我将介绍如何创建基于语句和基于过程的 Handlder 程序。

使用 ROA 产品的一个主要好处是无需费时费力地修改和测试现有程序。对于需要维护结构不良的现有 RPG 代码或利用编程模型提供的模块功能的开发人员来说,这种分离 IO 的方法可能是一个 “全新的起点”。

图 1 简要展示了 Handlder 程序如何提供一个通往一个依赖 SQL 进行数据访问的现有过程集的桥梁。这些过程可以是使用嵌入式 SQL 编写的外部存储过程,也可以完全以 SQL PL 编写。在本文中,我将用到使用嵌入式 SQL 的 RPG 子过程。

图 1. 使用一个 Handlder 程序来分离 IO
图 1. 使用一个 Handlder 程序来分离 IO

Handler 使用 ILE 向 RPG 程序提供一个原型化接口。要使用 Handler 程序,现有 RPG 程序必须是一个 ILE 程序(也称为 RPG IV)。因此,如果现有 RPG 程序不是 ILE,那么首先需要将 RPG 程序转换为 ILE。为此,要用到 Convert RPG Source (CVTRPGSRC) 命令。

我们的 Handlder 程序包含一个主模块,它将 RPG 操作导向一个对应的 Handlder 程序模块。这允许按照应用程序类型定制主模块。例如,针对只有输入的文件的 Handlder 程序不需要更新模块。

各个 Handler 程序模块包含将 RPG IO 操作转换为适当的 SQL 操作的逻辑。转换代码存储在子过程中。另外,Handler 程序模块包含一些原型化调用,它们调用一个包含嵌入式 SQL 子过程的 RPG SQL 服务程序。目前,可能有 19 个数据库相关 RPG IO 操作最终需要通过我们的 Handler 程序处理,我使用其中 4 个:OPEN、CHAIN、UPDATE 和 CLOSE。

图 2 包含描绘上述场景的 Rational Visualize Application Diagram。Handlder 程序主过程 UPDHANDLER 将入站 RPG IO 操作提交到适当的子过程。子过程在调用对应的 RPG SQL 服务程序子过程之前执行转换逻辑。

图 2. RDP Visualize Application Diagram
图 2. RDP Visualize Application Diagram

图 2 的放大图

Handlder 程序主过程 (UPDHANDLER)

基于格式的 Handler 程序需要定义要处理的文件的记录格式,为此,需要在 Handler 程序模块中编码用于文件定义、文件记录格式、文件密匙和 SQL 空指示符数组的模板,如 清单 2 所示。这些模板被编码为 RPG 全局变量,以便允许将来重用通用模板名称并最小化代码修改。

清单 2. 定义记录格式模板
     FrcdFile_t IF   E           K DISK    TEMPLATE 
     F                                     EXTDESC('EMPADDRESS') 
     F                                     RENAME(empAddr:rcdFormat) 

     D rcdFormat_t... 
     D                 DS                  LIKEREC(rcdFormat) 
     D                                     TEMPLATE 

     D keys_t          DS                  LIKEREC(rcdFormat:*KEY) 
     D                                     TEMPLATE 

     D Ind_Array_t... 
     D                 S              5i 0 DIM(7) 
     D                                     TEMPLATE

清单 2 包含一个文件模板定义代码示例。IBM i 6.1 中新引入的 TEMPLATE 关键字通知 RPG 编译器:这个文件仅用于字段定义,不需要、也不允许为该文件进行 IO 操作。EXTDESC 关键字不需要。在本例中,它用于定义包含 Handlder 程序中使用的记录格式定义的真实文件。

RENAME 关键字用于提供一个通用格式名称,以便减少代码更改,如果这个代码用作其他格式 Handlder 程序的模板的话。rcdFormat_t 数据结构模板展示了这一点,该模板基于重命名的格式。这个数据结构模板用于 Handlder 程序中的其他模块中。

SQL 空指示符数组用于 RPG HANDLE_CHAIN 和 RPG HANDLE_UPDATE 子过程中。空指示符值在 SQL FETCH 语句执行过程中填充。空指示符值在执行 SQL UPDATE 语句之前通过 RPG HANDLE_UPDATE 子过程设置。

为一个文件指定 HANDLER 关键字之后,针对该文件执行的所有显式或隐式 IO 操作都将通过 Handlder 程序处理。程序员的职责是编码必要的指令来处理 IO 操作并提供适当的结果。ROA 产品随 RPG include 文件 (QRNOPENACC) 一起提供,该文件包含传递到 Handlder 程序的参数的子字段定义。

清单 3 包含 UPHANDLER 程序中的主过程的示例。输入参数 (rpgIO) 是一个数据结构,其定义方式类似于 QrnOpenAccess_t 模板,该模板是 QRNOPENACC include 模块的一部分。主过程在以下子字段上操作:

  • rpgStatus 字段用于输入和输出,表明成功或失败。0 表示操作成功。Handlder 程序在异常发生时提供一个有效的文件状态码。ILE RPG 参考手册定义了这些状态码。
  • rpgOperation 字段包含一个代码,对应于由正在被处理的 RPG 程序刚刚执行的 RPG 操作。这个 include 模块为每个 IO 操作码提供一个已命名常量(即 QrnOperation_OPEN、QrnOperation_CHAIN,等等)。RPG Select 语句控制基于 RPG 操作将执行哪些子过程。rpgIO 数据结构被传递给每个子过程,rpgStatus 字段被用作一个返回字段。如果一个子过程将 rpgStatus 设置为 1299,则表示出现了一个不可恢复的错误,处理程序将被停用(*INLR 打开)。
清单 3. Handlder 程序主过程 RPG 代码示例
     D UPDHANDLER      PI 
     D rpgIO... 
     D                                     LIKEDS(QrnOpenAccess_T) 
      /COPY QOAR/QRPGLESRC,QRNOPENACC 
      /FREE 

       rpgIO.rpgStatus = *Zero; 

       select; 
         when rpgIO.rpgOperation = QrnOperation_OPEN; 
                rpgIO.rpgStatus = Handle_Open(rpgIO); 
         when rpgIO.rpgOperation = QrnOperation_CHAIN; 
                rpgIO.rpgStatus = Handle_Chain(rpgIO); 
         when rpgIO.rpgOperation = QrnOperation_UPDATE; 
              rpgIO.rpgStatus = Handle_Update(rpgIO); 
         when rpgIO.rpgOperation = QrnOperation_CLOSE; 
              rpgIO.rpgStatus = Handle_Close (rpgIO); 
         Other; 
       ENDSL; 

       //If unrecoverable error then shutdown handler 
       If rpgIO.rpgStatus = 1299; 
         *INLR = *On; 
       ENDIF; 
       return; 
      /END-FREE

子过程接口

每个 RPG IO 操作子过程都使用一个公共接口,该接口包含 rpgIO 参数和一个与 rpgStatus 码对应的返回字段。另外,每个过程都在输入时将 rpgStatus 设置为 0 并使用 RPG Monitor 功能来处理意外错误。这样的错误出现时,rpgStatus 字段被设置为 1299(检测到其他 I/O 错误)。这将导致正被处理的 RPG 程序内部出现一个异常。

清单 4 包含这个公共 Handlder 程序子过程接口的一个代码段。

清单 4. 公共子过程接口原型
     P Handle_Open     B 
     D Handle_Open     PI                  LIKE(rpgIO.rpgStatus) 
     D rpgIO... 
     D                                     LIKEDS(QrnOpenAccess_T) 

     D* Local fields 
     D retField        S                   LIKE(rpgIO.rpgStatus) 
          
      /FREE 
       retField = *Zero; 
       Monitor; 
      //routine specific data and code begins here 
       On-Error; 
          retField = 1299; 
       ENDMON; 

       return retField; 

      /END-FREE 

     P Handle_Open     E

处理隐式和显式 RPG 打开操作

RPG 文件打开可以在 RPG 初始化阶段隐式发生,也可以通过 RPG OPEN 操作或在文件定义上使用 USROPN 关键字显式发生。在多数情况下,隐式打开就足够了。显式 OPEN 操作的使用在 RPG 程序和 Handlder 程序之间提供更多交互。

应该在 Handlder 程序 OPEN 例程中完成的第一件事是设置 IO Information Feedback 数据结构的指针和长度。这些是意外错误出现时可以在 RPG 程序中使用的结构。它允许您将额外的状态信息返回 RPG 程序。清单 5 包含设置 rpgIO 数据结构中的这些值的代码段。

清单 5. IO Feedback Information
        rpgIO.openFeedback = %Alloc(80); 
        rpgIO.ioFeedback = %Alloc(160); 
        rpgIO.deviceFeedback = %Alloc(126); 
        rpgIO.openFeedbackLen = 80; 
        rpgIO.ioFeedbackLen = 160; 
        rpgIO.deviceFeedbackLen = 126;

下一个重要项目是 rpgIO stateInfo 指针。这个可选指针用于分配临时存储空间,以便存储对 Handlder 程序的调用和来自 Handlder 程序的调用之间需要保留的信息。这种项目的一个例子是记录前映像。Handlder 程序将比较记录前映像和从 RPG 更新操作传递到 Handlder 程序中的值。我们将在 Handle-Update 子过程讨论中进一步讨论这一点。

清单 6 包含的代码示例展示了如何声明一个状态信息结构,然后根据该结构的大小向 stateInfo 指针分配存储空间。

清单 6. stateInfo 数据结构代码示例
      //stateinfo data structure template 
     D rpgSI_t... 
     D                 DS                  TEMPLATE 
     D  OLD_ROW_p                      *     
      /FREE  
       rpgIO.stateInfo = %Alloc(%Size(rpgSI_t));

现在,我们已经准备好构造将用于返回输入记录、以响应 RPG CHAIN 操作的 SQL 语句了。这个语句将是一个传递到 Prepare_SQL_Statement 子过程的字符串变量。图 3 详细展示了处理一个 RPG 隐式文件打开操作的过程和主要函数的代码片段。RPG 程序打开的文件的名称被传递到 rpgIO externalFile 结构中的 Handlder 程序。这个结构包含两个子字段:库和名称。如果库列包含 *LIBL,那么只有外部文件名称被用于表引用,否则,库和名称列将被合并以形成一个被限定的表引用。然后,表引用变量被用于 SQL Select 语句 FROM 子句。对 RPG PREPARE_SQL_STATEMENT 子过程的调用失败时,将向正被处理的 RPG 程序返回 rpgStatus 值 1299。图 3 中的 HANDLE_OPEN 流程图标下方的代码段展示了这一点。

图 3. 处理 RPG 打开操作
图 3. 处理 RPG 打开操作

一个 WHERE 子句被添加到一个 Select 语句字符串,该字符串表示 RPG CHAIN 操作中使用的键字段对应的一列或多列。在本例中,EMPNO 是表的唯一键。此时,EMPNO 的值是未知的,因此一个参数标记 (?) 被用作占位符。实际值将在第一个 RPG 键 IO 操作发生时提供。一个 FOR FETCH ONLY 子句被添加到 Select 语句,以避免记录锁定并利用 SQL 自动块操作的优势。

SQL 语句一旦完成格式化,将被传递到 RPG PREPARE_SQL_STATEMENT 子过程。包含嵌入式 SQL 语句的 RPG 过程通常被创建为独立模块,然后用于创建服务程序。服务程序可以在编译时被绑定到 Handlder 程序。SQL 模块也必须包含格式模板代码,如 清单 2 所示。另外,要支持 SQL 扩展指示符的使用,SQL 模块要么需要使用预编译器命令的 OPTION 参数上的 *EXTIND 进行编译,要么包含一个指定 EXTIND(*YES) 的 SQL Set Option 语句。Set Option 语句需要在模块中的其他 SQL 语句之前指定。

图 3 包含 RPG PREPARE_SQL_STATEMENT 子过程的一个 RPG 代码示例,该子过程准备一个动态 SQL 语句字符串 (v_SQL_String),如果成功,就为准备好的 SQL 语句声明一个 SQL 游标。SQL 语句字符串 (p_SQL_String) 从 RPG HANDLE_OPEN 子过程传递到 RPG 子过程,以响应 RPG 打开操作。

处理 RPG CHAIN 操作

RPG 程序更新通过键访问的行之前,必须执行一个读操作,以便锁定该行,进行更新。使用键执行随机读操作的最常见 RPG 方法是 CHAIN 操作。图 4 详细展示了处理一个 RPG CHAIN 操作的过程以及主要函数的代码段。

图 4. 处理 RPG CHAIN 操作
图 4. 处理 RPG CHAIN 操作

使用基于结构的 IO 方法时,RPG 将为输入和输出缓冲区创建初始存储空间,并为空指示符映射创建存储空间。rpgIO 参数包含指向这些存储空间区域的指针。RPG HANDLE_CHAIN 子过程定义类似于此前定义的全局模板的数据结构。数据结构中包含的数据基于 RPG 提供的指针。清单 7 提供数据结构定义的源代码示例。图 4 包含 RPG HANDLE_CHAIN 子过程的 RPG 源代码示例。

清单 7. 处理 RPG CHAIN
     D Handle_Chain    PI                  LIKE(rpgIO.rpgStatus) 
     D rpgIO                               LIKEDS(QrnOpenAccess_T)     

     D i               S              5i 0 
     D keys            DS                  LIKEDS(keys_t) 
     D                                     BASED(rpgIO.key) 
     D inpRcd          DS                  LIKEDS(rcdFormat_t) 
     D                                     BASED(rpgIO.inputBuffer) 
     D OLD_ROW         DS                  LIKEDS(rcdFormat_t) 
     D                                     BASED(rpgSI.OLD_ROW_p) 
     D rpgSI... 
     D                 DS                  LIKEDS(rpgSI_t) 
     D                                     BASED(rpgIO.stateInfo) 
     D IndAry          S              5i 0 DIM(%elem(Ind_Array_t)) 
     D InpNullMap      S               N   DIM(%elem(Ind_Array_t)) 
     D                                     BASED(rpgIO.inputNullMap)

一旦 RPG 程序接收到数据,inputBuffer 指针将被 RPG 设置为空。为了比较记录前后映像,Handlder 程序必须创建一个输入记录副本。原来的行的存储空间被定义为 清单 6 中描述的 stateInfo 数据结构的一部分。这确保数据在 CHAIN 和后续 UPDATE 操作之间保留。

如果列被定义为支持空值,那么您必须相应更新空指示符映射。RPG 使用一个 1 字符字段(比如一个指示符)来确定列是否包含空值。如果列不包含空值,则 SQL 使用一个包含一个 0 的小整数;反之则包含 -1。

在 SQL 中,没有与 RPG CHAIN 对等的函数,因此我选择使用 FETCH FIRST 语句和一个游标作为适当的替代选项。这也解释了使用 PREPARE、DECLARE、OPEN 和 CLOSE SQL 游标语句的原因。

另一种思路的人可能建议使用 SELECT INTO 作为 CHAIN 操作的替代选项。它在某种程度上类似于使用 CHAIN 的 RPG 程序,因为它隐式执行准备,打开并关闭函数;但是它的缺点大大超过了这个快捷功能。这些缺点是:

  1. SELECT INTO 必须返回单个行,否则就会失败。无法保证 RPG 程序正在 CHAIN 上使用一个唯一键。在这种情况下,返回 RPG 的行取决于创建键逻辑文件时使用的是哪个用于处理 DDS 关键字的重复键(LIFO(默认值)、FIFO 或 LCFO)。
  2. 上述问题可以通过使用 DISTINCT 和一个或多个时间戳列模拟 FIFO 或 LCFO 来回避;但是,DB2 将使用一个额外步骤来消除重复。如果有大量重复键值,这可能会导致性能大幅降低。
  3. 由于 SELECT INTO 返回单个行,因此它不能用于其他 RPG 读操作。但是 FETCH 能用于返回一行或多行,允许单个 RPG 子过程用于 CHAIN、READ 或 READE 操作。

图 4 包含 FETCH_FIRST_FROM_OPEN_CURSOR 子过程的一个 RPG 源代码样例。RPG HANDLE_CHAIN 子过程传递这个键(或多个键)、inputBuffer 指针和指示符数组。

这个键(或多个键)用于替代这个参数标记(或多个标记),参数标记被定义为在 RPG HANDLE_OPEN 子过程中创建的 SQL 语句字符串的一部分。OPEN CURSOR 语句作为 FETCH 过程的一部分执行。如果 OPEN 成功,则 SQL FETCH FIRST 语句被执行。inpRcd 结构和 indAry 参数包含成功 FETCH 的结果。SQL OPEN 和 FETCH 的结合等同于代表 RPG CHAIN 执行的系统指令。本质上,OPEN 定位到索引中,FETCH FIRST 将基于索引提供的 RRN 检索第一行。

在同一个会话或作业中第二次执行 OPEN 后,SQL 游标将变得可重用。这意味着后续 SQL OPEN 和 CLOSE 操作只是将游标重新定位到结果集的第一行。无论 RPG 程序是否正在使用 LR 指示符,这种行为都会发生。这允许将 LR 持续用作一个内务整理工具,同时避免了 SQL 打开和关闭开销的高昂成本。

处理 RPG UPDATE 操作

要理解 RPG HANDLE_UPDATE 子过程,我们需要讨论扩展指示符变量支持的概念。在 DB2 for i 6.1 发布之前,SQL Update 和 Insert 语句能够使用一个值为 -1 的指示符变量,将支持空值的列设置为空值。使用 6.1 版中引入的扩展指示符变量支持,可以通过提供额外的指示符值来扩展更新和插入功能。

最有趣的一个功能是使用指示符值 -7 来允许更新绕过一列,就好像该列不是 UPDATE 语句的一部分。这个支持允许您编写一个可用于任何更新事务的通用更新过程,而不管正在更新哪些列。图 5 详细展示了处理一个 RPG UPDATE 操作的过程和主要函数的代码段。

图 5. 处理 RPG 更新操作
图 5. 处理 RPG 更新操作

图 5 中的来自 RPG HANDLE_UPDATE 子过程的代码段显示,更新指示符数组被初始化为 -7。outputBuffer 中的值与从 inputBuffer 保存的值相比较。对于每个不同的值,该列的指示符变量被设置为 0。可以添加额外的代码来执行一个检查,检查新值是不是一个用户定义值,用以表明数据库值应该被设置为 NULL 或针对该列定义的默认值。如果是前一种情况,那么该列的指示符变量被设置为 -1,这导致该列被更新为空值。如果是后一种情况,那么该列的指示符变量被设置为 -5,这将把该列的值设置为默认值。如果新旧值相同,那么指示符变量仍然是 -7,该列被忽略。RPG HANDLE_UPDATE 子过程是特定于格式的,必须针对每个文件进行定制。

比较完所有列之后,将执行 RPG UPDATE_COLUMNS_USING_EXTENDED_INDICATORS 子过程。RPG UPDATE_COLUMNS_USING_EXTENDED_INDICATORS 子过程接受两个参数:updRcd 和 Ind_Ary。updRcd 参数包含表中的可更新列的值(已更改的值和未更改的值)。Ind_Ary 列包含 SQL 扩展指示符设置。

图 5 包含 RPG UPDATE_COLUMNS_USING_EXTENDED_INDICATORS 子过程的一个 RPG 源代码样例。UPDATE 上使用的表名是包含清单 2 中使用的格式的文件的名称。在 IBM Data Access Reengineering 策略中,这个文件称为代理文件。要详细了解如何现代化数据访问,请访问 DB2 for i 网站

指示符变量不能通过数组索引技术指定。每个指示符变量都必须单独命名。为绕过这个限制,我使用一个数据结构来为每个数组元素定义一个已命名指示符变量。这个数据结构基于 Ind_Ary 的地址。清单 8 包含将输入指示符数组重新定义到一个数据结构的 RPG 源代码示例。

清单 8. 使用扩展指示符的更新过程
     D Update_Columns_Using_Extended_Indicators... 
     D                 PI              N 
     D  updRcd... 
     D                                      LIKEDS(rcdFormat_t) 
     D  Ind_Ary... 
     D                                      LIKE(Ind_Array_t) 
     D                                      DIM(%elem(Ind_Array_t)) 
     D* Local fields 
     D retField        S               N 
     
     D Ind_Ary_DS      DS                  BASED(Ind_Ary_Ptr) 
     D   Ind_Ary_1                    5i 0 
     D   Ind_Ary_2                    5i 0 
     D   Ind_Ary_3                    5i 0 
     D   Ind_Ary_4                    5i 0 
     D   Ind_Ary_5                    5i 0 
     D   Ind_Ary_6                    5i 0 
     D   Ind_Ary_7                    5i 0 

      /FREE 
       Ind_Ary_Ptr = %Addr(Ind_Ary);

处理隐式和显式 RPG 关闭操作

Handlder 程序必须执行两个函数:一是关闭 SQL 游标;二是取消 stateInfo 数据结构使用的内存的分配。图 6 详细展示了处理一个隐式或显式 RPG CLOSE 操作的过程和主要函数的代码段。

图 6. 处理 RPG CLOSE 操作
图 6. 处理 RPG CLOSE 操作

RPG 程序打开 Last Record (*INLR) 指示符,这导致针对已处理文件的一个隐式 CLOSE 操作。Handlder 程序拦截 RPG CLOSE 操作并将控制权传递给 RPG HANDLE_CLOSE 子过程。分配给 stateInfo 指针的内存被取消。一个调用被发送到 RPG CLOSE_SQL_CURSOR 子过程,该子过程包含嵌入的 SQL CLOSE 语句。


实现场景

下列场景用于测试 Handlder 程序代码:(1)使用一个 5250 显示文件的传统 RPG 程序;(2)从一个 Java 应用程序调用的外部存储过程。5250 场景在许多传统 RPG 商店中很常见,在这类商店中,有许多程序正在访问同一个表以进行更新。Java 场景描述的技术采用单个外部存储过程进行表更新,而不是在很多应用程序中包含多个 SQL UPDATE 语句。其目标是对同一个表的所有更新拥有单点控制,而不管更新的起源点。这些场景如 图 7 所示。

图 7. 混合的应用程序共享公共 Handlder 程序
图 7. 混合的应用程序共享公共 Handlder 程序

本质上,单个基于格式的 Handlder 程序能够服务于多个使用各种 UPDATE 方法的基于主机的 RPG 5250 程序。这包括使用数据结构或 RPG 内置函数 %FIELDS 更新文件或记录格式。另外,一旦一个 RPG 程序被注册为一个外部存储过程,任何支持存储过程调用的接口(Java、PHP,等等)都能访问这个 RPG 程序。这种外部存储过程方法还允许浏览器客户端等外部应用程序利用 DB2 for i 扩展指示符支持。

清单 9 包含一个用于将一个 RPG 程序注册为一个外部存储过程的 SQL CREATE PROCEDURE 语句的代码示例。

清单 9. 外部存储过程代码示例
 CREATE PROCEDURE TEST_UPDFIELDS ( 
      IN p_ADDRESS1 VARCHAR(30) 
     ,IN p_ADDRESS2 VARCHAR(30) 
     ,IN p_ADDRESS3 VARCHAR(30) 
     ,IN p_CITY VARCHAR(15)  
     ,IN p_STATE VARCHAR(10)   
     ,IN p_ZIPCODE VARCHAR(10) 
     ,IN Unique_Key CHAR(6) 	 ) 
	 LANGUAGE RPGLE 
	 PARAMETER STYLE GENERAL WITH NULLS 
	 RESULT SETS 2 
	 NOT DETERMINISTIC 
	 MODIFIES SQL DATA 
	 EXTERNAL NAME RPGEXTPROC

清单 10 包含已经注册为外部存储过程的现有 RPG 程序的代码。UPHANDLER 程序将处理的 RPG IO 操作码是 BOLD。这个 RPG 程序正在定义代理文件以便进行更新。清单 9 中显示的来自 SQL CREATE PROCEDURE 语句的 PARAMETER STYLE GENERAL WITH NULLS 子句指定,一个空指示符数组将作为一个额外参数被发送给外部存储过程。对于传递到外部存储过程的每个输入参数,数组中都将有一个元素。RPG 程序使用这个参数 (p_Ind_Ary) 来确定输入参数是否包含一个不等于 NULL (-1) 的值。如果是,那么使用输入参数值更改相应的数据列。由于文件 EMPADDRESS 正在被程序 UPDHANDLER 处理,因此 UPDATE 操作按照此前 图 5 中描述的方法处理。

清单 10. RPG 外部存储过程的代码示例
     FEMPADDRESSUF   E           K DISK 
     F                                     handler('UPDHANDLER') 

     D i               S              5i 0 

     D HandleTheInternet... 
     D                 PR                  EXTPGM('RPGEXTPROC') 
     D p_ADDRLINE1                         LIKE(ADDRLINE1) 
     D p_ADDRLINE2                         LIKE(ADDRLINE2) 
     D p_ADDRLINE3                         LIKE(ADDRLINE3) 
     D p_CITY                              LIKE(CITY) 
     D p_STATE                             LIKE(STATE) 
     D p_ZIPCODE                           LIKE(ZIPCODE) 
     D p_EMPNO                             LIKE(EMPNO) 
     D p_Ind_Ary... 
     D                                5i 0 DIM(7) 

     P HandleTheInternet... 
     P                 B 
     D HandleTheInternet... 
     D                 PI 
     D p_ADDRLINE1                         LIKE(ADDRLINE1) 
     D p_ADDRLINE2                         LIKE(ADDRLINE2) 
     D p_ADDRLINE3                         LIKE(ADDRLINE3) 
     D p_CITY                              LIKE(CITY) 
     D p_STATE                             LIKE(STATE) 
     D p_ZIPCODE                           LIKE(ZIPCODE) 
     D p_EMPNO                             LIKE(EMPNO) 
     D p_Ind_Ary... 
     D                                5i 0 DIM(7) 

     D inpRecord       Ds                  LIKEREC(EMPADDR:*INPUT) 

      /Free 
       Monitor; 

        CHAIN(e) p_EMPNO  EMPADDRESS inpRecord; 
        If %Found; 

        If p_Ind_Ary(1) = *Zero; 
          inpRecord.ADDRLINE1 = p_ADDRLINE1; 
        ENDIF; 
        If p_Ind_Ary(2) = *Zero; 
          inpRecord.ADDRLINE2 = p_ADDRLINE2; 
        ENDIF; 
        If p_Ind_Ary(3) = *Zero; 
          inpRecord.ADDRLINE3 = p_ADDRLINE3; 
        ENDIF; 
        If p_Ind_Ary(4) = *Zero; 
          inpRecord.CITY = p_CITY; 
        ENDIF; 
        If p_Ind_Ary(5) = *Zero; 
          inpRecord.STATE = p_STATE; 
        ENDIF; 
        If p_Ind_Ary(6) = *Zero; 
          inpRecord.ZIPCODE = p_ZIPCODE; 
        ENDIF; 

        inpRecord.EMPNO = p_EMPNO; 

        Update(E) EMPADDR inpRecord; 

        ENDIF; 

       On-Error; 
       ENDMON; 

       *INLR = *On; //Implicit CLOSE 
       Return; 

      /End-Free 

     P HandleTheInternet... 
     P                 E

结束语

在本文中,我向您介绍了如何使用 Rational Open Access: RPG Edition 转换传统的 “一次一条记录” 访问方法,以便利用扩展指示符变量支持之类的高级 SQL 技术,这只需在现有 RPG 程序中更改一行代码。

使用扩展指示符支持,单个程序可以基于一个公共键用于单个表的所有更新事务。这包括使用传统更新方法的现有程序和能够利用存储过程调用的外部接口。使用这种技术,更新一个表中的一个列子集无需构建多个 SQL 语句。

我还介绍了基于格式的 Handlder 程序的概念,这种 Handlder 程序允许您将 RPG 输入和输出缓冲区用作在 RPG 程序和 Handlder 程序之间移动数据的机制。基于格式的 Handlder 程序利用 RPG IV 的新模板功能。它还构建在外部描述的数据结构的概念之上,这是固定列表动态 SQL 使用的主机变量的一种软编码方法。

在未来的文章中,我将扩展这些概念,创建一个基于语句的 Handlder 程序,与变化列表动态 SQL 联合使用,这可以极大地减少需要的 Handlder 程序的数量。我将使用这些技术来利用以下高级 SQL 功能:

  • 大数据处理技术,该技术利用 SQL 块操作的 FETCH 和 INSERT 来克服不能被块操作的传统操作(例如 READE)。
  • 使用 SQL 搜索的 UPDATE 和 DELETE 技术进行大规模更新。
  • 使用一个 SQL MERGE 语句替代 RPG 传统归档和清除技术。
  • 存储过程结果集使用。
  • 使用 INSTEAD OF TRIGGER 支持的可更新 SQL Join 视图。

通过 IBM Rational Open Access: RPG Edition 产品,只需对现有程序进行很小的更改即可利用上述所有功能。


参考资料

可以在下面的网站中找到关于这个产品的更多信息:

要找到关于 DB2 for i 的更多信息,包括白皮书、培训和支持,请访问 DB2 for i 网站

要详细了解本文中用于创建 Visualizer Application Diagrams 的 Rational Developer for Power Systems 工具并下载评估版,请访问 developerWorks Rational Tools 下载网站

要获取最新的、关于 DB2 for i Data Access 现代化策略的高级知识,包括 Rational Open Access、Rational Developer for Power 和 Infosphere Data Architect 的实践体验,请在下面的网站注册 DB2 for i Database Modernization Workshop:http://ibm.com/systems/i/db2/db2educ_m.html

推荐读物

要深入了解本文提到的代理文件概念,以及 IBM Database Modernization 策略,请参阅以下红皮书:

访问 DB2 for i developerWorks 论坛,保持连接,提出问题并获取解决方案。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=IBM i, Rational, Information Management
ArticleID=774291
ArticleTitle=使用 Rational Open Access: RPG Edition 分离 RPG 数据库 IO
publish-date=12082011