pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
FDW 处理器函数返回一个通过 palloc 分配的FdwRoutine结构体,其中包含下文描述的回调函数指针。与扫描相关的函数是必需的,其余则是可选的。
FdwRoutine结构体类型声明在src/include/foreign/fdwapi.h中,其中还有更多细节。
void
GetForeignRelSize (PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
获取外部表的关系大小估计值。在开始规划扫描外部表的查询时,会调用此函数。root 是规划器中关于查询的全局信息;baserel 是规划器中关于此表的信息;foreigntableid 是该外部表的 pg_class OID。(foreigntableid 可以从规划器的数据结构中取得,但为了方便,这里显式传入。)
此函数应更新 baserel->rows,使其表示考虑限制条件过滤后,表扫描预计返回的行数。baserel->rows 的初始值只是一个固定的默认估计,应尽可能替换。如果能更准确地估计结果行的平均宽度,该函数也可以更新 baserel->width。。
更多信息请见第 52.4 节。
void
GetForeignPaths (PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid);
为外部表扫描创建可能的访问路径。此函数在查询规划期间调用,参数与先前已经调用过的 GetForeignRelSize 相同。
这个函数必须为外部表上的扫描生成至少一个访问路径(ForeignPath 节点),并且必须调用add_path把每一个这样的路径加入到baserel->pathlist中。推荐使用create_foreignscan_path来构造ForeignPath节点。该函数可以生成多个访问路径,例如一个具有合法pathkeys的路径可以表示预排序的结果。每个访问路径都必须包含代价估计,并且可以包含用于标识预期扫描方法的任意 FDW 私有信息。
更多信息请见第 52.4 节。
ForeignScan *
GetForeignPlan (PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
ForeignPath *best_path,
List *tlist,
List *scan_clauses);
根据选中的外部访问路径创建一个 ForeignScan 计划节点。此函数在查询规划结束时调用。参数除了与 GetForeignRelSize 相同的参数外,还包括选中的 ForeignPath(先前由 GetForeignPaths 生成)、计划节点应输出的目标列表,以及计划节点应实施的限制子句。
这个函数必须创建并返回一个ForeignScan计划节点,推荐使用make_foreignscan来构造该ForeignScan节点。
更多信息请见第 52.4 节。
void
BeginForeignScan (ForeignScanState *node,
int eflags);
开始执行外部扫描。执行器启动时会调用此函数。它应完成扫描开始前所需的初始化,但不应开始实际扫描(应等到首次调用 IterateForeignScan 时才开始)。ForeignScanState 节点已经创建,但其 fdw_state 字段仍为 NULL。可以通过 ForeignScanState 节点取得待扫描表的信息(特别是底层的 ForeignScan 计划节点,其中的 FDW 私有信息来自 GetForeignPlan)。 eflags 包含描述执行器针对该计划节点的运行模式的标志位。
注意,当(eflags & EXEC_FLAG_EXPLAIN_ONLY)为真时,这个函数不应执行任何外部可见的动作;它只应做使节点状态对ExplainForeignScan和EndForeignScan有效所需的最少工作。
TupleTableSlot * IterateForeignScan (ForeignScanState *node);
从外部数据源取得一行,并放入元组表槽中返回(应使用节点的 ScanTupleSlot)。如果没有更多行,则返回 NULL。元组表槽机制允许返回物理元组或虚拟元组;从性能角度看,大多数情况下后者更合适。注意,此函数在短期内存上下文中调用,该上下文会在各次调用之间重置。如果需要更长久的存储,请在 BeginForeignScan 中创建内存上下文,或者使用 es_query_cxt,它属于节点的 EState。
返回的行必须与被扫描外部表的列签名匹配。如果选择优化掉不需要取回的列,则应当在这些列的位置上填入空值。
注意PostgreSQL的执行器并不在乎被返回的行是否违背了定义在外部表列上的任何NOT NULL约束 — 但是规划器会在乎这一点,并且如果在一个声明不包含空值的列中出现了NULL值,规划器可能会错误地优化查询。如果当用户已经声明不允许出现空值时却遇到了NULL值,最合适的处理可能是产生一个错误(就像在数据类型失配的情况下所作的那样)。
void ReScanForeignScan (ForeignScanState *node);
从头重新扫描。注意,扫描所依赖的参数值可能已经改变,因此新扫描不一定返回完全相同的行。
void EndForeignScan (ForeignScanState *node);
结束扫描并释放资源。通常无需专门释放 palloc 分配的内存,但应清理打开的文件、到远程服务器的连接等资源。
如果一个 FDW 支持可写外部表,那么应根据该 FDW 的需要和能力提供以下部分或全部回调函数:
void
AddForeignUpdateTargets (Query *parsetree,
RangeTblEntry *target_rte,
Relation target_relation);
UPDATE 和 DELETE 操作作用于先前由表扫描函数取得的行。FDW 可能需要行 ID 或主键列值等额外信息,才能准确识别要更新或删除的行。为此,该函数可以添加额外的隐藏目标列,也称为 “junk” 目标列,并加入要从外部表取得的列列表,适用于 UPDATE 或 DELETE。
为此,应向 parsetree->targetList 添加 TargetEntry 项,其中包含要额外取得的值的表达式。每个这样的项都必须标记为 resjunk = true,且必须有不同的 resname,以便在执行时识别。避免使用与 ctid 或 Nwholerow 匹配的名称,因为核心系统可能生成这些名称的 junk 列。N
此函数在重写器而非规划器中被调用,因此可用的信息与规划器可用的信息略有不同。parsetree 是 UPDATE 或 DELETE 命令的语法解析树,而 target_rte 和 target_relation 描述目标外部表。
如果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->plans 或 plan 节点的其他子结构,可使用此值。
更多信息请见第 52.4 节。
如果PlanForeignModify指针被设置为NULL,就不会执行额外的规划期动作,传递给BeginForeignModify的 fdw_private 列表将为 NIL。
void
BeginForeignModify (ModifyTableState *mtstate,
ResultRelInfo *rinfo,
List *fdw_private,
int subplan_index,
int eflags);
开始执行外部表修改操作。执行器启动时会调用此例程,它应完成实际修改表之前所需的初始化。随后,会针对每个待插入、更新或删除的元组,分别调用 ExecForeignInsert, 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” 列。(planSlot 对于 INSERT 通常没有太大用处,但为了完整性仍会提供。)
返回值可以是一个包含实际被插入数据的槽(例如,触发器动作可能使其不同于所提供的数据),或者为 NULL,表示实际上没有插入任何行(通常也是触发器导致的)。传入的slot可重用于这一目的。
只有 INSERT 查询包含 RETURNING 子句时,才会使用返回槽中的数据。触发器需要所有列,但 FDW 可以根据 RETURNING 子句的内容,选择省略部分或全部列的返回,以进行优化。无论如何,都必须返回某个槽来表示成功,否则查询报告的行数会不正确。
如果ExecForeignInsert指针被设置为NULL,尝试向外部表插入将会失败并报告一个错误消息。
TupleTableSlot *
ExecForeignUpdate (EState *estate,
ResultRelInfo *rinfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
更新外部表中的一个元组。estate 是查询的全局执行状态。rinfo 是描述目标外部表的 ResultRelInfo 结构。slot 包含元组的新数据,与外部表的行类型定义一致。planSlot 包含由 ModifyTable 计划节点的子计划生成的元组;它与 slot 的不同之处在于可能包含额外的 “junk” 列。特别是,可以从此槽中取得由 AddForeignUpdateTargets 请求的所有 junk 列。
返回值可以是一个包含实际被更新数据的槽(例如,触发器动作可能导致它与提供的数据不同),或者为 NULL,表示实际上没有更新任何行(通常也是触发器导致的)。传入的slot可重用于这一目的。
只有 UPDATE 查询包含 RETURNING 子句时,才会使用返回槽中的数据。触发器需要所有列,但 FDW 可以根据 RETURNING 子句的内容,选择省略部分或全部列的返回,以进行优化。无论如何,都必须返回某个槽来表示成功,否则查询报告的行数会不正确。
如果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 子句。 触发器需要所有列,但 FDW 可以根据 RETURNING 子句的内容,选择优化掉部分或全部返回列。 不管怎样,某些槽必须被返回来指示成功,或者查询报告的行计数将会是错误的。
如果ExecForeignDelete指针被设置为NULL,尝试从外部表中删除将会失败并报告一个错误消息。
void
EndForeignModify (EState *estate,
ResultRelInfo *rinfo);
结束表更新并释放资源。通常无需专门释放 palloc 分配的内存,但应清理打开的文件、到远程服务器的连接等资源。
如果EndForeignModify指针被设置为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视图中的可更新性)。
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期间不会打印任何额外的信息。
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 设为零。)
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。