【実務・中級編】INDEXおよびVERIFY関数によるスキャンアルゴリズム – PL/Iの基本構文とデータ制御実践ガイド

おい、最近配属された若手のエース君。今、君が担当している勘定系バッチの文字列表操作、特に古いCOBOLからのコンバージョンや、手が入るたびに複雑怪奇になったPL/Iのストリング処理で頭を抱えていないかい?

「動いているから触りたくない」なんて言わずに、今日のテーマをしっかりモノにしてくれ。今回はPL/Iにおける文字列表検索の要である `INDEX`関数 と `VERIFY`関数 だ。これらは、メインフレームの巨大なレコードやVSAMから読み込んだ可変長・固定長データ(PIC Xなど)を高速にスキャンし、データ品質の担保や不正文字の排除を行うための必須武器だ。

PL/Iの深淵なる世界へようこそ。さっそく実務に直結する話を始めようか。

—

1. 識別子の懐の深さと「予約語がない」という諸刃の剣

本題に入る前に、PL/Iという言語のユニークな哲学に少しだけ触れておこう。
他の言語(例えばCOBOLやJavaなど)では、`INDEX` や `VERIFY` といったキーワードは厳格な「予約語(Reserved Words)」として扱われ、勝手に変数名として使うことは許されない。しかし、PL/Iには原則として予約語という概念が存在しない。

これが何を意味するか分かるかい?
極端な話、君は `INDEX` という名前の変数や構造体メンバを定義できてしまう。

1
DECLARE INDEX FIXED BIN(31); / やろうと思えばこういう定義も通ってしまう /

もしこれをやってしまうと、コンパイラは文脈から判断しようとするが、ソースコードの可読性は最悪になり、バグの温床になる。現場のコーディング標準では、組み込み関数(Built-in Functions)と同名の識別子を定義することは厳禁だ。そして、これらの関数を安全かつ確実にコンパイラに認識させるために、必ず `BUILTIN` 属性 を明示的に宣言する習慣をつけなければならない。これを忘れると、予期せぬコンパイルエラーや、もっとタチの悪い「自作サブルーチンへの誤呼び出し」という大事故を引き起こす。いいかい、`DCL INDEX BUILTIN;` のおまじないは、ベテランの命綱だ。

—

2. INDEX関数:文字列の海から目的のパーツを暴く

標準的な仕様と、一致しない場合の「冷酷な真実」

`INDEX(string, pattern)` は、文字列 `string` の中で、部分文字列 `pattern` が最初に出現する位置(文字単位のオフセット)を返す関数だ。

ここで、メインフレーム保守メンバが必ずハマる最大の罠を教えよう。
C言語やJavaに慣れたプログラマは、検索に失敗した場合の戻り値として `0` を期待しがちだ。しかし、PL/Iの `INDEX` 関数は、一致しなかった場合に `0` を返す。
……おや、合っているじゃないか、と思ったかい?

待て、ここが重要だ。PL/Iの文字列のインデックスは「1始まり(1-origin)」だ。最初の文字が `1` なのである。したがって、戻り値が `0` というのは「文字列の先頭」を意味するのではなく、「見つからなかった(Not Found)」を意味する。この仕様を忘れて `IF INDEX(DATA_AREA, ‘A’) THEN …` などと書き、先頭文字(位置1)にヒットしたケースをスルーしてしまうバグを、私はこれまでに何度現場で目撃したことか! ゼロ判定は厳格に `> 0` で行うこと。これが鉄則だ。

—

3. VERIFY関数:データ不正を秒速で検知するスキャンアルゴリズム

全桁チェックの決定版

次に紹介する `VERIFY(string, check_string)` は、実務のバッチ処理、特に出力前の一括バリデーションにおいて神のような存在だ。

この関数は、`string` の中で、`check_string` に含まれていない文字が最初に登場する位置を返す。
例えば、金額項目や口座番号がすべて数字(`’0123456789’`)で構成されているかをチェックしたい場合、以下のように書く。

1
IF VERIFY(TRIM(ACCOUNT_NO), ‘0123456789’) > 0 THEN
/ 数字以外の文字が混入している!異常系へ分岐 /

一文字ずつループを回して `IF` 文で判定するような前時代的なCOBOLのロジックを書く必要はない。IBMメインフレームのハードウェア命令(クロックやベクター機構)の恩恵を受け、内部スキャン処理は極めて高速に実行される。

—

4. 実践:VSAMレコードをスキャンするバッチプログラム例

百聞は一見に如かず。VSAM(KSDS)から読み込んだ顧客レコードに対して、`INDEX` と `VERIFY` を駆使し、データ不整合を検知して異常終了(あるいはエラーログ出力)を行う実用的なPL/Iソースコードを示す。大文字ベースの記述、適切なインデント、そして `BUILTIN` の宣言に注目してほしい。

1
//
/ プログラム名: CUSTVALC
/ 概要: VSAM顧客マスターを読み込み、項目内の不正文字および
/ 特定キーワードをINDEX/VERIFY関数で高速スキャンする /
//
CUSTVALC: PROC OPTIONS(MAIN);

/ 組み込み関数の明示的宣言(これがないと現場で怒られます) /
DCL INDEX BUILTIN;
DCL VERIFY BUILTIN;
DCL TRIM BUILTIN;
DCL LENGTH BUILTIN;

/ ファイル定義 (VSAM KSDS) /
DCL CUSTFILE FILE RECORD SEQUENTIAL INPUT
ENVIRONMENT(BUFND(4) BUFNI(2));

/ ワーキングストレージ領域 /
DCL 1 CUST_RECORD,
5 CUST_ID CHAR(8),
5 CUST_NAME CHAR(40),
5 CUST_DATA CHAR(100);

DCL EOF_FLAG CHAR(1) INIT(‘0’);
DCL ERROR_COUNT FIXED BIN(31) INIT(0);
DCL POS FIXED BIN(31);

/ ONユニットによるファイル終了(ENDFILE)の捕捉 /
ON ENDFILE(CUSTFILE) EOF_FLAG = ‘1’;

/ VSAMファイルオープン /
OPEN FILE(CUSTFILE);

/ メイン処理ループ /
DO WHILE (EOF_FLAG = ‘0’);
READ FILE(CUSTFILE) INTO(CUST_RECORD);

IF EOF_FLAG = ‘1’ THEN LEAVE;

/ 1. VERIFY関数による顧客IDの数値チェック(半角数字以外を許さない) /
IF VERIFY(CUST_ID, ‘0123456789’) > 0 THEN DO;
PUT SKIP LIST(‘【エラー】顧客IDに数値以外が混入: ‘ || CUST_ID);
ERROR_COUNT = ERROR_COUNT + 1;
END;

/ 2. INDEX関数による特定禁止文字列(例: ‘DROP’ や ‘SQL’ 等のインジェクション兆候)の検知 /
POS = INDEX(CUST_DATA, ‘RESTRICTED’);
IF POS > 0 THEN DO;
PUT SKIP LIST(‘【警告】禁止ワード検出. 顧客ID: ‘ || CUST_ID ||
‘ 位置: ‘ || TRIM(POS));
/ 必要に応じて異常フラグを立てる等の処理 /
END;

END;

/ クローズ処理 /
CLOSE FILE(CUSTFILE);

PUT SKIP LIST(‘スキャン完了. エラー総数 = ‘ || TRIM(ERROR_COUNT));

RETURN;
END CUSTVALC;

—

5. ベテランからの現場の教訓(デバッグのコツ)

最後に、実務でこの手のストリング関数を扱うときの「勘所」を伝授しよう。

1. パディング(空白埋め)の罠に気をつけろ
PL/Iの `CHAR(n)` 型は固定長だ。定義長よりも短い文字列を代入すると、右側に自動的に半角スペース(X’40’)がパディングされる。もし `INDEX(CUST_NAME, ‘SMITH’)` などとやった場合、名前の後ろの空白も含めてスキャンされるため、完全一致を狙うなら `TRIM` 関数を併用するか、あるいは `VARYING` 属性を持つ `CHAR(n) VARYING` を適切に使い分ける必要がある。
2. ONユニットとコンビネーション
VSAMのアクセスエラーやデータ変換エラー(CONVERSION)が発生した際は、あらかじめ `ON CONVERSION` などのONユニットを張っておくことで、異常終了を優雅にトラップし、ダンプを出さずに安全にリカバーできる。ストリング操作と例外処理の連携は、堅牢なメインフレームシステムの基本中の基本だ。

基礎がしっかりしていれば、どんなに古くて巨大なPL/Iのコードベースであっても、恐れることは何もない。コンパイラの挙動とメモリの動きを頭に描きながら、优雅(エレガント)なコードを書き上げてくれよ。期待しているぞ!

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