メニューボタン
IBMi海外記事2019.08.01

データはどこに生息しているのか? 新しい基礎知識

Jon Paris 著

XML構文解析やOpen Accessなど、最近のRPGへの追加機能についてのガイダンスを行うたびに、最近のリリースでこの言語に対してなされてきたデータ定義のいくつかの機能強化に関し、何らかの「補習」教育を行う必要があることに気付かされます。

これらの機能強化のほとんどがこの言語に追加されたのは、何年も前のD-仕様書が必須だった頃のことでしたが、それらについて差し迫った必要性がなかったとしたら、それらは気付かれないまま過ぎてしまったのかもしれません。結局のところ、このニュースレターの最も熱心な読者でさえ、使用しなかったとしたら、10年前にここで読んだことのほとんどをおそらく忘れていることでしょう。つまり、これらの機能のいくつかが追加されたのは、実にそれほど前のことだったということです。

「Guru Classics」としてどの記事をアップデートすべきか決めるに当たって、フリーフォーム データ宣言に対する変更や関連する機能強化を踏まえて、一歩下がってこれらの「新しい基礎知識」の記事を再検討してみる良い機会なのではないかと思うに至りました。この最初の記事では、RPGでデータがどのように処理されるのかに関する、いくつかの基礎的な事項を中心に説明します。

データはどこに生息しているのか?

我々のほとんどは、あまりにも長い間、RPGを使用してきたために、RPGでのフィールドのストレージがどのような構成になっているかについての基本的知識を時々忘れてしまいがちです(あるいは、一度も学んでいないのかもしれません)。そのため、まずはいくつかの基礎的なポイントから見てゆくこととしましょう。

1.プログラムの中には、レコードなどというものはありません。考えてみれば、当たり前のことです。同じフィールド名を複数のファイルで使用することができるのですから。フィールドが「レコード」(「データ構造」を考えてみてください)の一部であるとしたら、そうはいきません。実際、レコードを読み取るときには、コンパイラーは、それぞれのフィールドの内容をレコード バッファーから個々の格納場所へ移動するコードを生成します。同様に、レコードが書き込まれるときには、コンパイラーは個々のフィールドからデータを収集し、それをバッファーに入れます。デバッグでプログラムをステップ実行したことがあれば、おそらくこの動作についてはご存じでしょう。たとえば、READ命令を実行したときは、次のステートメントに移動するには複数回F10を押す必要があります。数えてみれば、レコード内のフィールドごとに1つのステップが必要なことが分かります(余談ですが、プログラムのCtl-OptまたはH-仕様書でコンパイラー ディレクティブOPTION(*NODEBUGIO)をコーディングすることで、この動作を回避することができます)。

2.レコードで連続的であるフィールドが、メモリーで連続的でないことがあります。月次合計高のような連続するフィールドを配列として処理できるように、ポインターと基底付き構造を使用するのを目にすることがあります。これは、実にまずいアイデアです。うまく動作することもありますが . . . しばらくの間は。まったく関連のない変更の後で再コンパイルした結果、突然動作しなくなることがあります。実際、そうなるのを見たこともあります。安全にこれを行う方法については、後述のポイント4を参照してください。

3.データベース内の符号付き/ゾーン数値であるフィールドは、通常、プログラムではパックに変換されます。これは、歴史的な理由から、すべての内部的に定義された数値フィールドを、コンパイラーがデフォルトでパック10進数に変換するためです。そのため、フィールドのデータがバッファーからその内部のメモリー ロケーションへ移動されるときに、パックへの変換も行われます。このことに対して「そういうことは私のプログラムでは起こらない」とお思いであれば、おそらく後述のポイント4でその理由が分かるでしょう。

似たようなことが、日付フィールドでも起こります。日付フィールドは、常にプログラムのデフォルトのフォーマットで内部的に格納されます。Ctl-OptまたはH-仕様書で何も指定されていない場合は、*ISOになります。注: これを制御するのは、Ctl-OptまたはH-仕様書であり、システム値ではありません。プログラムで「実際の」日付を広く使用する場合は、この点を理解することが重要です。

たとえば、データベース上のすべての日付フィールドを*USAフォーマットとして定義しているとします。日付フィールドは実際にはバイナリーの日数の値としてディスクに格納され、データがプログラムに渡される前に指定されたフォーマット(この例では*USA)に変換されます。プログラムでデフォルトの日付フォーマットが指定されていない場合は、それぞれの日付フィールドがそのメモリー ロケーションに移動されるたびに、再び変換が行われることになります。今度は*USAから*ISOへの変換です。すべての日付に対してそれぞれ2回の変換です。しかも、それは1つの読み取り命令でのみであり、アップデートではこのプロセスが逆になります。したがって、実際の日付フィールドはパフォーマンスが良くないと思われるのも当然と言えば当然なのでしょう。こうなることを理解すれば、内部フォーマットと外部フォーマットが一致するようにするために、すべての日付フィールドでデフォルトの*ISOを使用するか、Ctl-OptまたはH-仕様書のDATFMTエントリーを上手に使用することで、この問題を回避することができます。

4.データ構造で指定することによって、フィールドはデータ型が維持され、メモリー内の既知のロケーションを占有することが保証されるようにすることができます。どのようなデータ構造でもかまいません。外部的に定義される必要はありません。また、フィールドの長さまたはデータ型の指定も必要ありません。フィールド名を指定すれば、コンパイラーがすべて処理します。

表の行内の一連のフィールドを配列として再定義する必要がある場合は、この方法で行います。たとえそれらがレコード内で連続してなくても、問題ありません。以下の短いコード サンプルに、その方法を示します。

以下は、レコード レイアウトのDDSです。

R SALESREC1
  CUSTOMER          4
  Q1SALES           7S 2 
  Q1QTY             5S 0 
  Q2SALES           7S 2 
  Q2QTY             5S 0 
  Q3SALES           7S 2 
  Q3QTY             5S 0 
  Q4SALES           7S 2 
  Q4QTY             5S 0

そして、以下はデータ宣言です。

Dcl-Ds  SalesData;
  Q1SALES;
  Q2SALES;
  Q3SALES;
  Q4SALES;

  SalesForQtr   Pos(1)  Like(Q1SALES) Dim(4);
End-Ds;

注: 7.4で導入された(7.3にはPTFで導入)新たな定義キーワードを使用すると、POSキーワードで必要とされるハードコーディングの位置の使用を避けることができます。その新しいキーワードはSAMEPOSで、配列の開始位置をソフトコーディングで定義する方法を提供します。この場合、SalesForQtrの定義は、次のようにコーディングできます。

SalesForQtr   SamePos(Q1SALES)  Like(Q1SALES) Dim(4);

SamePosキーワードの詳細については、「7.4で提供されるRPGの新機能」という記事を参照してください。

以下のコンパイラーのXrefリストのデータから見て取れるように、QnSALESフィールドはそれらのデータ型(S)が維持されており、一方、QnQTYフィールドは内部的にパックとして再定義されています。

CUSTOMER              A(4) 
Q1QTY                 P(5,0) 
Q1SALES               S(7,2) 
Q2QTY                 P(5,0) 
Q2SALES               S(7,2) 
Q3QTY                 P(5,0) 
Q3SALES               S(7,2) 
Q4QTY                 P(5,0) 
Q4SALES               S(7,2) 
SALESDATA             DS(28) 
SALESFORQTR(4)        S(7,2)

このように、符号付き/ゾーン フィールドがパックに変更されることは、最初にプログラムおよびプロシージャーの呼び出しのプロトタイプを書くことから始めるときには、トラブルの元になることがあります。この問題はポイント5につながります。

5.常に明示的であれ。パラメーターのデータ型およびサイズをプロトタイプで定義します。一般に、LIKEキーワードを使用して個々のフィールドを定義する場合、それは自ら問題を招いているようなものです。最初にプロトタイプを書いたときに、LIKE(Q1QTY)を指定していたとします。そのパラメーターはパックとして定義されることになります。しかし、プログラムに変更が加えられ、Q1QTYがデータ構造に格納されるようになるとしたらどうでしょうか。プロトタイプでLIKEキーワードを使用しているため、この場合、そのパラメーターは符号付きに指定されてしまいます。プログラマーはこうした変更を想定するでしょうか。私の経験で言えば、そうはしないと思います。実際、そのような変更が行われると、インターネット サイトに質問が投稿されることになることがよくあります。幸いにも、この問題をプロトタイプが防いでくれているということでもあり、少なくともその問題について知ることはできます。CALL/PARMの「古き悪しき時代」には、実行時までその問題について気付かなかったものです。さらには、一切気付かないこともありました。プロトタイプでは常にLIKEDSおよびLIKERECキーワードを使用する、と付け加えておきたいと思います。これらのキーワードはそうした問題と無関係だからです。

6.さらにもっと明示的であれ。データ構造の数値および可変長文字フィールドに対して初期化を指定します。データ構造の初期化のデフォルト値は、ブランクです。これには、そのDSの中のどの数値または可変長サブフィールドも含まれます。数値フィールドでの問題はかなり分かりやすいと言えます。ブランクは0と同じではないため、エラーの元になります。

可変長フィールドでの問題はあまり分かりやすくありません。可変長フィールドではブランクが完全に有効であるためです。問題が生じるのは、実際のフィールドの最初の文字部分は2(または4)バイトのバイナリー長に先行されていて、フィールドのその部分のブランクが無効なフィールド長になるからです。

これに対処する最も簡単な方法は、データ構造定義行でINZキーワードを指定することです。この場合、コンパイラーは、データ構造のすべてのフィールドをそれぞれに適切なデフォルト値に初期化します。何らかの理由でそのようにしたくない場合は、それぞれの数値および可変長フィールドで明示的にINZをコーディングします。

7.データ構造I/Oを使用することによって、10進数データ エラーを回避します。IBMはV5R2で、外部記述ファイルに対して入出力命令を実行する機能をデータ構造に導入しました。プログラムで記述されたファイルでそのような入出力命令を実行することはそれまでも可能でしたが、外部記述ファイルで実行できるようになったのはV5R2が最初でした。これらについては2007年にTed Holtがこの記事で取り上げているため、ここでは基本的な仕組みまでは掘り下げませんが、Tedはその機能のこうした側面については言及していませんでした。

前述したように、レコードが読み取られるときに、個々のフィールドがバッファーからそれらの格納場所へコピーされます。しかし、データ構造I/Oを使用する場合は、そうはなりません。データ構造I/Oを使用すると、レコード全体がバッファーからDSへバイト ストリームとしてコピーされます。データがバイト レベルで処理されるため、読み取り時に10進数データ エラーは起こりません。素晴らしい機能です。

もちろん、後に問題のあるフィールドを参照するのであれば、何が起きるか分かりません。しかし、少なくとも、どのフィールドでエラーがあるか正確に知ることができ、MONITORを使用してエラーをトラップして、是正処置を取ることができます。そのようにすれば、ユーザーはいまいましい死のグリーン スクリーンを見ずに済み、それがあるべき方法と言えるでしょう。私の経験からすると、10進数データ エラーの問題をデバッグしていると、ほとんどの場合、そのフィールドが論理的にどのようにも使用されそうにないことに気付かされるのが常であり、そのことによってエラーはさらに苛立たしいものになります。

あわせて読みたい記事

PAGE TOP