PL/Iでファイル操作の基本をマスター!DEC ARE FILE属性の謎を解き明かそう(初学者向け)
「JavaやCOBOLは触ったことあるけど、PL/I?メインフレーム?なんだか難しそう…」
そう思っていませんか?大丈夫です!PL/Iの世界も、一つずつ紐解いていけば、意外と親しみやすいんです。特に、基幹システムで欠かせないファイル操作。今回は、PL/Iの `DECLARE FILE` 属性に焦点を当て、RECORDモードでのファイル定義でよく使う `SEQUENTIAL`、`DIRECT`、`INPUT`、`OUTPUT`、`UPDATE` といったキーワードが、実際どのようにファイルとプログラムのやり取りを制御しているのか、そしてそれが「排他制御」にどう関わってくるのかを、分かりやすく解説していきますね。
PL/Iプログラムの「顔」と「心臓」:PACKAGE、PROCEDURE、OPTIONS(MAIN)
PL/Iプログラムの基本構造を理解するために、まずはプログラムの「顔」と「心臓」にあたる部分を見てみましょう。
MYPROG: PROCEDURE OPTIONS(MAIN);
/ ここにプログラムの処理を書きます /
END MYPROG;
- `MYPROG:`
これはプログラムの名前ですね。Javaでいうクラス名や、COBOLでいうプログラム名のようなものです。
- `PROCEDURE`
これが「手続」であることを示しています。PL/Iでは、プログラムのまとまりを `PROCEDURE` で定義するのが一般的です。
- `OPTIONS(MAIN)`
これは、この `PROCEDURE` がプログラムのエントリーポイント、つまり「ここから実行を開始してくださいね」という目印になります。Javaでいう `public static void main(String[] args)` に近いイメージですね。
そして、もう少し大きなプログラムになると、`PACKAGE` という概念も登場します。
MYPACKAGE: PACKAGE;
/ 関連する PROCEDURE や変数などをまとめて定義します /
MYPROG: PROCEDURE OPTIONS(MAIN);
/ ここにプログラムの処理を書きます /
END MYPROG;
END MYPACKAGE;
`PACKAGE` は、関連するプログラム要素(手続きや変数など)をひとまとめにするための「箱」のようなものです。これにより、プログラムの構造が整理され、管理しやすくなります。Javaでいうパッケージや、COBOLのコピーブックのような役割も担うことがあります。
ファイル定義の「設計図」:DECLARE FILE属性
さて、いよいよ本題のファイル定義です!PL/Iでファイルを扱うには、まず `DECLARE` 文を使って、そのファイルがどのようなものなのかをプログラムに教えてあげる必要があります。これが、いわばファイルの「設計図」にあたる部分です。
特に、レコード単位でデータを読み書きする `RECORD` モードでのファイル定義では、 `FILE` 属性の指定が重要になってきます。
DCL MYFILE FILE … ;
この `…` の部分に、ファイルの特性を表す様々な属性を指定していくわけです。今回は、 `SEQUENTIAL`、`DIRECT`、`INPUT`、`OUTPUT`、`UPDATE` に焦点を当ててみましょう。
1. データの「流れ」を指定:SEQUENTIAL と DIRECT
まず、ファイルへのアクセス方法を大きく二つに分けられます。
- `SEQUENTIAL` (シーケンシャル)
これは、ファイルに記録されているデータを、先頭から順番に一つずつ読み込んでいく、あるいは書き込んでいく方法です。まるで、本を最初から最後まで順番に読んでいくようなイメージですね。
バッチ処理で、大量のデータをまとめて処理する際によく使われます。
DCL INPUT_SEQ_FILE FILE SEQUENTIAL INPUT;
この例では、「`INPUT_SEQ_FILE` という名前のファイルは、シーケンシャルに読み込むためのものですよ」と宣言しています。
- `DIRECT` (ダイレクト)
こちらは、ファイルのどこにアクセスしたいかを指定して、ピンポイントで読み書きする方法です。本の特定のページを直接開くようなイメージです。
特定のレコードを検索したり、更新したりするような、ランダムアクセスが必要な場合に便利です。
DCL UPDATE_DIR_FILE FILE DIRECT UPDATE;
この例では、「`UPDATE_DIR_FILE` という名前のファイルは、ダイレクトに更新するために使いますよ」と宣言しています。
2. データの「方向」を指定:INPUT、OUTPUT、UPDATE
次に、ファイルに対して「何をするか」という方向性を指定します。
- `INPUT` (入力)
ファイルからデータを読み込む(読み出し専用)場合に指定します。プログラムがファイルにデータを書き込むことはできません。
DCL CUSTOMER_DATA_IN FILE SEQUENTIAL INPUT;
「`CUSTOMER_DATA_IN` ファイルからは、シーケンシャルにデータを読み込むだけ」という意図になります。
- `OUTPUT` (出力)
ファイルに新しいデータを書き込む(書き込み専用)場合に指定します。既存のファイルに追記することもできますが、基本的には新規作成や、既存の内容を上書きするイメージです。
DCL NEW_REPORT_OUT FILE SEQUENTIAL OUTPUT;
「`NEW_REPORT_OUT` ファイルには、シーケンシャルにデータを書き込んでいく」というイメージですね。
- `UPDATE` (更新)
ファイルからデータを読み込み、さらにそのデータを更新したり、削除したりすることもできる場合に指定します。まさに、既存のファイルの内容を「更新」できる強力な属性です。
DCL EMPLOYEE_FILE FILE DIRECT UPDATE;
「`EMPLOYEE_FILE` には、ダイレクトにアクセスして、読み込みも書き込みも更新もする」という、柔軟な操作が可能になります。
属性の組み合わせ:排他制御への影響
ここが、PL/Iのファイル定義における、ちょっと面白い、そして重要なポイントです。これらの属性は単独で指定するだけでなく、組み合わせて使うことができます。そして、その組み合わせによって、ファイルへのアクセス方法や、他のプログラムとの「取り合い」、つまり排他制御に影響が出てくるんです。
どんな組み合わせがあるの?
例えば、以下のような組み合わせが考えられます。
- `SEQUENTIAL INPUT`: 順番に読み込むだけ。他のプログラムが書き込もうとしても、このファイルは「読み込み中」と見なされることがあります。(ただし、これはOSやファイルシステムの設定にも依存します)
- `SEQUENTIAL OUTPUT`: 順番に書き込むだけ。他のプログラムからの読み込みは可能ですが、既に書き込み中の場合は、待たされる可能性があります。
- `SEQUENTIAL UPDATE`: 順番に読み込み、更新も可能。この属性で開かれたファイルは、他のプログラムが同じファイルに対して `UPDATE` 属性で開こうとすると、待たされたり、エラーになったりする可能性が高くなります。まるで、「今、このファイルで作業中だから、ちょっと待ってね!」という状態です。
- `DIRECT INPUT`: 特定の場所から読み込むだけ。
- `DIRECT OUTPUT`: 特定の場所に書き込むだけ。
- `DIRECT UPDATE`: 特定の場所を読み込み、更新も可能。これが一番「排他制御」が厳しくなるケースが多いです。あるレコードを `DIRECT UPDATE` で開いて更新している間に、別のプログラムが同じレコードを `DIRECT UPDATE` で開こうとすると、ロックがかかって待たされる、あるいはエラーになる、ということが起こりやすくなります。
排他制御とは?なぜ重要?
排他制御とは、複数のプログラムが同時に同じファイルにアクセスしようとしたときに、データの不整合を防ぐための仕組みです。
例えば、ある社員の給与情報を更新するプログラムが2つ同時に動いたとしましょう。
- プログラムAが、社員Xの給与を100円増やす。
- プログラムBも、社員Xの給与を100円増やす。
もし排他制御がなければ、プログラムAが更新した直後にプログラムBが読み込み、その古い情報をもとに計算してしまうかもしれません。結果として、給与が本来200円増えるところ、100円しか増えなかった、ということになりかねません。
`SEQUENTIAL UPDATE` や `DIRECT UPDATE` といった属性を指定することで、PL/I(そしてOS)は、ファイルやレコード単位で「ロック」をかけ、他のプログラムが同時に変更できないように制御してくれるんです。
コード例で見てみよう!
簡単な例ですが、 `SEQUENTIAL UPDATE` と `DIRECT UPDATE` でファイルを開く場合のイメージを見てみましょう。
MYAPP: PACKAGE;
/ 顧客マスターファイル(レコード長固定)を定義 /
DCL CUSTOMER_MASTER FILE
RECORD SEQUENTIAL UPDATE
ENVIRONMENT(‘MYLIB.CUSTMST.DAT’); / ファイル名(物理的な場所) /
DCL RECORD_AREA CHAR(200); / レコードを格納する作業領域 /
DCL CUSTOMER_ID CHAR(10); / 検索する顧客ID /
/ 顧客IDを取得(ここでは例として固定値) /
CUSTOMER_ID = ‘1234567890’;
/ ファイルを開く /
OPEN FILE(CUSTOMER_MASTER);
/ ——————————————— /
/ 例1: SEQUENTIAL UPDATE でレコードを更新する /
/ ——————————————— /
/
この後、READ FILE(CUSTOMER_MASTER) … THEN DO; でレコードを読み込み、
RECORD_AREA を更新して、WRITE FILE(CUSTOMER_MASTER) … で書き戻す、
という処理を順番に行います。
この間、他のプログラムがこのファイル全体を UPDATE モードで開こうとすると、
待たされたり、エラーになったりする可能性があります。
/
/ ——————————————— /
/ 例2: DIRECT UPDATE で特定のレコードを更新する /
/ ——————————————— /
/
まず、SEARCH KEY(CUSTOMER_ID) を使って、
目的のレコードを直接検索します。
FIND FILE(CUSTOMER_MASTER) KEY(CUSTOMER_ID);
もしレコードが見つかったら、
READ FILE(CUSTOMER_MASTER) INTO(RECORD_AREA);
でレコードを読み込み、
RECORD_AREA を更新して、
WRITE FILE(CUSTOMER_MASTER) FROM(RECORD_AREA);
で書き戻します。
この場合、特に「レコード単位」でロックがかかることが多く、
同じ顧客IDのレコードを他のプログラムが同時に UPDATE しようとすると、
より厳格に排他制御が行われます。
/
/ ファイルを閉じる /
CLOSE FILE(CUSTOMER_MASTER);
END MYAPP;
コードのポイント:
- `ENVIRONMENT(‘MYLIB.CUSTMST.DAT’)` の部分は、実際にファイルが存在するライブラリ名とファイル名を指定します。これはメインフレーム特有の表現ですね。
- `OPEN FILE(…)` でファイルをプログラムから利用できる状態にします。
- `READ FILE(…)` や `WRITE FILE(…)` といった命令で、実際にデータの読み書きを行います。
- `FIND FILE(…) KEY(…)` は、`DIRECT` アクセスで特定のレコードを探すための命令です。
- `CLOSE FILE(…)` でファイルの利用を終了し、OSにファイルを解放します。
まとめ:属性指定は「車の運転免許」のようなもの
`SEQUENTIAL`、`DIRECT`、`INPUT`、`OUTPUT`、`UPDATE`。これらの属性は、PL/Iでファイル操作を行う上での「車の運転免許」のようなものです。
- `INPUT`: 「運転はできるけど、助手席に乗ってるだけ」→ 読み取り専用
- `OUTPUT`: 「運転はできるけど、まだ助手席には乗れない」→ 書き込み専用
- `UPDATE`: 「運転もできるし、助手席にも乗れる」→ 読み書き両方可能
- `SEQUENTIAL`: 「一本道をひたすら走る」→ 順番にアクセス
- `DIRECT`: 「地図を見ながら、目的地に直接向かう」→ ランダムアクセス
これらの免許をどう組み合わせるかで、ファイルへのアクセス方法や、他のプログラムとの協調(排他制御)の仕方が決まってくるわけです。
最初は少し戸惑うかもしれませんが、一つ一つの属性が持つ意味を理解し、それをどう組み合わせるか、という視点を持つことで、PL/Iでのファイル操作がぐっと身近になるはずです。
これらの知識は、実際のバッチ改修や、レガシーシステムの調査・移行作業において、非常に役立つはずです。もし「あれ?このファイル、どうしてこんな属性で開いてるんだろう?」と疑問に思ったら、ぜひ今日の話を思い出してみてください。きっと、その理由が見えてくるはずですよ!
