カーソル(ポータル)。フロントエンド/バックエンドプロトコル経由

PostgreSQLでカーソルというと、ドキュメントにあるように次の3つで定義可能らしい。

  • DECLARE文経由
  • フロントエンド/バックエンドプロトコルからBindメッセージ経由
  • サーバプログラミングインタフェース(SPI)経由

カーソルはポータルとも呼ばれる(プログラム上ではPortalDataという構造体で表され、Poratl型で参照される)。ポータルは開いているカーソルと等価。今回はJDBCで、つまりフロントエンド/バックエンドプロトコルでオープン/クローズされるカーソルを中心に見てみる。

こんなプログラムで考えてみる。

PreparedStatement ps = conn.prepareStatement("select bid from branches");
ResultSet rs = ps.executeQuery();                                         ...(A)
rs = ps.executeQuery();                                                   ...(B)

ResultSetを閉じずに次のResultSetをオープンしたらどうなるのか試してみた。

(A)のときにフロントエンドからバックエンドメッセージへ送られるメッセージ

Parse
  • メインの処理はexec_parse_message()で行う。
  • パース処理、アナライズ処理、リライト処理、プラン処理まで行う。
  • プラン処理で作成したプランツリー等の情報をPreparedStatementという構造体にまとめてハッシュテーブルに格納する。
  • このハッシュテーブルはバックエンドごとに固有。つまり、プランツリーはバックエンド間で共有されることはない。
Bind
  • メインの処理はexec_bind_message()で行う。
  • Portalを作成する。
  • PreparedStatementからプランツリーを取り出し、Portalと関連付ける。
  • パラメータもPortalと関連付ける。
  • 実行時用のコンテキスト(QueryDesc)などを用意する。
Describe
  • メインの処理はexec_describe_portal_message()で行われる。
  • Poratlからプランツリーを取り出し、そのときのSELECTに並ぶカラムのメタデータを返す。
Execute
  • メインの処理はexec_execute_message()で行われる。
  • クエリの実行。
  • 最後のタプルまで取り出したらポータルに終わりのフラグを立てる。
  • タプルが最後まで取り出せたかかどうかはフロントエンドに伝える。

(B)のときにフロントエンドからバックエンドメッセージへ送られるメッセージ

ClosePortal
  • JDBCドライバでは、前のResultSetがオープンされたままならばクローズのメッセージをバックエンドに送る。
  • バックエンドのメインの処理はPortalDrop()で。
  • 古いポータルは削除される。
Bind
  • 新しいPortalを作成するなど、最初のBindと同じ。PreparedStatementはまだ生きているので再利用。
Describe
  • 最初のDescribeと同じ。(同じなのだから再度取得しなくてもいいような気がするけど。)
Execute
  • 最初のDescribeと同じ。

所感

  • Parseのフェーズでプランツリーを組み立ててしまうのでバインドピーキングの機能はないんじゃないかと思う。
  • JDBCドライバでは、ResultSetやPreparedStatementのクローズが漏れている場合は、PhantomReferenceを使ってクローズするようにしている。PhantomReferenceってこうやってつかうんだと感心した。今回のケースだと、ファイナライズのタイミングでクローズされるわけではないが、ファイナライズされるときにも対応できている。
  • カーソル(Portal)はBindのタイミングで作成され、プランツリーと関連付けられる。ParseとBindのおおまかな違いがわかってよかった。