PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

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'd 的 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 看到的总行数;如果这个 外部表从未做过 ANALYZE,它会是 -1。)

更多信息见 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(之前由 GetForeignPaths、 GetForeignJoinPaths 或 GetForeignUpperPaths 产生)、 该计划节点要发出的目标列表、 该计划节点要强制执行的限制子句, 以及 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) 为 true 时,这个函数不应当执行任何对外可见的动作; 它应当只做使节点状态对 ExplainForeignScan 和 EndForeignScan 有效的最低限度工作。

TupleTableSlot *
IterateForeignScan(ForeignScanState *node);

从外部源取回一行,把它放在一个元组表槽中返回 (应当使用节点的 ScanTupleSlot 来做这件事)。 如果没有更多行可用,则返回 NULL。 元组表槽基础设施允许返回物理元组或虚拟元组; 从性能角度看,在大多数情况下后者更可取。 注意它是在一个短寿命的内存上下文中被调用的,该上下文会在两次 调用之间被重置。 如果需要更长寿命的存储,请在 BeginForeignScan 中创建一个内存上下文, 或使用节点 EState 的 es_query_cxt。

返回的行必须匹配 fdw_scan_tlist 目标列表(如果提供了 的话),否则必须匹配被扫描外部表的行类型。 如果您选择优化掉对不需要列的取回,应当在这些列的位置插入 空值,或者生成省略了这些列的 fdw_scan_tlist 列表。

注意,PostgreSQL 的执行器并不在乎返回的 行是否违反外部表上定义的任何约束 — 但规划器在乎,如果外部 表中可见的行不满足某个已声明的约束,它可能会把查询优化 错。 当用户已声明约束应当成立而约束被违反时,抛出一个错误可能是 合适的(就像在数据类型不匹配的情况下您需要做的那样)。

void
ReScanForeignScan(ForeignScanState *node);

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

void
EndForeignScan(ForeignScanState *node);

结束扫描并释放资源。 通常,释放 palloc'd 的内存并不重要,但例如打开的文件和到远程 服务器的连接应当被清理。

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 传入)会随内外关系的 组合而变化。 为 joinrel 生成的 ForeignPath 路径必须包含它使用的连接 子句集合;如果规划器把该路径选为 joinrel 的最佳路径,规划器会用这些子句把 ForeignPath 路径转换成计划。

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

Note

从 PostgreSQL 16 开始, fs_relids 包含外连接的 range table 索引(如果本次连接涉及外连接)。 新字段 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_AGG 或 UPPERREL_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);

UPDATE 和 DELETE 操作是 针对之前由表扫描函数取回的行执行的。 FDW 可能需要额外的信息,例如行 ID 或主键列的值, 以确保它能够标识要更新或删除的确切行。 为了支持这一点,该函数可以在 UPDATE 或 DELETE 期间要从外部表取回的列列表中添加额外的 隐藏目标列,即 “junk” 列。

要做到这一点,构造一个代表您需要的额外值的 Var,并把它与 junk 列的名称一起传给 add_row_identity_var。 (如果需要多个列,可以多次这样做。) 对需要的每个不同的 Var,您必须选择一个不同的 junk 列名, 但除了 varno 字段之外完全相同的 Var 可以并且应当共享一个 列名。 核心系统使用的 junk 列名有: 表的 tableoid 列用 tableoid, ctid 用 ctid 或 ctidN, 标记为 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);

执行外部表上的插入、更新或删除所需的任何额外规划动作。 这个函数生成将附加到执行更新动作的 ModifyTable 计划节点的 FDW 私有 信息。 该私有信息必须具有 List 的形式,并会在执行阶段被交给 BeginForeignModify。

root 是规划器关于该查询的全局信息。 plan 是 ModifyTable 计划节点,除了 fdwPrivLists 字段之外它是完整的。 resultRelation 通过 range table 索引标识目标 外部表。 subplan_index 标识这是 ModifyTable 计划节点的哪个目标,从零 计数;如果您想索引 plan 节点的按目标关系划分的子结构,请使用 它。

更多信息见 Section 58.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 的区别在于可能包含额外的 “junk” 列。 (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 通常包含 额外的 “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'd 的内存并不重要,但例如打开的文件和到远程 服务器的连接应当被清理。

如果 EndForeignModify 指针被设为 NULL,则执行器关闭期间不执行任何 动作。

通过 INSERT 或 COPY FROM 插入分区表的元组会被路由到 分区。 如果 FDW 支持可路由的外部表分区,它还应当提供以下回调函数。 在外部表上执行 COPY FROM 时也会调用这些函数。

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

开始执行一个外部表上的插入操作。 无论外部表是被选中做元组路由的分区,还是 COPY FROM 命令中指定的目标,这个例程都会 在第一个元组被插入外部表之前被调用。 它应当执行实际插入之前所需的任何初始化。 随后,对要被插入外部表的元组会调用 ExecForeignInsert 或 ExecForeignBatchInsert。

mtstate 是被执行的 ModifyTable 计划节点的整体状态; 关于计划和执行状态的全局数据可以通过这个结构获得。 rinfo 是描述目标外部表的 ResultRelInfo 结构。 (ResultRelInfo 的 ri_FdwState 字段可供 FDW 存储它为 这个操作需要的任何私有状态。)

当由 COPY FROM 命令调用时, mtstate 中与计划相关的全局数据不会被 提供,随后为每个被插入元组调用的 ExecForeignInsert 的 planSlot 参数为 NULL,无论该外部表是被选中做元组路由的 分区还是命令中指定的目标。

如果 BeginForeignInsert 指针被设为 NULL,则不为初始化执行任何 动作。

注意,如果 FDW 不支持可路由的外部表分区和/或在外部表上执行 COPY FROM,这个函数或随后被调用的 ExecForeignInsert/ExecForeignBatchInsert 必须按需抛出错误。

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

结束插入操作并释放资源。 通常,释放 palloc'd 的内存并不重要,但例如打开的文件和到远程 服务器的连接应当被清理。

如果 EndForeignInsert 指针被设为 NULL,则不为终止执行任何 动作。

int
IsForeignRelUpdatable(Relation rel);

报告指定的外部表支持哪些更新操作。 返回值应当是规则事件号的位掩码,使用 CmdType 枚举来指示外部表支持哪些操作;也就是说, 对 UPDATE 是 (1 << CMD_UPDATE) = 4, 对 INSERT 是 (1 << CMD_INSERT) = 8, 对 DELETE 是 (1 << CMD_DELETE) = 16。

如果 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 字段。

更多信息见 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) 为 true 时,这个函数不应当执行任何对外可见的动作; 它应当只做使节点状态对 ExplainDirectModify 和 EndDirectModify 有效的最低限度工作。

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

TupleTableSlot *
IterateDirectModify(ForeignScanState *node);

当 INSERT、 UPDATE 或 DELETE 查询没有 RETURNING 子句时,在远程服务器上完成直接 修改之后直接返回 NULL。 当查询有该子句时,取回一个包含 RETURNING 计算所需数据的结果, 把它放在一个元组表槽中返回 (应当使用节点的 ScanTupleSlot 来做这件事)。 实际被插入、更新或删除的数据必须存储在 node->resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple 中。 如果没有更多行可用,返回 NULL。 注意它是在一个短寿命的内存上下文中被调用的,该上下文会在两次 调用之间被重置。 如果需要更长寿命的存储,请在 BeginDirectModify 中创建一个内存上下文, 或使用节点 EState 的 es_query_cxt。

返回的行必须匹配 fdw_scan_tlist 目标列表(如果提供了 的话),否则必须匹配被更新外部表的行类型。 如果您选择优化掉对 RETURNING 计算不需要列的取回, 应当在这些列的位置插入空值,或者生成省略了这些列的 fdw_scan_tlist 列表。

无论查询有没有该子句,查询报告的行数都必须由 FDW 自己 递增。 当查询没有该子句时,在 EXPLAIN ANALYZE 情况下,FDW 还必须为 ForeignScanState 节点递增行数。

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

void
EndDirectModify(ForeignScanState *node);

在远程服务器上完成直接修改之后进行清理。 通常,释放 palloc'd 的内存并不重要,但例如打开的文件和到远程 服务器的连接应当被清理。

如果 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 命令中请求了 RESTRICT 或 CASCADE 选项。

如果 restart_seqs 为 true, 原始的 TRUNCATE 命令请求了 RESTART IDENTITY 行为,否则请求的 是 CONTINUE IDENTITY 行为。

注意,原始 TRUNCATE 命令中指定的 ONLY 选项不会被传给 ExecForeignTruncate。 这一行为类似于 外部表上 SELECT、 UPDATE 和 DELETE 的回调函数。

对每个有外部表要截断的外部服务器, 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 枚举类型的成员。

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

如果 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 可以重新检查限定符并在 它们仍然满足时返回 true、否则返回 false,但它也可以把一个 替换元组存入提供的槽中。

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

58.2.7. 用于 EXPLAIN 的 FDW 例程 #

void
ExplainForeignScan(ForeignScanState *node,
                   ExplainState *es);

为外部表扫描打印额外的 EXPLAIN 输出。 这个函数可以调用 ExplainPropertyText 和 相关函数来向 EXPLAIN 输出添加字段。 es 中的标志字段可用于决定打印什么,而在 EXPLAIN ANALYZE 情况下,可以检查 ForeignScanState 节点的状态来提供 运行时统计。

如果 ExplainForeignScan 指针被设为 NULL, EXPLAIN 期间不会打印额外的 信息。

void
ExplainForeignModify(ModifyTableState *mtstate,
                     ResultRelInfo *rinfo,
                     List *fdw_private,
                     int subplan_index,
                     struct ExplainState *es);

为外部表更新打印额外的 EXPLAIN 输出。 这个函数可以调用 ExplainPropertyText 和 相关函数来向 EXPLAIN 输出添加字段。 es 中的标志字段可用于决定打印什么,而在 EXPLAIN ANALYZE 情况下,可以检查 ModifyTableState 节点的状态来提供 运行时统计。 前四个参数与 BeginForeignModify 的相同。

如果 ExplainForeignModify 指针被设为 NULL, EXPLAIN 期间不会打印额外的 信息。

void
ExplainDirectModify(ForeignScanState *node,
                    ExplainState *es);

为远程服务器上的直接修改打印额外的 EXPLAIN 输出。 这个函数可以调用 ExplainPropertyText 和 相关函数来向 EXPLAIN 输出添加字段。 es 中的标志字段可用于决定打印什么,而在 EXPLAIN ANALYZE 情况下,可以检查 ForeignScanState 节点的状态来提供 运行时统计。

如果 ExplainDirectModify 指针被设为 NULL, EXPLAIN 期间不会打印额外的 信息。

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 数组中。 必须返回实际收集的行数。 此外,把表中活动和死亡行总数的估计存入输出参数 totalrows 和 totaldeadrows。 (如果 FDW 没有死亡行的概念,把 totaldeadrows 设为零。)

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 可以忽略 ImportForeignSchemaStmt 的 local_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);

测试一次扫描能否在并行工作进程中执行。 只有当规划器认为并行计划有可能时才会调用这个函数, 如果该扫描在并行工作进程中运行是安全的,它应当返回 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 段消失之前采取某些动作的外部数据包装器应当实现 这个方法。

58.2.11. 用于异步执行的 FDW 例程 #

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,并为 ForeignScan 节点把 areq->callback_pending 设为 true,以便从下文描述的回调函数得到一次 回调。 如果没有更多元组可用,把槽设为 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 节点异步产出一个 元组。 这个函数应当以与 ForeignAsyncRequest 相同的方式设置 areq 中的输出参数。

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

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。