2025年9月25日: PostgreSQL 18 发布!
支持的版本:当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本:devel
不支持的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1

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

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

类型 FdwRoutinesrc/include/foreign/fdwapi.h 中声明,有关更多详细信息请参见该文件。

58.2.1. 用于扫描外表的 FDW 例程 #

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

获取外表的关系大小估计。这在扫描外表的查询规划开始时被调用。root 是规划器关于查询的全局信息;baserel 是规划器关于该表的 C信息;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(之前由 GetForeignPathsGetForeignJoinPathsGetForeignUpperPaths 生成)、将由计划节点发出的目标列表、将由计划节点强制执行的限制子句,以及 ForeignScan 的外部子计划,它用于 RecheckForeignScan 执行的重检查。(如果路径是用于连接而不是基本关系,则 foreigntableidInvalidOid。)

此函数必须创建并返回一个 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 中创建内存上下文以获得更长生命周期的存储,或者使用节点的 EStatees_query_cxt

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

请注意,PostgreSQL 的执行器不关心返回的行是否违反了外表上定义的任何约束——但规划器关心,如果外表中存在不满足声明约束的行,规划器可能会错误地优化查询。如果用户声明约束应保持为真而约束被违反,则可能需要引发错误(正如在数据类型不匹配的情况下需要做的那样)。

void
ReScanForeignScan(ForeignScanState *node);

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

void
EndForeignScan(ForeignScanState *node);

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

58.2.2. 用于扫描外连接的 FDW 例程 #

如果 FDW 支持远程执行外连接(而不是通过获取两个表的 C数据并本地执行连接),则应提供此回调函数。

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 路径,它将代表整个连接过程;为组件表和附属连接生成的路径将不被使用。连接路径的后续处理与扫描单个外表的路径的处理非常相似。一个区别是,生成的 ForeignScan 计划节点的 scanrelid 应设置为零,因为它不代表任何单个关系;相反,ForeignScan 节点的 fs_relids 字段代表连接的关系集。(后一个字段由核心规划器代码自动设置,FDW 无需填充。)另一个区别是,因为系统目录中找不到远程连接的列列表,所以 FDW 必须用 TargetEntry 节点的一个适当列表填充 fdw_scan_tlist,该列表代表其在运行时返回的元组中将提供的列集。

注意

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 是代表此步骤输入的 C关系。extra 参数提供其他详细信息,当前仅针对 UPPERREL_PARTIAL_GROUP_AGGUPPERREL_GROUP_AGG 设置,此时它指向一个 GroupPathExtraData 结构;或者对于 UPPERREL_FINAL,此时它指向一个 FinalPathExtraData 结构。(注意,添加到 output_relForeignPath 路径通常不会直接依赖于前一个处理步骤的路径,因为它们的处理预计将在外部完成。然而,检查前一个处理步骤生成的路径可能有助于避免冗余的规划工作。)

有关更多信息,请参见 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 期间要从外表检索的列列表中添加额外的隐藏的(或“垃圾”)目标列。

要做到这一点,构造一个表示您需要的额外值的 Var,并将其与垃圾列的名称一起传递给 add_row_identity_var。(如果您需要多个列,可以执行此操作多次。)您必须为每个需要的不同 Var 选择一个唯一的垃圾列名称,但除了 Var 字段 varno 不同之外,相同的 Var 可以并且应该共享一个列名。核心系统使用垃圾列名称 tableoid 来表示表的 tableoid 列,ctidctidN 来表示 ctidwholerow 来表示标记为 vartype = RECORD 的整行 Var,以及 wholerowN 来表示 vartype 等于表声明的行类型的整行 Var。当您能重用这些名称时(规划器将合并重复的相同垃圾列请求)。如果您需要除这些之外的其他类型的垃圾列,明智的做法是选择一个以您的扩展名命名的前缀,以避免与其他 FDW 冲突。

如果 AddForeignUpdateTargets 指针设置为 NULL,则不会添加额外的目标表达式。(这将使得实现 DELETE 操作成为不可能,尽管 UPDATE 仍然可行,如果 FDW 依赖于不变的主键来识别行。)

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

为外表上的插入、更新或删除执行任何额外的规划操作。此函数生成将附加到执行更新操作的 ModifyTable 计划节点上的 FDW 私有信息。此私有信息必须采用 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 不同。(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 相同,只是 slotsplanSlots 包含多个元组,并且 *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 请求的任何垃圾列都将从此槽中可用。

返回值是包含实际更新后的行的槽,或者如果没有实际更新行,则返回 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 请求的任何垃圾列。垃圾列必须用于识别要删除的元组。

返回值是包含已删除行的槽,或者如果没有删除行,则返回 NULL(通常是触发器的结果)。传入的 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);

开始执行外表上的插入操作。此例程在第一个元组被插入到外表之前调用,这两种情况都适用:当它是在表路由选择的分区时,以及在 COPY FROM 命令中指定的目标时。它应在实际插入之前执行任何必要的初始化。随后,将为要插入到外表中的元组调用 ExecForeignInsertExecForeignBatchInsert

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

当此函数由 COPY FROM 命令调用时,mtstate 中的计划相关全局数据将不提供,并且随后为每个插入的元组调用的 ExecForeignInsertplanSlot 参数为 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 枚举(即,UPDATECMD_UPDATEINSERTCMD_INSERTDELETECMD_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 计算不需要的列,则应在这些列位置插入 NULL,或者生成一个省略了这些列的 fdw_scan_tlist 列表。

无论查询是否有该子句,查询报告的行数必须由 FDW 本身增加。当查询没有该子句时,FDW 还必须在 EXPLAIN ANALYZE 的情况下为 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 数据结构的列表。

behaviorDROP_RESTRICTDROP_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 枚举类型的一个成员。

此函数在查询规划期间为出现在 UPDATEDELETESELECT 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 可以重新检查限定条件并返回 true(如果它们仍然被满足)或 false(否则),但它也可以将替换元组存储到提供的槽中。

为了实现连接下推,外表数据包装器通常会构建一个替代的本地连接计划,该计划仅用于重检查;这将成为 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 指针设置为 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 数组中。必须返回实际收集的行数。此外,将表中活动行和死行的估计总数存储到输出参数 totalrowstotaldeadrows 中。(如果 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 可以忽略 ImportForeignSchemaStmtlocal_schema 字段,因为核心服务器将自动将该名称插入到解析的 CREATE FOREIGN TABLE 命令中。

FDW 也不必关心实现 list_typetable_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,并将 areq->callback_pending 设置为 true,以便 ForeignScan 节点从下面描述的回调函数获得回调。如果没有更多可用元组,则将槽设置为 NULL 或空槽,并将 areq->request_complete 标志设置为 true。建议使用 ExecAsyncRequestDoneExecAsyncRequestPending 来设置 areq 中的输出参数。

void
ForeignAsyncConfigureWait(AsyncRequest *areq);

配置一个文件描述符事件,ForeignScan 节点希望等待该事件。仅当 ForeignScan 节点将 areq->callback_pending 标志设置为 true 时,才会调用此函数,并且应将该事件添加到 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 的最顶层父级参数化的路径转换为由子关系参数化时,会调用此函数。该函数用于重新参数化 ForeignPathfdw_private 成员中保存的任何路径或转换任何表达式节点。回调可以使用 reparameterize_path_by_childadjust_appendrel_attrsadjust_appendrel_attrs_multilevel,视情况而定。

提交更正

如果您在文档中看到任何不正确、不符合您对特定功能的经验或需要进一步澄清的内容,请使用 此表单 报告文档问题。