支援的版本: 最新 (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) 為真時,此函數不應執行任何外部可見的動作;它應該只做使節點狀態對 ExplainForeignScanEndForeignScan 有效的最低要求。

TupleTableSlot *
IterateForeignScan(ForeignScanState *node);

從外部來源提取一個資料列,並將其回傳到一個元組資料表槽中(節點的 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);

對外部資料表執行插入、更新或刪除所需的任何額外規劃動作。 此函式產生將附加到執行更新動作的 ModifyTable 計劃節點的 FDW 私有資訊。 此私有資訊必須採用 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 案例沒有太大意義,但為了完整性而提供。)

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

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

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

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

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

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

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

只有在 INSERT 語句涉及 WITH CHECK OPTION 的檢視表;或者如果外部資料表具有 AFTER ROW 觸發程序時,才會使用傳回槽中的資料。觸發程序需要所有欄位,但 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 請求的垃圾欄位都可以從此槽中取得。

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

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

如果 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'd 記憶體並不重要,但例如,應該清理打開的檔案和與遠端伺服器的連線。

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

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

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

開始在外部表格上執行插入操作。在以下兩種情況下,這個常式會在第一個元組被插入到外部表格之前立即被呼叫:當它是為元組路由選擇的分割區,以及當它是 COPY FROM 命令中指定的目標。它應該執行任何在實際插入之前需要的初始化。隨後,將會呼叫 ExecForeignInsertExecForeignBatchInsert 以將元組插入到外部表格中。

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

當這個函式被 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 列舉(也就是說,UPDATECMD_UPDATEINSERTCMD_INSERTDELETECMD_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);

在鎖定後,從外部表格重新提取一個元組槽 (tuple slot)。 estate 是查詢的全域執行狀態。erm 是描述目標外部表格和要獲取的列鎖定類型(如果有的話)的 ExecRowMark 結構。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);

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

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

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 陣列中。必須傳回收集的實際列數。此外,將資料表中 live 和 dead 列的總數估計值儲存到輸出參數 totalrowstotaldeadrows 中。(如果 FDW 沒有任何 dead 列的概念,則將 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 是要從中匯入資料表的遠端 schema 的名稱。list_type 標識如何篩選資料表名稱:FDW_IMPORT_SCHEMA_ALL 表示應匯入遠端 schema 中的所有資料表(在這種情況下,table_list 為空),FDW_IMPORT_SCHEMA_LIMIT_TO 表示僅包含 table_list 中列出的資料表,而 FDW_IMPORT_SCHEMA_EXCEPT 表示排除 table_list 中列出的資料表。options 是用於匯入過程的選項列表。選項的含義取決於 FDW。例如,FDW 可以使用選項來定義是否應匯入欄位的 NOT NULL 屬性。這些選項不必與 FDW 作為資料庫物件選項支援的選項有任何關係。

FDW 可以忽略 local_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);

測試一個掃描是否可以在一個平行工作者 (parallel worker) 中執行。只有當規劃器 (planner) 認為有可能產生平行計畫時,才會呼叫這個函式,而且如果該掃描可以在平行工作者中安全地執行,就應該回傳 true。如果遠端資料來源具有交易語意 (transaction semantics),通常情況並非如此,除非工作者連線到該資料的方式,能夠以某種方式與領導者 (leader) 共享相同的交易上下文 (transaction context)。

如果未定義此函式,則假定掃描必須在平行領導者中執行。請注意,回傳 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);

當 foreign-scan 計畫節點即將被重新掃描時,重新初始化平行運算所需的動態共享記憶體。這個函式是可選的,如果不需要可以省略。建議的做法是,這個函式只重置共享狀態,而 ReScanForeignScan 函式只重置本地狀態。目前,這個函式會在 ReScanForeignScan 之前被呼叫,但最好不要依賴這個順序。

void
InitializeWorkerForeignScan(ForeignScanState *node, shm_toc *toc,
                            void *coordinate);

根據領導者在 InitializeDSMForeignScan 期間設定的共享狀態,初始化平行工作者的本地狀態。這個函式是可選的,如果不需要可以省略。

void
ShutdownForeignScan(ForeignScanState *node);

當預期節點不會執行到完成時,釋放資源。並非所有情況下都會呼叫它;有時,可能會在沒有先呼叫這個函式的情況下呼叫 EndForeignScan。由於平行查詢使用的 DSM 段會在呼叫此回呼之後立即被銷毀,因此希望在 DSM 段消失之前採取一些動作的 foreign data wrappers 應該實作此方法。

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

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

bool
IsForeignPathAsyncCapable(ForeignPath *path);

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

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

void
ForeignAsyncRequest(AsyncRequest *areq);

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

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 節點非同步地產生一個 tuple。這個函式應該以與 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

提交更正

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