59.2. 外部数据包装器回调例程#
- 59.2.1. 用于扫描外部表的 FDW 例程
- 59.2.2. 用于扫描外部联接的 FDW 例程
- 59.2.3. 用于规划扫描后/联接后处理的 FDW 例程
- 59.2.4. 用于更新外部表的 FDW 例程
- 59.2.5. 用于
TRUNCATE
的 FDW 例程 - 59.2.6. 用于行锁定的 FDW 例程
- 59.2.7.
EXPLAIN
的 FDW 例程 - 59.2.8.
ANALYZE
的 FDW 例程 - 59.2.9.
IMPORT FOREIGN SCHEMA
的 FDW 例程 - 59.2.10. 并行执行的 FDW 例程
- 59.2.11. 异步执行的 FDW 例程
- 59.2.12. 路径重新参数化的 FDW 例程
FDW 处理程序函数返回一个 palloc'dFdwRoutine
结构,其中包含指向下面描述的回调函数的指针。扫描相关函数是必需的,其余函数是可选的。
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
看到的总行数;如果尚未对此外部表执行ANALYZE
,则它将为-1
。)
有关更多信息,请参阅第 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)
为 true 时,此函数不应执行任何外部可见的操作;它只应执行使节点状态对ExplainForeignScan
和EndForeignScan
有效所需的最低限度操作。
TupleTableSlot *
IterateForeignScan(ForeignScanState *node);
从外键源获取一行,在元组表槽中返回该行(节点的ScanTupleSlot
应为此目的使用)。如果没有更多行可用,则返回 NULL。元组表槽基础设施允许返回物理元组或虚拟元组;在大多数情况下,从性能角度来看,后一种选择更可取。请注意,此操作在调用之间将重置的短期内存上下文中调用。如果您需要更持久的存储,请在BeginForeignScan
中创建一个内存上下文,或使用节点EState
的es_query_cxt
。
如果提供了目标列表,则返回的行必须与fdw_scan_tlist
目标列表匹配,否则它们必须与正在扫描的外键表的行类型匹配。如果您选择优化以避免获取不需要的列,则应在这些列位置插入 null,或者生成一个省略这些列的fdw_scan_tlist
列表。
请注意,PostgreSQL的执行器并不关心返回的行是否违反了外键表上定义的任何约束——但规划器确实关心,并且如果外键表中存在不满足已声明约束的行,则可能会错误地优化查询。如果在用户声明约束应保持为真时违反了约束,则可能需要引发错误(就像在数据类型不匹配的情况下需要做的那样)。
void
ReScanForeignScan(ForeignScanState *node);
从头开始重新扫描。请注意,扫描所依赖的任何参数的值可能已更改,因此新扫描不一定返回完全相同行。
void
EndForeignScan(ForeignScanState *node);
结束扫描并释放资源。通常不必释放 palloc'd 内存,但例如应清理打开的文件和与远程服务器的连接。
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
,表示它将在运行时在返回的元组中提供的列集。
注意
从PostgreSQL16 开始,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
期间使用。
要执行此操作,请构造一个表示您需要的额外值的Var
,并将其传递给add_row_identity_var
,以及垃圾列的名称。(如果需要多列,您可以执行此操作多次。)您必须为每个不同的Var
选择一个不同的垃圾列名称,但对于varno
字段除外,相同的Var
可以且应共享一个列名称。核心系统使用垃圾列名称tableoid
表示表的tableoid
列,ctid
或ctid*
N*
表示ctid
,wholerow
表示用vartype
=RECORD
标记的整行Var
,wholerow*
N*
表示vartype
等于表声明的行类型的整行Var
。尽可能重复使用这些名称(规划器将合并对相同垃圾列的重复请求)。如果您需要除这些名称之外的另一种垃圾列,最好选择以扩展名作为前缀的名称,以避免与其他 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
通过其范围表索引标识目标外部表。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
不同,可能包含其他“垃圾”列。 (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
通常包含其他“垃圾”列。特别是,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
,则在执行器关闭期间不执行任何操作。
由INSERT
或COPY FROM
插入到分区表中的元组被路由到分区。如果 FDW 支持可路由的外部表分区,则它还应该提供以下回调函数。在外部表上执行COPY FROM
时也会调用这些函数。
void
BeginForeignInsert(ModifyTableState *mtstate,
ResultRelInfo *rinfo);
开始在外部表上执行插入操作。在以下两种情况下,在将第一个元组插入外部表之前都会调用此例程:当它是为元组路由选择的 partition,以及在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
必须设置为与语句类型(即UPDATE
的CMD_UPDATE
、INSERT
的CMD_INSERT
和DELETE
的CMD_DELETE
)对应的CmdType
枚举,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)
为 true 时,此函数不应执行任何外部可见的操作;它应仅执行使节点状态对ExplainDirectModify
和EndDirectModify
有效所需的最低限度操作。
如果BeginDirectModify
指针设置为NULL
,则不会尝试对远程服务器执行直接修改。
TupleTableSlot *
IterateDirectModify(ForeignScanState *node);
当INSERT
、UPDATE
或DELETE
查询没有RETURNING
子句时,只需在对远程服务器进行直接修改后返回 NULL。当查询有该子句时,获取一个包含RETURNING
计算所需数据的 result,在元组表槽中返回它(节点的ScanTupleSlot
应为此目的使用)。实际插入、更新或删除的数据必须存储在node->resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple
中。如果没有更多行可用,则返回 NULL。请注意,这是在调用之间将重置的短期内存上下文中调用的。如果需要更长期的存储,请在BeginDirectModify
中创建一个内存上下文,或使用节点的EState
的es_query_cxt
。
如果提供了fdw_scan_tlist
目标列表,则返回的行必须与该列表匹配,否则它们必须与正在更新的外表行的类型匹配。如果您选择优化掉获取不需要用于RETURNING
计算的列,则应在这些列位置插入 null,否则生成一个省略这些列的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
可以重新检查限定符,如果它们仍然满足则返回 true,否则返回 false,但它也可以将替换元组存储到提供的槽中。
要实现连接下推,外部数据包装器通常会构建一个备用本地连接计划,该计划仅用于重新检查;这将成为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);
测试是否可以在并行工作程序中执行扫描。只有当计划程序认为并行计划可能是可行时,才会调用此函数,并且如果在并行工作程序中运行该扫描是安全的,则应返回 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 段消失之前执行某些操作的外键数据包装器应实现此方法。
59.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
。建议使用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
。