【実務・中級編】DB2埋め込みSQLにおけるホスト変数マッピングとSQLDAの構造 – PL/Iの基本構文とデータ制御実践ガイド

おい、最近入った若手が「PL/Iって変数名の予約語がないから何でも使えて気楽ですね」なんて笑いながらコードを書いているのを見てな、思わず冷や汗をかいたんだよ。いや、確かにPL/IにはCOBOLみたいな厳格な予約語の縛りが少ない。コンテキストによってキーワードを解釈するから、変数名に `IF` や `READ` を使えちゃったりもする。だがな、そんなことやってごらんよ。DB2のプリコンパイラを通した瞬間、あるいは大規模なマイグレーションの解析時に、とんでもねぇ地雷を踏むことになる。

今日はな、基幹システムの現場で避けて通れない「DB2埋め込みSQLにおけるホスト変数マッピングとSQLDAの構造」について、骨の髄まで叩き込んでやる。特に、PL/Iのデータ型とDB2の型がどうメモリ上で噛み合っているか、アライメントの罠も含めて実戦形式で解説するから、しっかりついてきな。

—

1. 予約語を持たないPL/Iの自由度と、DB2連携における「落とし穴」

PL/Iの最大の特徴であり、同時に魔女の乳とも言えるのがその構文の柔軟性だ。例えば、以下のようなコードを見てくれ。

DECLARE IF FIXED(3) INITIAL(1);
IF = IF + 1;

こんなふざけた変数名でも、コンパイラは文脈から「あ、これは変数だな」「こいつは制御文の `IF` だな」と判断してコンパイルを通してしまう。しかし、これがDB2の埋め込みSQL(SQLJやコボル・PL/Iプリコンパイラ)に絡むと話は別だ。DB2はSQL文の中で独自のパーサを走らせる。そこにPL/I特有の「文脈依存の解釈」が持ち込まれると、プリコンパイルエラー(SQL0104Nなど)の嵐に見舞われるか、最悪の場合、意図しないホスト変数のマッピングを引き起こして本番障害のトリガーを引くことになる。

DB2とやり取りするホスト変数は、単なるローカル変数じゃない。メインフレームのOS(z/OS)とDB2サブシステムの間で、メモリ空間を直接(あるいは安全なバッファを介して)共有する命綱なんだ。ここを舐めてかかると、アライメント不整合による「S0C4(保護例外)」の悪夢を見る羽目になる。

—

2. PL/Iデータ型とDB2 SQLデータ型の正しいマッピング

まずは基本だ。PL/Iで定義した変数をDB2のテーブル列に正確にマップさせるには、両者のデータ型の「サイズ」と「内部表現」を完璧に一致させなければならない。

| DB2 SQL データ型 | PL/I ホスト変数定義 | 備考・注意点 |
| :— | :— | :— |
| `SMALLINT` | `DECLARE 係数 FIXED BIN(15);` | 2バイト整数。符号付き。 |
| `INTEGER` | `DECLARE 件数 FIXED BIN(31);` | 4バイト整数。符号付き。 |
| `BIGINT` | `DECLARE 巨大ID FIXED BIN(63);` | 8バイト整数。ENTERPRISE PL/Iで必須。 |
| `DECIMAL(p,s)` | `DECLARE 金額 FIXED DEC(p,s);` | ゾーン10進数またはパック10進数。 |
| `CHAR(n)` | `DECLARE 氏名 CHAR(n);` | 固定長文字。 |
| `VARCHAR(n)` | `DECLARE 住所 CHAR(n) VARYING;` | 可変長。先頭2バイトに長さが入る。 |

ここで新人がよく間違うのが、`FIXED DEC`(パック10進数)の桁数指定と、DB2の精度(Precision)の不一致だ。さらに、NULL値を受け取るためにはインジケータ変数(INDICATOR VARIABLE)が絶対に欠かせない。

—

3. 実践:ホスト構造体とインジケータ配列のコーディング作法

百聞は一見にしかずだ。実際のバッチプログラムを想定した、構造体(STRUCTURE)によるホスト変数マッピングのサンプルコードを見せておこう。大文字で綺麗に整えられた、我がチームの標準コーディング規準に基づく書き方だ。

/公式コーディングサンプル/
/ プログラム名: CUSTSRCH /
/ 処理概要: 顧客マスタから指定条件のレコードを検索し、詳細を出力する /
//
CUSTSRCH: PROC OPTIONS(MAIN);

/ 1. DB2通信領域(SQLCA)のインクルード /
EXEC SQL INCLUDE SQLCA;

/ 2. ホスト構造体(顧客情報)の定義 /
DECLARE 1 WK_CUSTOMER,
5 WK_CUST_ID FIXED BIN(31), / 顧客ID (INTEGER) /
5 WK_CUST_NAME CHAR(40), / 顧客名 (CHAR) /
5 WK_CUST_BAL FIXED DEC(9,2); / 残高 (DECIMAL) /

/ 3. インジケータ変数構造体(NULL判定用)の定義 /
DECLARE 1 WK_CUST_IND,
5 WK_ID_IND FIXED BIN(15), / 顧客ID用インジケータ /
5 WK_NAME_IND FIXED BIN(15), / 顧客名用インジケータ /
5 WK_BAL_IND FIXED BIN(15); / 残高用インジケータ /

/ 4. ワーキング変数の定義 /
DECLARE IV_SEARCH_ID FIXED BIN(31) INITIAL(10005);
DECLARE SW_EOF CHAR(1) INITIAL(‘0’);

DISPLAY(‘

顧客検索バッチ 開始 #’);

/ カーソルの宣言 /
EXEC SQL
DECLARE C1 CURSOR FOR
SELECT CUST_ID, CUST_NAME, CUST_BAL
FROM CUSTOMER_TBL
WHERE CUST_ID >= :IV_SEARCH_ID;

/ カーソルオープン /
EXEC SQL OPEN C1;

IF SQLCODE ^= 0 THEN DO;
DISPLAY(‘OPEN ERROR: SQLCODE = ‘ || TRIM(CHAR(SQLCODE)));
SIGNAL ERROR;
END;

/ フェッチループ /
DO WHILE (SW_EOF = ‘0’);

EXEC SQL
FETCH C1 INTO :WK_CUSTOMER INDICATOR :WK_CUST_IND;

SELECT (SQLCODE);
WHEN (0) DO;
/ NULLインジケータのチェックと値の処理 /
IF WK_BAL_IND < 0 THEN DISPLAY('顧客ID: ' || TRIM(CHAR(WK_CUST_ID)) || ' 残高はNULLです'); ELSE DISPLAY('顧客ID: ' || TRIM(CHAR(WK_CUST_ID)) || ' 氏名: ' || WK_CUST_NAME || ' 残高: ' || TRIM(CHAR(WK_BAL_IND))); END; WHEN (100) DO; SW_EOF = '1'; / データ終了 / OTHERWISE DISPLAY('FETCH ERROR: SQLCODE = ' || TRIM(CHAR(SQLCODE))); SW_EOF = '1'; SIGNAL ERROR; END; END; / カーソルクローズ / EXEC SQL CLOSE C1; DISPLAY('

顧客検索バッチ 正常終了 #’);

RETURN;

END CUSTSRCH;

—

4. メモリレイアウトとアライメントの罠(SQLDAの深層)

さて、ここからがシニアアーキテクトとしての本領発揮だ。上のコードでは静的なホスト構造体 (`WK_CUSTOMER`) を使ったが、汎用的な動的SQL(画面からの入力条件が毎回変わるようなジェネリックな抽出モジュールなど)を書く場合は、SQLDA(SQL Descriptor Area)を直接ハンドリングしなきゃならん。

ここで問題になるのが、メモリのアライメント(境界調整)だ。

1. 偶数境界の呪縛:
PL/Iの `FIXED BIN(31)`(4バイト整数)や `FIXED BIN(15)`(2バイト整数)は、それぞれ4の倍数、2の倍数のメモリアドレス(境界)に配置されないと、ハードウェアレベルで割込み(S0C4やS0C7)が発生する。
2. SQLDAの構造体レイアウト:
SQLDAは、ポインタ型(`POINTER`)や固定長/可変長のフィールドが複雑に絡み合う連続領域だ。もし手動でSQLDA領域を割り当てようとして、`UNALIGNED` 属性をうっかりつけたり、パディング(隙間バイト)の計算を誤ったりすると、DB2サブシステムへ渡した瞬間にストレージ違反でシステムがアベンドする。
3. BUILTIN関数の活用:
PL/Iの強力な組み込み関数(`ADDR` や `STORAGE`、あるいはポインタ演算用の `POINTER` 変換)を使ってSQLDAを構築する際は、コンパイラが勝手に挿入するパディングを意識しなきゃいけない。

動的SQLでSQLDAを使うときは、必ずコンパイラオプションでアライメントが適切に行われているか、そして生成されたSQLDAのヘッダ情報(`SQLDAID`, `SQLDABC`, `SQLN`, `SQLD`)が正しいバイト数を指しているかを、dump(CEEDUMP)で追えるようにしておけよ。これができないと、夜間バッチの障害時に原因特定で何時間もロスすることになる。

—

5. 先輩からの現場の教訓

いいか、最後にこれだけは覚えておけ。
PL/Iの自由度の高さに甘えて、コンテキストに依存した危うい変数命名をしたり、DB2とのデータ型マッピングを「なんとなく動くから」と適当に済ませたりするプログラマは、三流だ。基幹システムのデータは企業の血肉だ。一文字のズレ、1バイトのアライメントの不整合が、何百万人もの顧客データを吹っ飛ばす引き金になり得る。

構造体を定義するときは、必ずDB2側の定義書と照らし合わせ、インジケータ変数をセットで用意する。そして、コンパイルリストを出力して、メモリマップに無駄なパディングやアライメント違反の警告が出ていないかを確認する――これが、我々メインフレームエンジニアの誇りであり、守るべき鉄則だ。

さあ、理屈は分かったな。手を動かして、今日のバッチ改修のコードを見直してこい!

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