FDWハンドラ関数は、以下で説明するコールバックの関数ポインタを含む、pallocされたFdwRoutine構造体を返します。
スキャンに関連した関数は必須で、それ以外は省略可能です。
FdwRoutine構造体はsrc/include/foreign/fdwapi.hで宣言されていますので、追加情報はそちらを参照してください。
void
GetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
外部テーブルのリレーションサイズ見積もりを取得します。
この関数は、ある外部テーブルをスキャンするクエリのプラン作成の開始時に呼び出されます。
rootはそのクエリに関するプランナのグローバル情報です。
baserelはそのテーブルに関するプランナの情報です。
そして、foreigntableidはその外部テーブルのpg_class OIDです。
(foreigntableidはプランナデータ構造体からも取得できますが、手間を省くために明示的に渡されます。)
この関数は、検索条件によるフィルタリングも考慮に入れた、そのテーブルスキャンが返すと見込まれる件数にbaserel->rowsを更新するべきです。
baserel->rowsの初期値は固定のデフォルト見積もりなので、可能な限り置き換えられるべきです。この関数は、行の幅のよりよい見積もりを計算できるのであれば、baserel->widthを更新することも選択出来ます。
追加情報については57.4を参照してください。
void
GetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
外部テーブル対するスキャンとして可能なアクセスパスを作成します。
この関数はクエリのプラン作成中に呼び出されます。
引数は、先に呼ばれているGetForeignRelSizeと同じです。
この関数は、少なくとも一つのアクセスパス(ForeignPathノード)を作成して、それぞれのパスをbaserel->pathlistに追加するためにadd_pathを呼ばなければなりません。
ForeignPathノードを構築するにはcreate_foreignscan_pathを使うことが推奨されています。
この関数は、たとえばソート済みの結果を表現する有効なpathkeysを持つパスのような複数のアクセスパスを作成することが出来ます。
それぞれのアクセスパスはコスト見積もりを含まねばならず、また意図した特定のスキャン方式を識別するのに必要なFDW固有の情報を持つことが出来ます。
追加情報については57.4を参照してください。
ForeignScan *
GetForeignPlan(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
ForeignPath *best_path,
List *tlist,
List *scan_clauses,
Plan *outer_plan);
選択された外部アクセスパスからForeignScanプランノードを作成します。
この関数はクエリプラン作成の最後に呼び出されます。
引数は、GetForeignRelSizeと同じものに、選択されたForeignPath(事前にGetForeignPaths、GetForeignJoinPathsまたはGetForeignUpperPathsによって作成されたもの)、そのプランノードによって出力されるターゲットリスト、そのプランノードで強制される条件句、およびRecheckForeignScanが実行する再検査で使用されるForeignScanの外側のサブプランが追加されます。
(パスがベースリレーションではなく結合のためのものの場合、foreigntableidはInvalidOidになります。)
この関数はForeignScanプランノードを作成して返さなければなりません。ForeignScanノードを構築するにはmake_foreignscanを使うことが推奨されています。
追加情報については57.4を参照してください。
void
BeginForeignScan(ForeignScanState *node,
int eflags);
外部テーブルスキャンの実行を開始します。
この関数はエクゼキュータの起動中に呼び出されます。
スキャンを開始できるようになる前に、あらゆる必要な初期化を実行するべきですが、実際のスキャンの実行を始めるべきではありません(それは最初のIterateForeignScan呼び出しにおいて行われるべきです)。
ForeignScanStateノードは作成されていますが、そのfdw_stateフィールドはNULLのままです。
スキャンするテーブルの情報は、ForeignScanStateノード(特に、その先にあるGetForeignPlanから提供されたFDWプライベート情報を含む、ForeignScanプランノード)を通じてアクセス可能です。
eflagsは、このプランノードに関するエクゼキュータの操作モードを表すフラグビットを含みます。
(eflags & EXEC_FLAG_EXPLAIN_ONLY)が真の場合、この関数は外部に見える処理を実行すべきではないことに注意してください。
ExplainForeignScanやEndForeignScan用にノード状態を有効にするのに必要とされる最小限のことだけをすべきです。
TupleTableSlot * IterateForeignScan(ForeignScanState *node);
外部ソースから一行を取り出して、それをタプルテーブルスロットに入れて返します(この用途にはノードのScanTupleSlotを使うべきです)。
利用可能な行がない場合は、NULLを返します。
タプルテーブルスロット機構を使うと、物理タプルと仮想タプルのどちらでも返せます。
ほとんどの場合、パフォーマンスの観点から後者を選ぶのが良いでしょう。
この関数は、呼出しごとにリセットされる短命なメモリコンテキスト内で呼び出されることに注意してください。
より長命なストレージが必要な場合は、BeginForeignScanでメモリコンテキストを作成するか、ノードのEStateに含まれるes_query_cxtを使用してください。
返される行は、ターゲットリストfdw_scan_tlistが提供されたなら、それとマッチしなければならず、提供されていない場合はスキャンされている外部テーブルの行型とマッチしなければなりません。
不要な列を取り出さないように最適化することを選ぶなら、それらの列の位置にNULLを入れるか、あるいはそれらの列を除いたfdw_scan_tlistリストを生成するべきです。
PostgreSQLのエクゼキュータは返された行が外部テーブルに定義された制約に違反しているかどうかは気にしません。 しかし、プランナはそれに着目するので、宣言された制約に反する行が外部テーブル上にあった場合に、不正な最適化をするかもしれません。 ユーザが制約が成り立つと宣言したのに制約に違反した場合は(データ型が一致しなかった場合にする必要があるのと同様に)エラーを発生させるのが適切でしょう。
void ReScanForeignScan(ForeignScanState *node);
先頭からスキャンを再開します。 スキャンが依存するいずれかのパラメータが値を変更しているかもしれないので、新しいスキャンが必ずしも厳密に同じ行を返すとは限らないことに注意してください。
void EndForeignScan(ForeignScanState *node);
スキャンを終了しリソースを解放します。 通常、pallocされたメモリを解放することは重要ではありませんが、たとえば開いたファイルやリモートサーバへの接続などはクリーンアップするべきです。
FDWが外部テーブルの結合を(両方のテーブルのデータをフェッチして、ローカルで結合するのでなく)リモートで実行することをサポートする場合、次のコールバック関数を提供します。
void
GetForeignJoinPaths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra);
同じ外部サーバにある2つ(またはそれ以上)の外部テーブルの結合のための可能なアクセスパスを作成します。
このオプション関数は、問い合わせの計画時に呼び出されます。
GetForeignPathsと同じく、この関数は提供されたjoinrelのためのForeignPathパスを生成し、add_pathを呼んで、それらのパスを結合のために考慮されるパスの集合に追加します。
しかし、GetForeignPathsとは異なり、この関数が少なくとも1つのパスの作成に成功することは必要ではありません。
なぜなら、ローカルの結合を含んだパスはいつでも可能だからです。
この関数は、同じ結合のリレーションに対して、内側と外側のリレーションの異なる組み合わせで繰り返し呼び出されることに注意して下さい。 同じ作業の繰り返しを最小化することはFDWの責任です。
ForeignPathパスが結合のために選択されると、それは結合プロセス全体を代表することになり、構成テーブルとその関連の結合のために生成されたパスは使われなくなります。
結合パスの以降の処理は、単一の外部テーブルをスキャンするパスとほぼ同様に進みます。
1つの相違点は、結果として作られるForeignScan計画ノードのscanrelidが0にセットされるべき、ということで、これはそれが表現する単一のリレーションがないためです。
その代わりに、ForeignScanノードのfs_relidsフィールドが結合されるリレーションの集合を表します。
(後者のフィールドはコアのプランナのコードによって自動的にセットされるので、FDWによって設定される必要はありません。)
他の相違点は、リモートの結合についての列リストがシステムカタログにはないため、FDWはfdw_scan_tlistに適切なTargetEntryノードのリストを入れて、実行時に返されるタプル内の列の集合を表すようにしなければならないということです。
追加情報については57.4を参照してください。
FDWがリモート集約など、リモートでのスキャン/結合後の処理をサポートする場合、次のコールバック関数を提供します。
void
GetForeignUpperPaths(PlannerInfo *root,
UpperRelationKind stage,
RelOptInfo *input_rel,
RelOptInfo *output_rel,
void *extra);
上位リレーション処理のための、ありうるアクセスパスを作成します。上位リレーションはプランナ用語で、ウィンドウ関数、ソート、テーブル更新など、全てのスキャン/結合後の問い合わせのことです。
この省略可能な関数は問い合わせのプラン作成時に呼ばれます。
今のところ、これは問い合わせに含まれる全てのベースリレーションが同じFDWに属する場合だけ呼ばれます。
この関数では、FDWがどのようにリモートで実行するか分かっている全てのスキャン/結合後の処理にForeignPathパスを生成し、それらパスを指定された上位リレーションに加えるためにadd_pathを呼び出してください。
GetForeignJoinPathsの時と同様に、この関数が何らかのパス作成に成功する必要はありません。なぜなら、ローカル処理を含んでいるパスはいつでも可能だからです。
stageパラメータはどのスキャン/結合後の処理が現在考慮されているかを定めます。
output_relは本処理の計算方法をあらわすパスを受け取るであろう上位リレーションで、input_relは本処理への入力をあらわすリレーションです。
extraパラメータは追加の詳細を指定し、今のところUPPERREL_PARTIAL_GROUP_AGGかUPPERREL_GROUP_AGGが指定でき、GroupPathExtraData構造体へのポインタになります。
(注意:これらの処理は外部で実行されると考えられるため、output_relに加えられるForeignPathパスは、一般的にinput_relのパスへの直接の依存を全く持たないでしょう。
しかしながら、手前の処理段階のために以前に生成されたパスを検査することは、冗長なプラン作成活動を回避するのに役立ちます。)
追加情報については57.4を参照してください。
もしFDWが更新可能な外部テーブルをサポートする場合、FDWのニーズと能力に応じて、以下のコールバック関数の一部または全てを提供する必要があります。
void
AddForeignUpdateTargets(Query *parsetree,
RangeTblEntry *target_rte,
Relation target_relation);
UPDATEとDELETEの操作は、テーブルスキャン関数によって事前にフェッチされた行に対して実行されます。
FDWは、更新や削除の対象行を厳密に識別できるように行IDや主キー列の値といった追加情報を必要とするかもしれません。
それをサポートするために、この関数はUPDATEやDELETEの間に外部テーブルから取得される列のリストに追加の隠された(または「ジャンクの」)ターゲット列を追加することができます。
これを実行するには、フェッチする追加の値の式を含むTargetEntryエントリをparsetree->targetListに追加します。
それぞれのエントリはresjunk = trueとマークされなければならず、また実行時にエントリを識別できる異なるresnameを持つ必要があります。
コアシステムがそのような名前のジャンク列を生成できるように、ctidやNwholerow、wholerowと一致する名前は使用しないでください。
N
追加の式が単純な(Var型の)変数よりも複雑な場合は、それらをtargetlistに追加する前にeval_const_expressionsを実行する必要があります。
この関数はプラン生成中に呼び出されますが、提供される情報は他のプラン生成ルーチンで使用できる情報とは少し異なります。
parsetreeはUPDATEやDELETEコマンドの解析ツリーで、target_rteとtarget_relationは対象の外部テーブルを表します。
もしAddForeignUpdateTargetsポインタがNULLに設定されている場合は、追加のターゲット式は追加されません。
(FDWが行を識別するのに不変の主キーに依存するのであればUPDATEは依然として実現可能かもしれませんが、DELETE操作を実装することは不可能になるでしょう。)
List *
PlanForeignModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
外部テーブルに対する挿入、更新、削除に必要となる、追加のプラン生成アクションを実行します。
この関数は、更新処理を実行するModifyTableプランノードに追加されるFDW固有の情報を生成します。この固有情報はList形式でなければならず、また実行段階の間にBeginForeignModifyに渡されます。
rootはそのクエリに関するプランナのグローバル情報です。
planはfdwPrivListsフィールドを除いて完成しているModifyTableプランノードです。
resultRelationは対象の外部テーブルをレンジテーブルの添字で識別します。
subplan_indexはModifyTableプランノードの対象がどれであるかを0始まりで識別します。この情報はplan->plansなどのplanの下位構造を指定したい場合に使用してください。
追加情報は57.4を参照してください。
もしPlanForeignModifyポインタがNULLに設定されている場合は、追加のプラン作成時処理は実行されず、BeginForeignModifyに渡されるfdw_privateリストはNILになります。
void
BeginForeignModify(ModifyTableState *mtstate,
ResultRelInfo *rinfo,
List *fdw_private,
int subplan_index,
int eflags);
外部テーブルへの変更操作の実行を開始します。
このルーチンはエクゼキュータの起動中に呼び出されます。
実際のテーブル変更に先立って必要なあらゆる初期化処理を実行する必要があります。
その後、各タプルが挿入、更新、削除されるようにExecForeignInsert、ExecForeignUpdate、ExecForeignDeleteのいずれかが呼ばれます。
mtstateは実行されているModifyTableプランノード全体の状態です。プランに関する全般的なデータと実行状態はこの構造体経由で利用可能です。
rinfoは対象の外部テーブルを表すResultRelInfo構造体です。
(ResultRelInfoのri_FdwStateフィールドはこの操作で必要となる固有の状態をFDWが格納するのに利用できます。)
fdw_privateは、もしあればPlanForeignModifyで生成された固有データを含みます。
subplan_indexは、これがModifyTableプランノードのどのターゲットであるかを識別します。
eflagsは、このプランノードに関するエクゼキュータの操作モードを表すフラグビットを含みます。
(eflags & EXEC_FLAG_EXPLAIN_ONLY)が真の場合、この関数は外部に見える処理を実行すべきではないことに注意してください。
ExplainForeignModifyやEndForeignModify用にノード状態を有効にするのに必要な最小限のことだけを実行するべきです。
もしBeginForeignModifyポインタがNULLに設定されている場合は、エクゼキュータ起動時には追加処理は何も実行されません。
TupleTableSlot *
ExecForeignInsert(EState *estate,
ResultRelInfo *rinfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
外部テーブルにタプルを一つ挿入します。
estateはそのクエリのグローバルな実行状態です。
rinfoは対象の外部テーブルを表すResultRelInfo構造体です。
slotには挿入されるタプルが含まれます。その行型定義は外部テーブルと一致します。
planSlotにはModifyTableプランノードのサブプランが生成したタプルが含まれます。追加の「ジャンク」列を含みうる点において、slotとは異なります。(planSlotは一般的にINSERTのケースにおいてはそれほど意味を持ちませんが、完全性のために提供されます。)
戻り値は実際に挿入されたデータ(例えばトリガー処理の結果などにより、提供されたデータとは異なるかもしれません)を含むスロットか、または(こちらも一般的にトリガーの結果)実際には挿入されなかった場合はNULLです。
渡されたslotはこの用途に再利用可能です。
返却されたスロット内のデータはINSERTクエリがRETURNING句を持っていた場合もしくは外部テーブルがAFTER ROWトリガを持っていた場合にのみ使われます。
トリガは全列を必要としますが、FDWはRETURNING句の内容に応じて返却する列を一部にするかすべてにするかを最適化する余地があります。
それとは関係なく、処理成功を表すためになんらかのスロットは返却しなければなりません。さもないと、報告されるクエリの結果行数が誤った値になってしまいます。
もしExecForeignInsertポインタがNULLに設定されている場合は、外部テーブルへの挿入の試みはエラーメッセージとともに失敗します。
この関数は外部テーブルパーティションに転送対象のタプルを挿入する際、あるいはCOPY FROMを外部テーブルに対して実行する際にも呼び出されることに注意してください。
COPY FROMの場合、INSERTとはこの関数の呼び出され方は異なります。
FDWがそれをサポートすることを可能にする以下で説明するコールバック関数をご覧ください。
TupleTableSlot *
ExecForeignUpdate(EState *estate,
ResultRelInfo *rinfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
外部テーブル内のタプルを一つ更新します。
estateはそのクエリのグローバルな実行状態です。
rinfoは対象の外部テーブルを表すResultRelInfo構造体です。
slotにはタプルの新しいデータが含まれます。その行型定義は外部テーブルと一致します。
planSlotにはModifyTableプランノードのサブプランが生成したタプルが含まれます。追加の「ジャンク」列を含みうる点において、slotとは異なります。実際、AddForeignUpdateTargetsが要求するジャンク列はこのスロットから利用可能です。
戻り値は実際に更新されたデータ(例えばトリガー処理の結果などにより、提供されたデータとは異なるかもしれません)を含むスロットか、または(こちらも一般的にトリガーの結果)実際には更新されなかった場合はNULLです。
渡されたslotはこの用途に再利用可能です。
返却されたスロット内のデータはUPDATEクエリがRETURNING句を持っていた場合もしくは外部テーブルがAFTER ROWトリガを持っていた場合にのみ使われます。
トリガは全列を必要としますが、FDWはRETURNING句の内容に応じて返却する列を一部にするか全てにするかを最適化する余地があります。
それとは関係なく、処理成功を表すためになんらかのスロットは返却しなければなりません。さもないと、報告されるクエリの結果行数が誤った値になってしまいます。
もしExecForeignUpdateポインタがNULLに設定されている場合は、外部テーブルへの更新の試みはエラーメッセージとともに失敗します。
TupleTableSlot *
ExecForeignDelete(EState *estate,
ResultRelInfo *rinfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
外部テーブルからタプルを一つ削除します。
estateはそのクエリのグローバルな実行状態です。
rinfoは対象の外部テーブルを表すResultRelInfo構造体です。
slotにはタプルの新しいデータが含まれます。その行型定義は外部テーブルと一致します。
planSlotにはModifyTableプランノードのサブプランが生成したタプルが含まれます。実際、AddForeignUpdateTargetsが要求するジャンク列はこのスロットが運びます。ジャンク列は削除されるタプルを識別するために使用しなければなりません。
戻り値は実際に削除されたデータを含むスロットか、または(一般的にトリガーの結果)実際には削除されなかった場合はNULLです。
渡されたslotは返却するタプルを保持する用途に利用可能です。
返却されたスロット内のデータはDELETEクエリがRETURNING句を持っていた場合もしくは外部テーブルがAFTER ROWトリガを持っていた場合にのみ使われます。
トリガは全列を必要としますが、FDWはRETURNING句の内容に応じて返却する列を一部にするか全てにするかを最適化する余地があります。
それとは関係なく、処理成功を表すためになんらかのスロットは返却しなければなりません。さもないと、報告されるクエリの結果行数が誤った値になってしまいます。
もしExecForeignDeleteポインタがNULLに設定されている場合は、外部テーブルからの削除の試みはエラーメッセージとともに失敗します。
void
EndForeignModify(EState *estate,
ResultRelInfo *rinfo);
テーブル更新を終えてリソースを解放します。 通常、pallocされたメモリを解放することは重要ではありませんが、たとえば開いたファイルやリモートサーバへの接続などはクリーンアップするべきです。
もしEndForeignModifyポインタがNULLに設定されている場合は、エクゼキュータ終了時には追加処理は何も実行されません。
INSERTあるいはCOPY FROMでパーティション化テーブルに挿入されたタプルはパーティションに転送されます。
FDWが外部テーブルのパーティションへの転送をサポートしているなら、以下のコールバック関数も提供すべきです。
これらの関数は、外部テーブルでCOPY FROMが実行された時に呼び出されます。
void
BeginForeignInsert(ModifyTableState *mtstate,
ResultRelInfo *rinfo);
外部テーブルへの挿入操作の実行を開始します。
このルーチンは、タプル転送のためにパーティションが選択された場合か、COPY FROMコマンドでターゲットが指定された場合に、最初の行が外部テーブルに挿入される直前に呼び出されます。
この関数は、実際の挿入に先立つすべての必要な初期化を実行すべきです。
続いて、ExecForeignInsertが外部テーブルに挿入される個々のタブル毎に呼び出されます。
mtstateは、実行中のModifyTableプランノードの全体的な状態です。
プランのグローバルデータと実行状態がこの構造体を通じて得られます。
rinfoはResultRelInfo構造体で、ターゲットの外部テーブルを記述します。
(この操作中に必要なFDWのプライベート状態を保存するためにResultRelInfoのri_FdwStateフィールドが利用可能です。)
この関数がCOPY FROMコマンドで呼ばれると、外部テーブルがタプル転送で選択された対象なのか、あるいはコマンドがターゲットを指定したのかに関わらず、mtstate中のプランに関係するグローバルデータは提供されず、次に個々の挿入されるタプルに対して呼び出されるExecForeignInsertのplanSlotパラメータはNULLとなります。
BeginForeignInsertポインターがNULLなら、初期化処理は実施されません。
FDWが外部テーブルパーティションのタプル転送をサポートしていないか、または外部テーブルに対してCOPY FROMの実行をサポートしていないか、あるいはその両方なら、この関数あるいは以後呼ばれたExecForeignInsertは、必ず必要なだけエラーを引き起こします。
void
EndForeignInsert(EState *estate,
ResultRelInfo *rinfo);
挿入操作を終了してリソースを解放します。 通常、pallocされたメモリを解放することは重要ではありませんが、たとえば開いたファイルやリモートサーバへの接続などはクリーンアップするべきです。
EndForeignInsertポインターがNULLなら、終了処理は実施されません。
int IsForeignRelUpdatable(Relation rel);
指定された外部テーブルがどの更新処理をサポートしているかを報告します。
戻り値は、その外部テーブルがサポートする操作を表すルールイベント番号のビットマスクである必要があります。
UPDATE用の(1 << CMD_UPDATE) = 4、INSERT用の(1 << CMD_INSERT) = 8、DELETE用の(1 << CMD_DELETE) = 16といったCmdType列挙値を使います。
もしIsForeignRelUpdatableポインタがNULLに設定されていると、外部テーブルはExecForeignInsert、ExecForeignUpdate、ExecForeignDeleteを提供していると、それぞれ挿入、更新、削除をサポートしていると判断します。
この関数は、FDWが一部のテーブルについてのみ更新をサポートする場合にのみ必要です。
(そのような場合でも、この関数でチェックする代わりにクエリ実行関数でエラーにしても構いません。しかしながら、この関数はinformation_schemaのビューの表示で更新可否を判定するのに使用されます。)
外部テーブルへの挿入、更新、削除は、代替インタフェース一式を実装することで最適化できます。 通常の挿入、更新、削除のインタフェースは行をリモートサーバから取得し、その後、それらの行を一つずつ変更します。 一部の場合にはこの一行ごとのやり方は必要ですが、非効率とも言えます。 外部サーバについて行が本当はそれらを引き出すことなしに変更されるべきと判断できて、操作に影響を与えるローカルトリガーが無いならば、操作全体がリモートサーバで実行されるように計画することができます。 以下に示すインタフェースはこれを可能にします。
bool
PlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
リモートサーバ上で直接変更を実行することが安全かを判断します。
そうであれば、そのために必要なプラン作成の動作を実行した後にtrueを返します。
さもなくば、falseを返します。
この省略可能な関数は問い合わせのプラン作成時に呼ばれます。
この関数が成功すると、BeginDirectModify、IterateDirectModify、EndDirectModifyが実行段階で代わりに呼び出されます。
成功しなければ、テーブルの変更は前述のテーブル更新関数を使って実行されます。
パラメータはPlanForeignModifyに対するものと同じです。
リモートサーバで直接変更を実行するには、本関数は対象サブプランをリモートサーバ上で直接変更するForeignScanプランノードで書き換えしなければなりません。
ForeignScanのoperationフィールドにはCmdType列挙値を適切に、すなわち、UPDATEにはCMD_UPDATE、INSERTにはCMD_INSERT、DELETEにはCMD_DELETEを設定しなければいけません。
追加情報は57.4を参照してください。
PlanDirectModifyポインタにNULLが設定されている場合、
リモートサーバでの直接変更の実行は試みられません。
void
BeginDirectModify(ForeignScanState *node,
int eflags);
リモートサーバでの直接変更を実行する準備をします。
この関数はエクゼキュータが開始するときに呼び出されます。
この関数は(最初のIterateDirectModify呼び出しで実行されるであろう)直接変更より前に必要とされる全ての初期化を実行するべきです。
ForeignScanStateノードはすでに作られていますが、fdw_stateがフィールドはまだNULLです。
変更するテーブルに関する情報はForeignScanStateノードを通して(具体的にはPlanDirectModifyで提供されるFDWプライベート情報を含む、元となるForeignScanプランノードから)入手可能です。
eflagsは、このプランノードに関するエクゼキュータの操作モードを表すフラグビットを含みます。
(eflags & EXEC_FLAG_EXPLAIN_ONLY)が真の場合、この関数は外部に見える処理を実行すべきではないことに注意してください。
ExplainDirectModifyやEndDirectModify用にノード状態を有効にするのに必要な最小限のことだけを実行するべきです。
BeginDirectModifyポインタがNULLに設定されている場合、リモートサーバでの直接変更の実行は試みられません。
TupleTableSlot * IterateDirectModify(ForeignScanState *node);
INSERT、UPDATE、または、DELETEの問い合わせがRETURNING句を持たないときには、リモートサーバでの直接変更の後、単にNULLが返ります。
問い合わせがRETURNING句を持つときには、RETURNING計算に必要なデータを含む結果を一つ取り出し、タプルテーブルスロットでそれを返します(この用途にはノードのScanTupleSlotを使うべきです)。
実際に挿入、更新、削除されたデータはノードのEStateのes_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantupleに格納されなければなりません。
有効な行がそれ以上なければNULLを返します。
これは呼び出しの間でリセットされる寿命の短いメモリコンテキストで呼び出されることに注意してください。
より長命な格納場所を必要とするなら、BeginDirectModifyでメモリコンテキストを作るか、ノードのEStateのes_query_cxtを使ってください。
返される行は、ターゲットリストfdw_scan_tlistが提供されたなら、それとマッチしなければならず、提供されていない場合は変更されている外部テーブルの行型とマッチしなければなりません。
RETURNING計算に不要な列を取り出さないように最適化することを選ぶなら、それらの列の位置にNULLを入れるか、あるいはそれらの列を除いたfdw_scan_tlistリストを生成するべきです。
問い合わせがRETURNING句をもつかどうかによらず、問い合わせが報告する行数はFDW自身によって増加されなければなりません。
問い合わせがRETURNING句を持たないときも、FDWはEXPLAIN ANALYZEの場合のForeignScanState nodeむけに行数を増加させなければなりません。
IterateDirectModifyポインタがNULLに設定されている場合、リモートサーバでの直接変更の実行は試みられません。
void EndDirectModify(ForeignScanState *node);
リモートサーバでの直接変更の後、クリーンアップします。 通常、pallocされたメモリを解放することは重要ではありませんが、たとえば開いたファイルやリモートサーバへの接続などはクリーンアップするべきです。
EndDirectModifyポインタがNULLに設定されている場合、リモートサーバでの直接変更の実行は試みられません。
FDWが(57.5で説明される)遅延行ロックをサポートする場合は、以下のコールバック関数を提供する必要があります。
RowMarkType
GetForeignRowMarkType(RangeTblEntry *rte,
LockClauseStrength strength);
行の印付けでどのオプションを外部テーブルに使うかを報告します。
rteはテーブルのRangeTblEntryノードで、strengthは関連するFOR UPDATE/SHARE句があれば、それが要求するロックの強さを表します。
その結果は、RowMarkType列挙型のメンバーでなければなりません。
この関数はUPDATE、DELETE、SELECT FOR UPDATE/SHAREの問い合わせに現れ、かつUPDATEあるいはDELETEの対象ではない各外部テーブルについて、問い合わせの計画時に呼び出されます。
GetForeignRowMarkTypeのポインタがNULLに設定されていると、必ずROW_MARK_COPYオプションが使われます。
(これはRefetchForeignRowが決して呼び出されないので、それを提供する必要もない、ということを意味します。)
さらなる情報については57.5を参照してください。
HeapTuple
RefetchForeignRow(EState *estate,
ExecRowMark *erm,
Datum rowid,
bool *updated);
必要ならロックした後で、外部テーブルから1つのタプルを再フェッチします。
estateは問い合わせのグローバルな実行状態です。
ermは対象の外部テーブルおよび獲得する行ロックの種別(あれば)を記述するExecRowMark構造体です。
rowidはフェッチするタプルを特定するものです。
updatedは出力パラメータです。
この関数は、フェッチしたタプルをpallocして複製したものを返すか、あるいは行ロックが取得できなければNULLを返します。
獲得する行ロックの種別はerm->markTypeで指定されますが、この値は事前にGetForeignRowMarkTypeから返されたものです。
(ROW_MARK_REFERENCEは行のロックを獲得せずに、単にタプルを再フェッチすることを意味し、また、ROW_MARK_COPYはこのルーチンで使われることはありません。)
そして、*updatedはフェッチしたタプルが、以前に取得したものと同じではなく、更新されたバージョンであったときにtrueにセットされます。
(どちらなのかFDWが判断できない場合は、trueを返すことが推奨されます)。
デフォルトでは、行ロックの獲得に失敗したときはエラーを発生させるべきであることに注意してください。
NULLを返すのが適切なのは、erm->waitPolicyでSKIP LOCKEDオプションが指定されている場合だけです。
rowidは、再フェッチする行を以前読んだ時のctid値です。
rowid値はDatumとして渡されますが、現在はtidにしかなりません。
将来は行ID以外のデータ型が可能になることを期待して、関数APIとすることが選択されました。
RefetchForeignRowポインタがNULLの場合、行を再フェッチする試みは失敗し、エラーメッセージを発行します。
さらなる情報については57.5を参照してください。
bool
RecheckForeignScan(ForeignScanState *node,
TupleTableSlot *slot);
以前に戻されたタプルが、関連するスキャンおよび結合の制約とまだ一致しているか再検査し、更新されたバージョンのタプルを提供する場合もあります。
結合のプッシュダウンを行わない外部データラッパでは、通常は、これをNULLにセットし、代わりにfdw_recheck_qualsを適切にセットする方が便利でしょう。
しかし、外部結合をプッシュダウンする場合、すべてのベーステーブルに関する検査を結果のタプルに適用するだけでは、たとえすべての必要な属性がそこにあったとしても十分ではありません。
なぜなら一部の制約が一致しないことで、タプルが戻されない代わりに、一部の属性がNULLになってしまうかもしれないからです。
RecheckForeignScan制約を再検査し、それが依然として満たされていれば真を、そうでなければ偽を返すことができます。
それだけでなく、置換されたタプルを提供されたスロットに格納することもできます。
結合のプッシュダウンを実装する場合、外部データラッパは通常、再検査のためだけに使用される代替のローカル結合プランを構築します。
これがForeignScanの外部サブプランとなります。
再検査が必要な時は、このサブプランを実行して、結果のタプルをスロットに格納することができます。
どのベーステーブルも最大で1行しか返さないので、このプランは効率的である必要はありません。
例えば、すべての結合をネステッドループで実装することもできます。
関数GetExistingLocalJoinPathは、存在するパスから代替ローカルの結合プランとして使用可能な適当なローカル結合パスを検索するのに使われるかもしれません。
GetExistingLocalJoinPathは指定された結合リレーションのパスリストのパラメータ化されていないパスを検索します。
(そのようなパスが見つからなかった場合はNULLを返します。この場合、外部データラッパはそれ自身によりローカルパスを構築するかもしれず、あるいは、その結合むけのアクセスパスを作らないことを選択するかもしれません。)
EXPLAINのためのFDWルーチン
void
ExplainForeignScan(ForeignScanState *node,
ExplainState *es);
外部テーブルスキャンの追加のEXPLAIN出力を表示します。
EXPLAIN出力にフィールドを追加するためにExplainPropertyTextや関連する関数を呼び出すことができます。
esの中のフラグフィールドは何を表示するかを決めるのに使用できます。また、EXPLAIN ANALYZEの場合には、実行時統計情報を提供するためにForeignScanStateノードの状態を調べることができます。
もしExplainForeignScanポインタがNULLに設定されている場合は、EXPLAIN中に追加情報は表示されません。
void
ExplainForeignModify(ModifyTableState *mtstate,
ResultRelInfo *rinfo,
List *fdw_private,
int subplan_index,
struct ExplainState *es);
外部テーブル更新の追加のEXPLAIN出力を表示します。
EXPLAIN出力にフィールドを追加するためにExplainPropertyTextや関連する関数を呼び出すことができます。
esの中のフラグフィールドは何を表示するかを決めるのに使用できます。また、EXPLAIN ANALYZEの場合には、実行時統計情報を提供するためにForeignScanStateノードの状態を調べることができます。
最初の4つの引数はBeginForeignModifyと同じです。
もしExplainForeignModifyポインタがNULLに設定されている場合は、EXPLAIN中に追加情報は表示されません。
void
ExplainDirectModify(ForeignScanState *node,
ExplainState *es);
リモートサーバでの直接変更について追加EXPLAIN出力を表示します。
この関数はEXPLAIN出力にフィールドを加えるためにExplainPropertyTextと関連の関数を呼ぶことができます。
esの中のフラグフィールドは何を表示するかを決めるのに使用できます。また、EXPLAIN ANALYZEの場合には、実行時統計情報を提供するためにForeignScanStateノードの状態を調べることができます。
ExplainDirectModifyポインタがNULLに設定されている場合は、EXPLAIN中に追加情報は表示されません。
ANALYZEのためのFDWルーチン
bool
AnalyzeForeignTable(Relation relation,
AcquireSampleRowsFunc *func,
BlockNumber *totalpages);
この関数はANALYZEが外部テーブルに対して実行されたときに呼び出されます。
もしFDWがこの外部テーブルの統計情報を収集できる場合は、そのテーブルからサンプル行を集める関数のポインタとページ単位でのテーブルサイズの見積もりをそれぞれfuncとtotalpagesに渡しtrueを返す必要があります。
そうでない場合は、falseを返します。
もしFDWが統計情報の収集をどのテーブルについてもサポートしない場合は、AnalyzeForeignTableポインタをNULLにすることもできます。
もし提供される場合は、サンプル収集関数はこのようなシグネチャを持つ必要があります。
int
AcquireSampleRowsFunc(Relation relation,
int elevel,
HeapTuple *rows,
int targrows,
double *totalrows,
double *totaldeadrows);
最大targrows行のランダムサンプルをテーブルから収集し、呼び出し元が提供するrows配列に格納する必要があります。
実際に収集された行の数を返す必要があります。
さらに、テーブルに含まれる有効行と不要行の合計数の見積もりを出力パラメータのtotalrowsとtotaldeadrowsに返す必要があります。(もしFDWが不要行という概念を持たない場合はtotaldeadrowsを0に設定してください。)
IMPORT FOREIGN SCHEMAのためのFDWルーチン
List * ImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);
外部テーブル作成コマンドのリストを取得します。 この関数はIMPORT FOREIGN SCHEMAを実行する時に呼び出され、その文の解析木と外部サーバが使用するOIDとを渡されます。 C文字列のリストを返し、その各文字列はCREATE FOREIGN TABLEコマンドを含んでいる必要があります。 これらの文字列はコアサーバが解析して実行します。
ImportForeignSchemaStmt構造体において、remote_schemaはリモートスキーマの名前で、そこからテーブルがインポートされます。
list_typeはテーブル名のフィルタ方法を指定します。
ここで、FDW_IMPORT_SCHEMA_ALLはリモートスキーマのすべてのテーブルをインポートすること(この場合、table_listは空にします)、
FDW_IMPORT_SCHEMA_LIMIT_TOはtable_listに列挙されたテーブルだけを含めること、
そしてFDW_IMPORT_SCHEMA_EXCEPTはtable_listに列挙されたテーブルを除外することを意味します。
optionsはインポートのプロセスで使用されるオプションのリストです。
オプションの意味はFDWに依存します。
例えば、FDWは列のNOT NULL属性をインポートするかどうかを定めるオプションを使うことができます。
これらのオプションはFDWがデータベースオブジェクトのオプションとしてサポートするものと何ら関係ある必要はありません。
FDWはImportForeignSchemaStmtのlocal_schemaフィールドを無視しても良いです。
なぜなら、コアサーバは解析されたCREATE FOREIGN TABLEコマンドにその名前を自動的に挿入するからです。
FDWはlist_typeおよびtable_listで指定されるフィルタの実装にも注意する必要はありません。
なぜなら、コアサーバはそれらのオプションによって除外されるテーブルに対して戻されたコマンドをすべて自動的にスキップするからです。
しかし、除外されるテーブルについてコマンドを作成する作業を回避するのは、そもそも役立つことが多いです。
関数IsImportableForeignTable()は指定の外部テーブル名がフィルタを通るかどうかの検査に役立つかもしれません。
FDWがテーブル定義のインポートをサポートしない場合は、ImportForeignSchemaポインタをNULLにセットすることができます。
ForeignScanノードは、オプションとして、パラレル実行をサポートします。
並列ForeignScanは複数プロセスで実行され、全ての協調プロセスにわたって各行が一度だけ返るようにしなければなりません。
これを行うため、プロセスは動的共有メモリの固定サイズチャンクを通して調整をはかることができます。
この共有メモリは全プロセスで同じアドレスに割り当てされることが保証されませんので、ポインタを含まないようにしなければなりません。
以下のコールバックは一般に全て省略可能ですが、パラレル実行をサポートするためには必要です。
bool
IsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
スキャンがパラレルワーカーで実行できるかテストします。 この関数はプランナが並列プランが可能であろうと考えるときだけ呼ばれます。また、そのスキャンにとってパラレルワーカーで実行するのが安全であるとき真を返すべきです。 リモートデータソースがトランザクションのセマンティクスを持つ場合は、一般にあてはまりません。ただし、ワーカーのデータへの接続を何らかの形でリーダーとして同じトランザクション文脈を共有させることができる場合を除きます。
この関数が定義されていない場合、スキャンはパラレルリーダー内で実行しなければならないと想定されます。 真を返すことは、スキャンがパラレルワーカーで実行可能であるだけで、パラレルに実行可能であることを意味するのでは無いことに注意してください。 そのため、この関数を定義することはパラレル実行がサポートされていないときでも役立つ可能性があります。
Size EstimateDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt);
並列操作に必要とされるであろう動的共有メモリ量を推定します。 これは実際に使われる量よりも大きくてよいですが、小さくてはいけません。 戻り値はバイト単位です。 この関数はオプションであり、必要でない場合は省略することができます。 しかし省略された場合、FDWの使用のために共有メモリが割り当てられないため、次の3つの関数も省略しなければなりません。
void
InitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
void *coordinate);
並列処理で必要とされる動的共有メモリを初期化します。
coordinateはEstimateDSMForeignScanの戻り値に等しいサイズの共有メモリ領域へのポインタです。
この関数はオプションであり、必要でない場合は省略することができます。
void
ReInitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
void *coordinate);
外部スキャンプランノードが再スキャンされようとしているときに、並列操作に必要な動的共有メモリーを再初期化します。
この関数はオプションであり、必要でない場合は省略することができます。
ReScanForeignScan関数はローカル状態のみをリセットし、この関数は共有状態のみをリセットすることをお勧めします。
現在、この関数はReScanForeignScanより前に呼び出されますが、その順序に依存しないようにする方が良いでしょう。
void
InitializeWorkerForeignScan(ForeignScanState *node, shm_toc *toc,
void *coordinate);
InitializeDSMForeignScanでリーダーがセットアップした共有状態に基づくパラレルワーカーのローカル状態を初期化します。
この関数はオプションであり、必要でない場合は省略することができます。
void ShutdownForeignScan(ForeignScanState *node);
ノードが完了するまで実行されないことが予想されるときにリソースを解放します。
これはすべてのケースで呼ばれるわけではありません。
EndForeignScanは、この関数が最初に呼び出されなくても呼び出されることがあります。
このコールバックが呼び出された直後に、並列クエリで使用されるDSM(動的共有メモリ)セグメントが破棄されるため、DSMセグメントがなくなる前に何らかのアクションを実行する外部データラッパーがこのメソッドを実装する必要があります。
List *
ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
RelOptInfo *child_rel);
この関数は、child_relで指定された子リレーションの最上位の親によってパラメータ化されたパスを、子リレーションによってパラメータ化されたパスに変換する際に呼び出されます。
この関数はパスをパラメータ化する、あるいはForeignPathのfdw_privateメンバーに保存されている式ノードを変換するために使用されます。
このコールバックは必要に応じて、reparameterize_path_by_child、adjust_appendrel_attrsあるいはadjust_appendrel_attrs_multilevelを呼び出すことができます。