选择 打开 改范围 完整检索页
受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10
当前 PostgreSQL 版本不在支持生命周期内。
您可以参阅当前版本的对应页面,或其他在上面列出的活跃大版本。

56.2. 外部数据包装器回调例程 #

FDW 处理器函数返回一个通过 palloc 分配的FdwRoutine结构体,其中包含下文描述的回调函数指针。与扫描相关的函数是必需的,其余则是可选的。

FdwRoutine结构体类型声明在src/include/foreign/fdwapi.h中,其中还有更多细节。

56.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_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(先前由 GetForeignPathsGetForeignJoinPathsGetForeignUpperPaths 生成)、计划节点应输出的目标列表、计划节点应实施的限制子句,以及外层子计划,其所属节点为 ForeignScan;此外层子计划用于以下回调执行的复查:RecheckForeignScan。(如果路径对应连接而非基础关系,foreigntableidInvalidOid。)

这个函数必须创建并返回一个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)为真时,这个函数不应执行任何外部可见的动作;它只应做使节点状态对ExplainForeignScanEndForeignScan有效所需的最少工作。

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 分配的内存,但应清理打开的文件、到远程服务器的连接等资源。

56.2.2. 扫描外部连接的 FDW 例程 #

如果一个 FDW 支持远程执行外部表连接(而不是先取回两个表的数据再在本地执行连接),它应当提供这个回调函数:

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

为属于同一外部服务器的两个或多个外部表的连接创建可能的访问路径。此可选函数在查询规划期间调用。与 GetForeignPaths 一样,此函数应生成 ForeignPath 路径,针对给定的 joinrel,并调用 add_path,将这些路径加入该连接的候选路径集合。但与 GetForeignPaths 不同,此函数不必保证至少创建一条路径,因为始终可以采用本地连接的路径。

注意为相同的连接关系将会重复地调用这个函数用来生成内外关系的不同组合。FDW 需要负责最小化其中重复的工作。

如果某条 ForeignPath 路径被选中用于该连接,它就表示整个连接过程;为组成该连接的各表及其子连接生成的路径将不会再使用。之后对该连接路径的处理,和处理扫描单个外部表的路径大体相同。一个区别是,所得 ForeignScan 计划节点的 scanrelid 应设为零,因为它不代表某个单一关系;相反,ForeignScan 节点的 fs_relids 字段表示被连接的关系集合。(后者由核心规划器代码自动设置,FDW 无需填充。)另一个区别是,由于远程连接的列列表无法从系统目录中找到,FDW 必须用适当的 TargetEntry 节点列表填充 fdw_scan_tlist,表示它在运行时会在返回的元组中提供哪些列。

更多信息请见Section 56.4

56.2.3. 规划扫描或连接之后处理的 FDW 例程 #

如果一个 FDW 支持执行远程的扫描/连接后处理,例如远程聚合,那么它应该提供这个回调函数:

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

创建可能的访问路径,用于上层关系处理;这是规划器对扫描或连接之后的所有查询处理步骤的称呼,包括聚合、窗口函数、排序和表更新。此可选函数在查询规划期间调用。目前,只有查询涉及的所有基础关系都属于同一个 FDW 时,才会调用它。对于 FDW 能在远程执行的扫描或连接之后的处理,此函数应生成 ForeignPath 路径,并调用 add_path,将这些路径加入指定的上层关系。与 GetForeignJoinPaths 一样,此函数不必保证成功创建任何路径,因为始终可以采用本地处理的路径。

stage 参数标识当前考虑的是扫描或连接之后的哪个步骤。output_rel 是应接收该步骤计算路径的上层关系,input_rel 是表示该步骤输入的关系。(注意,加入 output_relForeignPath 路径通常不会直接依赖 input_rel 的路径,因为这些处理应在外部完成。不过,检查为前一处理步骤生成的路径,有助于避免重复的规划工作。)

更多信息请见Section 56.4

56.2.4. 更新外部表的 FDW 例程 #

如果一个 FDW 支持可写外部表,那么应根据该 FDW 的需要和能力提供以下部分或全部回调函数:

void
AddForeignUpdateTargets (Query *parsetree,
                         RangeTblEntry *target_rte,
                         Relation target_relation);

UPDATEDELETE 操作作用于先前由表扫描函数取得的行。FDW 可能需要行 ID 或主键列值等额外信息,才能准确识别要更新或删除的行。为此,该函数可以添加额外的隐藏目标列,也称为 junk 目标列,并加入要从外部表取得的列列表,适用于 UPDATEDELETE

为此,应向 parsetree->targetList 添加 TargetEntry 项,其中包含要额外取得的值的表达式。每个这样的项都必须标记为 resjunk = true,且必须有不同的 resname,以便在执行时识别。避免使用与 ctidNwholerowwholerowN 匹配的名称,因为核心系统可能生成这些名称的 junk 列。如果额外表达式比简单的 Var 更复杂,在加入目标列表之前,必须先用 eval_const_expressions 处理。

虽然此函数在规划期间调用,但提供的信息与其他规划例程得到的信息略有不同。parsetreeUPDATEDELETE 命令的语法解析树,而 target_rtetarget_relation 描述目标外部表。

如果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->plansplan 节点的其他子结构,可使用此值。

更多信息请见Section 56.4

如果PlanForeignModify指针被设置为NULL,就不会执行额外的规划期动作,传递给BeginForeignModifyfdw_private 列表将为 NIL。

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

开始执行外部表修改操作。执行器启动时会调用此例程,它应完成实际修改表之前所需的初始化。随后,会针对每个待插入、更新或删除的元组,分别调用 ExecForeignInsertExecForeignUpdateExecForeignDelete

mtstate 是正在执行的 ModifyTable 计划节点的整体状态;可通过该结构体访问计划和执行状态的全局数据。rinfo 是描述目标外部表的 ResultRelInfo 结构体。(ResultRelInfori_FdwState 字段可供 FDW 存储本次操作所需的任意私有状态。)fdw_private 包含由PlanForeignModify生成的私有数据(如果有的话)。subplan_index 标识这是 ModifyTable 计划节点的哪个目标。eflags 包含描述执行器对该计划节点操作模式的标志位。

注意,当(eflags & EXEC_FLAG_EXPLAIN_ONLY)为真时,这个函数不应执行任何外部可见的动作;它只需做最少的工作,使节点状态对ExplainForeignModifyEndForeignModify有效。

如果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 子句,或外部表具有 AFTER ROW 触发器时,才会使用返回槽中的数据。触发器需要所有列,但 FDW 可以根据 RETURNING 子句的内容,选择省略部分或全部列的返回,以进行优化。无论如何,都必须返回某个槽来表示成功,否则查询报告的行数会不正确。

如果ExecForeignInsert指针被设置为NULL,尝试向外部表插入将会失败并报告一个错误消息。

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

更新外部表中的一个元组。estate 是查询的全局执行状态。rinfo 是描述目标外部表的 ResultRelInfo 结构。slot 包含元组的新数据,与外部表的行类型定义一致。planSlot 包含由 ModifyTable 计划节点的子计划生成的元组;它与 slot 的不同之处在于可能包含额外的 junk 列。特别是,可以从此槽中取得由 AddForeignUpdateTargets 请求的所有 junk 列。

返回值可以是一个包含实际被更新数据的槽(例如,触发器动作可能导致它与提供的数据不同),或者为 NULL,表示实际上没有更新任何行(通常也是触发器导致的)。传入的slot可重用于这一目的。

只有 UPDATE 查询包含 RETURNING 子句,或外部表具有 AFTER ROW 触发器时,才会使用返回槽中的数据。触发器需要所有列,但 FDW 可以根据 RETURNING 子句的内容,选择省略部分或全部列的返回,以进行优化。无论如何,都必须返回某个槽来表示成功,否则查询报告的行数会不正确。

如果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,在执行器关闭期间不会采取任何动作。

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 计划节点,由它在远程服务器上执行直接修改。ForeignScanoperation 字段必须设为相应的 CmdType 枚举值:UPDATE 对应 CMD_UPDATEINSERT 对应 CMD_INSERTDELETE 对应 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)为真时,这个函数不应执行任何外部可见的动作。它只应做使该节点状态对ExplainDirectModifyEndDirectModify有效所需的最少工作。

如果BeginDirectModify指针被设置为NULL,不会尝试在远程服务器上执行直接修改。

TupleTableSlot *
IterateDirectModify (ForeignScanState *node);

INSERTUPDATEDELETE 查询没有 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,不会尝试在远程服务器上执行直接修改。

56.2.5. 行锁的 FDW 例程 #

如果一个 FDW 希望支持晚期行锁定(如Section 56.5中所述),它必须提供下列回调函数:

RowMarkType
GetForeignRowMarkType (RangeTblEntry *rte,
                       LockClauseStrength strength);

报告外部表应使用哪种行标记选项。rteRangeTblEntry 节点,对应该表;strength 描述相关 FOR UPDATE/SHARE 子句所请求的锁强度(如有)。结果必须是 RowMarkType 枚举类型的成员。

这个函数在查询规划期间会为每一个出现在UPDATEDELETE或者SELECT FOR UPDATE/SHARE查询中的外部表调用,并且该外部表不是UPDATEDELETE的目标。

如果GetForeignRowMarkType指针被设置为NULL,将总是使用ROW_MARK_COPY选项(这意味着将不会调用RefetchForeignRow,因此也不必提供它)。

更多信息请见Section 56.5

HeapTuple
RefetchForeignRow (EState *estate,
                   ExecRowMark *erm,
                   Datum rowid,
                   bool *updated);

从外部表重新取得一个元组,必要时先将其锁定。estate 是查询的全局执行状态。ermExecRowMark 结构,描述目标外部表及需要取得的行锁类型(如有)。rowid 标识要取得的元组。updated 是输出参数。

此函数应返回用 palloc 分配的所取元组的副本;如果无法取得行锁,则返回 NULL。需要取得的行锁类型由 erm->markType 定义,它是先前 GetForeignRowMarkType 返回的值。(ROW_MARK_REFERENCE 表示只重新取得元组,不取得任何锁;此例程不会收到 ROW_MARK_COPY。)

此外,如果取得的是一个更新过的版本而不是之前获得的同一版本,*updated应被设置为true(如果 FDW 无法确定这一点,推荐总是返回true)。

注意,默认情况下,无法取得行锁应抛出错误;只有 erm->waitPolicy 指定了 SKIP LOCKED 选项时,才适合返回 NULL

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;在这种情况下,外部数据包装器可以自行构造本地路径,或者选择不为该连接创建访问路径)。

56.2.6. 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指针被设置为NULLEXPLAIN期间不会打印出额外的信息。

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

56.2.8. 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_type以及table_list所指定的过滤,因为核心服务器将自动根据那些选项跳过为被排除的表所返回的命令。不过,起初就避免为被排除的表创建命令当然更好。函数IsImportableForeignTable()可以用来测试一个给定的外部表名是否能通过该过滤器。

如果 FDW 不支持导入表定义,ImportForeignSchema指针可以被设置为NULL

56.2.9. 并行执行的 FDW 例程 #

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 段消失前执行某些动作的外部数据包装器应当实现这个方法。