FDWコールバック関数のGetForeignRelSize
、GetForeignPaths
、GetForeignPlan
、PlanForeignModify
、GetForeignJoinPaths
、GetForeignUpperPaths
、PlanDirectModify
はPostgreSQLプランナの動作と協調しなければなりません。ここでは、これらの関数がすべき事に関するいくつかの注意事項を述べます。
root
とbaserel
に含まれる情報は、外部テーブルから取得する必要のある情報の量(とそれによるコスト)を削減するために使用できます。
baserel->baserestrictinfo
は、取得される行をフィルタリングする制約条件(WHERE
句)を含んでいるため、特に興味深いものです。(コアのエグゼキュータが代わりにそれらをチェックできるので、FDW自身がこれらの制約を適用しなければならないわけではありません。)
baserel->reltarget->exprs
はどの列が取得される必要があるかを決定するのに使用できます。ただし、このリストはForeignScan
プランノードから出力すべき列しか含んでおらず、条件検査には必要だがクエリからは出力されない列は含まないことに注意してください。
様々なプライベートフィールドがFDWのプラン作成関数で情報を格納する目的で利用できます。 一般的に、プラン作成の最後に回収できるように、FDW固有フィールドに格納するものは全てpallocで確保すべきです。
baserel->fdw_private
は、void
ポインタで、FDWのプラン作成関数で特定の外部テーブルに関する情報を格納する目的で利用できます。
コアプランナは、RelOptInfo
ノードが作成されるときにNULLで初期化するときを除いて、このフィールドに一切に触れません。
このフィールドは、GetForeignRelSize
からGetForeignPaths
やGetForeignPaths
からGetForeignPlan
といったように情報を順次伝えるの便利で、結果として再計算を省くことができます。
GetForeignPaths
では、ForeignPath
ノードのfdw_private
フィールドに固有情報を格納することで、異なるアクセスパスを区別できます。fdw_private
はList
ポインタとして宣言されていますが、コアプランナがこのフィールドを操作することはないため、実際にはなんでも格納できます。
しかし、バックエンドのデバッグサポート機能を利用できるようにnodeToString
でダンプ出来る形式を使うのが最良の手法です。
GetForeignPlan
では、選択されたForeignPath
ノードのfdw_private
フィールドを調べて、ForeignScan
プランノード内に格納されプラン実行時に利用可能なfdw_exprs
とfdw_private
の二つのリストを生成することができます。
これらは両方ともcopyObject
がコピーできる形式でなければなりません。
fdw_private
リストにはこれ以外に制約はなく、コアバックエンドによって解釈されることはありません。
fdw_exprs
リストがNILでない場合は、クエリ実行時に実行されることを意図した式ツリーが含まれていることが期待されます。
これらのツリーは、完全に実行可能な状態にするためにプランナによる後処理を受けます。
GetForeignPlan
では、一般的に渡されたターゲットリストはそのままプランノードにコピーできます。
渡されたscan_clauses
リストはbaserel->baserestrictinfo
と同じ句を含みますが、実行効率のよい別の順番に並べ替えることもできます。
FDWにできるのがRestrictInfo
ノードをscan_clauses
リストから(extract_actual_clauses
を使って)抜き出して、全ての句をプランノードの条件リストに入れるだけ、といった単純なケースでは、全ての句は実行時にエグゼキュータによってチェックされます。
より複雑なFDWは内部で一部の句をチェックできるかもしれませんが、そのような場合には、エグゼキュータが再チェックのために時間を無駄にしないように、それらの句はプランノードの条件リストから削除できます。
たとえば、ローカル側で評価されたsub_expression
の値があればリモートサーバ側で実行出来るとFDWが判断するような、foreign_variable
=
sub_expression
といった形式の条件句をFDWが識別するかもしれません。
パスのコスト見積もりに影響するので、そのような句の実際の識別はGetForeignPaths
でなされるべきです。
おそらく、そのパスのfdw_private
フィールドは識別された句のRestrictInfo
ノードをさすポインタを含むでしょう。
そして、GetForeignPlan
はその句をscan_clauses
から取り除き、実行可能な形式にほぐされることを保障するためにsub_expression
をfdw_exprs
に追加するでしょう。
また、おそらく、実行時に何をすべきかをプラン実行関数に伝えるためにプランノードのfdw_private
フィールドに制御情報を入れるでしょう。
リモートサーバに送られたクエリは、実行時にfdw_exprs
式ツリーを評価して得られた値をパラメータ値とするWHERE
のようなものを伴うでしょう。
foreign_variable
= $1
READ COMMITTED
分離レベルでの正しい動作を保証するため、プランノードの条件リストから除かれた句はすべて、代わりにfdw_recheck_quals
に追加されるか、RecheckForeignScan
で再検査される必要があります。
問い合わせに含まれる他のテーブルで同時更新があった場合、エグゼキュータはタプルが元の条件を、それも場合によっては異なるパラメータ値の組み合わせに対して満たすことを確認する必要があるかもしれません。
fdw_recheck_quals
を使うのは、RecheckForeignScan
の内部で検査を実装するより、通常は簡単でしょう。
しかしこの方法は、外部結合がプッシュダウンされる場合は不十分です。
なぜなら、この場合の結合タプルはタプル全体を拒絶せずに、一部のフィールドをNULLにしてしまうからです。
FDWがセットできる別のForeignScan
フィールドにfdw_scan_tlist
があります。
これはこのプランノードについてFDWが返すタプルを記述するものです。
単純な外部テーブルスキャンに対しては、これをNIL
にセットすることができ、それは戻されるタプルが外部テーブルで宣言された行型を持つことを意味します。
NIL
でない値はVar、あるいは返される列を表す式、あるいはその両方を含む対象のリスト(TargetEntry
のリスト)でなければなりません。
これは例えば、FDWが問い合わせのために必要ないと気づいた列を無視したことを示すのに使えるかもしれません。
また、FDWが問い合わせで使われる式をローカルで計算するより安価に計算できるなら、それらの式をfdw_scan_tlist
に追加することができます。
結合プラン(GetForeignJoinPaths
が作るパスから作成される)は、それが返す列の集合を記述するfdw_scan_tlist
を必ず提供しなければならないことに注意して下さい。
FDWはそのテーブルの条件句のみに依存するパスを常に少なくとも一つは生成すべきです。結合クエリでは、例えばforeign_variable
=
local_variable
といった結合句に依存するパス(群)を生成することもできます。
そのような句はbaserel->baserestrictinfo
には見つからず、リレーションの結合リストにあるはずです。
そのような句を使用するパスは「パラメータ化されたパス」と呼ばれます。
このようなパスでは、選択された結合句(群)で使用されているリレーション(群)をparam_info
の適合する値から特定しなければなりません;その値を計算するにはget_baserel_parampathinfo
を使用します。
GetForeignPlan
では、結合句のlocal_variable
部分がfdw_exprs
に追加され、実行時には通常の条件句と同じように動作します。
FDWがリモートでの結合をサポートする場合、GetForeignPaths
がベーステーブルに対して処理するのとほぼ同じように、GetForeignJoinPaths
は潜在的なリモートの結合に対してForeignPath
を生成することになります。
意図した結合に関する情報は、上記と同じ方法でGetForeignPlan
に送ることができます。
しかし、baserestrictinfo
は結合のリレーションには関連がなく、代わりに、特定の結合に関連するJOIN句はGetForeignJoinPaths
に別のパラメータ(extra->restrictlist
)として渡されます。
FDWはグルーピングや集約のような、スキャンや結合のレベルより上位のプラン動作の直接実行を追加的にサポートできるかもしれません。
このような方法を行うには、FDWはパスを生成して、それを適切な上位リレーションに挿入する必要があります。
例えば、リモート集約をあらわすパスはadd_path
を使ってUPPERREL_GROUP_AGG
リレーションに挿入されるべきです。
このパスは外部リレーションに対する単純なスキャンパスを読むことによるローカル集約実行とコストに基づいて比較されます(このようなパスが提供されなければならないことに注意してください、さもないとプラン時にエラーになります)。
リモート集約パスが、通常そうなりますが、勝った場合には、パスはGetForeignPlan
を呼ぶ通常の手段でプランに変換されます。
もし問い合わせの全てのベースリレーションが同じFDWから来るなら、このようなパスを生成するのに推奨される場所は、各上位リレーション(すなわち各スキャン/結合後の処理の段階)に対して呼び出されるGetForeignUpperPaths
コールバック関数の中です。
PlanForeignModify
と55.2.4. 外部テーブル更新のためのFDWルーチンで記述された他のコールバックは、外部リレーションは通常の方法でスキャンされ、それから個別の行変更がローカルのModifyTable
プランノードで駆動されるという想定をもとに設計されています。
この方法は変更が外部テーブルと同様にローカルテーブルを読む必要がある一般的な場合に必要です。
しかしながら、操作が全体的に外部サーバで実行できるなら、FDWはそのようにするパスを生成してUPPERREL_FINAL
上位リレーションに挿入することができます。ここではModifyTable
方式に対して競合します。
この方式は、55.2.5. 行ロックのためのFDWルーチンで記述された行ロックコールバックを使うのでなしに、リモートSELECT FOR UPDATE
を実装するのにも使われます。
UPPERREL_FINAL
に挿入されたパスは問い合わせの全ての振る舞いの実装に責任があることに留意してください。
UPDATE
やDELETE
のプランを生成しているとき、
PlanForeignModify
とPlanDirectModify
は、事前にスキャンプラン生成関数で作られたbaserel->fdw_private
データを使うために、その外部テーブルのためのRelOptInfo
構造体を検索することができます。
しかしながら、INSERT
では対象テーブルはスキャンされないので対応するRelOptInfo
は存在しません。
PlanForeignModify
から返されるList
には、ForeignScan
プランノードのfdw_private
リストと同様に、copyObject
がコピーの仕方を知っている構造体しか保持してはいけないという制約があります。
ON CONFLICT
句のあるINSERT
は競合の対象の指定をサポートしません。
なぜなら、リモートのテーブルの一意制約や排他制約についての情報がローカルにはないからです。
これは結果的にON CONFLICT DO UPDATE
がサポートされないことを意味します。
なぜなら、競合の対象の指定が必須だからです。