TRUNCATE
的 FDW 常式EXPLAIN
的 FDW 常式ANALYZE
的 FDW 常式IMPORT FOREIGN SCHEMA
的 FDW 常式FDW 處理函式會傳回一個 palloc'd 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
。(初始值基於資料行資料類型和上次 ANALYZE
測量的資料行平均寬度值。) 此外,如果函式可以計算出外部資料表總資料列數的更好估算值,則此函式可以更新 baserel->tuples
。(初始值來自 pg_class
。reltuples
,代表上次 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
(先前由 GetForeignPaths
、GetForeignJoinPaths
或 GetForeignUpperPaths
產生)、計劃節點要發出的目標清單、計劃節點要強制執行的限制子句,以及 ForeignScan
的外部子計劃,該子計劃用於 RecheckForeignScan
執行的重新檢查。(如果路徑是用於聯結而不是基本關係,則 foreigntableid
為 InvalidOid
。)
這個函數必須建立並回傳一個 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)
為真時,此函數不應執行任何外部可見的動作;它應該只做使節點狀態對 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'd 記憶體並不重要,但例如,開啟的檔案和與遠端伺服器的連線應該被清理。
如果 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 節 以取得更多資訊。
如果 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_AGG
或 UPPERREL_GROUP_AGG
進行設定,在這種情況下,它指向 GroupPathExtraData
結構;或針對 UPPERREL_FINAL
進行設定,在這種情況下,它指向 FinalPathExtraData
結構。(請注意,新增到 output_rel
的 ForeignPath
路徑通常不會直接依賴 input_rel
的路徑,因為預期它們的處理會在外部完成。然而,檢查先前為前一個處理步驟產生的路徑,有助於避免重複的規劃工作。)
請參閱 第 57.4 節 以取得更多資訊。
如果 FDW 支援可寫入的外部資料表,則應根據 FDW 的需求和功能提供部分或全部下列回呼函式
void AddForeignUpdateTargets(PlannerInfo *root, Index rtindex, RangeTblEntry *target_rte, Relation target_relation);
UPDATE
和 DELETE
操作會針對先前由資料表掃描函式提取的資料列執行。 FDW 可能需要額外資訊,例如資料列 ID 或主鍵欄位的值,以確保它可以識別要更新或刪除的確切資料列。 為了支援這一點,此函式可以將額外的隱藏欄位,或稱作 「垃圾」目標欄位,新增到 UPDATE
或 DELETE
期間要從外部資料表檢索的欄位清單中。
為此,請建構一個 Var
,表示您需要的額外值,並將其與垃圾欄位的名稱一起傳遞給 add_row_identity_var
。(如果需要多個欄位,您可以執行多次。) 您必須為每個不同的 Var
選擇不同的垃圾欄位名稱,除了 varno
欄位之外,完全相同的 Var
可以並且應該共用一個欄位名稱。 核心系統使用垃圾欄位名稱 tableoid
作為資料表的 tableoid
欄位、ctid
或 ctid
作為 N
ctid
、wholerow
作為標記為 vartype
= RECORD
的完整資料列 Var
,以及 wholerow
作為 N
vartype
等於資料表宣告的資料列類型的完整資料列 Var
。 盡可能重複使用這些名稱(規劃器會合併重複的相同垃圾欄位請求)。 如果您需要除這些之外的另一種垃圾欄位,最好選擇以您的擴充功能名稱作為前綴的名稱,以避免與其他 FDW 衝突。
如果 AddForeignUpdateTargets
指標設定為 NULL
,則不會新增額外的目標運算式。(這將無法實作 DELETE
操作,但如果 FDW 依賴不變的主鍵來識別資料列,則 UPDATE
仍然可行。)
List * PlanForeignModify(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index);
對外部資料表執行插入、更新或刪除所需的任何額外規劃動作。 此函式產生將附加到執行更新動作的 ModifyTable
計劃節點的 FDW 私有資訊。 此私有資訊必須採用 List
的形式,並且將在執行階段傳遞給 BeginForeignModify
。
root
是規劃器關於查詢的整體資訊。plan
是 ModifyTable
計劃節點,除了 fdwPrivLists
欄位之外,該節點是完整的。resultRelation
透過其範圍資料表索引識別目標外部資料表。subplan_index
識別 ModifyTable
計劃節點的哪個目標,從零開始計數;如果您想索引到 plan
節點的每個目標關係子結構中,請使用此項。
請參閱 第 57.4 節 以取得更多資訊。
如果 PlanForeignModify
指標設定為 NULL
,則不會採取額外的計劃時間動作,並且傳遞給 BeginForeignModify
的 fdw_private
清單將為 NIL。
void BeginForeignModify(ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, int eflags);
開始執行外部資料表修改操作。 此常式在執行器啟動期間呼叫。 它應執行在實際資料表修改之前所需的任何初始化。 隨後,將針對要插入、更新或刪除的元組呼叫 ExecForeignInsert/ExecForeignBatchInsert
、ExecForeignUpdate
或 ExecForeignDelete
。
mtstate
是正在執行的 ModifyTable
計劃節點的整體狀態;關於計劃和執行狀態的全域資料可透過此結構取得。 rinfo
是描述目標外部資料表的 ResultRelInfo
結構。(ResultRelInfo
的 ri_FdwState
欄位可用於 FDW 儲存此操作所需的任何私有狀態。)fdw_private
包含由 PlanForeignModify
產生的私有資料(如果有的話)。 subplan_index
識別 ModifyTable
計劃節點的哪個目標。 eflags
包含描述此計劃節點執行器操作模式的旗標位元。
請注意,當 (eflags & EXEC_FLAG_EXPLAIN_ONLY)
為 true 時,此函式不應執行任何外部可見的動作;它應該只執行使節點狀態對 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
子句,或者涉及 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
相同,除了 slots
和 planSlots
包含多個元組,並且 *numSlots
指定這些陣列中元組的數量。
傳回值是一個包含實際插入資料的槽陣列(這可能與提供的資料不同,例如,由於觸發程序的操作)。傳入的 slots
可以重複使用於此目的。成功插入的元組數量會在 *numSlots
中傳回。
只有在 INSERT
語句涉及 WITH CHECK OPTION
的檢視表;或者如果外部資料表具有 AFTER ROW
觸發程序時,才會使用傳回槽中的資料。觸發程序需要所有欄位,但 FDW 可以選擇優化,省略傳回部分或所有欄位,具體取決於 WITH CHECK OPTION
約束的內容。
如果 ExecForeignBatchInsert
或 GetForeignModifyBatchSize
指標設定為 NULL
,則嘗試插入到外部資料表將會使用 ExecForeignInsert
。如果 INSERT
具有 RETURNING
子句,則不會使用此函式。
請注意,當將路由元組插入到外部資料表分割區或在外部資料表上執行 COPY FROM
時,也會呼叫此函式,在這種情況下,它的呼叫方式與 INSERT
的情況不同。請參閱以下描述的回呼函式,允許 FDW 支援該功能。
int GetForeignModifyBatchSize(ResultRelInfo *rinfo);
回報單個 ExecForeignBatchInsert
呼叫可以處理的指定外部資料表的最大元組數。執行器會將最多給定數量的元組傳遞給 ExecForeignBatchInsert
。rinfo
是描述目標外部資料表的 ResultRelInfo
結構。預期 FDW 會提供一個外部伺服器和/或外部資料表選項,供使用者設定此值,或者一些硬編碼的值。
如果 ExecForeignBatchInsert
或 GetForeignModifyBatchSize
指標設定為 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
,則在執行器關閉期間不會採取任何動作。
透過 INSERT
或 COPY FROM
插入到分割表格的元組會被路由到分割區。如果 FDW 支援可路由的外部表格分割區,它也應該提供以下回呼函式。當在外部表格上執行 COPY FROM
時,也會呼叫這些函式。
void BeginForeignInsert(ModifyTableState *mtstate, ResultRelInfo *rinfo);
開始在外部表格上執行插入操作。在以下兩種情況下,這個常式會在第一個元組被插入到外部表格之前立即被呼叫:當它是為元組路由選擇的分割區,以及當它是 COPY FROM
命令中指定的目標。它應該執行任何在實際插入之前需要的初始化。隨後,將會呼叫 ExecForeignInsert
或 ExecForeignBatchInsert
以將元組插入到外部表格中。
mtstate
是正在執行的 ModifyTable
計畫節點的整體狀態;關於計畫和執行狀態的全域資料可以透過這個結構取得。rinfo
是一個描述目標外部表格的 ResultRelInfo
結構。(FDW 可以使用 ResultRelInfo
的 ri_FdwState
欄位來儲存任何它需要的關於此操作的私有狀態。)
當這個函式被 COPY FROM
命令呼叫時,mtstate
中的計畫相關的全域資料不會被提供,並且隨後為每個插入的元組呼叫的 ExecForeignInsert
的 planSlot
參數將會是 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 分別提供 ExecForeignInsert
、ExecForeignUpdate
或 ExecForeignDelete
,則外部表格被假定為可插入、可更新或可刪除。只有當 FDW 支援一些可更新和一些不可更新的表格時,才需要這個函式。(即便如此,也可以在執行常式中拋出錯誤,而不是在這個函式中進行檢查。然而,這個函式被用來確定 information_schema
視圖中顯示的可更新性。)
透過實作一組替代介面,可以優化一些對外部表格的插入、更新和刪除操作。用於插入、更新和刪除的普通介面從遠端伺服器獲取資料列,然後一次修改一個資料列。在某些情況下,這種逐列方法是必要的,但它可能效率低下。如果外部伺服器能夠在不實際檢索的情況下確定哪些資料列應該被修改,並且如果沒有會影響操作的本地結構(列級本地觸發器、儲存的產生欄位或來自父視圖的 WITH CHECK OPTION
约束),則可以安排將整個操作在遠端伺服器上執行。下面描述的介面使這成為可能。
bool PlanDirectModify(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index);
決定是否可以安全地在遠端伺服器上執行直接修改。如果是,則在執行該操作所需的計畫動作後,回傳 true
。否則,回傳 false
。這個可選函式在查詢計畫期間被呼叫。如果此函式成功,則會在執行階段呼叫 BeginDirectModify
、IterateDirectModify
和 EndDirectModify
。否則,表格修改將使用上面描述的表格更新函式來執行。參數與 PlanForeignModify
相同。
為了在遠端伺服器上執行直接修改,此函式必須使用執行遠端伺服器上直接修改的 ForeignScan
計畫節點來重寫目標子計畫。ForeignScan
的 operation
和 resultRelation
欄位必須被適當地設定。operation
必須被設定為與語句種類相對應的 CmdType
列舉(也就是說,UPDATE
為 CMD_UPDATE
,INSERT
為 CMD_INSERT
,DELETE
為 CMD_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 時,此函式不應執行任何外部可見的動作;它應該只做使節點狀態對 ExplainDirectModify
和 EndDirectModify
有效的最低要求。
如果 BeginDirectModify
指標設為 NULL
,則不會嘗試在遠端伺服器上執行直接修改。
TupleTableSlot * IterateDirectModify(ForeignScanState *node);
當 INSERT
、UPDATE
或 DELETE
查詢沒有 RETURNING
子句時,在遠端伺服器上進行直接修改後,僅返回 NULL。 當查詢有該子句時,提取一個包含 RETURNING
計算所需資料的結果,並將其以元組表格槽 (tuple table slot) 的形式返回(節點的 ScanTupleSlot
應為此目的而使用)。 實際插入、更新或刪除的資料必須儲存在 node->resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple
中。 如果沒有更多列可用,則返回 NULL。 請注意,這是在一個短暫的記憶體環境中呼叫的,該環境將在每次呼叫之間重置。 如果您需要更長時間的儲存空間,請在 BeginDirectModify
中建立一個記憶體環境,或者使用節點 EState
的 es_query_cxt
。
如果提供了 fdw_scan_tlist
目標列表,則返回的列必須與其匹配,否則它們必須與正在更新的外部表格的列類型匹配。 如果您選擇優化掉提取 RETURNING
計算不需要的欄位,則應在這些欄位位置插入 null 值,或者產生一個省略這些欄位的 fdw_scan_tlist
列表。
無論查詢是否有子句,查詢報告的列數都必須由 FDW 本身遞增。 當查詢沒有子句時,FDW 還必須在 EXPLAIN ANALYZE
的情況下,遞增 ForeignScanState
節點的列數。
如果 IterateDirectModify
指標設為 NULL
,則不會嘗試在遠端伺服器上執行直接修改。
void EndDirectModify(ForeignScanState *node);
在遠端伺服器上進行直接修改後,進行清理。 通常釋放 palloc 配置的記憶體並不重要,但例如,應清理開啟的檔案和到遠端伺服器的連線。
如果 EndDirectModify
指標設為 NULL
,則不會嘗試在遠端伺服器上執行直接修改。
TRUNCATE
的 FDW 常式 #void ExecForeignTruncate(List *rels, DropBehavior behavior, bool restart_seqs);
截斷外部表格。 當在外部表格上執行 TRUNCATE 時,會呼叫此函數。rels
是要截斷的外部表格的 Relation
資料結構的列表。
behavior
是 DROP_RESTRICT
或 DROP_CASCADE
,表示原始 TRUNCATE
命令中分別請求了 RESTRICT
或 CASCADE
選項。
如果 restart_seqs
是 true
,則原始 TRUNCATE
命令請求了 RESTART IDENTITY
行為,否則請求了 CONTINUE IDENTITY
行為。
請注意,原始 TRUNCATE
命令中指定的 ONLY
選項不會傳遞給 ExecForeignTruncate
。 此行為與外部表格上的 SELECT
、UPDATE
和 DELETE
的回呼函數類似。
對於每個要截斷外部表格的外部伺服器,會呼叫一次 ExecForeignTruncate
。 這表示包含在 rels
中的所有外部表格必須屬於同一個伺服器。
如果 ExecForeignTruncate
指標設為 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 節。
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,在這種情況下,外部資料包裝器可以自行構建本地路徑,或者選擇不為該連接建立存取路徑。)
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
期間不會列印額外的資訊。
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 列的總數估計值儲存到輸出參數 totalrows
和 totaldeadrows
中。(如果 FDW 沒有任何 dead 列的概念,則將 totaldeadrows
設定為零。)
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_type
和 table_list
指定的篩選,因為核心伺服器會自動跳過根據這些選項排除的資料表的任何傳回命令。但是,避免首先為排除的資料表建立命令的工作通常很有用。可以使用 IsImportableForeignTable()
函數來測試給定的外部資料表名稱是否會通過篩選。
如果 FDW 不支援匯入資料表定義,則可以將 ImportForeignSchema
指標設定為 NULL
。
一個 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 應該實作此方法。
一個 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
。建議使用 ExecAsyncRequestDone
或 ExecAsyncRequestPending
來設定 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
中的輸出參數。
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
。
如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的地方,請使用此表單來報告文件問題。