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

57.2. 外来数据包装器回调例程 #

57.2.1. FDW 扫描外来表例程
57.2.2. FDW 扫描外来联接例程
57.2.3. FDW 计划后扫描/联接处理例程
57.2.4. FDW 更新外来表例程
57.2.5. FDW TRUNCATE例程
57.2.6.用于行锁的 FDW 例程
57.2.7.用于 EXPLAIN 的 FDW 例程
57.2.8.用于 ANALYZE 的 FDW 例程
57.2.9.用于 IMPORT FOREIGN SCHEMA 的 FDW 例程
57.2.10.用于并行执行的 FDW 例程
57.2.11.用于异步执行的 FDW 例程
57.2.12.用于路径重新参数化的 FDW 例程

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

FdwRoutine 结构类型在 src/include/foreign/fdwapi.h 中声明,请参考该文档了解更多详细信息。

57.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。)

请参阅 第 57.4 节 了解其他详细信息。

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

为扫描外部表创建可行的访问路径。这在查询规划时被调用。参数与之前已调用的 GetForeignRelSize 相同。

此函数必须至少生成一个访问路径(ForeignPath 节点)用于在外部表上进行扫描,并且必须调用 add_path 将每一路径添加到 baserel->pathlist。建议使用 create_foreignscan_path 来构建 ForeignPath 节点。此函数可以生成多个访问路径,例如,具有有效 pathkeys 来表示预排序结果的路径。每个访问路径必须包含成本估算,并且可以包含任何 FDW 私有信息,而此信息对于标识预期的特定扫描方法是必需的。

请参阅 第 57.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 节点。

请参阅 第 57.4 节 了解其他详细信息。

void
BeginForeignScan(ForeignScanState *node,
                 int eflags);

开始执行外部扫描。这在执行程序启动期间被调用。它应在扫描开始前执行任何必需的初始化,而不应开始执行实际扫描(这应在首次调用 IterateForeignScan 时进行)。ForeignScanState 节点已创建,但其 fdw_state 字段仍为 NULL。有关需要扫描的表的信息可以通过 ForeignScanState 节点进行访问(特别是,通过底层的 ForeignScan 计划节点,其包含 GetForeignPlan 提供的任何 FDW 私有信息)。eflags 包含用于此计划节点描述执行程序的操作模式的标志位。

请注意,当 (eflags & EXEC_FLAG_EXPLAIN_ONLY) 为 true 时,此函数不应执行任何外部可见操作;它仅应执行使节点状态对 ExplainForeignScanEndForeignScan 有效所需的最低操作。

TupleTableSlot *
IterateForeignScan(ForeignScanState *node);

从外部源提取一行,在元组表插槽中返回 (ScanTupleSlot 应该用于此目的)。如果没有更多行,返回 NULL。元组表插槽基础设施允许返回物理元组或虚拟元组;在大多数情况下,后者在性能方面更好。请注意,这是在会在调用之间重置的短寿命内存上下文中调用的。如果您需要更长时间的存储,请在 BeginForeignScan 中创建一个内存上下文,或使用节点 EStatees_query_cxt

如果提供目标列表,返回的行必须匹配 fdw_scan_tlist 目标列表,否则必须匹配正在扫描的外表行的类型。如果您选择优化掉不必要的列,您应该在这些列的位置插入 null,或者生成一个不包含这些列的 fdw_scan_tlist 列表。

请注意,PostgreSQL 的执行器不关心返回的行是否违反定义在外表上的任何约束,但规划器确实关心,并且如果外表中有不满足声明约束的行,可能会错误地优化查询。如果在用户声明约束应为 true 的情况下违反约束,可能需要引发错误(就像在数据类型不匹配的情况下需要做的那样)。

void
ReScanForeignScan(ForeignScanState *node);

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

void
EndForeignScan(ForeignScanState *node);

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

57.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 不同,此函数不必成功创建至少一个路径,因为总是可能涉及本地联接的路径。

请注意,这个函数将针对同一个 JOIN 关系使用不同的内部关系和外部关系组合重复地调用,因此 FDW 负责最小化重复工作。

另外需要注意,传递为 extra->restrictlist 的要应用于 JOIN 的 JOIN 子句集合会根据内部关系和外部关系的组合而有所不同。为 joinrel 生成的 ForeignPath 路径必须包含它使用的 JOIN 子句集合,如果规划器将其选为 joinrel 的最佳路径,那么规划器将使用该集合将 ForeignPath 路径转换为计划。

如果为 JOIN 选择 ForeignPath 路径,它将表示整个 JOIN 过程;针对组件表和子 JOIN 生成的路径将不会使用。JOIN 路径的后续处理过程与针对单个外部表进行扫描的路径处理过程非常类似。不同之处在于:对于生成的 ForeignScan 计划节点,其 scanrelid 应当设为零,因为它不存在单个关系可供其表示;相反,ForeignScan 节点的 fs_relids 字段将表示已连接的一组关系。(该后一个字段是核心规划器代码自动设置的,无需 FDW 填充)。另一个不同之处在于:由于无法从系统目录中找到远程 JOIN 的列列表,因此 FDW 必须用适当的 TargetEntry 节点列表填充 fdw_scan_tlist,该列表表示它将在其返回的元组中于运行时提供的一组列。

注意

PostgreSQL 16 开始,fs_relids 包括外部 JOIN 的范围表索引,假如有任何外部 JOIN 参与到此 JOIN 中。新字段 fs_base_relids 仅包括基础关系索引,因此模拟了 fs_relids 的旧语义。

请参阅 第 57.4 节 了解其他详细信息。

57.2.3. 规划在扫描/JOIN 之后进行处理的 FDW 例程 #

如果某个 FDW 支持执行远程扫描/JOIN 后的处理,例如远程聚集,则它应当提供此回调函数

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

创建可能访问路径以进行上层关系处理,这是计划程序用于全部扫描/联接后的查询处理的术语,例如聚合、窗口函数、排序和表格更新。此可选函数在查询规划过程中进行调用。当前仅当查询涉及的所有基本关系都属于同一 FDW 时,才会进行该调用。此函数应针对任何 FDW 知道如何远程执行的扫描/联接后处理生成ForeignPath路径(使用create_foreign_upper_path进行构建),并调用add_path将这些路径添加到指示的上层关系中。GetForeignJoinPathsadd_path类似,此函数无需一定成功创建任何路径,因为总是可以使用涉及本地处理的路径。

stage参数标识当前正在考虑的哪一步扫描/联接后步骤。output_rel是应接收表示此步骤计算的路径的上层关系,input_rel是表示此步骤输入的关系。extra参数提供其他详细信息,当前仅针对UPPERREL_PARTIAL_GROUP_AGGUPPERREL_GROUP_AGG进行设置,在这种情况下,它指向GroupPathExtraData结构;或针对UPPERREL_FINAL进行设置,在这种情况下,它指向FinalPathExtraData结构。(注意添加到output_relForeignPath路径通常不会与input_rel的路径有任何直接依赖关系,因为它们的处理应在外部进行。但是,检查先前针对前一处理步骤生成的路径有助于避免冗余规划工作。)

请参阅 第 57.4 节 了解其他详细信息。

57.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 字段之外,其他字段相同,可以并且应该共享列名称。核心系统使用垃圾列名称 tableoid 作为表的 tableoid 列,ctidctidN 作为 ctidwholerow 作为用 vartype = RECORD 标记的整行 Var,以及 wholerowN 作为整行的 Var,其中 vartype 等于表的声明行类型。当您能够(计划程序将合并对相同垃圾列的重复请求)时,请重新使用这些名称。如果您除了这些名称之外还需要其他类型的垃圾列,最好选择一个以您的扩展名称为前缀的名称,以避免与其他 FDW 发生冲突。

如果 AddForeignUpdateTargets 指针设置为 NULL,则不会添加其他目标表达式。(这样将无法实现 DELETE 操作,但如果 FDW 依赖于不变的主键来标识行,则 UPDATE 仍然可行。)

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

执行针对外表的插入、更新或删除操作所需的任何其他计划操作。此函数生成将附加到执行更新操作的 ModifyTable 计划节点的 FDW 专用信息。此专用信息必须具有 List 的形式,并且将在执行阶段提供给 BeginForeignModify

root 是计划程序关于查询的全局信息。 planModifyTable 计划节点,除了 fdwPrivLists 字段之外,它已完成。 resultRelation 通过其范围表索引标识目标外表。 subplan_index 标识此计划节点的 ModifyTable 计划节点的哪个目标,从零开始计数;如果您想对 plan 节点的每个目标关系子结构编制索引,则使用此目标关系子结构。

请参阅 第 57.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) 为 true 时,此函数不应执行任何外部可见操作;它仅应完成使节点状态对 ExplainForeignModifyEndForeignModify 有效所需的最低限度操作。

如果 BeginForeignModify 指针设置为 NULL,则不会在执行程序启动期间执行任何操作。

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

在外部表中插入一个元组。 estate 是查询的全局执行状态。 rinfo 是描述目标外部表的 ResultRelInfo 结构。 slot 包含要插入的元组;它将与外部表的行类型定义匹配。 planSlot 包含由 ModifyTable 计划节点的子计划生成的元组;它与 slot 不同,因为它可能包含其他垃圾列。(对于 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。如果 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 是查询的全局执行状态。 rinfoResultRelInfo 描述目标外部表的结构。slot 在调用时不包含任何有用信息,但可用于保存返回的元组。 planSlot 包含由 ModifyTable 计划节点的子计划生成的元组;特别是,它将携带 AddForeignUpdateTargets 请求的任何垃圾列。垃圾列必须用于识别要删除的元组。

返回值要么包含已被删除行的插槽,要么在没有行被删除的情况下为 NULL(通常是由于触发器)。传入的 插槽 可用于保存要返回的元组。

只有在 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);

开始在外表上执行插入操作。无论是在为元组路由选择的 partition 还是在 COPY FROM 命令中指定的目标中插入元组,在第一个元组插入外表之前都会调用此例程。它应执行在实际插入之前所需的任何初始化。随后,将调用 ExecForeignInsertExecForeignBatchInsert 以插入到外表中的元组。

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

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 枚举值);即,对 UPDATE(1 << CMD_UPDATE) = 4;对 INSERT(1 << CMD_INSERT) = 8;对 DELETE(1 << CMD_DELETE) = 16

如果 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 必须设置为与语句类型(即,对于 UPDATECMD_UPDATE;对于 INSERTCMD_INSERT;对于 DELETECMD_DELETE)相对应的 CmdType 枚举,resultRelation 参数必须被复制至 resultRelation 字段。

请参阅 第 57.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 时,此函数不应该执行任何外部可见的操作;它应该只执行使节点状态对 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,则不会尝试在远程服务器上执行直接修改。

57.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,则尝试截断外部表会失败并显示错误消息。

57.2.6. 行锁定 FDW 例程 #

如果 FDW 希望支持 延迟行锁定(如 第 57.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,因此不需要提供它。)

有关更多信息,请参见 第 57.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,则尝试重新读取行时会失败并显示错误消息。

有关更多信息,请参见 第 57.5 节

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

重新检查先前返回的元组是否仍然与相关的扫描和联接限定符匹配,并可能提供元组的修改版本。对于不执行联接下推的外来数据包装器,通常更方便将此设置为 NULL,而适当地设置 fdw_recheck_quals 即可。但是,当下推外部联接时,即使存在所有所需属性,也无法仅仅对结果元组重新应用与所有基本表相关的检查,因为如果不匹配某些限定符,某些属性可能会变为 NULL,而不是不返回元组。RecheckForeignScan 可以重新检查限定符,如果它们仍然满足限定符则返回真,否则返回假,但它还可以在提供的插槽中存储替换元组。

为了实现联接下推,外来数据包装器通常会构建一个仅用于重新检查的替代本地联接计划;这将成为 ForeignScan 的外部子计划。当需要重新检查时,可以执行此子计划,并且可以将结果元组存储在插槽中。这个计划不必有效,因为没有基本表将返回多于一行;例如,它可以将所有联接都实现为嵌套循环。函数 GetExistingLocalJoinPath 可以用于在指定联接关系的路径列表中搜索适用于替换本地联接计划的本地联接路径。GetExistingLocalJoinPath 寻找未参数化的路径(如果找不到,则返回 NULL,在这种情况下,外来数据包装器可以通过自身构建本地路径,也可以选择不为该联接创建访问路径)。

57.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 期间不会打印其他信息。

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

57.2.9. 用于 IMPORT FOREIGN SCHEMA 的 FDW 例程 #

List *
ImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid);

获取一个外部表创建命令的列表。当执行外部模式导入时调用此函数,并且该语句的解析树以及要使用的外部服务器的 OID 传递给该函数。它应当返回 C 字符串列表,其中每个 C 字符串都必须包含一个 创建外部表 命令。核心服务器将会解析并执行这些字符串。

导入外部模式语句 结构中,远程模式 是要从其导入表的远程模式的名称。 列表类型 识别如何筛选表名:FDW_IMPORT_SCHEMA_ALL 表示应当导入远程模式中的所有表(在这种情况下,表列表 为空),FDW_IMPORT_SCHEMA_LIMIT_TO 表示仅包含在 表列表 中列出的表,FDW_IMPORT_SCHEMA_EXCEPT 表示排除在 表列表 中列出的表。 选项 是用于导入过程的选项列表。选项的含义取决于 FDW。例如,FDW 可以使用选项来定义是否应当导入列的 NOT NULL 属性。这些选项不需要与 FDW 所支持的选项作为数据库对象选项有所关系。

FDW 可以忽略 导入外部模式语句本地模式 字段,因为核心服务器将自动将该名称插入到已解析的 创建外部表 命令中。

而且,FDW 也不必解决由 列表类型表列表 指定的筛选的实现问题,因为核心服务器将会自动跳过针对根据那些选项排除的表的任何已返回的命令。但是,一开始就避免针对排除的表创建命令的工作通常很有用。函数 IsImportableForeignTable() 对于测试给定的外部表名称是否将通过筛选可能很有用。

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

57.2.10. 并行执行的 FDW 例程 #

一个 外部分析 节点可以选择支持并行执行。并行 外部分析 将在多个进程中执行,并且必须在所有协作进程中返回每一行恰好一次。为此,进程可以通过固定大小的动态共享内存块进行协调。不能保证此共享内存将在每个进程中以相同的地址进行映射,因此它不能包含指针。下面的函数都是可选的,但如果要支持并行执行,则大多数函数都是必需的。

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 片段消失前采取某些措施的外键数据包装器应实施此方法。

57.2.11. FDW 异步执行例程 #

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

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。建议使用 ExecAsyncRequestDoneExecAsyncRequestPendingareq 中设置输出参数。

void
ForeignAsyncConfigureWait(AsyncRequest *areq);

配置 ForeignScan 节点希望等待的文件描述符事件。仅当 ForeignScan 节点具有 areq->callback_pending 标记集时,才会调用此函数,并应向 areq 描述的父 Append 节点的 as_eventset 中添加该事件。有关其他信息,请参阅 src/backend/executor/execAsync.cExecAsyncConfigureWait 的注释。当出现文件描述符事件时,将调用 ForeignAsyncNotify

void
ForeignAsyncNotify(AsyncRequest *areq);

处理已发生的关联事件,然后从 ForeignScan 节点异步产生一个元组。此函数应以与 ForeignAsyncRequest 相同的方式在 areq 中设置输出参数。

57.2.12. FDW 路由用于重新参数化 # 的路径

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

在将由给定子级关系 child_rel 的最靠上的父级参数化的路径参数化为由子级关系参数化的过程中调用此函数。该函数可用于重新参数化任何路径或转换存储在 ForeignPath 的给定 fdw_private 成员中的任何表达式节点。回调可根据需要使用 reparameterize_path_by_childadjust_appendrel_attrsadjust_appendrel_attrs_multilevel