Postgresql 中文操作指南

59.2. Foreign Data Wrapper Callback Routines #

FDW 处理程序函数返回一个 palloc 分配的 FdwRoutine 结构,其中包含指向下面描述的回调函数的指针。扫描相关函数是必需的,其他函数是可选的。

FdwRoutine 类型的结构在 src/include/foreign/fdwapi.h 中声明,有关更多详细信息,请参阅该部分。

59.2.1. FDW Routines for Scanning Foreign Tables #

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。)

有关其他信息,请参见 Section 59.4

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

在扫描外键表时创建可能访问路径。这在查询计划过程中调用。参数与已经调用的 GetForeignRelSize 相同。

该函数必须为扫描外键表生成至少一个访问路径(ForeignPath 节点),并且必须调用 add_path 将每个此类路径添加到 baserel→pathlist 中。建议使用 create_foreignscan_path 构建 ForeignPath 节点。该函数可以生成多个访问路径,例如,具有有效 pathkeys 的路径,以表示预排序结果。每个访问路径都必须包含成本估计,并且可以包含识别预期特定扫描方法所需的任何 FDW 私有信息。

有关其他信息,请参见 Section 59.4

ForeignScan *
GetForeignPlan(PlannerInfo *root,
               RelOptInfo *baserel,
               Oid foreigntableid,
               ForeignPath *best_path,
               List *tlist,
               List *scan_clauses,
               Plan *outer_plan);

根据选定的外部访问路径创建 ForeignScan 计划节点。这在查询规划的末尾调用。参数就像 GetForeignRelSize 一样,加上选定的 ForeignPath(先前由 GetForeignPathsGetForeignJoinPathsGetForeignUpperPaths 生成)、计划节点要发出的目标列表、计划节点要执行的限制条件以及由 RecheckForeignScan 执行的重新检查所用的 ForeignScan 的外部子计划。(如果路径用于连接而不是基础关系,则 foreigntableidInvalidOid。)

此函数必须创建并返回 ForeignScan 计划节点;建议使用 make_foreignscan 构建 ForeignScan 节点。

有关其他信息,请参见 Section 59.4

void
BeginForeignScan(ForeignScanState *node,
                 int eflags);

开始执行外键扫描。这是在执行器的启动期间调用的。它应执行扫描开始前所需的任何初始化,但不要开始执行实际扫描(应该在第一次调用 IterateForeignScan 时完成)。ForeignScanState 节点已经创建,但是它的 fdw_state 字段仍然为空。关于要扫描的表的信息可以通过 ForeignScanState 节点(特别是包含由 GetForeignPlan 提供的任何 FDW 私有信息的底层 ForeignScan 计划节点)进行访问。eflags 包含描述执行器此计划节点的操作模式的标志位。

请注意,当 (eflags & EXEC_FLAG_EXPLAIN_ONLY) 为真时,该函数不应执行任何外部可见操作;它应该只做使节点状态对 ExplainForeignScanEndForeignScan 有效所需的最低限度工作。

TupleTableSlot *
IterateForeignScan(ForeignScanState *node);

从外部源中获取一行,并将它返回到元组表插槽中(该节点的 ScanTupleSlot 应为此目的使用)。如果没有更多行,则返回 NULL。元组表插槽基础设施允许返回物理或虚拟元组;在大多数情况下,从性能的角度来看,后一种选择更可取。请注意,这是在一个会在调用之间重置的短暂内存上下文中调用的。如果您需要更长时间的存储,请在 BeginForeignScan 中创建一个内存上下文,或使用节点 EStatees_query_cxt

返回的行必须匹配提供的 fdw_scan_tlist 目标列表,否则它们必须匹配正在扫描的外键表的行类型。如果您选择优化检索不需要的列,则应在那些列位置中插入空值,或生成一个省略那些列的 fdw_scan_tlist 列表。

请注意,PostgreSQL 的执行器不关心返回的行是否违反在外键表上定义的任何约束——但是规划器确实关心,并且如果外键表中有不满足已声明约束的行时,可能会错误地优化查询。如果当用户宣布约束应为真时违反了约束,则可以引发错误(就像在数据类型不匹配的情况下需要执行的操作一样)。

void
ReScanForeignScan(ForeignScanState *node);

从头开始重新扫描。请注意,扫描所依赖的任何参数的值都可能已更改,因此新扫描不一定返回完全相同行的结果。

void
EndForeignScan(ForeignScanState *node);

结束扫描并释放资源。通常,释放 palloc 记忆并不重要,但是例如打开文件和与远程服务器的连接应该清理。

59.2.2. FDW Routines for Scanning Foreign Joins #

如果 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 有责任最大限度地减少重复工作。

如果为连接选择 ForeignPath 路径,它将代表整个连接过程;不会使用为组件表和辅助连接生成的路经。连接路径的后继处理过程与扫描单个外键表的路径十分相似。一个区别是结果 ForeignScan 计划节点的 scanrelid 应设置为零,因为它不表示单个关系;相反,ForeignScan 节点的 fs_relids 字段表示被连接的关系集。(后一个字段由核心规划器代码自动设置,无需由 FDW 填充。)另一个区别在于,由于无法从系统目录中找到远程连接的列列表,FDW 必须使用合适的 TargetEntry 节点列表来填充 fdw_scan_tlist,表示它将在返回的元组中在运行时提供的列集。

Note

从 PostgreSQL 16 开始,fs_relids 包含外部连接的 rangetable 索引(如果此类连接中包含任何索引)。新字段 fs_base_relids 仅包含基本关系索引,因此模拟了 fs_relids 的旧语义。

有关其他信息,请参见 Section 59.4

59.2.3. FDW Routines for Planning Post-Scan/Join Processing #

如果 FDW 支持执行远程扫描后/连接后处理,例如远程聚合,则应提供此回调函数:

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

创建_upper relation_处理(这是计划中所有扫描后/连接查询处理的术语,例如聚合、窗口函数、排序和表更新)的可能访问路径。此可选函数在查询计划期间调用。目前,仅当查询中涉及的所有基本关系属于同一个 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_的路径,因为它们的处理期望在外部完成。但是,检查先前为之前的处理步骤生成的路径可以帮助避免冗余计划工作。)

有关其他信息,请参见 Section 59.4

59.2.4. FDW Routines for Updating Foreign Tables #

如果 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 选择一个不同的垃圾列名称,但 Var_s that are identical except for the _varno 字段可以且应该共享一个列名称。核心系统使用垃圾列名称 tableoid 来表示表中的 tableoid 列, ctidctid_N for ctid, wholerow for a whole-row Var marked with vartype = RECORD, and wholerow_N for a whole-row Varvartype 等于表的声明行类型。只要可能,就重新使用这些名称(规划程序将合并对相同垃圾列的重复请求)。如果您需要的垃圾列类型与此不同,最好选择一个以您的扩展名作为前缀的名称,以避免与其他 FDW 冲突。

如果_AddForeignUpdateTargets_指针设置为_NULL_,则不会添加额外的目标表达式。(这将使得不可能实现_DELETE_操作,但如果 FDW 依赖于不变的主键来识别行,则_UPDATE_仍然可行。)

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

对外部表执行插入、更新或删除所需的任何其他计划操作。此函数生成 FDW 私有信息,该信息将附加到执行更新操作的_ModifyTable_计划节点。此私有信息必须采用_List_的形式,并将在执行阶段传递给_BeginForeignModify_。

_root_是计划者有关查询的全局信息。_plan_是_ModifyTable_计划节点,除了_fdwPrivLists_字段外,它已经完成。_resultRelation_通过范围表索引识别目标外部表。_subplan_index_识别这是_ModifyTable_计划节点的哪个目标,从零计数;如果您要索引到_plan_节点的目标关系子结构中,请使用此方法。

有关其他信息,请参见 Section 59.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_结构。(_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_的参数相同,但_slots_和_planSlots_包含多个元组,*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 请求的任何垃圾列。

返回值是要么包含按实际更新的行槽(这可能与提供的数据不同,例如由于触发器操作),要么是空值,如果没有实际更新任何行(同样通常是由于触发器)。已传递的 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 请求的任何垃圾列。垃圾列必须用于标识要删除的元组。

返回值是要么包含已被删除行的槽,要么是空值,如果没有删除任何行(通常是由于触发器)。已传递的 slot 可用于保存要返回的元组。

返回槽中的数据仅当 DELETE 查询有 RETURNING 子句或外键表有一个 AFTER ROW 触发器时使用。触发器需要所有列,但 FDW 可以选择根据 RETURNING 子句的内容优化返回部分或所有列。无论如何,都必须返回一些槽以表示成功,否则查询的报告行计数会错误。

如果 ExecForeignDelete 指针设置为 NULL,则尝试从外键表中删除会失败,并显示错误消息。

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

结束表更新并释放资源。通常不必释放 palloc 的内存,但例如应清理打开的文件和与远程服务器的连接。

如果 EndForeignModify 指针设置为 NULL,则在执行器关闭期间不会执行任何操作。

INSERTCOPY FROM 插入到分区表的元组将路由到分区。如果 FDW 支持可路由的外键表分区,它还应提供以下回调函数。这些函数在对外键表执行 COPY FROM 时也会被调用。

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

开始在外键表上执行插入操作。在将第一个元组插入外键表之前(无论是为元组路由选择的 partition,还是 COPY FROM 命令中指定的目标),都会调用此例程。它应该执行实际插入前所需的任何初始化。随后,将对要插入外键表的元组调用 ExecForeignInsertExecForeignBatchInsert

mtstate 是正在执行的 ModifyTable 计划节点的整体状态;可以通过此结构获取有关计划和执行状态的全局数据。rinfo 是描述目标外键表的 ResultRelInfo 结构。FDW 能够为该操作存储它所需的任何私有状态的 ResultRelInfori_FdwState 字段。)

当由 COPY FROM 命令调用时,不会提供 mtstate 中与计划相关的全局数据,并且 ExecForeignInsertplanSlot 参数随后每个插入的元组都会调用 NULL,无论外键表是为元组路由选择的 partition 还是命令中指定的目标。

如果将 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_UPDATE,对于 INSERTCMD_INSERT,对于 DELETECMD_DELETE),并且 resultRelation 参数必须复制到 resultRelation 字段。

有关其他信息,请参见 Section 59.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 计算所需数据的 result,并将其以元组表槽的形式返回(应使用节点的 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,则不会尝试在远程服务器上执行直接修改。

59.2.5. FDW Routines for TRUNCATE #

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,则尝试截断外部表会失败,并出现错误消息。

59.2.6. FDW Routines for Row Locking #

如果 FDW 希望支持 late row locking(如 Section 59.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,所以也不必提供它。)

有关更多信息,请参见 Section 59.5

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

在锁定需要锁定的元组槽后,重新获取该元组槽。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,则尝试重新获取行会失败,并出现错误消息。

有关更多信息,请参见 Section 59.5

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

重新检查先前返回的元组是否仍然匹配相关的扫描和连接限定符,并可能提供元组的修改版本。对于不执行连接下推的外部数据包装器,通常将此设置为 NULL 而相应地设置 fdw_recheck_quals 更方便。然而,当连接外连接被下推时,重新应用与所有基本表相关的检查而只对结果元组仍是不够的,即使所有必需的属性都存在,因为未能匹配某些限定符可能会导致某些属性变为 NULL,而不是不返回元组。RecheckForeignScan 可以重新检查限定符,如果它们仍满足条件则返回 true,否则返回 false,但它也可以将替换元组存储到提供的槽中。

为了实现连接下推,外部数据包装器通常会构造一个仅用于重新检查的替代本地连接计划;这将成为 ForeignScan 的外部子计划。当需要重新检查时,可以执行此子计划,并且可以将结果元组存储在槽中。此计划不必是高效的,因为没有基本表会返回多于一行的内容;例如,它可以将所有连接都实现为嵌套循环。函数 GetExistingLocalJoinPath 可用于搜索现有路径以查找合适的本地连接路径,该路径可用作替代本地连接计划。GetExistingLocalJoinPath 在指定连接关系的路径列表中搜索一个未参数化的路径。(如果它找不到这样的路径,它将返回 NULL,在这种情况下,外部数据包装器可以自己构建本地路径,也可以选择不为此连接创建访问路径。)

59.2.7. FDW Routines for EXPLAIN #

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 期间不会打印额外信息。

59.2.8. FDW Routines for ANALYZE #

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 设置为零。)

59.2.9. FDW Routines for IMPORT FOREIGN SCHEMA #

List *
ImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);

获取外部表创建命令的列表。在执行 IMPORT FOREIGN SCHEMA 时调用此函数,并为该语句传递解析树以及要使用的外部服务器的 OID。它应该返回一个 C 字符串列表,其中每个字符串都必须包含一个 CREATE FOREIGN TABLE 命令。核心服务器将解析并执行这些字符串。

ImportForeignSchemaStmt struct 中,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

59.2.10. FDW Routines for Parallel Execution #

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 段消失前执行某些操作的外键数据封装程序应实现此方法。

59.2.11. FDW Routines for Asynchronous Execution #

ForeignScan 节点可以选择支持异步执行,如 src/backend/executor/README 所述。以下函数均为可选的,但如果要支持异步执行,则都需要。

bool
IsForeignPathAsyncCapable(ForeignPath *path);

测试给定的 ForeignPath 路径是否能够异步扫描底层外键关系。仅当给定路径是 AppendPath 路径的子级且规划器认为异步执行可提高性能时,此函数才会在查询规划结束时调用,并且如果给定的路径能够异步扫描外键关系,则应返回 true。

如果未定义此函数,则假定给定的路径使用 IterateForeignScan 扫描外键关系。(这意味着将永远不会调用下面描述的回调函数,因此也无需提供它们。)

void
ForeignAsyncRequest(AsyncRequest *areq);

ForeignScan 节点方式异步生成一个元组。areq 是描述 ForeignScan 节点及其父 Append 节点(向其请求元组)的 AsyncRequest 结构。此函数应将元组存储到 areq→result 指定的槽中,并将 areq→request_complete 设置为 true;或如果它需要等待核心服务器外部的事件(例如网络 I/O),并且无法立即生成任何元组,将标志设置为 false,并将 areq→callback_pending 设置为 true,以便 ForeignScan 节点从下面描述的回调函数获得回调。如果没有更多元组可用,则将槽设置为 NULL 或空槽,并将 areq→request_complete 标志设置为 true。建议在 areq 中使用 ExecAsyncRequestDoneExecAsyncRequestPending 设置输出参数。

void
ForeignAsyncConfigureWait(AsyncRequest *areq);

配置文件描述符事件,ForeignScan 节点希望等待该事件。仅当 ForeignScan 节点设置了 areq→callback_pending 标志时才调用此函数,并且应将事件添加到 areq 描述的父 Append 节点的 as_eventset。有关更多信息,请参阅 src/backend/executor/execAsync.cExecAsyncConfigureWait 的注释。发生文件描述符事件时,将调用 ForeignAsyncNotify

void
ForeignAsyncNotify(AsyncRequest *areq);

处理已发生的相关事件,然后以 ForeignScan 节点方式异步生成一个元组。此函数应以与 ForeignAsyncRequest 相同的方式在 areq 中设置输出参数。

59.2.12. FDW Routines for Reparameterization of Paths #

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

此函数在将由给定子关系 child_rel 的最顶层父级参数化的路径转换为由子关系参数化时调用。该函数用于重新参数化任何路径或转换保存在给定 ForeignPath 成员中的 fdw_private 中的任何表达式节点。回调可以根据需要使用 reparameterize_path_by_childadjust_appendrel_attrsadjust_appendrel_attrs_multilevel