59.2. 外部数据包装器回调例程 #
FDW 处理器函数返回一个通过 palloc 分配的 FdwRoutine 结构体,其中包含下文描述的回调函数指针。与扫描相关的函数是必需的,其余则是可选的。
FdwRoutine 结构体类型声明在 src/include/foreign/fdwapi.h 中,其中还有更多细节。
59.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。)
更多信息请见第 59.4 节。
void
GetForeignPaths(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
为一个外部表上的扫描创建可能的访问路径。这个函数在查询规划过程中被调用。参数和 GetForeignRelSize 相同,后者已经被调用过了。
这个函数必须为外部表上的扫描生成至少一个访问路径(ForeignPath 节点),并且必须调用 add_path 把每一个这样的路径加入到 baserel->pathlist 中。推荐使用 create_foreignscan_path 来构造 ForeignPath 节点。该函数可以生成多个访问路径,例如一个具有合法 pathkeys 的路径可以表示预排序的结果。每个访问路径都必须包含代价估计,并且可以包含用于标识预期扫描方法的任意 FDW 私有信息。
更多信息请见第 59.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 节点。
更多信息请见第 59.4 节。
void
BeginForeignScan(ForeignScanState *node,
int eflags);
开始执行外部扫描。这个函数在执行器启动期间被调用。它应执行扫描开始前所需的任何初始化工作,但不应启动实际扫描(那会在第一次调用 IterateForeignScan 时进行)。ForeignScanState 节点已经创建,但其 fdw_state 字段仍为 NULL。关于待扫描表的信息可通过 ForeignScanState 节点访问,特别是底层的 ForeignScan 计划节点,其中包含了由 GetForeignPlan 提供的任何 FDW 私有信息。eflags 包含描述执行器对该计划节点操作模式的标志位。
注意,当(eflags & EXEC_FLAG_EXPLAIN_ONLY) 为真时,这个函数不应执行任何外部可见的动作;它只应做使节点状态对 ExplainForeignScan 和 EndForeignScan 有效所需的最少工作。
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 分配的内存,但例如打开的文件和到远程服务器的连接应当清理。
59.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 应负责尽量减少重复工作。
如果某条 ForeignPath 路径被选中用于该连接,它就表示整个连接过程;为组成该连接的各表及其子连接生成的路径将不会再使用。之后对该连接路径的处理,和处理扫描单个外部表的路径大体相同。一个区别是,所得 ForeignScan 计划节点的 scanrelid 应设为零,因为它不代表某个单一关系;相反,ForeignScan 节点的 fs_relids 字段表示被连接的关系集合。(后者由核心规划器代码自动设置,FDW 无需填充。)另一个区别是,由于远程连接的列列表无法从系统目录中找到,FDW 必须用适当的 TargetEntry 节点列表填充 fdw_scan_tlist,表示它在运行时会在返回的元组中提供哪些列。
注意
从 PostgreSQL 16 开始,如果该连接中涉及外连接,fs_relids 将包含这些外连接的范围表索引。新增字段 fs_base_relids 则只包含基本关系索引,因此模拟了 fs_relids 旧有的语义。
更多信息请见第 59.4 节。
59.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 的路径,因为预期这些处理是在外部完成的。不过,检查前一个处理步骤先前生成的路径,有助于避免重复的规划工作。)
更多信息请见第 59.4 节。
59.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 列名,但如果若干个 Var 除了 varno 字段外完全相同,则它们可以也应该共享列名。核心系统使用下列 junk 列名:表的 tableoid 列使用 tableoid;ctid 使用 ctid 或 ctid;带有 Nvartype = RECORD 标记的整行 Var 使用 wholerow;而 vartype 等于该表声明行类型的整行 Var 使用 wholerow。能复用这些名称时应尽量复用(规划器会合并对相同 junk 列的重复请求)。如果你还需要这些之外的其他 junk 列,最好选择以你的扩展名作为前缀的名称,以避免与其他 FDW 冲突。
N
如果 AddForeignUpdateTargets 指针被设置为 NULL,则不会添加额外的目标表达式。(这会使 DELETE 操作无法实现,不过如果 FDW 依赖一个不会变化的主键来标识行,UPDATE 仍可能可行。)
List *
PlanForeignModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
执行外部表上的插入、更新或删除所需的任何附加规划动作。这个函数生成 FDW 私有信息,该信息将被附加到执行该修改操作的 ModifyTable 计划节点上。这个私有信息必须采用 List 形式,并会在执行阶段传递给 BeginForeignModify。
root 是规划器关于该查询的全局信息。plan 是 ModifyTable 计划节点,它除了 fdwPrivLists 字段之外是完整的。resultRelation 通过目标外部表的范围表索引来标识它。subplan_index 标识这是 ModifyTable 计划节点的哪个目标,从零开始计数;如果你希望索引到 plan 节点的逐目标关系子结构中,请使用它。
更多信息请见第 59.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) 为真时,这个函数不应执行任何外部可见的动作;它只需做最少的工作,使节点状态对 ExplainForeignModify 和 EndForeignModify 有效。
如果 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 相同,除了 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 分配的内存,但例如打开的文件和到远程服务器的连接应当清理。
如果 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 的内存并不重要,但是打开的文件和与远程服务器的连接应该被清除。
如果 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 提供了 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 字段中。
更多信息请见第 59.4 节。
如果 PlanDirectModify 指针被设置为 NULL,不会尝试在远程服务器上执行直接修改。
void
BeginDirectModify(ForeignScanState *node,
int eflags);
准备在远程服务器上执行直接修改。这个函数会在执行器启动时被调用。它应执行直接修改开始前所需的任何初始化工作;实际的直接修改应在第一次调用 IterateDirectModify 时进行。ForeignScanState 节点已经创建,但其 fdw_state 字段仍为 NULL。有关待修改表的信息可通过 ForeignScanState 节点访问,特别是底层的 ForeignScan 计划节点,其中包含了由 PlanDirectModify 提供的任何 FDW 私有信息。eflags 包含描述执行器对该计划节点操作模式的标志位。
注意,当(eflags & EXEC_FLAG_EXPLAIN_ONLY) 为真时,这个函数不应执行任何外部可见的动作。它只应做使该节点状态对 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 分配的内存,但应清理打开的文件、到远程服务器的连接等资源。
如果 EndDirectModify 指针被设置为 NULL,不会尝试在远程服务器上执行直接修改。
59.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,那么截断外部表的尝试将失败并报出错误消息。
59.2.6. 用于行锁定的 FDW 例程 #
如果一个 FDW 希望支持晚期行锁定(如第 59.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,因此也不必提供它)。
更多信息请见第 59.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,重新取得行的尝试将会失败并伴随有一个错误消息。
更多信息请见第 59.5 节。
bool
RecheckForeignScan(ForeignScanState *node,
TupleTableSlot *slot);
重新检查之前返回的元组是否仍然匹配相关的扫描和连接条件,并且可能提供该元组的一个修改版本。对于不执行连接下推的外部数据包装器,通常把这设置为 NULL 并且恰当地设置 fdw_recheck_quals 会更方便。不过当外连接被下推时,把与所有基表相关的检查重新应用在结果元组上是不够的,即便所有需要的属性都存在也是如此,因为某个条件不匹配的结果可能是某些属性变成 NULL,而不是完全不返回该元组。RecheckForeignScan 可以重新检查这些条件,并在它们仍然满足时返回真,否则返回假;它也可以把一个替换元组存入所提供的槽中。
要实现连接下推,外部数据包装器通常会构造一个替代性的本地连接计划,它只用于重新检查。这将成为 ForeignScan 的外层子计划。在需要执行重检查时,可以执行这个子计划,并把结果元组存入槽中。该计划不必很高效,因为不会有任何基表返回超过一行。例如,它可以把所有连接都实现为嵌套循环。函数 GetExistingLocalJoinPath 可用于在已有路径中搜索合适的本地连接路径,并将其用作替代性的本地连接计划。GetExistingLocalJoinPath 会在指定连接关系的路径列表中搜索一个非参数化路径(如果找不到这样的路径,它将返回 NULL;在这种情况下,外部数据包装器可以自行构造本地路径,或者选择不为该连接创建访问路径)。
59.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 期间不会打印出额外的信息。
59.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 设为零。)
59.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。
59.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 段消失前执行某些动作的外部数据包装器应当实现这个方法。
59.2.11. 异步执行的 FDW 例程 #
ForeignScan 节点可以选择支持异步执行,如 src/backend/executor/README 中所述。以下函数都是可选的,但是如果要支持异步执行,则都是必需的。
bool IsForeignPathAsyncCapable(ForeignPath *path);
测试给定的 ForeignPath 路径是否可以异步扫描底层外部关系。当给定路径是 AppendPath 路径的直接子路径,并且规划器认为异步执行提高性能时,才会在查询规划结束时调用该函数。如果给出的路径能够异步扫描外部关系,则应返回真。
如果这个函数没有被定义,则假定给定的路径使用 IterateForeignScan 扫描外部关系。(这意味着下面描述的回调函数将永远不会被调用,因此它们也不需要被提供。)
void ForeignAsyncRequest(AsyncRequest *areq);
从 ForeignScan 节点异步生成一个元组。areq 是一个 AsyncRequest 结构体,描述该 ForeignScan 节点,以及向它请求元组的父 Append 节点。该函数应该将元组存储在 areq->result 指定的槽中,并将 areq->request_complete 设置为 true;或者,如果它需要等待核心服务器外部的事件,例如网络 I/O,并且不能立刻生成任何元组,则将该标志设置为 false,并且将 areq->callback_pending 设置为 true,以便 ForeignScan 节点从下面描述的回调函数中获取回调。如果没有更多的元组可用,则将槽位设置为 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 中的输出参数。
59.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。
报告文档问题
阅读 上游文档. 通过 PostgreSQL 文档反馈表单.