【テクニカル・上級編】DB2埋め込みSQLにおけるホスト構造体(HOST STRUCTURE)の利用 – PL/Iの基本構文とデータ制御実践ガイド

識別子と予約語の魔力:PL/Iホスト構造体がDB2現場で魅せる「真の挙動」

メインフレームの現場で長くPL/IとDB2(IMS/DBやCICSも含めて)に向き合ってきたアーキテクトなら、一度は深夜の緊急呼出しで冷や汗をかいた経験があるはずだ。
「なぜ、この単純なSQLで `-303` や `-501` が発生するのか?」
「なぜ、ホスト構造体の一括参照(FETCHやINSERT)で、予期せぬデータ化けやパディング(境界調整)アベンドが起きるのか?」

JavaやC#といったモダン言語のオブジェクト指向や、型安全なORMに慣れきった若いエンジニアがレガシー移行の現場に参画すると、決まってこう言う。
「PL/Iには明確な予約語(Reserved Words)がほとんど存在しない。だから何でも変数名にできて柔軟だ」と。

……ふっ。甘い、と言わざるを得ない。
予約語の縛りが緩い(コンテキスト依存のキーワードが多い)ということは、一歩間違えばコンパイラを欺き、あるいはコンパイラ自身が我々の意図を誤解して、アセンブラレベルでとんでもないコードを生成するリスクと表裏一体なのだ。特に、DB2の埋め込みSQL(Embedded SQL)において「ホスト構造体(Host Structure)」を扱うとき、この言語特性は牙をむく。

今回は、PL/Iのデータ制御の根幹と、DB2ホスト構造体の裏側でコンパイラとプリコンパイラが何をやっているのか、その深淵を覗いてみよう。

—

1. 予約語を持たないPL/Iの強みと、DB2プリコンパイルの闇

PL/Iの最大にして最強の特徴、そして時に最大の呪いは、「厳密な予約語(Reserved Words)がほぼ存在しない」という点にある。例えば、`READ` や `WRITE`、さらには `IF` や `GO TO` でさえも、文脈によってはプログラマが自由に変数の名前として定義できてしまう。

/ 狂気の変数宣言例:コンパイラはこれを許容するが、人間の正気を疑う /
DECLARE READ FIXED BINARY(31);
DECLARE IF CHARACTER(10);

/ 本来の制御文と変数名が混ざり合うカオス /
READ = 10;
IF IF = ‘ERROR’ THEN …

この「文脈依存性(Contextual Keywords)」は、自由度の高いビジネスロジックを記述する上では強力だった。しかし、DB2のプリコンパイラ(SQLコプロセッサ)が絡むと話が変わる。

プリコンパイラの視点とホスト構造体の展開

DB2のSQL文の中でホスト変数やホスト構造体を参照するとき、プリコンパイラはPL/Iのソースコードをスキャンし、ホスト変数をSQL通信エリア(SQLCA)やDB2のランタイムへ引き渡すためのコードへと置き換える。

ここで問題になるのが、「構造体(STRUCTURE)」の階層定義と、PL/Iコンパイラが内部で行うアライメント(境界調整)のミスマッチだ。

/ 良くある顧客マスターのホスト構造体定義 /
DECLARE 1 T_CUST_REC,
5 CUST_ID CHAR(6),
5 CUST_NAME CHAR(40),
5 CUST_BALANCE FIXED DECIMAL(11,2);

この構造体をそのまま `EXEC SQL SELECT … INTO :T_CUST_REC FROM …` と記述したとする。
一見、すっきりとスマートに書けているように見えるが、ここにメインフレームアーキテクチャ特有の「罠」が潜んでいる。

—

2. パックデシマル(FIXED DECIMAL)の符号反転とアベンドの罠

基幹システムのバッチ処理で最も恐れられるものの一つが、突然の S0C7アベンド(データ例外:Data Exception) だ。

`FIXED DECIMAL(p, q)` は、IBM汎用機ではお馴染みの「パック10進数(Packed Decimal)」としてメモリ上に展開される。1バイトに2桁の数字が入り、最下位ニブル(右側の4ビット)には符号(`C`, `D`, `F` など)が格納される。

ここで、ホスト構造体を用いた一括フェッチにおいて、以下の2点を見落としているシステムは高確率で爆発する。

1. 暗黙のパディング(Boundary Alignment)
PL/Iコンパイラは、デフォルトではパフォーマンス最適化のために、半ワード(2バイト)、全ワード(4バイト)、ダブルワード(8バイト)の境界にデータを合わせようとする。特に `FIXED BINARY(31)` や浮動小数点数が構造体に混ざると、人間が意図しない「空きバイト(パディング)」がメンバー間に挿入される。
DB2のプリコンパイラが生成するI/Oエリアと、PL/I側が解釈する構造体のオフセットがズレた瞬間、データは完全に破壊される。

2. 外部からの不正データ流入による符号破壊
他システムから連携されたフラットファイルや、レガシーな電文データを一度ストラクチャに受け、それをそのままDB2に突っ込む、あるいはその逆のルートを通る際、パックデシマルの最下位ニブルが不正な値(例: `E` や `A` など、許容されないゾーン/パック符号)になっていると、DB2側で `-303`(ホスト変数と列のデータタイプ不一致、あるいは値の切り捨てエラー)を引き起こすか、PL/I側に戻された瞬間にS0C7を誘発する。

—

3. 実践:安全かつ堅牢なホスト構造体定義とポインタ操作の極意

では、マイグレーションを見据え、かつ極限の信頼性を求められる基幹システムにおいて、ホスト構造体はどのように扱うべきか。
答えの一つが、「ベース変数(Based Variable)とポインタを用いた明示的なメモリ制御」と、「コンパイラオプションによるアライメントの完全統制」である。

以下に、現場で使える堅牢なPL/Iコードの骨子を示す。

/ ================================================================= /
/ プログラム名: CUSTSR01 /
/ 概要: DB2ホスト構造体を用いた安全な一括フェッチとメモリ制御 /
/ ================================================================= /
CUSTSR01: PROCEDURE OPTIONS(MAIN);

/ コンパイラオプションの明示(ALIGN指定によりパディング挙動を制御) /
/ ※実際にはJCLのCOMPILEカード等で指定するが、コーディング規約としても重要 /

/ 1. 独立したSQL通信エリアとインジケータ変数の定義 /
EXEC SQL INCLUDE SQLCA;

/ 2. ホスト構造体の定義(レベル番号とデータ属性の厳格化) /
/ NOTE: FIXED BINARY と CHAR を混在させる場合、順序と長さに注意 /
DECLARE 1 DB_CUST_STRUCT,
5 S_CUST_ID CHAR(6), / 顧客ID /
5 S_CUST_NAME CHAR(40), / 顧客名 /
5 S_CUST_AMT FIXED DEC(11,2), / 取引金額 /
5 S_FILLER CHAR(4); / 境界調整用空き /

/ インジケータ構造体(NULL値判定用) /
DECLARE 1 DB_CUST_IND,
5 I_CUST_ID SMALLINT,
5 I_CUST_NAME SMALLINT,
5 I_CUST_AMT SMALLINT,
5 I_FILLER SMALLINT;

/ 3. 動的メモリ操作のためのベース変数とポインタの定義 /
DECLARE P_WORK_MEM POINTER;
DECLARE 1 WORK_RECORD BASED(P_WORK_MEM),
3 W_ID CHAR(6),
3 W_DATA CHAR(48);

/ 4. カーソル宣言 /
EXEC SQL
DECLARE C1 CURSOR FOR
SELECT CUST_ID, CUST_NAME, CUST_AMT
FROM CUSTOMER_TBL
WHERE CUST_STATUS = ’01’;

/ 5. 処理本体 /
EXEC SQL OPEN C1;
IF SQLCODE ^= 0 THEN GOTO ERROR_RTN;

DO WHILE(TRUE);
/ ホスト構造体による一括フェッチ(インジケータ付き) /
EXEC SQL
FETCH C1 INTO :DB_CUST_STRUCT INDICATOR :DB_CUST_IND;

IF SQLCODE = 100 THEN LEAVE; / データ終了 /
IF SQLCODE < 0 THEN GOTO ERROR_RTN; / 例:ポインタとベース変数を使った動的バッファへの転記処理 / / (メインフレーム上のGETMAIN/STORAGE取得領域を模した安全な操作) / P_WORK_MEM = ADDR(DB_CUST_STRUCT); / 構造体のアドレスをバインド / / ここで個別ビジネスロジックを展開 / CALL PROCESS_LOGIC(WORK_RECORD); END; EXEC SQL CLOSE C1; RETURN; ERROR_RTN: / エラーハンドリング / DISPLAY('DB2 ERROR OCCURRED. SQLCODE = ' || SQLCODE); EXEC SQL WHENEVER SQLERROR CONTINUE; EXEC SQL CLOSE C1; SIGNAL ERROR; PROCESS_LOGIC: PROCEDURE(P_REC); DECLARE 1 P_REC BASED, 3 R_ID CHAR(6), 3 R_BODY CHAR(48); / データの整合性チェックやログ出力など / END PROCESS_LOGIC; END CUSTSR01; ---

4. アーキテクトが知るべき「移行(Migration)」へのインプリケーション

JavaやC#、あるいはクラウドネイティブなコンテナ環境へレガシーシステムをマイグレーションする際、この「PL/Iのホスト構造体」が最大の障壁の一つになる。

モダン言語には、PL/Iの「レベル番号による階層構造(01, 05, 03…)」や「パディングを考慮した暗黙のバイナリレイアウト」をそのまま表現できる概念が乏しい。C#の `[StructLayout(LayoutKind.Sequential)]` や Javaの `ByteBuffer` を駆使してエミュレーションすることになるが、ここで浮動小数点数やパックデシマルの解釈違いが起きると、移行後のデータ品質担保で致命傷を負う。

移行設計時のチェックリスト

1. 構造体メンバの順序とアライメントの可視化:
コンパイラオプション(`ALIGN` / `NOALIGN`)の差異が、生成されるバイナリにどう影響しているかをリストアップする。
2. パックデシマルの完全排除またはコンバート:
移行先DB(PostgreSQLやOracleなど)のDECIMAL型へ安全にインポートできるよう、事前にCOBOL/PL/I側でゾーン10進数や標準数値型へ変換するバッチを挟む設計にする。
3. 予約語の競合チェック:
自動翻訳ツール(レガシーコンバーター)を通す際、PL/I特有の文脈依存キーワードが移行先言語(Java等)の予約語と衝突してコンパイルエラーの嵐になる現象を事前に予測し、名前解決のルールを定義しておく。

—

結びにかえて

PL/Iの構文規則、そしてホスト構造体を用いたDB2アクセスは、一見すると古臭い技術のパッチワークに見えるかもしれない。しかし、その裏側にあるのは、「ハードウェアの限界性能を極限まで引き出し、1バイトの無駄もなくデータを処理し続ける」という、メインフレームエンジニアたちの執念と洗練されたアーキテクチャの結晶である。

その挙動の深層を理解せずして、真のレガシー移行やモダナイゼーションは語れない。
「なぜそのコードが存在するのか」「コンパイラとハードウェアは何を考えてそのバイナリを吐き出すのか」。その問いを持ち続けることこそが、私たちテックリードに課された責務なのだ。

タイトルとURLをコピーしました