はじめに:PL/IとDB2カーソル処理が挑む「極限の信頼性」
こんにちは。メインフレームの深淵を覗き続け、数々のレガシーマイグレーションの修羅場をくぐり抜けてきたシステムアーキテクトの私だ。
現代のオープン系エンジニアから見れば、PL/I(Programming Language One)という言語は、古き良き遺物、あるいはブラックボックスに映るかもしれない。しかし、金融や公共の基幹システムで今なお稼働し続けるIBMメインフレーム上において、PL/IとDB2(IBM DB2 for z/OS)の結合は、秒間何千件ものトランザクションをミリ秒の狂いもなく処理し続ける、いわば「心臓部」である。
今回は、その心臓部を司るDB2カーソル処理のライフサイクル(DECLARE / OPEN / FETCH / CLOSE)に焦点を当てる。
単なるSQLの教科書的な説明はしない。コンパイラが裏で何をやっているのか、パックデシマルの符号反転が引き起こすサイレントエラーの恐怖、そしてJavaやC#へのマイグレーション時に我々アーキテクトが直面する絶望的な非互換性の罠まで、現場の血肉となった知見を余すところなく語り尽くそう。
—
1. 識別子の自由度と「予約語を持たない」PL/Iの危険な美学
本題に入る前に、PL/Iの根本的な言語仕様に触れておく必要がある。JavaやC#などのモダン言語とは異なり、PL/Iには厳密な意味での「予約語(Reserved Words)」が存在しない。
1
/ こんな狂気じみた変数宣言もコンパイラは文句言わずに通す /
DECLARE SELECT FIXED BINARY(31);
DECLARE OPEN CHARACTER(10);
DECLARE FETCH FIXED DECIMAL(5,0);
「`SELECT`を変数名に使える」というのは一見すると自由度の高さに思えるが、コンパイラは前後の文脈(コンテキスト)から「これはSQLの予約語なのか、それともプログラマが定義した変数なのか」を必死に推論している。
埋め込みSQL(EXEC SQL)を含むプログラムにおいて、この仕様は時にコンパイラ(プリコンパイラ)を混乱させ、意図しないプリコンパイルエラーや、最悪の場合は誤ったコード生成を引き起こす原因となる。
基幹システムの保守において、変数の命名規則に「SQLキーワードを絶対に避ける」という不文律があるのは、このためだ。アーキテクトとしては、コーディング規約で厳格に縛るべき最初のポイントである。
—
2. DB2カーソルライフサイクルの完全制御とコンパイラ最適化
複数行を効率的に処理するためのカーソル制御フローは、以下の4ステップで完結する。これをPL/Iのバッチプログラムで実装する場合の、実用的なコードパターンを見ていこう。
1
/ —————————————————————- /
/ DB2 CURSOR LIFE-CYCLE SAMPLE FOR HIGH-RELIABILITY BATCH /
/ —————————————————————- /
DEMO_PGM: PROC OPTIONS(MAIN);
DCL 1 WS-SQLCA,
5 SQLCAID CHAR(8),
5 SQLCABC FIXED(31,0),
5 SQLCODE FIXED(31,05), / 戻り値コード /
5 SQLSTATE CHAR(5);
DCL 1 EMP_RECORD,
5 EMP_ID CHAR(6),
5 EMP_NAME CHAR(30),
5 EMP_SAL DECIMAL(9,2);
/ 1. カーソル宣言 (DECLARE) /
/ ※プレースホルダやホスト変数を用いた動的検索の準備 /
EXEC SQL
DECLARE C1 CURSOR FOR
SELECT EMP_ID, EMP_NAME, SALARY
FROM EMPLOYEE
WHERE DEPARTMENT = ‘DEV1’
FOR FETCH ONLY; / 読み取り専用指定による最適化 /
/ エラーハンドリングの共通化 /
EXEC SQL WHENEVER SQLERROR GOTO SQL_ERROR_RTN;
/ 2. カーソルオープン (OPEN) /
EXEC SQL OPEN C1;
DO FOREVER;
/ 3. フェッチ処理 (FETCH) /
EXEC SQL FETCH C1 INTO :EMP_RECORD.EMP_ID,
:EMP_RECORD.EMP_NAME,
:EMP_RECORD.EMP_SAL;
SELECT (SQLCODE);
WHEN (0)
— 通常処理:ホスト変数からベース変数への展開
CALL PROCESS_RECORD();
WHEN (100)
— データ終了 (EOF)
LEAVE;
OTHERWISE
— 予期せぬSQL例外
GOTO SQL_ERROR_RTN;
END;
END;
/ 4. カーソルクローズ (CLOSE) /
EXEC SQL CLOSE C1;
RETURN;
SQL_ERROR_RTN:
/ アベンド(ABEND)前のダンプ採取とログ出力 /
DISPLAY(‘DB2 ERROR OCCURRED. SQLCODE = ‘ || SQLCODE);
DISPLAY(‘SQLSTATE = ‘ || SQLSTATE);
SIGNAL ERROR; / 強制アベンド /
END DEMO_PGM;
アーキテクトの眼:`FOR FETCH ONLY` の重要性
上記のコードで `FOR FETCH ONLY`(または `FOR UPDATE OF`)を明示している点に注目してほしい。これを怠ると、DB2オプティマイザは将来の更新(UPDATE/DELETE WHERE CURRENT OF)に備えて、不要な排他制御(行ロックやページロックのエスカレーションリスク)を裏で引き起こす。
バッチ処理のスループットを極限まで高めるためには、コンパイラオプション(`OPTIMIZE(2)`など)の活用と合わせ、静的SQLのアクセスパス設計に細心の注意を払う必要がある。
—
3. 現場の悪夢:パックデシマル(COMP-3)の内部符号反転とデータ破損
PL/Iおよびメインフレーム開発において、最も恐ろしいバグの一つが「パックデシマル(`DECIMAL` / `FIXED DECIMAL`)」の符号ニブル(最下位バイトの右側4ビット)の破損である。
DB2からフェッチした数値をホスト変数に格納する際、COBOLやPL/Iは内部的にゾーン10進数やパック10進数としてメモリ上に展開する。
もし、マイグレーション時のデータ移行ミスや、外部連携ファイルの文字コード変換(EBCDICからASCII、あるいはその逆)の過程で、符号部分(正なら `C`、負なら `D`、符号なしなら `F` など)が破壊されるとどうなるか。
PL/Iのランタイムは、この変数が演算に使われた瞬間に S0C7アベンド(Data Exception) を引き起こす。
ダンプリストをナローに覗き込み、ストレージ・ダンプの16進数表現(`X’1234567E’` のような値)から「おっと、符号ニブルが `E` に化けてやがる」と瞬時に見抜くのが、真のメインフレームエンジニアのスキルだ。
動的メモリ操作において、ポインタ(`POINTER`)や基底変数(`BASED`変数)を用いてストレージを直接マッピングする際も、このパックデシマルのアライメント(境界調整)を誤ると、容赦なくメモリー・バイオレーションやS0C4アベンドの洗礼を受けることになる。
—
4. マイグレーション(Java/C#化)における致命的なパラダイムシフト
現在、多くの企業がPL/Iで書かれた基幹システムをJava(Spring Bootなど)やC#へ移行している。しかし、この「DB2カーソル処理のライフサイクル」をオープン系言語へ移植する際、アーキテクトが直面する壁は高くて厚い。
① カーソルクローズの暗黙的解放とメモリリーク
PL/Iバッチでは、プログラムが終了すれば(あるいは異常終了時であっても)、DB2スレッドの切断に伴いカーソルは強制的にクローズされる。
しかし、JavaのJPA/Hibernateや、C#のEntity Framework CoreなどのORM、あるいはJDBC直叩きのレイヤにおいて、例外発生時に `finally` ブロックや `try-with-resources` で適切にステートメントやResultSetを閉じないと、DB2側でカーソルがリークし、最終的に `DSXA`(最大オープンカーソル数オーバー)のシステム障害を引き起こす。
② 例外処理(SQLCODE vs 例外スロー)の非対称性
PL/Iでは `SQLCODE` を愚直に `SELECT` 文や `IF` 文で判定する手続き型プログラミングのスタイルが主流だ。
これをJavaのモダンな例外駆動型アーキテクチャに書き換える際、「SQLCODE = 100(EOF)」を例外として扱うべきか、単なる制御フローのブレークとして扱うべきか、設計思想のコンフリクトが必ず起きる。ここを安易に「すべての非ゼロSQLCODEを例外スロー」に設計すると、バッチ全体のパフォーマンスが致命的に劣化する。
—
おわりに:レガシーの知見はオープン系でも武器になる
PL/IにおけるDB2カーソル制御は、単なるデータの取得手段ではない。メモリの物理レイアウト、コンパイラの最適化、そして何重ものエラーハンドリングが織りなす、極めて緻密なエンジニアリングの結晶である。
モダンな言語やクラウド環境に移行したとしても、裏側で動いているデータベースの挙動、トランザクションのライフサイクル、そしてリソース管理の本質は1ミリも変わらない。
PL/Iという最も硬派な言語を極めた我々だからこそ、次世代のシステムでも「落ちない、止まらない、揺るがない」高信頼アーキテクチャを構築できるのだ。
レガシーの深淵を知る者よ、自信を持って次の移行プロジェクトを成功に導いてほしい。
