PL/Iでキー付きファイル(VSAM)を自在に操る!KEYED属性とKEY/KEYTOオプション徹底解説
おい、諸君。最近、PL/Iで書かれた古いバッチプログラムの改修や、VSAMファイル周りの挙動に頭を悩ませているって話をよく聞くんだ。特に、キー付きファイルへのアクセスとなると、「あれ?このKEYED属性って何だっけ?」とか、「READ文でKEYオプションを使うとき、どういう挙動になるんだっけ?」なんて疑問が湧いてくること、あるだろ?
今日は、そんな諸君の悩みを一気に解消すべく、PL/Iにおける`KEYED`属性と、`READ`/`WRITE`文で使う`KEY`/`KEYTO`オプションについて、現場で培った経験を交えながら、とことん実践的に解説していく。退屈なマニュアルの丸写しなんてことはしない。現場で本当に役立つ、生きた知識を伝授するから、しっかりついてきてくれよな。
PL/Iプログラムの基本構造とファイル入出力の心構え
まず、PL/Iプログラムの基本的な構造に軽く触れておこう。PL/Iのプログラムは、通常`PACKAGE`や`PROCEDURE`で構成され、実行されるエントリーポイントには`OPTIONS(MAIN)`が付与される。
/ メインプロシージャ /
MYPROG: PROCEDURE OPTIONS(MAIN);
/ 宣言部 /
DECLARE … ;
/ 実行部 /
…
RETURN;
END MYPROG;
ファイル入出力、特にVSAMのようなキー付きファイルとなると、単にデータを読み書きするだけでなく、レコードの特定やエラーハンドリングが非常に重要になる。ここで、PL/Iの`ON`ユニットの存在を忘れてはならない。ファイル入出力エラーが発生した場合に、プログラムが異常終了するのを防ぎ、適切な後処理を行うための強力な味方だ。
ON ENDFILE(myfile) BEGIN;
/ ファイル終端時の処理 /
PUT SKIP LIST(‘ファイル終端に到達しました。’);
GO TO end_of_program;
END;
ON ERROR BEGIN;
/ その他のI/Oエラー時の処理 /
PUT SKIP LIST(‘I/Oエラーが発生しました。’);
/ エラーコードなどを調査し、適切な処置を行う /
GO TO abnormal_termination;
END;
これらの`ON`ユニットを適切に設定しておくことで、予期せぬエラーが発生しても、プログラムは冷静に、そして確実に処理を継続できるようになる。これが、大規模バッチ改修で培われる「堅牢なプログラム」を作るための、最初のステップであり、最も重要な心構えなんだ。
KEYED属性:キー付きファイルへの扉を開ける鍵
さて、本題の`KEYED`属性だ。これは、ファイルがキーによってレコードを識別・アクセスすることを、PL/Iコンパイラに伝えるための宣言だ。VSAMのKSDS(Key Sequenced Data Set)のようなファイルに対して、この属性を付けずにアクセスしようとすると、期待通りの動作にならないどころか、コンパイルエラーや実行時エラーの原因となる。
例えば、VSAMファイル`MYVSAM.DATA`をキーでアクセスしたい場合、以下のように宣言する。
DCL MYVSAM FILE KEYED ENVIRONMENT(VSAM);
ここで、`ENVIRONMENT(VSAM)`は、このファイルがVSAMファイルであることを明示している。`KEYED`属性が付与されていることで、PL/Iは、このファイルに対してキーを使ったランダムアクセス(直接アクセス)が可能であることを認識する。
READ文とKEYオプション:狙ったレコードをピンポイントで取得!
レコードを1件ずつ順番に読んでいくシーケンシャルアクセスとは異なり、キーを指定して目的のレコードを直接取得するのがランダムアクセスだ。PL/Iでは、`READ`文に`KEY`オプションを付けることで、これを実現する。
`KEY`オプションで指定するキーの値は、ファイル定義時に指定したキーフィールドと一致している必要がある。このキーフィールドは、`RECORD`フォーマットのファイルの場合、`BASED`属性を持つ構造体や配列として宣言し、その構造体内の特定のフィールドにキーを設定するのが一般的だ。
/ ファイル定義 /
DCL MYVSAM FILE KEYED ENVIRONMENT(VSAM);
/ レコード構造体定義 (KEY FIELDの長さはVSAM定義と一致させる) /
DCL 1 VSAM_RECORD BASED(VSAM_PTR),
2 CUST_ID CHAR(10), / キーフィールド /
2 CUST_NAME CHAR(50),
2 CUST_ADDR CHAR(100);
/ キー変数の宣言 /
DCL RECORD_KEY CHAR(10);
/ キーに設定する値 /
RECORD_KEY = ‘1234567890’;
/ KEYEDファイルからのレコード読み込み /
ON ENDFILE(MYVSAM) GO TO EOF_HANDLER; / ファイル終端時の処理 /
ON ERROR(MYVSAM) GO TO ERROR_HANDLER; / I/Oエラー時の処理 /
/ KEYオプションでレコードを特定 /
READ FILE(MYVSAM) KEY(RECORD_KEY) INTO(VSAM_RECORD);
/ 取得したレコードの処理 /
PUT SKIP LIST(‘顧客ID: ‘ || CUST_ID || ‘, 名前: ‘ || CUST_NAME);
EOF_HANDLER:
/ ファイル終端時の処理 /
RETURN;
ERROR_HANDLER:
/ I/Oエラー時の処理 /
PUT SKIP LIST(‘VSAMファイルアクセスエラーが発生しました。’);
RETURN;
この例では、`RECORD_KEY`に指定した値 `’1234567890’` に一致するキーを持つレコードが、`MYVSAM`ファイルから読み込まれる。`INTO(VSAM_RECORD)`で、読み込まれたレコードデータが`VSAM_RECORD`構造体に格納される。`VSAM_RECORD`は`BASED(VSAM_PTR)`で宣言されているが、`READ`文で`INTO`句を指定した場合、`VSAM_PTR`は自動的にレコードの開始アドレスを指すようになっている。
「キーが見つからない」エラーとの付き合い方
ここで、よくある落とし穴に触れておこう。`KEY`オプションで指定したキーを持つレコードがファイルに存在しない場合、`KEYNOTFOUND`という例外条件が発生する。これを適切に処理しないと、プログラムは異常終了してしまう。
/ … (前略) … /
/ KEYNOTFOUND例外を捕捉 /
ON KEYNOTFOUND(MYVSAM) BEGIN;
PUT SKIP LIST(‘指定されたキー: ‘ || RECORD_KEY || ‘ は見つかりませんでした。’);
/ ここで、キーが見つからなかった場合の代替処理を記述 /
/ 例えば、次のキーを試す、エラーログを出力するなど /
GO TO next_record; / 例: 次のレコード処理へ /
END;
/ KEYEDファイルからのレコード読み込み /
READ FILE(MYVSAM) KEY(RECORD_KEY) INTO(VSAM_RECORD);
/ … (後略) … /
`ON KEYNOTFOUND(ファイル名)` を使用することで、キーが見つからなかった場合の処理を記述できる。ここでは、単純にメッセージを出力し、`next_record`ラベルに分岐しているが、実際の現場では、より詳細なエラーログの記録や、代替キーでの再試行など、状況に応じた柔軟な対応が求められる。
WRITE文とKEYオプション:意図した場所にレコードを書き込む!
`WRITE`文でも`KEY`オプションは使用できる。これは、キーを指定して、そのキーに対応するレコードの場所に新しいレコードを書き込む、あるいは既存のレコードを更新する場合に用いられる。
/ … (前略) … /
/ 新規レコードの作成 /
RECORD_KEY = ‘9876543210’;
CUST_ID = ‘9876543210’;
CUST_NAME = ‘株式会社テスティング’;
CUST_ADDR = ‘東京都サンプル町’;
/ KEYオプションで書き込み先を指定 /
WRITE FILE(MYVSAM) KEY(RECORD_KEY) FROM(VSAM_RECORD);
/ … (後略) … /
この場合、`RECORD_KEY`で指定されたキーが存在しない場合は、新規レコードとして追加される。もし、そのキーを持つレコードが既に存在する場合、`WRITE`文の動作はVSAMの定義や`FILE`ステートメントのオプションによって変わるが、一般的にはレコードの更新が行われる(ただし、レコード長が同じ場合など、条件がある)。
UPDATEステートメントとの使い分け
VSAMファイルで既存レコードの更新を行う場合、`UPDATE`ステートメントを使うこともできる。`UPDATE`は、`READ`文でレコードを読み込んだ後、そのレコードを修正し、同じキーで再度書き戻す、という一連の操作を簡潔に記述できる。
/ … (前略) … /
/ 既存レコードの読み込み /
RECORD_KEY = ‘1122334455’;
READ FILE(MYVSAM) KEY(RECORD_KEY) INTO(VSAM_RECORD);
/ レコード内容の更新 /
CUST_NAME = ‘株式会社アップデート’;
/ UPDATEステートメントで更新 /
UPDATE FILE(MYVSAM) KEY(RECORD_KEY); / 読み込んだレコードを、同じキーで更新 /
/ … (後略) … /
`UPDATE`ステートメントは、`READ`で取得したレコードを直接更新するという意図が明確になるため、コードの可読性が向上する。どちらを使うべきかは、プログラムのロジックや、開発チームのコーディング規約によって判断すると良いだろう。
KEYTOオプション:レコードからキーを自動抽出!
`KEYTO`オプションは、`READ`文でレコードを読み込んだ際に、そのレコードのキーフィールドの値を、指定した変数に自動的に格納してくれる便利な機能だ。これにより、レコードからキーを別途抽出する手間が省ける。
/ … (前略) … /
/ キーを格納する変数の宣言 /
DCL READ_KEY_VALUE CHAR(10);
/ KEYEDファイルからのレコード読み込み、KEYTOオプションでキーを自動取得 /
READ FILE(MYVSAM) KEYTO(READ_KEY_VALUE) INTO(VSAM_RECORD);
/ 取得したレコードの処理 /
PUT SKIP LIST(‘読み込んだレコードのキー: ‘ || READ_KEY_VALUE || ‘, 顧客名: ‘ || CUST_NAME);
/ … (後略) … /
この例では、`MYVSAM`ファイルからレコードを読み込む際に、そのレコードのキーフィールドの値が`READ_KEY_VALUE`変数に自動的にコピーされる。これは、特にファイル全体をシーケンシャルに読みながら、キーで特定の処理を行いたい場合などに非常に役立つ。
BUILTIN関数との連携:より洗練されたファイル制御
PL/Iには、ファイル制御をより高度に行うための様々な`BUILTIN`関数が用意されている。`KEY`や`KEYTO`オプションと組み合わせることで、さらに柔軟な処理が可能になる。
例えば、`GETENV`関数を使って環境変数からキーの値を取得したり、`SUBSTR`関数でキーの一部を操作したりといったことが考えられる。
DCL MYVSAM FILE KEYED ENVIRONMENT(VSAM);
DCL 1 VSAM_RECORD BASED(VSAM_PTR),
2 ITEM_CODE CHAR(8), / キーフィールド /
2 ITEM_NAME CHAR(30);
DCL TARGET_KEY CHAR(8);
DCL ENV_VAR_KEY CHAR(100); / 環境変数から取得したキー /
/ 環境変数からキーを取得 /
ENV_VAR_KEY = GETENV(‘TARGET_ITEM_KEY’); / 環境変数名 ‘TARGET_ITEM_KEY’ /
/ 環境変数から取得した値の一部をキーとして使用 /
TARGET_KEY = SUBSTR(ENV_VAR_KEY, 1, 8); / 先頭8文字をキーとする /
ON ENDFILE(MYVSAM) GO TO EOF_HANDLER;
ON KEYNOTFOUND(MYVSAM) GO TO NOTFOUND_HANDLER;
/ KEYEDファイルからレコードを読み込み、キーを指定 /
READ FILE(MYVSAM) KEY(TARGET_KEY) INTO(VSAM_RECORD);
PUT SKIP LIST(‘取得アイテムコード: ‘ || ITEM_CODE || ‘, アイテム名: ‘ || ITEM_NAME);
EOF_HANDLER:
RETURN;
NOTFOUND_HANDLER:
PUT SKIP LIST(‘指定されたアイテムコード: ‘ || TARGET_KEY || ‘ は見つかりませんでした。’);
RETURN;
このように、`BUILTIN`関数を駆使することで、外部からの入力や環境設定に応じて、動的にキーを生成・操作することが可能になる。これは、バッチ処理でパラメータを柔軟に受け取ったり、ジョブ制御言語(JCL)で渡された情報を活用したりする際に、非常に強力な武器となる。
まとめ:現場で活きるPL/Iファイル制御の奥義
さて、今日はPL/Iにおける`KEYED`属性、`READ`/`WRITE`文の`KEY`/`KEYTO`オプション、そして`ON`ユニットや`BUILTIN`関数との連携について、実践的な解説を行ってきた。
- `KEYED`属性: VSAMのようなキー付きファイルへのアクセスを宣言する。これがないと始まらない。
- `READ`文 + `KEY`オプション: 指定したキーを持つレコードを直接読み込む。キーが見つからない時の`ON KEYNOTFOUND`処理は必須だ。
- `WRITE`文 + `KEY`オプション: 指定したキーの場所にレコードを書き込む(新規または更新)。
- `UPDATE`ステートメント: 読み込んだレコードを直接更新する際に便利。
- `READ`文 + `KEYTO`オプション: 読み込んだレコードのキーを自動的に変数に格納する。
- `ON`ユニット: `ENDFILE`, `ERROR`, `KEYNOTFOUND`などの例外条件を捕捉し、プログラムの堅牢性を高める。
- `BUILTIN`関数: 環境変数取得(`GETENV`)や文字列操作(`SUBSTR`)などと連携させ、より動的で柔軟なファイル制御を実現する。
これらの知識は、単にPL/Iの言語仕様を理解するだけでなく、実際のメインフレームシステムがどのように動作しているのか、そして、いざという時にどうやってトラブルシューティングを行うのか、という現場のエンジニアに不可欠な「勘」や「センス」を磨くための土台となる。
今回の解説が、君たちのPL/Iプログラミング、そしてメインフレームシステム保守・開発の一助となれば幸いだ。これからも、現場で培った知見を、こうして惜しみなく伝えていくから、期待していてくれよ。何か分からないことがあれば、いつでも声をかけてくれ。
