Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17 / 18

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

58.2.1. 用于扫描外部表的FDW例程
58.2.2. 用于扫描外部表连接的 FDW 例程
58.2.3. 用于规划扫描/连接后处理的 FDW 例程
58.2.4. 更新外部表的FDW例程
58.2.5. TRUNCATE 的 FDW 例程
58.2.6. 用于行锁定的 FDW 例程
58.2.7. EXPLAIN的FDW例程
58.2.8. ANALYZE的FDW例程
58.2.9. IMPORT FOREIGN SCHEMA的 FDW 例程
58.2.10. 并行执行的 FDW 例程
58.2.11. 异步执行的 FDW 例程
58.2.12. 用于路径重新参数化的 FDW 例程

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

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

58.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 看到的总行数;如果其值为 -1,则表示该外部表尚未执行过 ANALYZE。)

更多信息请见Section 58.4

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

为一个外部表上的扫描创建可能的访问路径。这个函数在查询规划过程中被调用。参数和GetForeignRelSize相同,后者已经被调用过了。

这个函数必须为外部表上的扫描生成至少一个访问路径(ForeignPath 节点),并且必须调用add_path把每一个这样的路径加入到baserel->pathlist中。推荐使用create_foreignscan_path来构造ForeignPath节点。该函数可以生成多个访问路径,例如一个具有合法pathkeys的路径可以表示预排序的结果。每个访问路径都必须包含代价估计,并且可以包含用于标识预期扫描方法的任意 FDW 私有信息。

更多信息请见Section 58.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 执行重检查。(如果该路径针对的是连接而不是基本关系,foreigntableid 将是 InvalidOid。)

这个函数必须创建并返回一个ForeignScan计划节点,推荐使用make_foreignscan来构造该ForeignScan节点。

更多信息请见Section 58.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 中创建内存上下文,或者使用该节点的 es_query_cxt(位于 EState 中)。

如果提供了fdw_scan_tlist目标列表,则返回的行必须与之匹配;否则,它们必须匹配被扫描外部表的行类型。如果选择优化掉不需要取回的列,则应当在这些列的位置上填入空值,或者生成一个省略这些列的fdw_scan_tlist列表。

注意PostgreSQL的执行器并不在乎被返回的行是否违背了定义在该外部表上的任何约束 — 但是规划器会在乎这一点,并且如果在外部表中有可见行不满足一个约束,规划器可能会错误地优化查询。如果当用户已经声明一个约束应该为真时它却被违背,最合适的处理可能是产生一个错误(就像在数据类型失配的情况下所作的那样)。

void
ReScanForeignScan(ForeignScanState *node);

从头开始重启一个扫描。注意扫描所依赖的任何参数可能已经改变了值,因此新扫描不一定会返回完全相同的行。

void
EndForeignScan(ForeignScanState *node);

结束扫描并释放资源。通常无需释放用 palloc 分配的内存,但例如打开的文件和到远程服务器的连接应当清理。

58.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 的形式传入,它会随着内外关系组合的不同而变化。该 ForeignPath 路径(针对 joinrel 生成)必须包含它所使用的那组连接子句;如果规划器将该 ForeignPath 路径选作 joinrel 的最佳路径,就会使用这些信息把它转换成计划。

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

Note

PostgreSQL 16 开始,如果该连接中涉及外连接,fs_relids 将包含这些外连接的范围表索引。新增字段 fs_base_relids 则只包含基本关系索引,因此模拟了 fs_relids 旧有的语义。

更多信息请见Section 58.4

58.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_rel 中的 ForeignPath 路径通常不会直接依赖于 input_rel 的路径,因为预期这些处理是在外部完成的。不过,检查前一个处理步骤先前生成的路径,有助于避免重复的规划工作。)

更多信息请见Section 58.4

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

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

void
AddForeignUpdateTargets(PlannerInfo *root,
                        Index rtindex,
                        RangeTblEntry *target_rte,
                        Relation target_relation);

UPDATEDELETE操作是在先前由表扫描函数取回的行上执行的。FDW 可能需要额外的信息(例如行 ID 或主键列的值)来保证它能够定位要更新或删除的准确行。为支持这一点,这个函数可以向将在 UPDATEDELETE 期间从外部表取回的列列表中添加额外的隐藏列,也就是junk目标列。

为此,构造一个表示所需额外值的 Var,并将它连同 junk 列名一起传给 add_row_identity_var。(如果需要多个列,可以多次这样做。)你必须为每个不同的 Var 选择不同的 junk 列名,但如果若干个 Var 除了 varno 字段外完全相同,则它们可以也应该共享列名。核心系统使用下列 junk 列名:表的 tableoid 列使用 tableoidctid 使用 ctidctidN;带有 vartype = RECORD 标记的整行 Var 使用 wholerow;而 vartype 等于该表声明行类型的整行 Var 使用 wholerowN。能复用这些名称时应尽量复用(规划器会合并对相同 junk 列的重复请求)。如果你还需要这些之外的其他 junk 列,最好选择一个以前缀为你的扩展名的名称,以避免与其他 FDW 冲突。

如果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节点的逐目标关系子结构中,请使用它。

更多信息请见Section 58.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)为真时,这个函数不应执行任何外部可见的动作;它只需做最少的工作,使节点状态对ExplainForeignModifyEndForeignModify有效。

如果BeginForeignModify指针被设置为NULL,在执行器启动期间将不会采取任何动作。

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

向外部表中插入一个元组。estate 是该查询的全局执行状态。rinfo 是描述目标外部表的 ResultRelInfo 结构。slot 包含要插入的元组;它将匹配该外部表定义的行类型。planSlot 包含由 ModifyTable 计划节点的子计划生成的元组;它与 slot 的不同之处在于可能包含额外的 junk 列。(对于 INSERT 情况,planSlot 通常没有太大意义,但为了完整性仍然提供它。)

返回值可以是一个包含实际被插入数据的槽(例如,触发器动作可能使其不同于所提供的数据),或者为 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。 如果INSERTRETURNING子句,这个函数不被使用。

请注意,把路由后的元组插入外部表分区,或者在外部表上执行 COPY FROM 时,也会调用这个函数;在这些情况下,它的调用方式与 INSERT 时有所不同。 请参阅下面所述的允许 FDW 支持的回调函数。

int
GetForeignModifyBatchSize(ResultRelInfo *rinfo);

报告一次ExecForeignBatchInsert调用能够为指定外部表处理的最大元组数。 执行器会向ExecForeignBatchInsert传递最多不超过该数量的元组。 rinfo是描述目标外部表的ResultRelInfo结构体。 FDW 应为用户提供一个外部服务器和/或外部表选项来设置这个值,或者提供某个硬编码值。

如果ExecForeignBatchInsertGetForeignModifyBatchSize指针被设置为NULL,那么对外部表的插入将使用ExecForeignInsert

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

更新外部表中的一个元组。 estate是查询的全局执行状态。 rinfo是描述目标外部表的ResultRelInfo结构。 slot包含元组的新数据;它将匹配外部表的行类型定义。 planSlot包含由ModifyTable计划节点的子计划生成的元组。 与slot不同,这个元组只包含查询所修改列的新值,因此不要依赖外部表的属性号去索引planSlot。 此外,planSlot通常包含额外的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命令的目标时,在第一个元组被插入该外部表之前会调用该例程。 它应该执行实际插入之前所需的任何初始化工作。 随后,为每一个被插入到该外部表的元组都将调用ExecForeignInsertExecForeignBatchInsert

mtstate 是正在执行的 ModifyTable 计划节点的整体状态;可通过该结构访问计划和执行状态的全局数据。rinfo 是描述目标外部表的 ResultRelInfo 结构。(ResultRelInfori_FdwState 字段可供 FDW 存储本次操作所需的任意私有状态。)

当此函数由 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提供了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 枚举值(也就是 UPDATE 对应 CMD_UPDATEINSERT 对应 CMD_INSERTDELETE 对应 CMD_DELETE),并且必须把 resultRelation 参数复制到 resultRelation 字段中。

更多信息请见Section 58.4

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

void
BeginDirectModify(ForeignScanState *node,
                  int eflags);

准备在远程服务器上执行直接修改。这个函数会在执行器启动时被调用。它应执行直接修改开始前所需的任何初始化工作;实际的直接修改应在第一次调用IterateDirectModify时进行。ForeignScanState节点已经创建,但其fdw_state字段仍为 NULL。有关待修改表的信息可通过ForeignScanState节点访问,特别是底层的ForeignScan计划节点,其中包含了由PlanDirectModify提供的任何 FDW 私有信息。eflags包含描述执行器对该计划节点操作模式的标志位。

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

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

TupleTableSlot *
IterateDirectModify(ForeignScanState *node);

INSERTUPDATEDELETE 查询没有 RETURNING 子句时,在远程服务器上完成直接修改后只需返回 NULL。当查询具有该子句时,需要取回一条结果,其中包含 RETURNING 计算所需的数据,并将其放入元组表槽中返回(应使用该节点的 ScanTupleSlot)。实际被插入、更新或删除的数据必须存放在 node->resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple 中。如果没有更多可用行,则返回 NULL。注意,此函数是在一个短生命周期的内存上下文中调用的,该上下文会在两次调用之间被重置。如果需要更长寿命的存储,请在 BeginDirectModify 中创建内存上下文,或者使用该节点 EStatees_query_cxt

如果提供了fdw_scan_tlist目标列表,则被返回的行必须匹配它。否则,被返回的行必须匹配被更新的外部表的行类型。如果选择优化掉RETURNING计算不需要的列,应该在这些列的位置上插入空值,或者生成一个忽略这些列的fdw_scan_tlist列表。

无论查询是否带有该子句,查询报告的行数都必须由 FDW 自行递增。当查询不带该子句时,在 EXPLAIN ANALYZE 情况下,FDW 还必须递增 ForeignScanState 节点上的行计数。

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

void
EndDirectModify(ForeignScanState *node);

在远程服务器上的直接修改后进行清理。通常释放用 palloc 分配的内存并不重要,但是诸如打开的文件和到远程服务器的连接应该被清除。

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

58.2.5. TRUNCATE 的 FDW 例程 #

void
ExecForeignTruncate(List *rels,
                    DropBehavior behavior,
                    bool restart_seqs);

截断外部表。当在外部表上执行TRUNCATE时会调用这个函数。rels 是待截断外部表的 Relation 数据结构列表。

behavior 的值要么是 DROP_RESTRICT,要么是 DROP_CASCADE,分别表示原始 TRUNCATE 命令请求了 RESTRICTCASCADE 选项。

如果restart_seqstrue,原始的TRUNCATE命令需要RESTART IDENTITY行为,否则将需要CONTINUE IDENTITY行为。

注意,原始 TRUNCATE 命令中指定的 ONLY 选项不会传递给 ExecForeignTruncate。这种行为类似于外部表上的 SELECTUPDATEDELETE 的回调函数。

对于每个需要截断外部表的外部服务器,ExecForeignTruncate 都会被调用一次。 这意味着 rels 中包含的所有外部表都必须属于同一个服务器。

如果ExecForeignTruncate指针被设置为NULL,那么截断外部表的尝试将失败并报出错误消息。

58.2.6. 用于行锁定的 FDW 例程 #

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

RowMarkType
GetForeignRowMarkType(RangeTblEntry *rte,
                      LockClauseStrength strength);

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

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

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

更多信息请见Section 58.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 58.5

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

重新检查之前返回的元组是否仍然匹配相关的扫描和连接条件,并且可能提供该元组的一个修改版本。对于不执行连接下推的外部数据包装器,通常把这设置为NULL并且恰当地设置fdw_recheck_quals会更方便。不过当外连接被下推时,把与所有基表相关的检查重新应用在结果元组上是不够的,即便所有需要的属性都存在也是如此,因为某个条件不匹配的结果可能是某些属性变成 NULL,而不是完全不返回该元组。RecheckForeignScan可以重新检查这些条件,并在它们仍然满足时返回真,否则返回假;它也可以把一个替换元组存入所提供的槽中。

要实现连接下推,外部数据包装器通常会构造一个替代性的本地连接计划,它只用于重新检查。这将成为 ForeignScan 的外层子计划。在需要执行重检查时,可以执行这个子计划,并把结果元组存入槽中。该计划不必很高效,因为不会有任何基表返回超过一行。例如,它可以把所有连接都实现为嵌套循环。函数 GetExistingLocalJoinPath 可用于在已有路径中搜索合适的本地连接路径,并将其用作替代性的本地连接计划。GetExistingLocalJoinPath 会在指定连接关系的路径列表中搜索一个非参数化路径(如果找不到这样的路径,它将返回 NULL;在这种情况下,外部数据包装器可以自行构造本地路径,或者选择不为该连接创建访问路径)。

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

58.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数组中。实际被收集的行的数量必须被返回。另外,将表中有效行和死亡行的总数存储到输出参数totalrowstotaldeadrows中(如果FDW没有死亡行的概念,将totaldeadrows设置为 0 )。

58.2.9. 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

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

58.2.11. 异步执行的 FDW 例程 #

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

bool
IsForeignPathAsyncCapable(ForeignPath *path);

测试给定的ForeignPath路径是否可以异步扫描底层外部关系。 当给定路径是AppendPath路径的直接子路径,并且规划器认为异步执行提高性能时,才会在查询计划结束时调用该函数。 如果给出的路径能够异步扫描外部关系,则将返回真。

如果这个函数没有被定义,则假定给定的路径使用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。 建议使用ExecAsyncRequestDoneExecAsyncRequestPending来设置areq中的输出参数。

void
ForeignAsyncConfigureWait(AsyncRequest *areq);

配置一个 ForeignScan 节点希望等待的文件描述符事件。只有当该 ForeignScan 节点的 areq->callback_pending 标志已被设置时,才会调用这个函数;它应当把该事件添加到 areq 所描述的父 Append 节点的 as_eventset 中。更多信息请参阅 src/backend/executor/execAsync.cExecAsyncConfigureWait 的注释。文件描述符事件发生时,将调用 ForeignAsyncNotify

void
ForeignAsyncNotify(AsyncRequest *areq);

处理已经发生的相关事件,然后从ForeignScan节点异步地产生一个元组。 该函数将设置areq 中的输出参数,与ForeignAsyncRequest的方法相同。

58.2.12. 用于路径重新参数化的 FDW 例程 #

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

这个函数在把一个按给定子关系 child_rel 的最顶层父关系参数化的路径转换为按该子关系本身参数化时调用。它用于重参数化保存在给定 ForeignPathfdw_private 成员中的任意路径,或者调整其中保存的任意表达式节点。该回调可按需使用 reparameterize_path_by_childadjust_appendrel_attrsadjust_appendrel_attrs_multilevel