FDW 处理器函数返回一个通过 palloc 分配的FdwRoutine结构体,其中包含下文描述的回调函数指针。与扫描相关的函数是必需的,其余则是可选的。
FdwRoutine结构体类型声明在src/include/foreign/fdwapi.h中,其中还有更多细节。
void
GetForeignRelSize(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
获取外部表的关系大小估计值。在开始规划扫描外部表的查询时,会调用此函数。root 是规划器中关于查询的全局信息;baserel 是规划器中关于此表的信息;foreigntableid 是该外部表的 pg_class OID。(foreigntableid 可以从规划器的数据结构中取得,但为了方便,这里显式传入。)
此函数应更新 baserel->rows,使其表示考虑限制条件过滤后,表扫描预计返回的行数。baserel->rows 的初始值只是一个固定的默认估计,应尽可能替换。如果能更准确地估计结果行的平均宽度,该函数也可以更新 baserel->width。(初始值基于列的数据类型,以及上次 ANALYZE 测得的列平均宽度。)此外,如果能更准确地估计外部表的总行数,该函数可以更新 baserel->tuples。(初始值来自 pg_class.reltuples,表示上次 ANALYZE 所见的总行数。)
更多信息请见Section 56.4。
void
GetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
为外部表扫描创建可能的访问路径。此函数在查询规划期间调用,参数与先前已经调用过的 GetForeignRelSize 相同。
这个函数必须为外部表上的扫描生成至少一个访问路径(ForeignPath 节点),并且必须调用add_path把每一个这样的路径加入到baserel->pathlist中。推荐使用create_foreignscan_path来构造ForeignPath节点。该函数可以生成多个访问路径,例如一个具有合法pathkeys的路径可以表示预排序的结果。每个访问路径都必须包含代价估计,并且可以包含用于标识预期扫描方法的任意 FDW 私有信息。
更多信息请见Section 56.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节点。
更多信息请见Section 56.4。
void
BeginForeignScan(ForeignScanState *node,
int eflags);
开始执行外部扫描。执行器启动时会调用此函数。它应完成扫描开始前所需的初始化,但不应开始实际扫描(应等到首次调用 IterateForeignScan 时才开始)。ForeignScanState 节点已经创建,但其 fdw_state 字段仍为 NULL。可以通过 ForeignScanState 节点取得待扫描表的信息(特别是底层的 ForeignScan 计划节点,其中的 FDW 私有信息来自 GetForeignPlan)。 eflags 包含描述执行器针对该计划节点的运行模式的标志位。
注意,当(eflags & EXEC_FLAG_EXPLAIN_ONLY)为真时,这个函数不应执行任何外部可见的动作;它只应做使节点状态对ExplainForeignScan和EndForeignScan有效所需的最少工作。
TupleTableSlot * IterateForeignScan(ForeignScanState *node);
从外部数据源取得一行,并放入元组表槽中返回(应使用节点的 ScanTupleSlot)。如果没有更多行,则返回 NULL。元组表槽机制允许返回物理元组或虚拟元组;从性能角度看,大多数情况下后者更合适。注意,此函数在短期内存上下文中调用,该上下文会在各次调用之间重置。如果需要更长久的存储,请在 BeginForeignScan 中创建内存上下文,或者使用 es_query_cxt,它属于节点的 EState。
如果提供了fdw_scan_tlist目标列表,则返回的行必须与之匹配;否则,它们必须匹配被扫描外部表的行类型。如果选择优化掉不需要取回的列,则应当在这些列的位置上填入空值,或者生成一个省略这些列的fdw_scan_tlist列表。
注意PostgreSQL的执行器并不在乎被返回的行是否违背了定义在该外部表上的任何约束 — 但是规划器会在乎这一点,并且如果在外部表中有可见行不满足一个约束,规划器可能会错误地优化查询。如果当用户已经声明一个约束应该为真时它却被违背,最合适的处理可能是产生一个错误(就像在数据类型失配的情况下所作的那样)。
void ReScanForeignScan(ForeignScanState *node);
从头重新扫描。注意,扫描所依赖的参数值可能已经改变,因此新扫描不一定返回完全相同的行。
void EndForeignScan(ForeignScanState *node);
结束扫描并释放资源。通常无需专门释放 palloc 分配的内存,但应清理打开的文件、到远程服务器的连接等资源。
如果一个 FDW 支持远程执行外部表连接(而不是先取回两个表的数据再在本地执行连接),它应当提供这个回调函数:
void
GetForeignJoinPaths(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
JoinType jointype,
JoinPathExtraData *extra);
为属于同一外部服务器的两个或多个外部表的连接创建可能的访问路径。此可选函数在查询规划期间调用。与 GetForeignPaths 一样,此函数应生成 ForeignPath 路径,针对给定的 joinrel(使用 create_foreign_join_path 构建),并调用 add_path,将这些路径加入该连接的候选路径集合。但与 GetForeignPaths 不同,此函数不必保证至少创建一条路径,因为始终可以采用本地连接的路径。
注意为相同的连接关系将会重复地调用这个函数用来生成内外关系的不同组合。FDW 需要负责最小化其中重复的工作。
如果某条 ForeignPath 路径被选中用于该连接,它就表示整个连接过程;为组成该连接的各表及其子连接生成的路径将不会再使用。之后对该连接路径的处理,和处理扫描单个外部表的路径大体相同。一个区别是,所得 ForeignScan 计划节点的 scanrelid 应设为零,因为它不代表某个单一关系;相反,ForeignScan 节点的 fs_relids 字段表示被连接的关系集合。(后者由核心规划器代码自动设置,FDW 无需填充。)另一个区别是,由于远程连接的列列表无法从系统目录中找到,FDW 必须用适当的 TargetEntry 节点列表填充 fdw_scan_tlist,表示它在运行时会在返回的元组中提供哪些列。
更多信息请见Section 56.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 的路径,因为预期这些处理是在外部完成的。不过,检查前一个处理步骤先前生成的路径,有助于避免重复的规划工作。)
更多信息请见Section 56.4。
如果一个 FDW 支持可写外部表,那么应根据该 FDW 的需要和能力提供以下部分或全部回调函数:
void
AddForeignUpdateTargets(Query *parsetree,
RangeTblEntry *target_rte,
Relation target_relation);
UPDATE 和 DELETE 操作作用于先前由表扫描函数取得的行。FDW 可能需要行 ID 或主键列值等额外信息,才能准确识别要更新或删除的行。为此,该函数可以添加额外的隐藏目标列,也称为 “junk” 目标列,并加入要从外部表取得的列列表,适用于 UPDATE 或 DELETE。
为此,应向 parsetree->targetList 添加 TargetEntry 项,其中包含要额外取得的值的表达式。每个这样的项都必须标记为 resjunk = true,且必须有不同的 resname,以便在执行时识别。避免使用与 ctid、Nwholerow 或 wholerow 匹配的名称,因为核心系统可能生成这些名称的 junk 列。如果额外表达式比简单的 Var 更复杂,在加入目标列表之前,必须先用 Neval_const_expressions 处理。
虽然此函数在规划期间调用,但提供的信息与其他规划例程得到的信息略有不同。parsetree 是 UPDATE 或 DELETE 命令的语法解析树,而 target_rte 和 target_relation 描述目标外部表。
如果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->plans 或 plan 节点的其他子结构,可使用此值。
更多信息请见Section 56.4。
如果PlanForeignModify指针被设置为NULL,就不会执行额外的规划期动作,传递给BeginForeignModify的 fdw_private 列表将为 NIL。
void
BeginForeignModify(ModifyTableState *mtstate,
ResultRelInfo *rinfo,
List *fdw_private,
int subplan_index,
int eflags);
开始执行外部表修改操作。执行器启动时会调用此例程,它应完成实际修改表之前所需的初始化。随后,会针对每个待插入、更新或删除的元组,分别调用 ExecForeignInsert, ExecForeignUpdate 或 ExecForeignDelete。
mtstate 是正在执行的 ModifyTable 计划节点的整体状态;可通过该结构体访问计划和执行状态的全局数据。rinfo 是描述目标外部表的 ResultRelInfo 结构体。(ResultRelInfo 的 ri_FdwState 字段可供 FDW 存储本次操作所需的任意私有状态。)fdw_private 包含由PlanForeignModify生成的私有数据(如果有的话)。subplan_index 标识这是 ModifyTable 计划节点的哪个目标。eflags 包含描述执行器对该计划节点操作模式的标志位。
注意,当(eflags & EXEC_FLAG_EXPLAIN_ONLY)为真时,这个函数不应执行任何外部可见的动作;它只需做最少的工作,使节点状态对ExplainForeignModify和EndForeignModify有效。
如果BeginForeignModify指针被设置为NULL,在执行器启动期间将不会采取任何动作。
TupleTableSlot *
ExecForeignInsert(EState *estate,
ResultRelInfo *rinfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
向外部表插入一个元组。estate 是查询的全局执行状态。rinfo 是描述目标外部表的 ResultRelInfo 结构。slot 包含待插入的元组,与外部表的行类型定义一致。planSlot 包含由 ModifyTable 计划节点的子计划生成的元组;它与 slot 的不同之处在于可能包含额外的 “junk” 列。(planSlot 对于 INSERT 通常没有太大用处,但为了完整性仍会提供。)
返回值可以是一个包含实际被插入数据的槽(例如,触发器动作可能使其不同于所提供的数据),或者为 NULL,表示实际上没有插入任何行(通常也是触发器导致的)。传入的slot可重用于这一目的。
返回槽中的数据只会在以下情况下使用:INSERT 语句带有 RETURNING 子句,或者涉及带有 WITH CHECK OPTION 的视图;又或者外部表具有 AFTER ROW 触发器。 触发器需要所有列,但 FDW 可以根据 RETURNING 子句或 WITH CHECK OPTION 约束的内容,选择优化掉部分或全部返回列。 不管怎样,某些槽必须被返回来指示成功,或者查询报告的行计数将会是错误的。
如果ExecForeignInsert指针被设置为NULL,尝试向外部表插入将会失败并报告一个错误消息。
注意,将路由后的元组插入外部表分区,或对外部表执行 COPY FROM 时,也会调用此函数,但调用方式与 INSERT 情况不同。下文介绍了使 FDW 支持这些情况的回调函数。
TupleTableSlot *
ExecForeignUpdate(EState *estate,
ResultRelInfo *rinfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
更新外部表中的一个元组。estate 是查询的全局执行状态。rinfo 是描述目标外部表的 ResultRelInfo 结构。slot 包含元组的新数据,与外部表的行类型定义一致。planSlot 包含由 ModifyTable 计划节点的子计划生成的元组;它与 slot 的不同之处在于可能包含额外的 “junk” 列。特别是,可以从此槽中取得由 AddForeignUpdateTargets 请求的所有 junk 列。
返回值可以是一个包含实际被更新数据的槽(例如,触发器动作可能导致它与提供的数据不同),或者为 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 请求的 junk 列。必须使用这些 junk 列来识别待删除的元组。
返回值可以是一个包含实际被删除行的槽,也可以是 NULL,表示实际上没有删除任何行(通常是触发器导致的)。传入的slot可被用来保存待返回的元组。
返回槽中的数据只会在以下情况下使用:DELETE 查询带有 RETURNING 子句,或者外部表具有 AFTER ROW 触发器。 触发器需要所有列,但 FDW 可以根据 RETURNING 子句的内容,选择优化掉部分或全部返回列。 不管怎样,某些槽必须被返回来指示成功,或者查询报告的行计数将会是错误的。
如果ExecForeignDelete指针被设置为NULL,尝试从外部表中删除将会失败并报告一个错误消息。
void
EndForeignModify(EState *estate,
ResultRelInfo *rinfo);
结束表更新并释放资源。通常无需专门释放 palloc 分配的内存,但应清理打开的文件、到远程服务器的连接等资源。
如果EndForeignModify指针被设置为NULL,在执行器关闭期间不会采取任何动作。
被INSERT或者COPY FROM插入到分区表中的元组会被路由到分区。如果一个FDW支持可路由的外部表分区,它还应该提供下面的回调函数。当在外部表上执行COPY FROM时,也会调用这些函数。
void
BeginForeignInsert(ModifyTableState *mtstate,
ResultRelInfo *rinfo);
开始在外部表上执行插入操作。无论外部表是元组路由选中的分区,还是 COPY FROM 命令指定的目标,都会在第一个元组插入该外部表之前调用此例程。它应完成实际插入之前所需的初始化。随后,会针对每个待插入外部表的元组调用 ExecForeignInsert。
mtstate 是正在执行的 ModifyTable 计划节点的整体状态;可通过该结构体访问计划和执行状态的全局数据。rinfo 是描述目标外部表的 ResultRelInfo 结构体。(ResultRelInfo 的 ri_FdwState 字段可供 FDW 存储本次操作所需的任意私有状态。)
当此函数由 COPY FROM 命令调用时,与计划相关的全局数据 mtstate 不会提供;随后对每个插入元组调用 ExecForeignInsert 时,其 planSlot 参数都是 NULL,无论该外部表是作为元组路由选中的分区,还是命令中指定的目标。
如果BeginForeignInsert指针被设置为NULL,则不会执行初始化操作。
请注意,如果 FDW 不支持可路由的外部表分区和/或在外部表上执行 COPY FROM,则这个函数或随后对 ExecForeignInsert 的调用必须在需要时抛出错误。
void
EndForeignInsert(EState *estate,
ResultRelInfo *rinfo);
结束插入操作并且释放资源。通常释放palloc的内存并不重要,但是打开的文件和与远程服务器的连接应该被清除。
如果EndForeignInsert指针被设置为NULL,则不会执行终止操作。
int IsForeignRelUpdatable(Relation rel);
报告指定外部表支持哪些更新操作。返回值应为规则事件编号的位掩码,用于表示外部表支持的操作,采用 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 字段必须设为相应的 CmdType 枚举值:UPDATE 对应 CMD_UPDATE,INSERT 对应 CMD_INSERT,DELETE 对应 CMD_DELETE。
更多信息请见Section 56.4。
如果PlanDirectModify指针被设置为NULL,不会尝试在远程服务器上执行直接修改。
void
BeginDirectModify(ForeignScanState *node,
int eflags);
准备在远程服务器上执行直接修改。执行器启动时会调用此函数。它应完成直接修改之前所需的初始化(实际修改应等到首次调用 IterateDirectModify 时才执行)。ForeignScanState 节点已经创建,但其 fdw_state 字段仍为 NULL。可以通过 ForeignScanState 节点取得待修改表的信息(特别是底层的 ForeignScan 计划节点,其中的 FDW 私有信息来自 PlanDirectModify)。 eflags 包含描述执行器针对该计划节点的运行模式的标志位。
注意,当(eflags & EXEC_FLAG_EXPLAIN_ONLY)为真时,这个函数不应执行任何外部可见的动作。它只应做使该节点状态对ExplainDirectModify和EndDirectModify有效所需的最少工作。
如果BeginDirectModify指针被设置为NULL,不会尝试在远程服务器上执行直接修改。
TupleTableSlot * IterateDirectModify(ForeignScanState *node);
当 INSERT, UPDATE 或 DELETE 查询没有 RETURNING 子句时,在远程服务器上直接执行修改后,返回 NULL 即可。如果查询包含该子句,则应取得一条包含 RETURNING 计算所需数据的结果,并将其放入元组表槽中返回(应使用节点的 ScanTupleSlot)。实际插入、更新或删除的数据,必须存入 es_result_relation_info->ri_projectReturning->pi_exprContext->ecxt_scantuple,该字段属于节点的 EState。如果没有更多行,返回 NULL。注意,此函数在短期内存上下文中调用,该上下文会在各次调用之间重置。如果需要更长久的存储,请在 BeginDirectModify 中创建内存上下文,或者使用 es_query_cxt,该字段属于节点的 EState。
如果提供了fdw_scan_tlist目标列表,则被返回的行必须匹配它。否则,被返回的行必须匹配被更新的外部表的行类型。如果选择优化掉RETURNING计算不需要的列,应该在这些列的位置上插入空值,或者生成一个忽略这些列的fdw_scan_tlist列表。
无论查询是否带有该子句,查询报告的行数都必须由 FDW 自行递增。当查询不带该子句时,在 EXPLAIN ANALYZE 情况下,FDW 还必须递增 ForeignScanState 节点上的行计数。
如果IterateDirectModify指针被设置为NULL,不会尝试在远程服务器上执行直接修改。
void EndDirectModify(ForeignScanState *node);
在远程服务器上直接修改后进行清理。通常无需专门释放 palloc 分配的内存,但应清理打开的文件、到远程服务器的连接等资源。
如果EndDirectModify指针被设置为NULL,不会尝试在远程服务器上执行直接修改。
如果一个 FDW 希望支持晚期行锁定(如Section 56.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,因此也不必提供它)。
更多信息请见Section 56.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 56.5。
bool
RecheckForeignScan(ForeignScanState *node,
TupleTableSlot *slot);
重新检查先前返回的元组是否仍满足相关扫描和连接条件,并可能提供该元组的修改版本。对于不执行连接下推的外部数据包装器,通常将此项设为 NULL,并适当设置 fdw_recheck_quals 会更方便。不过,如果下推了外连接,仅对结果元组重新检查所有基础表的相关条件还不够,即使所需属性都在也是如此,因为不满足某个条件时,可能应将某些属性设为 NULL,而不是不返回元组。RecheckForeignScan 可以重新检查条件,仍满足时返回 true,否则返回 false;它也可以将替代元组存入所提供的槽。
要实现连接下推,外部数据包装器通常会构造一个替代性的本地连接计划,它只用于重新检查。这将成为 ForeignScan 的外层子计划。在需要执行重检查时,可以执行这个子计划,并把结果元组存入槽中。该计划不必很高效,因为不会有任何基表返回超过一行。例如,它可以把所有连接都实现为嵌套循环。函数 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 数组。必须返回实际收集的行数。此外,还应将表中存活行和死行总数的估计值,分别存入输出参数 totalrows 和 totaldeadrows。(如果 FDW 没有死行的概念,则将 totaldeadrows 设为零。)
IMPORT FOREIGN SCHEMA的 FDW 例程 #List * ImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);
取得外部表创建命令的列表。执行 IMPORT FOREIGN SCHEMA 时会调用此函数,传入该语句的语法解析树和要使用的外部服务器的 OID。它应返回一个 C 字符串列表,每个字符串必须包含一条 CREATE FOREIGN TABLE 命令。这些字符串将由核心服务器解析并执行。
在ImportForeignSchemaStmt结构体中,remote_schema是要从其中导入这些表的远程模式的名称。list_type标识如何过滤表名:FDW_IMPORT_SCHEMA_ALL表示该远程模式中的所有表都应该被导入(这种情况下table_list为空),FDW_IMPORT_SCHEMA_LIMIT_TO表示只包括table_list中列出的表,而FDW_IMPORT_SCHEMA_EXCEPT则表示排除table_list中列出的表。options是一个用于该导入处理的选项列表。选项的含义由 FDW 决定。例如,一个 FDW 可以用一个选项来定义是否应该导入列的NOT NULL属性。这些选项不需要与那些 FDW 支持的数据库对象选项有什么关系。
FDW 可以忽略 ImportForeignSchemaStmt 的 local_schema 字段,因为核心服务器会自动把该名称插入解析后的 CREATE FOREIGN TABLE 命令中。
FDW 也不必担心实现list_type以及table_list所指定的过滤,因为核心服务器将自动根据那些选项跳过为被排除的表所返回的命令。不过,起初就避免为被排除的表创建命令当然更好。函数IsImportableForeignTable()可以用来测试一个给定的外部表名是否能通过该过滤器。
如果 FDW 不支持导入表定义,ImportForeignSchema指针可以被设置为NULL。
ForeignScan节点可以选择支持并行执行。一个并行的ForeignScan会在多个进程中执行,并且每个元组在这些协作进程中必须只返回一次。为做到这一点,各进程可以通过固定大小的动态共享内存块协作。并不保证这部分共享内存在每个进程中都映射到相同地址,因此其中不能包含指针。下面这些函数通常都是可选的,但若要支持并行执行,就必须提供其中的大部分。
bool
IsForeignScanParallelSafe(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte);
测试某个扫描是否可以在并行工作进程中执行。只有当规划器认为可以使用并行计划时才会调用这个函数;如果该扫描在并行工作进程中安全,此函数应返回真。如果远程数据源具有事务语义,通常这并不成立,除非工作进程到该数据源的连接能够以某种方式共享与领导者相同的事务环境。
如果没有定义这个函数,则假定该扫描必须放在并行领导者中。注意,返回真并不意味着该扫描本身可以并行完成,只是说明它可以在并行工作进程中执行。因此,即便不支持并行执行,定义这个方法也可能有用。
Size EstimateDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt);
估算并行操作所需的动态共享内存的数量。这可能比实际要用的数量更大,但是绝不能更小。返回值的单位是字节。这个函数是可选的,并且在不需要时可以省略。但是如果它被省略,接下来的三个函数也必须被省略,因为不会为FDW分配共享内存。
void
InitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
void *coordinate);
初始化并行操作所需的动态共享内存。coordinate指向一块共享内存区域,其尺寸等于EstimateDSMForeignScan的返回值。这个函数是可选的,并且在不需要时可以省略。
void
ReInitializeDSMForeignScan(ForeignScanState *node, ParallelContext *pcxt,
void *coordinate);
当外部扫描计划将要被重新扫描时,重新初始化并行操作所要求的动态共享内存。这个函数是可选的,并且在不需要时可以省略。推荐的措施是这个函数只重置共享状态,而ReScanForeignScan函数仅重置本地状态。当前,这个函数将在ReScanForeignScan之前被调用,但是最好不要依赖于这种顺序。
void
InitializeWorkerForeignScan(ForeignScanState *node, shm_toc *toc,
void *coordinate);
基于领导者在InitializeDSMForeignScan期间建立的共享状态初始化并行工作者的本地状态。这个函数是可选的,并且在不需要时可以省略。
void ShutdownForeignScan(ForeignScanState *node);
在预期该节点不会执行到完成时释放资源。这个函数并不会在所有情况下都被调用;有时可能会在没有先调用它的情况下直接调用EndForeignScan。由于并行查询使用的 DSM 段会在这个回调被调用后立即销毁,因此希望在 DSM 段消失前执行某些动作的外部数据包装器应当实现这个方法。
List *
ReparameterizeForeignPathByChild(PlannerInfo *root, List *fdw_private,
RelOptInfo *child_rel);
这个函数在把一个按给定子关系 child_rel 的最顶层父关系参数化的路径转换为按该子关系本身参数化时调用。它用于重参数化保存在给定 ForeignPath 的 fdw_private 成员中的任意路径,或者调整其中保存的任意表达式节点。该回调可按需使用 reparameterize_path_by_child、adjust_appendrel_attrs 或 adjust_appendrel_attrs_multilevel。