RPGの観点からみたPHP-パート5:データベースへのアクセス
ネイティブ、DB2、MySQLの各APIを使ったSystem iデータベースへのPHP流アクセス方法
前稿「RPGの観点からみたPHP - パート4: PHPの詰め合わせ」では、PHPにおけるデータベース・アクセスについてご説明しました。私がご紹介した例は、だれもがアクセスできるようにということでSQLiteをベースにしました。本シリーズの今回は、もう一歩先に進んでIBM i上のPHPデータベースへアクセスするという素晴らしい世界に入ります。
データベース・アクセス
RPGプログラムからデータベースにアクセスする方法が多数あるのと同じで、PHPにも多数のオプションがあります。RPGのREAD、CHAINなどといった命令コードと等価のPHPを提供するネイティブAPIの他に、PHPでは組み込みSQLのサポートをDB2 APIやMySQL APIの形で提供しています。PHPにはこの他にも多数のデータベース・オプションがありますが、上記の3つのAPIが、DB2のテーブルに保存されているデータを直接処理することができます。上記の3つのAPIの中にMySQLがあるのに驚かれたかもしれませんが、本稿をお読みいただくとお分かりいただける通り、MySQLは興味深い利点をいくつか備えたオプションです。
今回は私が全部を説明するのではなく、2人の仲間に協力を要請しました。私のパートナーであるスーザン・ガントナー氏には、後述の「MySQLのテーブル・アクセス」の章でIBM iにおけるMySQLについて解説していただき、「IBM i プログラマのためのPHPガイド」の著者であるジェフ・オレン氏には、後述の「DB2のテーブル・アクセス」の章でDB2オプションについて論じていただきます。そして私は残りのネイティブAPIについてこの後すぐに説明します。
本稿では、私たちは3人とも、パート4で紹介した2つのSQLiteの例をもとに修正を加えて、同じ機能を違った方法で提供します。どの方法も似ている部分もあれば、SQLiteの特定の機能が欠けているためリエンジニアリングした部分もあります。能書きはここまでにして、ネイティブAPIのオプションから解説します。
ネイティブI/O API
RPGのプログラムで組み込みSQLをご存じの方にとっては、パート4のSQLiteの例はわかりやすかったと思います。I/Oの大部分にRPGのREAD、SETLL、CHAINなどの命令コードを使用している方は、ここでご紹介する方法の方がもう少し簡単に理解できるかもしれません。
以下にご紹介する2つのスクリプトのソース・コードは、systeminetwork.com/codeの"System iNEWS"本号のダウンロード・バンドルで入手可能です。両者の基本的なフローは同じですから、図1と図2のスクリプトの関連する部分だけを抜き出しています。
簡単なファイル・リスト
簡単なファイル・リストの例(図―1)ではいくつかの変更を加えましたが、基本的なロジックは、本シリーズのパート4の図―2にある元のSQLite版とほぼ同じです。図―1のBでi5_connect()関数を使ってシステムへ接続するところから始まっています。
接続パラメータ
通常の使用では、3つのパラメータを渡します。1番目のパラメータは、接続したいシステムの名前です。これには通常、「localhost」またはそれに相当するIPアドレス(つまり「127.0.0.1」)を指定します。ホスト名を指定するのは奇妙に思えるかもしれませんが、他に選択肢がないのです。この機能を実装しているベースのソフトウェアが、WindowsシステムやLinuxシステムからもIBM iのデータにアクセスできるようにしているためであるとご説明すると、少し奇妙に感じられなくなるのではないでしょうか。(詳細についてはeasycom-aura.comにあるEasyComのウェブサ
イトをご覧ください。) こうした状況を考えると、ホストの識別子がなぜ必要なのかがおわかりいただけると思います。
2番目のパラメータは、接続する際に使用するユーザーIDで、3番目のパラメータはそのユーザーIDのパスワードです。ユーザーIDもパスワードも、空文字列でもかまいません(たとえば、$i5conn = i5_connect( 'localhost','','', ... )といったようにです)。空文字列を使った場合は、特別なユーザーIDであるNOBODYを使用して接続することとなります。このIDは、Zendのインストール処理の間に、いくつかの関数のデフォルトのユーザーIDとして作成されたものです。システム構成によってはNOBODY IDは役に立つ場合もあり、役に立たない場合もあります。私のシステムでは、この例にあるようなファイルへのアクセスにさえも十分な権限を持ちません。皆さんのシステムではもっと役に立つように修正されているかもしれませんが、NOBODY IDのデフォルトの設定は最小限の権限しか与えられていません。
4番目にオプションのパラメータがありますが、今回の例では使用していません。このパラメータは連想配列の形式で提供しなければならず、その目的はライブラリ・リストやジョブ名などを接続用にセットできるようにすることです。この使用法については、i5_pconnect()のオプションについて後述する際に、簡単な例でいくつか見てみましょう。
接続のチェック
接続に失敗すれば、スクリプトの残りの部分を実行することは意味がありませんので、次に実行すべきなのは接続が成功したか否かを調べることです(図―1のC)。接続が失敗したら、iErrMsg()という関数を呼び出して(図―1のA)問題をレポートしています。このように、同じコードを際限なく繰り返すことなく、IBM i固有のエラー・データをメッセージに簡単に取り入れることができます。
このルーチンでは、まず関数i5_error()を呼び出します。すると4つの*要素からなる配列が戻り値として返ってきます。このうち3番目と4番目の要素に着目します。これらの要素は例外メッセージID(たとえばCPF5715)と、それに関連付けられた代入変数入りのメッセージ・テキスト(たとえば「ライブラリPARTNER400中のファイルPCUSTOMERがありません。」)です。次にこの2つの要素を自分のメッセージ・テキスト中に取り入れると、それがHTMLテーブルに表示されます。i5_error()の値は、私がここで使用している接続用APIやデータベース・アクセス用APIだけでなく、i5_ファミリーのどの関数にも適用可能です。
*いろいろ試していくうちに、今の実装が4つの要素ではなく、8つの要素を返してくるように見える場合があるかもしれません。後半の4つは前半の4つの繰り返しのように見えますが、インデックスが数字ではなく文字となっています。このトピックに関するドキュメントを探し出せなかったので、後半の4つの要素に依存することはお勧めしません。これについてはZend社に問い合わせ中ですので、回答があり次第、宿題フォーラムの中で報告します。
パート4のSQLiteの例では、端末エラーがあっても処理を続けるようなスクリプトとしましたが、実運用環境では、エラー・メッセージを表示した後はスクリプトの実行を中止する場合の方がより一般的です。それが少々過激なdie()という名前の関数の目的で、私の例の場合iErrMsg()関数の最後でこれを呼び出しています。RPGプログラムにもそのような命令コードがあればと、特にユーザーを対象としたプログラムを作成しているときに時折思ったりするのではないでしょうか。さてPHPには実際このような関数があり、エラーが発生した状況等で頻繁に使用されています。また、die()関数はテキスト文字列のパラメータを1つ受けることができます。このパラメータが渡された場合は、スクリプトの実行が終了する前に出力されます。私の例ではこれを利用しませんでした。コードをなるべく簡単にしたかったというのがその理由なのですが、echo()関数の代わりにdie()関数を使用して、エラー・メッセージを出力すればよかったかもしれません。たとえば次のようにです。
die( '<tr><td collapse="4">' . $msgText . '<br>Message ... )
ファイルのオープン
次にi5_open()関数でファイルをオープンします(図―1のD)。1番目のパラメータについては説明の必要がありませんね。2番目のパラメータは、実行したいオープンのタイプを指定します。この例ではファイルを読み込みたいのですから、定数I5_OPEN_READ を使用しました。実際これがデフォルトの動作なのですが、デフォルトに頼るのではなく明示的なコードにしたかったのです。この他のオプションとしては、読み書きモードでファイルをオープンするためのI5_OPEN_READWRITEと、読んでお分かりの通りコミットメント・コントロール下でファイルをオープンするI5_OPEN_COMMITがあります。
3番目にオプションのパラメータがあります。これは接続リソースです。このパラメータにリソース変数$i5_connを指定しても良かったのです。しかし先ほど接続を確立した時に、$i5_connが、その後スクリプトの中で使用するその他のi5_...関数のデフォルトになっているのです。前述の通り、デフォルトには頼りすぎないようにしたいのですが、私はIBM iのPHPスクリプトの中では単一の接続しか使用しないので、この場合は例外とします。
データの処理
これですべて準備が整いましたので、ようやくファイル中のデータを処理することができます。図―1のEで、関数i5_fetch_assoc()を呼び出しているところがこれに相当します。この関数の呼び出しで、データベースから連想配列の形で1つの行(レコード)を取り出します。この連想配列では、フィールド名がキーで値がデータとなっています。この概念について皆さんはもうご存じのはずです。データ行をフェッチしてくる部分はwhile()条件文の中に含まれており、ファイルの終端に到達するまで続きます。終端に到達すると変数$rowが空になり、条件文がfalseとなってループが終了します(2010年6月、「RPGの観点からみたPHP - パート3: PHP関数」の「真実性とは何か?」を参照ください)。ループ内のロジックはSQLiteの例とまったく変わっていません。戻り値の連想配列が同じ形式だからです。
接続のクローズ
最後だからといって重要ではないということではありませんが、関数i5_close()で単に接続をクローズします(F)。すると、LRをオンにセットした状態でRPGプログラムを終了するのと同様に、ファイルがクローズされます。
最初の例については以上です。ご覧になってお分かりの通り、SQLiteの例(パート4の図―2)から変更したのはほんの少しの部分で、ロジックの流れは変えていません。
サブファイル・ページングの例
2番目の例の場合は、もう少し変更を加える必要がありました。i5_fetch_assoc()などの基本的なI/O関数にはSQLite版(パート4の図―4)で使用した、結果セットを一度に10行までに制限するという、範囲制限の機能に相当するものがないからです。ご存じの通り、通常のRPG I/Oの命令コードにはそのような関数はありませんし、i5_...ファミリーが真似しようとしたのはそのような命令コードなのです。
ではどのようにして「サブファイル」のページングのロジックを処理したかですって?それは「単純なプログラミング」によるものであり、そうだからこそそれをコーディングする方法は多数あります。私のSQLiteの例(パート4の図―4)では、セッション変数を使用して次のページ用の開始キーと、現在の画面を表示するのに使用する開始キーを保存しています。これに加えて、結果セットに戻される行の範囲を制限するSQLiteの機能があれば十分でした。このロジックの背景にある概念を拡張して、表示される各ページ用のキーを保存することにしました。したがって現在のページ番号もセッション変数に保存することで、前のページや次のページへの移動リクエストも、この配列へのインデックス付けをしてファイルの位置取りをするキーを取得して、処理することができます。利用可能なオプションがいくつかある中で、前のページへの移動リクエストを、ファイルを逆読みすること(つまりREADPに相当するものを使用すること)で処理できたかもしれません。ただし私が使用した方法では、ユーザーが指定された任意のページに移動する追加のロジックを追加することができ、これはウェブ・アプリケーションではGoogleのクエリーのようにごく一般的なことです。
基本的な変更
まず、エラーのレポート、接続の確立、ファイルのオープンに関しては、ファイル・リストの例(図―1)で加えた変更と同じ変更を、サブファイル・ページングの例(図―2)にも加えています。ファイル・リストのスクリプトの最初の方でもう1つ変更した点は、スクリプトを2回目以降に起動した時の処理に関連するものです。今はこの変更点については飛ばして、サブファイル・ページングの例がどのようにして最初のリクエストを処理しているのかをまず見てみましょう。
最初のページ・リクエスト
このロジックは図―2のCで始まり、スクリプトが現在のページ番号($currentPage)をゼロにセットし、この変数を配列$startPositionsへのインデックスとして使用します。これにより前述の配列がセットアップされ、最終的には各ページを構築するのに使用する開始キーを保存することになります。私はこの場合、この最初の要素を空文字に設定することを選択します。RPGでは*LOVALを使っていたでしょうが、後述する通り、ローバリュー・キーを渡さずに、事前に定義された定数を使用してファイルの最初から読みたいということを示します。
図―2のDでは、ファイルの位置取りに使用するキーを配列から取り出し、$startという変数に保存しています。もちろん、この値はこの例の場合は空になりますが、後述する通り、スクリプトを以後呼び出す場合はそうなりません。次のステップは、ファイルを正しいレコードに位置取りすることです。この処理は図―2のEで、最初のページと2ページ目以降のどちら用にファイルの位置取りをしようとしているのかを決めるところから始まります。いずれの場合も、RPGのSETGT/SETLLに相当する関数i5_seek()を使用しています。最初のパラメータは$customerFileというリソース変数で、ファイルがオープンされた時に受け取ります。2番目のパラメータは、実行したい「SETxx」のタイプを記述します。このスクリプトを最初に実行する時、そして特に最初のページを表示するようにリクエストされた時は、ファイルの先頭に位置取りする必要があります。これは組み込み定数I5_FIRSTを使用することで影響を受けます(図―2のG)。これにより、カーソルがファイルの1番目のレコードの前に位置取りされます。SETGTに相当するロジックの実装については、スクリプトが2ページ目以降の処理を説明する際に述べます。
ループの終了
図―2のHで、スクリプトはレコードを表示するwhileループに入ります。このバージョンとこれと等価のSQLite版(パート4の図―4)との違いは、今回の場合、最大ページサイズ($max)に到達した時にループを意図的に終了しなければならないということです。SQLite版では、取り出す行数を最大ページサイズまでに制限していたため、今回のように意図的に終了する必要がありませんでした。ループの終了は、現在のレコード数の値($count)が最大ページサイズ($max)より小さいかどうかのテストと、最初のスクリプトで実装したi5_fetch...操作でのチェックの結果とを、AND条件で掛け合わせて(&&)チェックしています。
ループを抜けたら、次のリクエストのために保存されるセッション変数をセットアップします。この処理は図―2のIに示されており、現在の顧客番号($custRec['CUSTOMER'])を$startPositions配列中の次のページ番号に一致するインデックス位置のところに保存しています。言い換えると、これにより現在のページ上の最後のレコードからのキーが、次のページを開始する「SETGT」操作用に使用できるようになるということです。更新された配列は次に、同じ名前のセッション変数に戻されて保存されます。次に、現在のページ番号($currentPage)が変数lastPageに保存され、接続がクローズされます。
スクリプトのバランスは、前述のパート4の図―4のSQLiteの例と同様で、ページに「次ページ」または「前ページ」ボタンを追加するという単純なものです。
追加のページ・リクエストの処理
これで、最初のリクエストに適用するロジックの違いは説明しましたので、図―2の一番上に戻って2回目以降のリクエストが来たらどうなるのか見てみましょう。
最初の大きな変更点は図―2のAで、セッション変数lastPage、つまり表示した最後のページのページ番号を取り出しています。この値は同じ名前の変数に割り当てられ、以後新しいページ番号を計算する際に使用されます。次にstartPositions配列を取り出して、キーの選択用に利用可能な状態にします。
そして図―2のBで、表示しようとしているページのページ番号を計算します。つまり、これが前のページを表示するリクエストである場合は$lastPageの数から1を引き、次のページを表示するリクエストである場合は1を足します。これにより図―1のDに到達した時点では正しいページ番号がセットされていることになり、正しい開始キーを選択することができます。今回図―2のEでファイルの位置取りのロジックに到達した時は、現在のページ番号はゼロではありませんので、i5_seek()に3つのパラメータを渡してファイルの位置取りをします(図―2のF)。2番目のパラメータには「>」をセットし、SETGTタイプの位置取りをしたい旨を指定し、3番目のパラメータは、この前(図―2のD)に配列$startPositionsから取り出したキーをセットします。
以上の説明ですといずれも実際よりもずっと複雑に思えるかもしれませんが、http://www2.systeminetwork.com/code/(※現在サイトが閉じられてしまっています)にあるコード・バンドルをダウンロードしてスクリプトを実行してみると、私が追加したロジックが変数$lastPageの内容と、配列$StartPositionsの現在の内容を表示させようとしていることがおわかりいただけるでしょう。これにより、スクリプトの実行が進むにつれて何が行われているのかがわかるようになり、「次ページ」ボタンや「前ページ」ボタンをいろいろ組み合わせたスクリプトを試してみることができるでしょう。
その他の細かい点
前述しましたが、i5_connect() APIの他にも、2つ目のバージョンのi5_pconnect()というAPIが利用可能です。この2つのAPIの大きな違いは、標準のi5_connect()で確立された接続はスクリプトの実行が終了するとともにクローズされますが、i5_pconnect()版の方はi5_pclose()命令により接続がクローズされるか、アクティブではない状態になってタイムアウトになるまで永続的な接続が確立されます。i5_pconnect()を呼び出した結果として確立された接続は、元の接続を作成したユーザー詳細と同じユーザー詳細をリクエストで指定した、以後の任意のi5_pconnect()リクエストが使用できます。その結果、接続リクエストに対するパフォーマンスが大幅に改善されます。
永続的な接続がもたらす別のオプションも有益です。永続的な接続ではタイムアウト時間を設定して、接続がある時間の間使用されていない場合にその接続をクローズさせることができます。また、永続的な接続を「private」と指定する識別子を指定することもできます。これは複数の永続的な接続が必要で、同じIDを使用するが異なるライブラリ・リストを使用する可能性がある場合に便利です。これらのオプションは、連想配列を4番目のパラメータとして渡すことでセットします。配列の値は、I5_OPTIONS_IDLE_TIMEOUTやI5_OPTIONS_PRIVATE_CONNECTIONなどといった事前に定義された名前の定数の形をしたキーに関連付けられています。どれがどれに相当するかを説明する必要はないでしょう。永続的な接続に関して最後に説明しておきたいのは、i5_close()を呼び出しても永続的な接続はクローズされないということです。永続的な接続を明示的にクローズさせたい場合は、i5_pclose()を使用する必要があります。
頻繁に使用されるもう1つのオプションが、I5_OPTIONS_INITLIBL です。このオプションは指定したライブラリを、接続ジョブのライブラリ・リストの最初に追加するというものです。複数のライブラリが必要な場合はカンマで名前を区切ってください。このオプションは両方のバージョンの...connect()で利用可能です。このオプションを使用した例と、ここで説明した他の2つのオプションを使用した例を以下に示します。
$connOptions = array(
I5_OPTIONS_PRIVATE_CONNECTION => 'OrderLookup',
I5_OPTIONS_IDLE_TIMEOUT=> '60',
I5_OPTIONS_INITLIBL =>'ORDERS, CUSTOMERS' );
$conn = i5_pconnect('localhost', 'MYUSERID', 'PASSWORD', $connOptions );
この他にも利用可能なオプションが多数あります。詳細についてはZend社のドキュメントを参照ください。
たくさんの選択肢
さてどのオプションを選択すればよいのでしょうか。私の答えは「全部」になります。ネイティブAPIは、私がPHPを使い始めたころよりも魅力がないと思っていると告白しますが、それぞれのオプションにはPHPツールキットの中で使い道があるのです。もちろん、ネイティブAPIは従来のRPGの方法をより忠実に真似しようとしていますが、ネイティブAPIと従来のRPGの命令コードの間には単純な一対一対応がないとう事実が、ネイティブAPIを使うのを躊躇させている場合があります。これは特に、新しいレコードを追加するのに「空」のレコードを作ってからそのレコードを必要な値で更新しなければならない場合に当てはまります。これがどのように動作するかの例について書かれたドキュメントやウェブがありますので、ここではこれ以上詳しく述べません。ご興味がある方がいらっしゃれば、宿題フォーラムのスレッドで話題として取り上げようと思います。
もちろん、WRITE、CHAINなどといった命令コードにもっと近い独自の関数を記述することもできますが、ネイティブRPGと全く同じに動作することはないでしょう。ですから、パフォーマンス上の大きな違いが存在するかもしれないという兆しがないこともあって、SQLではできない、あるいは簡単にはできない機能をネイティブAPIが提供している場合を除き、私はSQLのアプローチを使用する傾向にあります。
さてそれはさておき、SQLのアプローチが好きだといってもどのアプローチのことでしょう。私の答えは「状況によって異なる」というものです。私個人にとっては、MySQLを使用してウェブ・アプリケーションを開発することには非常に大きな利点があります。なぜなら、自分のアプリケーションを自分のMac上でテストでき(35,000フィート地点では非常に便利)、うまく動作していることが分かればすぐにIBM i上に導入できるからです。スーザン・ガントナー氏が後述の「MySQLのテーブル・アクセス」で説明しているように、DB2ストレージ・エンジンを使用することで従来のRPGプログラムとデータを共有する利点も依然として得られます。DB2が気に入ってくるほど、私の例で使っているページング・ロジックのシンプル・タイプのサポートが少し足りないとわかりました。ジェフ・オレン氏が1度に1ページを表示する後述の「DB2テーブルのアクセス」と同じロジックを実装するのに必要としたSQL文と、スーザンのコードを比べてみれば、私が言っていることがおわかりいただけると思います。もちろん、SugarCRMやJoomlaのようなオープンソースのパッケージ・アプリケーションを使用するならば、私はMySQLを絶対に使用します。
DB2のアプローチはどうでしょうか。少なくとも理論的には、MySQLのオプションよりもパフォーマンスが良いはずですが、自分ではまだテストしたことがないので、今のところは理論上の話です。通常のデータアクセスがすべて組み込みSQL経由なのであれば、必要性に迫られない限り2つの異なるSQL方言を扱うことは意味がありませんので、DB2のオプションを使用する方が理にかなっています。
ですから先ほど申した通り、「状況によって異なる」のです。
結びに
PHPをRPGの観点からみるシリーズの最後から2番目のパートです。最後のパートではいくつか細かい点について説明します。たとえば、私はネイティブI/Oオプションの大ファンではありませんが、コマンドの起動、スプール・ファイルの処理、オブジェクトの操作などに対応するオプションは本当に便利です。さらに、ウェブ・サービスの使用やPDFの生成等といった機能についてはまだ調査しておりません。まだまだ説明しきれていなことはたくさんあります。
いくつかの項目は、この形式に含めるにはあまりに大きすぎますので、そうした項目については読者の方から提示いただいた他のトピックと合わせて、いずれ別の形の記事でご説明したいと思います。こうしたトピックには、SmartyやZend View(Zendフレームワークの一部)などのテンプレート化システムのレビューや、実際に使われている多数のPHPフレームワーク・システムについて見てみること等を考えています。
次回の記事が届くまでプログラミングを楽しんでください。プログラミングが退屈になるなんて考えられません。
DB2のテーブル・アクセス
「RPGの観点からみたPHP - パート4: PHPの詰め合わせ」に掲載のコード例で、ジョン・パリス氏はSQLiteのテーブルにアクセスし、その結果として得られるデータセットのページを制御する方法を例示してくれました。私もデータベースのアクセスをIBM i上のDB2テーブルへのアクセスに変更して、同じ処理をやってみます。
最初のスクリプト(図―A)はわかりやすいテーブルのリスト表示です。CUSTOMERSファイルをオープンして、HTMLテーブル中の既存のすべての行を表示しています。幸いなことにCUSTOMERSファイルにはレコードが数個あるだけです。もし多数のレコードがあったら、出力ページ数が恐ろしく長くなります。それでもこのスクリプトは、ページ制御のスクリプトを作成するのに必要な基本的なポイントを提供してくれています(図―B)。
ファイル・リストの例
パート4の図―2と図―Aの違いで最初にお気づきになるのは、接続方法をdb2_connect()に変えたことと、必要な情報を保持するのに必要な変数をいくつか追加したことです(図―AのA)。db2_connectメソッドではデータベース・スキーマの名前が必要となります。場合によっては、「localhost」を使用することができ、それでそのまま動作することもあります。しかし今回の例では、WRKRDBDIRE コマンドを使用してローカル・データベース名を探し、その名前をスクリプト中で使用しています。db2_connectが使用する他の2つのパラメータは、ユーザー・プロファイル名とそのパスワードです。これらはIBM iに対して正当なユーザー・プロファイルとパスワードでなければなりません。当然ですが、実運用のPHPスクリプト中で私の例のようにこれを直接ハードコードしたくはないでしょうが、これは単なるスクリプト例なのでこのまま先に進みます。
元のスクリプト(パート4の図―2)に加えた次の変更点は、SQLクエリーの変更です。ジョン氏は元々sqlite_array_query()メソッドを使用していました。私はこのコードを変更してdb2_exec()メソッドを使用しました(図―AのB)。db2_execメソッドは2つのパラメータを受け取ります。最初のパラメータはデータベース接続リソース変数($dbconn)です。これは、元の例(パート4の図―2)のSQLiteデータベース接続リソースと全く同じです。2番目のパラメータは実行するSQLです。しかしdb2_execの実行結果は、sqlite_array_queryの実行結果とは異なります。SQLiteメソッドは配列を返しますが、db2_execはリソース変数を返します。
FOREACHループもWHILEループに変更した(図―AのC)のは、この結果の違いのためです。また、図―AのCのコードを変更してdb2_fetch_assoc()メソッドを使用するようにしました。このメソッドはリソース($selected)から行を取り出して、そのデータの値を連想配列に格納します。db2_fetch_assocが行を返さなくなるまでこの処理が続きます。結果として出来上がった配列は、パート4の図―2の元のSQLiteのコードで使用していたものと同じになりますので、ループ内のコードを変更する必要はありません。最後の変更点は、db2_close()メソッドを使用してデータベース接続をクローズするところ(図―AのD)です。
サブファイル・ページングの例
ページ制御の例(図―B)をdb2のメソッドを使用するように改造するために、図―Aのファイル・リストの例で作成したコードに同じ変更(つまりメソッドをdb_に変更)を加えました。ただしこの場合は行を選択するSQLも変更する必要がありました。なぜなら、パート4の図―4にある元のSQLiteの例で使用しているLIMIT/OFFSET関数を、DB2がサポートしていないためです。
この変更でSQLが若干複雑なものになりますが、難しくはありません。私はSQL OLAP関数をいくつか使用しています。SQL OLAP関数はpublib.boulder.ibm.com/iseries/にあるIBM i Information Centerでは探すのが少し難しいかもしれません(探すには、関数の名前ではなくOLAPを検索してみてください)。中でも私はROW_NUMBER関数とOVER関数を使用しています(図―BのA)。これらの関数が果たす機能は、SQLiteのLIMIT/OFFSET関数を真似する方法を提供することです。その結果が手に入りさえすれば、$start変数と$max変数に基づいた結果から正しいサブセットを選択すればよいのです。これは元の例(パート4の図―4)でジョン氏が行った方法と同じです。
これに関係することとして、ROW_NUMBER関数とOVER関数を使用するのは、この結果を得るための唯一の方法ではありません。スクロール可能なカーソルを使用するなど他にも例があります。何事においてもそうですが、同じタスクを遂行するにはいろいろな方法があります。いろいろなソリューションを試してみて一番良い方法を探してみてください。
MySQLのテーブル・アクセス
これまではSQLiteを介したIBM iデータへのアクセス方法と、DB2のデータにアクセスする2つの異なる方法、すなわちネイティブAPIを使用する方法とDB2 APIを使用する方法、について説明してきました。もう1つ別のデータベース・アクセスの選択肢である、MySQLについて見てみましょう。MySQLの興味深い点は、MySQLのデータとDB2のデータを同時にアクセスまたは更新ができるということです。これはDB2 for i Storage Engine for MySQLの魔法のおかげなのですが、これについてはちょっとおいておきましょう。まずMySQLを使用してデータベースへアクセスした場合と、「RPGの観点からみたPHP - パート4: PHPの詰め合わせ」 にある元のSQLite版との、構文上の違いについて見てみましょう。
ファイル・リストの例
図―AのAで、関数sqlite_open()の代わりに(パート4の図―2)、関数mysqli_connect()を介してデータベース接続が行われています。渡されるパラメータは、ホスト名(通常は「localhost」)、MySQLのユーザー名とパスワード、そして今回の場合、MySQLのデータベース名になります。MySQLのデータベース管理システムには独自のセキュリティ・システムがありますから、IBM i上でMySQLを稼働させていたとしても、ここで指定するユーザー名とパスワードはIBM iのユーザー・プロファイルとは関係がありません。そうではなくて、ここで指定するユーザー名とパスワードは、MySQLデータベース中で設定されるものです。データベース名はIBM i上のライブラリ名と大体似たものになっています。今回の例の場合、1つのデータベースのテーブルにアクセスすれば十分なので、単に接続時に指定しました。しかし必要に応じて、接続時にデータベース名を指定するのを省略し、別の関数呼び出しの際に指定することで、複数のデータベースに対してクエリーを発行することができます。
図―AのBでは、データベース接続リソース変数($dbconn)とSQL文をパラメータとして、mysqli_query()関数でクエリーが指定されています。SQL文が今回の単純な例よりもっと複雑な場合は、SQL文を変数にして変数名を2番目のパラメータとして指定する方が良いかもしれません。
図―AのCでは、関数mysqli_fetch_array()が各行のデータをフェッチして、$rowという名前の配列に順次格納しています。 ここで私は2つの関数を使用しています。1つはクエリーを指定する関数で、もう1つは各行を取り出す関数です。これは、ジェフ氏が「DB2のテーブル・アクセス」の例で行っていたのと同じことです。また2つの関数を使用するのは、RPGの組み込みSQLで使用されているモデルに近いもので、クエリー文がDeclare Cursor文で指定されていて、これとは別にFetch文を使用して結果セットから各行を取り出しています。この違いがあるために、行を処理するのにSQLite版で使用されているforeachループではなくwhileループを使用しました。
図―AのDでは、関数mysqli_close() が関数sqlite_close()の代わりに使用されています。こうした細かな違いを除けば、MySQL版(図―A)とSQLite版(パート4の図―2)は似たものになっています。
サブファイルのページングの例
図―Bのサブファイルのページングの例にも同様の変更を加えました。すなわちMySQLへの接続(図―BのA)、クエリー文の設定(図―BのB)、行の取り出し(図―BのC)、接続のクローズ(図―BのE)です。
他の唯一の変更点は、表示する行がこれ以上ない時に「次ページ」ボタンが押された場合、「これ以上表示するレコードはありません」という条件用のテストです(図―BのD)。SQLiteの例 (パート4の図―4)で使用されているテクニックがうまくいきます。というのも「open」アクションがデータも取り出してくれるからです。MySQLを使用すると、前述の通りこの2つのアクションは別々の関数呼び出しによって実行されますので、条件のテストをwhileループの最後に移動しました。これを除けば、MySQL版のコードはSQLite版のコードとほぼ同じになります。
同時に二つの利益を享受
PHPのスクリプトの背後で起こっている、小さいながら重要な差異が1つあります。PCUSTOMERSというMySQLテーブルのデータは、DB2のテーブルに格納されています。 MySQLで PCUSTOMERSを作成した時は、IBM DB2 for iストレージ・エンジンを使用したい旨を指定しました。データはDB2に格納されていますので、同じデータに対して対話形式であるいは組み込みSQLまたはRPGのREAD、UPDATE命令、お気に入りのネイティブ・クエリー・ツール、果てはDFUなどを使って、DB2からアクセスしたり更新したりできるということになります。
この柔軟性の大きさがもたらすパワーは注目に値します。データを同期させるために抽出や更新を実行する必要はありません。1つのアプリケーションがRPGで記述され、同じデータを必要とするもう1つのアプリケーションは、他の言語あるいは環境で実現されているからです。PHPプログラムは、MySQLを使用してブラウザ画面に表示するデータを処理することができますが、これは前述のように、ページ制御としてはよりわかりやすいロジックとなっています。と同時に、RPGのプログラムは、IBM i上でDB2を使用して「ネイティブに」作成されたかのように、データに対してアクセスすることができます。
こうした2つのデータベース管理システム間でリアルタイムにデータを共有できることが信じられないというのであれば、アクション・オンラインでの簡単なデモを参照ください。
本当にどちらの方法でもできるのです。