支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1

57.2. 外部資料包裝器回呼常式 #

FDW 處理常式函式會傳回一個 palloc'd FdwRoutine 結構,其中包含指向以下描述的回呼函式的指標。 掃描相關的函式是必要的,其餘的是可選的。

FdwRoutine 結構類型在 src/include/foreign/fdwapi.h 中宣告,請參閱該檔案以取得更多詳細資訊。

57.2.1. 用於掃描外部表格的 FDW 常式 #

void
GetForeignRelSize(PlannerInfo *root,
                  RelOptInfo *baserel,
                  Oid foreigntableid);

取得外部表格的關係大小估計值。 這會在查詢的規劃開始時呼叫,該查詢會掃描外部表格。 root 是規劃器關於查詢的全域資訊;baserel 是規劃器關於此表格的資訊;而 foreigntableid 是外部表格的 pg_class OID。(foreigntableid 可以從規劃器資料結構中取得,但為了節省精力,會明確傳遞它。)

此函式應更新 baserel->rows,使其成為表格掃描傳回的預期資料列數,並考量限制條件限定詞完成的篩選。 baserel->rows 的初始值只是一個常數預設估計值,如果可能,應替換它。 如果函式可以計算出更好的平均結果資料列寬度估計值,它也可以選擇更新 baserel->width。(初始值基於欄位資料類型和上次 ANALYZE 測量的欄位平均寬度值。) 此外,如果函式可以計算出更好的外部表格總資料列計數估計值,則此函式可以更新 baserel->tuples。(初始值來自 pg_classreltuples,它代表上次 ANALYZE 看到的總資料列計數;如果未在此外部表格上執行 ANALYZE,則它將為 -1。)

有關其他資訊,請參閱第 57.4 節

void
GetForeignPaths(PlannerInfo *root,
                RelOptInfo *baserel,
                Oid foreigntableid);

為掃描外部表格建立可能的存取路徑。 這會在查詢規劃期間呼叫。 參數與 GetForeignRelSize 相同,該函數已被呼叫。

此函式必須為掃描外部表格產生至少一個存取路徑(ForeignPath 節點),並且必須呼叫 add_path 以將每個此類路徑新增到 baserel->pathlist。 建議使用 create_foreignscan_path 來建置 ForeignPath 節點。 該函式可以產生多個存取路徑,例如,一個具有有效 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(先前由 GetForeignPathsGetForeignJoinPathsGetForeignUpperPaths 產生)、要由規劃節點發出的目標清單、要由規劃節點強制執行的限制子句,以及 ForeignScan 的外部子規劃,用於 RecheckForeignScan 執行的重新檢查。(如果路徑是聯結而不是基礎關係,則 foreigntableidInvalidOid。)

此函式必須建立並傳回 ForeignScan 規劃節點;建議使用 make_foreignscan 來建置 ForeignScan 節點。

有關其他資訊,請參閱第 57.4 節

void
BeginForeignScan(ForeignScanState *node,
                 int eflags);

開始執行外部掃描。這個函式會在執行器啟動時被呼叫。它應該執行掃描開始前所需的任何初始化動作,但不應該開始執行實際的掃描(實際掃描應該在第一次呼叫 IterateForeignScan 時執行)。ForeignScanState 節點已經被建立,但它的 fdw_state 欄位仍然是 NULL。關於要掃描的表格的資訊可以透過 ForeignScanState 節點存取(特別是從底層的 ForeignScan 計劃節點,該節點包含由 GetForeignPlan 提供的任何 FDW 私有資訊)。eflags 包含描述此計劃節點的執行器操作模式的旗標位元。

請注意,當 (eflags & EXEC_FLAG_EXPLAIN_ONLY) 為 true 時,此函式不應執行任何外部可見的動作;它只應執行使節點狀態對 ExplainForeignScanEndForeignScan 有效所需的最小值。

TupleTableSlot *
IterateForeignScan(ForeignScanState *node);

從外部來源提取一行,並將其返回到元組表格槽 (tuple table slot) 中(應該使用節點的 ScanTupleSlot 來完成此目的)。如果沒有更多列可用,則返回 NULL。元組表格槽基礎架構允許返回物理或虛擬元組;在大多數情況下,從效能的角度來看,後者是更佳的選擇。請注意,此函式是在短暫的記憶體上下文中被呼叫的,該記憶體上下文將在每次呼叫之間被重置。如果您需要更長時間的儲存空間,請在 BeginForeignScan 中建立記憶體上下文,或使用節點的 EStatees_query_cxt

如果提供了 fdw_scan_tlist 目標列表,則返回的列必須與之匹配,否則它們必須與正在掃描的外部表格的列類型匹配。如果您選擇最佳化掉對不需要的列的提取,則應在這些列的位置插入 null,或者生成一個省略了這些列的 fdw_scan_tlist 列表。

請注意,PostgreSQL 的執行器並不關心返回的列是否違反了外部表格上定義的任何約束 — 但規劃器很關心,如果外部表格中存在不滿足已宣告約束的列,則可能會錯誤地最佳化查詢。如果使用者宣告了約束應該為真,但約束卻被違反了,那麼就應該引發一個錯誤(就像在資料類型不匹配的情況下需要做的那樣)。

void
ReScanForeignScan(ForeignScanState *node);

從頭開始重新啟動掃描。請注意,掃描所依賴的任何參數可能已經更改了值,因此新的掃描不一定會返回完全相同的列。

void
EndForeignScan(ForeignScanState *node);

結束掃描並釋放資源。通常釋放 palloc'd 記憶體並不重要,但例如,打開的檔案和到遠端伺服器的連線應該被清理乾淨。

57.2.2. 用於掃描外部聯結的 FDW 常式 #

如果 FDW 支援遠端執行外部聯結(而不是透過提取兩個表格的資料並在本地執行聯結),則它應該提供此回呼函式。

void
GetForeignJoinPaths(PlannerInfo *root,
                    RelOptInfo *joinrel,
                    RelOptInfo *outerrel,
                    RelOptInfo *innerrel,
                    JoinType jointype,
                    JoinPathExtraData *extra);

為屬於同一外部伺服器的兩個(或多個)外部表格的聯結建立可能的存取路徑。這個可選函式在查詢規劃期間被呼叫。與 GetForeignPaths 一樣,此函式應該為提供的 joinrel 生成 ForeignPath 路徑(使用 create_foreign_join_path 來建立它們),並呼叫 add_path 將這些路徑添加到為聯結考慮的路徑集合中。但與 GetForeignPaths 不同的是,此函式不必成功建立至少一條路徑,因為涉及本地聯結的路徑總是可能的。

請注意,對於相同的聯結關係,此函式將被重複調用,並使用內部和外部關係的不同組合;FDW 應盡量減少重複的工作。

另請注意,應用於聯結的聯結子句集合(作為 extra->restrictlist 傳遞)因內部和外部關係的組合而異。為 joinrel 生成的 ForeignPath 路徑必須包含它使用的聯結子句集合,如果該路徑被規劃器選為 joinrel 的最佳路徑,則規劃器將使用它將 ForeignPath 路徑轉換為計劃。

如果為聯結選擇了 ForeignPath 路徑,它將代表整個聯結過程;為組成表格和輔助聯結生成的路徑將不會被使用。聯結路徑的後續處理與掃描單個外部表格的路徑的處理方式非常相似。一個不同之處在於,結果 ForeignScan 計劃節點的 scanrelid 應設定為零,因為沒有單個關係代表它;相反,ForeignScan 節點的 fs_relids 欄位代表已聯結的關係集合。(後者欄位由核心規劃器程式碼自動設定,無需由 FDW 填寫。)另一個不同之處在於,由於無法從系統目錄中找到遠端聯結的列列表,因此 FDW 必須使用適當的 TargetEntry 節點列表填寫 fdw_scan_tlist,表示它在執行時將在返回的元組中提供的列集合。

注意

PostgreSQL 16 開始,fs_relids 包括外部聯結的 rangetable 索引(如果有的話)。新欄位 fs_base_relids 僅包含基本關係索引,因此模擬 fs_relids 的舊語意。

有關其他資訊,請參閱第 57.4 節

57.2.3. 用於規劃掃描後/聯結處理的 FDW 常式 #

如果 FDW 支援執行遠端掃描後/聯結處理(例如遠端聚合),則它應該提供此回呼函式。

void
GetForeignUpperPaths(PlannerInfo *root,
                     UpperRelationKind stage,
                     RelOptInfo *input_rel,
                     RelOptInfo *output_rel,
                     void *extra);

上層關係處理建立可能的存取路徑,這是規劃器對所有掃描後/聯結查詢處理(例如聚合、視窗函式、排序和表格更新)的術語。這個可選函式在查詢規劃期間被呼叫。目前,只有當查詢中涉及的所有基本關係都屬於同一個 FDW 時,才會呼叫它。此函式應該為 FDW 知道如何遠端執行的任何掃描後/聯結處理生成 ForeignPath 路徑(使用 create_foreign_upper_path 來建立它們),並呼叫 add_path 將這些路徑添加到指示的上層關係中。與 GetForeignJoinPaths 一樣,此函式不必成功建立任何路徑,因為涉及本地處理的路徑總是可能的。

stage 參數會識別目前正在考慮的後掃描/連接步驟。output_rel 是接收代表此步驟計算路徑的上層關聯,而 input_rel 則是代表此步驟輸入的關聯。extra 參數提供額外的詳細資訊,目前僅針對 UPPERREL_PARTIAL_GROUP_AGGUPPERREL_GROUP_AGG 進行設定,在這種情況下,它指向一個 GroupPathExtraData 結構;或者針對 UPPERREL_FINAL 進行設定,在這種情況下,它指向一個 FinalPathExtraData 結構。(請注意,添加到 output_relForeignPath 路徑通常不會直接依賴於 input_rel 的路徑,因為它們的處理預計會在外部完成。然而,檢查先前為上一個處理步驟生成的路徑,有助於避免冗餘的規劃工作。)

有關其他資訊,請參閱第 57.4 節

57.2.4. 用於更新外部表格的 FDW 常式 #

如果 FDW 支援可寫入的外部表格,它應該根據 FDW 的需求和功能提供以下部分或全部回呼函式。

void
AddForeignUpdateTargets(PlannerInfo *root,
                        Index rtindex,
                        RangeTblEntry *target_rte,
                        Relation target_relation);

UPDATEDELETE 操作是針對先前由表格掃描函式提取的列執行的。FDW 可能需要額外的資訊,例如列 ID 或主鍵列的值,以確保它可以識別要更新或刪除的確切列。為了支援這一點,此函式可以將額外的隱藏或無用目標列添加到要在 UPDATEDELETE 期間從外部表格檢索的列列表中。

要做到這一點,請構造一個表示您需要的額外值的 Var,並將其連同無用列的名稱一起傳遞給 add_row_identity_var。(如果需要多個列,您可以多次執行此操作。)對於您需要的每個不同的 Var,您必須選擇一個不同的無用列名稱,但除了 varno 欄位之外相同的 Var 可以並且應該共享一個列名稱。核心系統將無用列名稱 tableoid 用於表格的 tableoid 列,ctidctidN 用於 ctidwholerow 用於標記為 vartype = RECORD 的整列 Var,以及 wholerowN 用於 vartype 等於表格宣告列類型的整列 Var。在可以的情況下重複使用這些名稱(規劃器會合併對相同無用列的重複請求)。如果您需要除這些之外的其他種類的無用列,明智的做法可能是選擇以您的擴充名稱為前綴的名稱,以避免與其他 FDW 發生衝突。

如果 AddForeignUpdateTargets 指標設定為 NULL,則不會添加額外的目標表達式。(這將使 DELETE 操作無法實現,但如果 FDW 依賴於不變的主鍵來識別列,則 UPDATE 可能仍然可行。)

List *
PlanForeignModify(PlannerInfo *root,
                  ModifyTable *plan,
                  Index resultRelation,
                  int subplan_index);

對外部表格上的插入、更新或刪除執行所需的任何其他規劃操作。此函式會產生 FDW 私有資訊,這些資訊將附加到執行更新操作的 ModifyTable 計劃節點。此私有資訊必須採用 List 的形式,並將在執行階段傳遞給 BeginForeignModify

root 是規劃器關於查詢的整體資訊。planModifyTable 計劃節點,除了 fdwPrivLists 欄位之外是完整的。resultRelation 按其範圍表格索引識別目標外部表格。subplan_index 識別此 ModifyTable 計劃節點的哪個目標,從零開始計數;如果您想索引到 plan 節點的每個目標關係子結構中,請使用此索引。

有關其他資訊,請參閱第 57.4 節

如果 PlanForeignModify 指標設定為 NULL,則不會採取額外的規劃時動作,並且傳遞給 BeginForeignModifyfdw_private 列表將為 NIL。

void
BeginForeignModify(ModifyTableState *mtstate,
                   ResultRelInfo *rinfo,
                   List *fdw_private,
                   int subplan_index,
                   int eflags);

開始執行外部表格修改操作。此常式在執行器啟動期間被呼叫。它應該在實際表格修改之前執行任何所需的初始化。隨後,將針對要插入、更新或刪除的元組呼叫 ExecForeignInsert/ExecForeignBatchInsertExecForeignUpdateExecForeignDelete

mtstate 是正在執行的 ModifyTable 計劃節點的整體狀態;關於計劃和執行狀態的全域資料可透過此結構取得。rinfo 是描述目標外部表格的 ResultRelInfo 結構。(ResultRelInfori_FdwState 欄位可用於 FDW 儲存此操作所需的任何私有狀態。)fdw_private 包含由 PlanForeignModify 產生的私有資料(如果有的話)。subplan_index 識別此 ModifyTable 計劃節點的哪個目標。eflags 包含描述此計劃節點的執行器操作模式的旗標位。

請注意,當 (eflags & EXEC_FLAG_EXPLAIN_ONLY) 為 true 時,此函式不應執行任何外部可見的動作;它應該只執行使節點狀態對於 ExplainForeignModifyEndForeignModify 有效所需的最小值。

如果 BeginForeignModify 指標設定為 NULL,則在執行器啟動期間不會採取任何動作。

TupleTableSlot *
ExecForeignInsert(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

將一個元組插入到外部表格中。estate 是查詢的全域執行狀態。rinfo 是描述目標外部表格的 ResultRelInfo 結構。slot 包含要插入的元組;它將匹配外部表格的列類型定義。planSlot 包含由 ModifyTable 計劃節點的子計劃產生的元組;它與 slot 的不同之處在於可能包含額外的無用列。(planSlot 通常對於 INSERT 情況不太重要,但為了完整性而提供。)

傳回值是一個包含實際插入資料的 slot(這可能與提供的資料不同,例如由於觸發程式動作的結果),或者如果實際上沒有插入任何列則傳回 NULL(同樣,通常是由於觸發程式的結果)。傳入的 slot 可以重複用於此目的。

僅當 INSERT 語句具有 RETURNING 子句或涉及 WITH CHECK OPTION 的檢視表;或者如果外部表格具有 AFTER ROW 觸發程式時,才使用傳回的 slot 中的資料。觸發程式需要所有列,但 FDW 可以選擇優化掉傳回某些或所有列,具體取決於 RETURNING 子句或 WITH CHECK OPTION 約束的內容。無論如何,必須傳回一些 slot 以表明成功,否則查詢報告的列計數將錯誤。

如果 ExecForeignInsert 指標設定為 NULL,則嘗試插入到外部表格中將會失敗並顯示錯誤訊息。

請注意,當將路由過的元組插入到外部資料表分割區,或在外部資料表上執行 COPY FROM 時,也會呼叫此函式。在這些情況下,它的呼叫方式與 INSERT 情況不同。請參閱下方描述的回呼函式,這些函式允許 FDW 支援這種情況。

TupleTableSlot **
ExecForeignBatchInsert(EState *estate,
                       ResultRelInfo *rinfo,
                       TupleTableSlot **slots,
                       TupleTableSlot **planSlots,
                       int *numSlots);

將多個元組批量插入到外部資料表中。參數與 ExecForeignInsert 相同,除了 slotsplanSlots 包含多個元組,且 *numSlots 指定這些陣列中的元組數量。

回傳值是一個包含實際插入資料的 slots 陣列(這可能與提供的資料不同,例如由於觸發程序動作的結果)。傳入的 slots 可以重複使用於此目的。成功插入的元組數量將回傳於 *numSlots 中。

只有在 INSERT 語句涉及具有 WITH CHECK OPTION 的視窗,或者外部資料表具有 AFTER ROW 觸發程序時,才會使用回傳的 slot 中的資料。觸發程序需要所有欄位,但 FDW 可以選擇最佳化,根據 WITH CHECK OPTION 約束的內容,省略回傳某些或所有欄位。

如果 ExecForeignBatchInsertGetForeignModifyBatchSize 指標設定為 NULL,則嘗試插入到外部資料表將會使用 ExecForeignInsert。如果 INSERT 具有 RETURNING 子句,則不會使用此函式。

請注意,當將路由過的元組插入到外部資料表分割區,或在外部資料表上執行 COPY FROM 時,也會呼叫此函式。在這些情況下,它的呼叫方式與 INSERT 情況不同。請參閱下方描述的回呼函式,這些函式允許 FDW 支援這種情況。

int
GetForeignModifyBatchSize(ResultRelInfo *rinfo);

報告單次 ExecForeignBatchInsert 呼叫可以處理的指定外部資料表的最大元組數量。執行器最多將給定的元組數量傳遞給 ExecForeignBatchInsertrinfo 是描述目標外部資料表的 ResultRelInfo 結構。預期 FDW 會提供一個外部伺服器和/或外部資料表選項,供使用者設定此值,或一些硬式編碼的值。

如果 ExecForeignBatchInsertGetForeignModifyBatchSize 指標設定為 NULL,則嘗試插入到外部資料表將會使用 ExecForeignInsert

TupleTableSlot *
ExecForeignUpdate(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

更新外部資料表中的一個元組。estate 是查詢的全局執行狀態。rinfo 是描述目標外部資料表的 ResultRelInfo 結構。slot 包含元組的新資料;它將符合外部資料表的列類型定義。planSlot 包含由 ModifyTable 計劃節點的子計劃產生的元組。與 slot 不同,此元組僅包含查詢所變更的欄位的新值,因此不要依賴外部資料表的屬性編號來索引到 planSlot 中。此外,planSlot 通常包含額外的垃圾欄位。特別是,AddForeignUpdateTargets 請求的任何垃圾欄位都將可從此 slot 取得。

回傳值是一個包含實際更新列的 slot(這可能與提供的資料不同,例如由於觸發程序動作的結果),如果沒有實際更新列,則為 NULL(同樣,通常是由於觸發程序)。傳入的 slot 可以重複使用於此目的。

只有在 UPDATE 語句具有 RETURNING 子句或涉及具有 WITH CHECK OPTION 的視窗,或者外部資料表具有 AFTER ROW 觸發程序時,才會使用回傳的 slot 中的資料。觸發程序需要所有欄位,但 FDW 可以選擇最佳化,根據 RETURNING 子句或 WITH CHECK OPTION 約束的內容,省略回傳某些或所有欄位。無論如何,必須回傳一些 slot 以指示成功,否則查詢回報的列計數將會錯誤。

如果 ExecForeignUpdate 指標設定為 NULL,則嘗試更新外部資料表將會失敗並顯示錯誤訊息。

TupleTableSlot *
ExecForeignDelete(EState *estate,
                  ResultRelInfo *rinfo,
                  TupleTableSlot *slot,
                  TupleTableSlot *planSlot);

從外部資料表中刪除一個元組。estate 是查詢的全局執行狀態。rinfo 是描述目標外部資料表的 ResultRelInfo 結構。slot 在呼叫時不包含任何有用的內容,但可用於保存回傳的元組。planSlot 包含由 ModifyTable 計劃節點的子計劃產生的元組;特別是,它將攜帶 AddForeignUpdateTargets 請求的任何垃圾欄位。垃圾欄位必須用於識別要刪除的元組。

回傳值是一個包含已刪除列的 slot,如果沒有刪除列,則為 NULL(通常是由於觸發程序)。傳入的 slot 可用於保存要回傳的元組。

只有在 DELETE 查詢具有 RETURNING 子句,或者外部資料表具有 AFTER ROW 觸發程序時,才會使用回傳的 slot 中的資料。觸發程序需要所有欄位,但 FDW 可以選擇最佳化,根據 RETURNING 子句的內容,省略回傳某些或所有欄位。無論如何,必須回傳一些 slot 以指示成功,否則查詢回報的列計數將會錯誤。

如果 ExecForeignDelete 指標設定為 NULL,則嘗試從外部資料表刪除將會失敗並顯示錯誤訊息。

void
EndForeignModify(EState *estate,
                 ResultRelInfo *rinfo);

結束資料表更新並釋放資源。通常釋放 palloc'd 記憶體並不重要,但例如,應該清理開啟的檔案和與遠端伺服器的連線。

如果 EndForeignModify 指標設定為 NULL,則在執行器關閉期間不會採取任何動作。

透過 INSERTCOPY FROM 插入到分割資料表的元組會被路由到分割區。如果 FDW 支援可路由的外部資料表分割區,則它也應該提供以下回呼函式。當在外部資料表上執行 COPY FROM 時,也會呼叫這些函式。

void
BeginForeignInsert(ModifyTableState *mtstate,
                   ResultRelInfo *rinfo);

開始對外部資料表執行插入操作。此常式會在第一筆元組插入外部資料表之前呼叫,無論該外部資料表是針對元組路由選擇的分區,還是 COPY FROM 指令中指定的目標。它應該執行實際插入之前所需的任何初始化。隨後,將會呼叫 ExecForeignInsertExecForeignBatchInsert 以將元組插入外部資料表。

mtstate 是正在執行的 ModifyTable 計畫節點的整體狀態;關於計畫和執行狀態的全域資料可透過此結構取得。rinfo 是描述目標外部資料表的 ResultRelInfo 結構。(ResultRelInfori_FdwState 欄位可用於 FDW 儲存此操作所需的任何私有狀態。)

當此函數由 COPY FROM 指令呼叫時,mtstate 中的計畫相關全域資料將不會提供,並且後續針對每個插入的元組呼叫的 ExecForeignInsertplanSlot 參數將為 NULL,無論外部資料表是針對元組路由選擇的分區,還是指令中指定的目標。

如果 BeginForeignInsert 指標設定為 NULL,則不會採取任何初始化動作。

請注意,如果 FDW 不支援可路由的外部資料表分區和/或在外部資料表上執行 COPY FROM,則此函數或後續呼叫的 ExecForeignInsert/ExecForeignBatchInsert 必須根據需要拋出錯誤。

void
EndForeignInsert(EState *estate,
                 ResultRelInfo *rinfo);

結束插入操作並釋放資源。通常釋放 palloc 配置的記憶體並不重要,但例如,應該清理開啟的檔案和與遠端伺服器的連線。

如果 EndForeignInsert 指標設定為 NULL,則不會採取任何終止動作。

int
IsForeignRelUpdatable(Relation rel);

報告指定的外部資料表支援哪些更新操作。傳回值應該是一個規則事件編號的位元遮罩,指示外部資料表支援哪些操作,使用 CmdType 列舉;也就是說,(1 << CMD_UPDATE) = 4 代表 UPDATE(1 << CMD_INSERT) = 8 代表 INSERT,而 (1 << CMD_DELETE) = 16 代表 DELETE

如果 IsForeignRelUpdatable 指標設定為 NULL,則假設如果 FDW 分別提供 ExecForeignInsertExecForeignUpdateExecForeignDelete,則外部資料表是可以插入、更新或刪除的。只有當 FDW 支援某些可更新的資料表和某些不可更新的資料表時,才需要此函數。(即便如此,也可以在執行常式中拋出錯誤,而不是在此函數中進行檢查。但是,此函數用於確定 information_schema 視圖中顯示的可更新性。)

透過實作一組替代介面,可以最佳化對外部資料表的一些插入、更新和刪除。插入、更新和刪除的普通介面從遠端伺服器提取列,然後一次修改這些列。在某些情況下,這種逐列方法是必要的,但效率可能不高。如果遠端伺服器可以確定應修改哪些列而無需實際檢索它們,並且如果沒有會影響操作的本機結構(列級本機觸發器、儲存的產生欄位或來自父視圖的 WITH CHECK OPTION 約束),則可以安排讓整個操作在遠端伺服器上執行。下面描述的介面使這成為可能。

bool
PlanDirectModify(PlannerInfo *root,
                 ModifyTable *plan,
                 Index resultRelation,
                 int subplan_index);

決定在遠端伺服器上執行直接修改是否安全。如果是,則在執行該操作所需的計畫動作後,傳回 true。否則,傳回 false。此可選函數在查詢計畫期間呼叫。如果此函數成功,則會在執行階段呼叫 BeginDirectModifyIterateDirectModifyEndDirectModify。否則,將使用上面描述的資料表更新函數來執行資料表修改。參數與 PlanForeignModify 的參數相同。

為了在遠端伺服器上執行直接修改,此函數必須使用 ForeignScan 計畫節點來重寫目標子計畫,該計畫節點在遠端伺服器上執行直接修改。ForeignScanoperationresultRelation 欄位必須設定為適當的值。operation 必須設定為與語句種類對應的 CmdType 列舉(也就是說,CMD_UPDATE 代表 UPDATECMD_INSERT 代表 INSERT,而 CMD_DELETE 代表 DELETE),並且 resultRelation 參數必須複製到 resultRelation 欄位。

有關其他資訊,請參閱第 57.4 節

如果 PlanDirectModify 指標設定為 NULL,則不會嘗試在遠端伺服器上執行直接修改。

void
BeginDirectModify(ForeignScanState *node,
                  int eflags);

準備在遠端伺服器上執行直接修改。這在執行器啟動期間呼叫。它應該執行直接修改之前所需的任何初始化(該初始化應在首次呼叫 IterateDirectModify 時完成)。ForeignScanState 節點已經建立,但其 fdw_state 欄位仍然為 NULL。有關要修改的資料表的資訊可透過 ForeignScanState 節點(特別是從底層 ForeignScan 計畫節點,其中包含 PlanDirectModify 提供的任何 FDW 私有資訊)存取。eflags 包含描述執行器此計畫節點的操作模式的旗標位元。

請注意,當 (eflags & EXEC_FLAG_EXPLAIN_ONLY) 為 true 時,此函數不應執行任何外部可見的動作;它應該只做讓節點狀態對 ExplainDirectModifyEndDirectModify 有效所需的最小值。

如果 BeginDirectModify 指標設定為 NULL,則不會嘗試在遠端伺服器上執行直接修改。

TupleTableSlot *
IterateDirectModify(ForeignScanState *node);

INSERTUPDATEDELETE 查詢沒有 RETURNING 子句時,在遠端伺服器上直接修改後,僅返回 NULL。當查詢具有此子句時,提取一個包含 RETURNING 計算所需資料的結果,並將其以元組表格槽 (tuple table slot) 的形式返回 (應使用節點的 ScanTupleSlot 來達成此目的)。實際插入、更新或刪除的資料必須儲存在 node->resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple 中。如果沒有更多列可用,則返回 NULL。請注意,這會在生命週期短暫的記憶體內容中被呼叫,該內容將在每次調用之間重置。如果您需要更長時間的儲存,請在 BeginDirectModify 中建立記憶體內容,或使用節點 EStatees_query_cxt

如果提供了 fdw_scan_tlist 目標清單,則返回的列必須與之匹配,否則它們必須與要更新的外部表格的列類型匹配。如果您選擇優化掉未用於 RETURNING 計算的欄位的提取,則應在這些欄位位置插入 null,或者產生一個省略這些欄位的 fdw_scan_tlist 清單。

無論查詢是否具有該子句,查詢所報告的列計數都必須由 FDW 本身遞增。當查詢沒有該子句時,FDW 也必須在 EXPLAIN ANALYZE 的情況下,遞增 ForeignScanState 節點的列計數。

如果 IterateDirectModify 指標設為 NULL,則不會嘗試在遠端伺服器上執行直接修改。

void
EndDirectModify(ForeignScanState *node);

在遠端伺服器上直接修改後進行清理。通常釋放 palloc 分配的記憶體並不重要,但例如,開啟的檔案和與遠端伺服器的連線應進行清理。

如果 EndDirectModify 指標設為 NULL,則不會嘗試在遠端伺服器上執行直接修改。

57.2.5. 用於 TRUNCATE 的 FDW 常式 #

void
ExecForeignTruncate(List *rels,
                    DropBehavior behavior,
                    bool restart_seqs);

截斷外部表格。當在外部表格上執行 TRUNCATE 時,將會呼叫此函數。rels 是要截斷的外部表格的 Relation 資料結構的清單。

behaviorDROP_RESTRICTDROP_CASCADE,表示原始 TRUNCATE 命令中分別請求了 RESTRICTCASCADE 選項。

如果 restart_seqstrue,則原始 TRUNCATE 命令請求了 RESTART IDENTITY 行為,否則請求了 CONTINUE IDENTITY 行為。

請注意,原始 TRUNCATE 命令中指定的 ONLY 選項不會傳遞給 ExecForeignTruncate。此行為類似於外部表格上 SELECTUPDATEDELETE 的回呼函數。

ExecForeignTruncate 會針對要截斷外部表格的每個外部伺服器調用一次。這表示 rels 中包含的所有外部表格必須屬於同一個伺服器。

如果 ExecForeignTruncate 指標設為 NULL,則截斷外部表格的嘗試將失敗並顯示錯誤訊息。

57.2.6. 用於列鎖定的 FDW 常式 #

如果 FDW 希望支援延遲列鎖定 (如第 57.5 節中所述),則它必須提供以下回呼函數

RowMarkType
GetForeignRowMarkType(RangeTblEntry *rte,
                      LockClauseStrength strength);

報告要用於外部表格的列標記選項。rte 是表格的 RangeTblEntry 節點,而 strength 描述了相關 FOR UPDATE/SHARE 子句請求的鎖定強度 (如果有的話)。結果必須是 RowMarkType 列舉類型的一個成員。

對於出現在 UPDATEDELETESELECT FOR UPDATE/SHARE 查詢中,且不是 UPDATEDELETE 目標的每個外部表格,都會在查詢規劃期間呼叫此函數。

如果 GetForeignRowMarkType 指標設為 NULL,則始終使用 ROW_MARK_COPY 選項。(這意味著永遠不會呼叫 RefetchForeignRow,因此也不需要提供它。)

有關更多資訊,請參閱 第 57.5 節

void
RefetchForeignRow(EState *estate,
                  ExecRowMark *erm,
                  Datum rowid,
                  TupleTableSlot *slot,
                  bool *updated);

從外部表格重新提取一個元組槽,如果需要,在鎖定它之後。estate 是查詢的整體執行狀態。ermExecRowMark 結構,描述了目標外部表格和要獲取的列鎖定類型 (如果有的話)。rowid 識別要提取的元組。slot 在呼叫時不包含任何有用的資訊,但可用於保存返回的元組。updated 是一個輸出參數。

此函數應將元組儲存到提供的槽中,如果無法獲得列鎖定,則清除它。要獲取的列鎖定類型由 erm->markType 定義,它是先前由 GetForeignRowMarkType 返回的值。(ROW_MARK_REFERENCE 表示僅重新提取元組而不獲取任何鎖定,並且此常式永遠不會看到 ROW_MARK_COPY。)

此外,如果提取的是元組的更新版本,而不是先前獲得的相同版本,則應將 *updated 設為 true。(如果 FDW 無法確定此情況,建議始終返回 true。)

請注意,預設情況下,無法獲取列鎖定應導致引發錯誤;僅當 erm->waitPolicy 指定了 SKIP LOCKED 選項時,才適合返回空槽。

rowid 是先前為要重新提取的列讀取的 ctid 值。儘管 rowid 值作為 Datum 傳遞,但目前它只能是 tid。選擇此函數 API 是希望將來可以允許其他資料類型作為列 ID。

如果 RefetchForeignRow 指標設為 NULL,則重新提取列的嘗試將失敗並顯示錯誤訊息。

有關更多資訊,請參閱 第 57.5 節

bool
RecheckForeignScan(ForeignScanState *node,
                   TupleTableSlot *slot);

重新檢查先前返回的元組是否仍然匹配相關的掃描和連接限定詞,並可能提供元組的修改版本。對於不執行連接下推的外部資料包裝器,通常將此設定為 NULL 並適當地設定 fdw_recheck_quals 會更方便。但是,當下推外部連接時,即使所有需要的屬性都存在,僅僅將與所有基本表格相關的檢查重新應用於結果元組是不夠的,因為無法匹配某些限定詞可能會導致某些屬性變為 NULL,而不是不返回元組。RecheckForeignScan 可以重新檢查限定詞,如果它們仍然滿足則返回 true,否則返回 false,但它也可以將替換元組儲存到提供的槽中。

為了實作 join 下推(join pushdown),外部資料包裝器通常會建立一個替代的本地 join 計畫,該計畫僅用於重新檢查;這會變成 ForeignScan 的外部子計畫。當需要重新檢查時,可以執行此子計畫,並且將產生的元組儲存在插槽中。這個計畫不需要非常有效率,因為沒有基本資料表會傳回超過一個的列;例如,它可以將所有 join 實作為巢狀迴圈。可以使用函數 GetExistingLocalJoinPath 來搜尋現有的路徑,以找到合適的本地 join 路徑,該路徑可以用作替代的本地 join 計畫。GetExistingLocalJoinPath 在指定 join 關聯的路徑清單中搜尋未參數化的路徑。(如果它沒有找到這樣的路徑,則傳回 NULL,在這種情況下,外部資料包裝器可以自行建立本地路徑,或者選擇不為該 join 建立存取路徑。)

57.2.7. 用於 EXPLAIN 的 FDW 常式 #

void
ExplainForeignScan(ForeignScanState *node,
                   ExplainState *es);

為外部資料表掃描列印額外的 EXPLAIN 輸出。此函數可以呼叫 ExplainPropertyText 和相關函數,以將欄位新增到 EXPLAIN 輸出中。es 中的旗標欄位可用於決定要列印的內容,並且可以檢查 ForeignScanState 節點的狀態,以在 EXPLAIN ANALYZE 的情況下提供執行時統計資料。

如果 ExplainForeignScan 指標設定為 NULL,則在 EXPLAIN 期間不會列印任何額外資訊。

void
ExplainForeignModify(ModifyTableState *mtstate,
                     ResultRelInfo *rinfo,
                     List *fdw_private,
                     int subplan_index,
                     struct ExplainState *es);

為外部資料表更新列印額外的 EXPLAIN 輸出。此函數可以呼叫 ExplainPropertyText 和相關函數,以將欄位新增到 EXPLAIN 輸出中。es 中的旗標欄位可用於決定要列印的內容,並且可以檢查 ModifyTableState 節點的狀態,以在 EXPLAIN ANALYZE 的情況下提供執行時統計資料。前四個參數與 BeginForeignModify 相同。

如果 ExplainForeignModify 指標設定為 NULL,則在 EXPLAIN 期間不會列印任何額外資訊。

void
ExplainDirectModify(ForeignScanState *node,
                    ExplainState *es);

為遠端伺服器上的直接修改列印額外的 EXPLAIN 輸出。此函數可以呼叫 ExplainPropertyText 和相關函數,以將欄位新增到 EXPLAIN 輸出中。es 中的旗標欄位可用於決定要列印的內容,並且可以檢查 ForeignScanState 節點的狀態,以在 EXPLAIN ANALYZE 的情況下提供執行時統計資料。

如果 ExplainDirectModify 指標設定為 NULL,則在 EXPLAIN 期間不會列印任何額外資訊。

57.2.8. 用於 ANALYZE 的 FDW 常式 #

bool
AnalyzeForeignTable(Relation relation,
                    AcquireSampleRowsFunc *func,
                    BlockNumber *totalpages);

當在外部資料表上執行 ANALYZE 時,會呼叫此函數。如果 FDW 可以收集此外部資料表的統計資料,則應傳回 true,並且在 func 中提供一個指向將從資料表中收集樣本列的函數的指標,以及在 totalpages 中提供資料表大小(以頁為單位)的估計值。否則,傳回 false

如果 FDW 不支援收集任何資料表的統計資料,則可以將 AnalyzeForeignTable 指標設定為 NULL

如果提供,樣本收集函數必須具有以下簽章:

int
AcquireSampleRowsFunc(Relation relation,
                      int elevel,
                      HeapTuple *rows,
                      int targrows,
                      double *totalrows,
                      double *totaldeadrows);

應從資料表中收集最多 targrows 列的隨機樣本,並將其儲存到呼叫者提供的 rows 陣列中。必須傳回收集的實際列數。此外,將資料表中存活和已刪除列的總數的估計值儲存到輸出參數 totalrowstotaldeadrows 中。(如果 FDW 沒有任何已刪除列的概念,則將 totaldeadrows 設定為零。)

57.2.9. 用於 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 可以忽略 ImportForeignSchemaStmtlocal_schema 欄位,因為核心伺服器會自動將該名稱插入到已剖析的 CREATE FOREIGN TABLE 命令中。

FDW 也不必擔心實作 list_typetable_list 指定的過濾,因為核心伺服器會自動跳過根據這些選項排除的資料表的任何傳回命令。但是,避免首先為排除的資料表建立命令的工作通常很有用。函數 IsImportableForeignTable() 可能有助於測試給定的外部資料表名稱是否會通過過濾器。

如果 FDW 不支援匯入資料表定義,則可以將 ImportForeignSchema 指標設定為 NULL

57.2.10. 用於平行執行的 FDW 常式 #

一個 ForeignScan 節點可以選擇性地支援平行執行。平行 ForeignScan 將在多個程序中執行,並且必須在所有協作程序中精確地傳回每一列一次。為了做到這一點,程序可以透過固定大小的動態共享記憶體區塊進行協調。無法保證此共享記憶體在每個程序中都映射到相同的位址,因此它不能包含指標。以下函數都是可選的,但如果支援平行執行,則大多數都是必需的。

bool
IsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel,
                          RangeTblEntry *rte);

測試掃描是否可以在平行工作程序中執行。只有當規劃器認為可能存在平行計畫時,才會呼叫此函數,如果掃描在平行工作程序中安全運行,則應傳回 true。如果遠端資料來源具有交易語意,通常情況並非如此,除非工作程序與資料的連線可以某種方式與領導者共享相同的交易環境。

如果未定義此函數,則假定掃描必須在平行領導者中進行。請注意,傳回 true 並不意味著掃描本身可以平行完成,僅表示可以在平行工作程序中執行掃描。因此,即使不支援平行執行,定義此方法也可能很有用。

Size
EstimateDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt);

估計平行運作所需的動態共享記憶體數量。這可能高於實際使用的數量,但絕對不能低於實際使用的數量。傳回值以位元組為單位。這個函式是可選的,如果不需要可以省略;但如果省略此函式,則接下來的三個函式也必須省略,因為不會為 FDW 分配任何共享記憶體。

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 區段消失之前採取某些動作的外部資料封裝器應實作此方法。

57.2.11. FDW 非同步執行的常式 #

一個 ForeignScan 節點可以選擇性地支援非同步執行,如 src/backend/executor/README 中所述。以下函式都是可選的,但如果要支援非同步執行,則都需要。

bool
IsForeignPathAsyncCapable(ForeignPath *path);

測試給定的 ForeignPath 路徑是否可以非同步掃描底層的外部關聯。只有在查詢規劃結束時,當給定的路徑是 AppendPath 路徑的直接子路徑,並且規劃器認為非同步執行可以提高效能時,才會呼叫此函式,如果給定的路徑能夠非同步掃描外部關聯,則應傳回 true。

如果未定義此函式,則假定給定的路徑使用 IterateForeignScan 掃描外部關聯。(這意味著下面描述的回呼函式將永遠不會被呼叫,因此也不需要提供。)

void
ForeignAsyncRequest(AsyncRequest *areq);

ForeignScan 節點非同步產生一個元組。areq 是一個 AsyncRequest 結構,描述了 ForeignScan 節點和從它請求元組的父 Append 節點。這個函式應該將元組儲存到 areq->result 指定的槽中,並將 areq->request_complete 設為 true;或者,如果需要等待核心伺服器外部的事件(例如網路 I/O),並且無法立即產生任何元組,則將標記設為 false,並將 areq->callback_pending 設為 true,以便 ForeignScan 節點從下面描述的回呼函式取得回呼。如果沒有更多元組可用,則將槽設為 NULL 或空槽,並將 areq->request_complete 標記設為 true。建議使用 ExecAsyncRequestDoneExecAsyncRequestPendingareq 中設定輸出參數。

void
ForeignAsyncConfigureWait(AsyncRequest *areq);

設定 ForeignScan 節點希望等待的檔案描述器事件。只有當 ForeignScan 節點設定了 areq->callback_pending 標記時,才會呼叫此函式,並且應將事件新增到 areq 描述的父 Append 節點的 as_eventset 中。有關其他資訊,請參閱 src/backend/executor/execAsync.c 中的 ExecAsyncConfigureWait 的註解。當檔案描述器事件發生時,將會呼叫 ForeignAsyncNotify

void
ForeignAsyncNotify(AsyncRequest *areq);

處理已發生的相關事件,然後從 ForeignScan 節點非同步產生一個元組。這個函式應該以與 ForeignAsyncRequest 相同的方式在 areq 中設定輸出參數。

57.2.12. 用於路徑重新參數化的 FDW 常式 #

List *
ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
                                 RelOptInfo *child_rel);

當轉換由給定子關聯 child_rel 的最頂層父項參數化的路徑,以由子關聯參數化時,將會呼叫此函式。此函式用於重新參數化儲存在 ForeignPath 的給定 fdw_private 成員中的任何路徑或轉換任何表達式節點。回呼可以根據需要使用 reparameterize_path_by_childadjust_appendrel_attrsadjust_appendrel_attrs_multilevel

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的地方,請使用此表格來回報文件問題。