TRUNCATE
的 FDW 例程EXPLAIN
的 FDW 例程ANALYZE
的 FDW 例程IMPORT FOREIGN SCHEMA
的 FDW 例程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
,如果它可以计算出更准确的结果行平均宽度估计值。(初始值基于列数据类型和由上次 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
(之前由 GetForeignPaths
、GetForeignJoinPaths
或 GetForeignUpperPaths
生成),要由计划节点发出的目标列表,要由计划节点强制执行的限制子句,以及 ForeignScan
的外部子计划,该子计划用于由 RecheckForeignScan
执行的重新检查。(如果路径用于联接而不是基本关系,则 foreigntableid
为 InvalidOid
。)
此函数必须创建并返回一个 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)
为真时,此函数不应执行任何外部可见的操作;它只应执行使节点状态对于 ExplainForeignScan
和 EndForeignScan
有效的必要操作。
TupleTableSlot * IterateForeignScan(ForeignScanState *node);
从外部来源获取一行,并将其返回到元组表槽中(应使用节点的 ScanTupleSlot
用于此目的)。如果不再有行可用,则返回 NULL。元组表槽基础结构允许返回物理元组或虚拟元组;在大多数情况下,从性能的角度来看,后一种选择更可取。请注意,这在短暂的内存上下文中被调用,该上下文将在每次调用之间被重置。如果您需要更持久的存储,请在 BeginForeignScan
中创建一个内存上下文,或者使用节点的 EState
的 es_query_cxt
。
返回的行必须与 fdw_scan_tlist
目标列表匹配(如果提供了目标列表),否则必须与要扫描的外部表的行类型匹配。如果您选择优化掉不需要的列获取,则应在这些列位置插入空值,或者生成一个省略了这些列的 fdw_scan_tlist
列表。
请注意,PostgreSQL 的执行器不关心返回的行是否违反了在外部表上定义的任何约束——但规划器关心,如果外部表中存在不满足声明约束的可见行,则规划器可能会错误地优化查询。如果在用户声明该约束应始终为真的情况下违反了约束,则可能需要引发错误(就像在数据类型不匹配的情况下需要做的那样)。
void ReScanForeignScan(ForeignScanState *node);
从头开始重新启动扫描。请注意,扫描依赖的任何参数的值都可能已更改,因此新的扫描不一定返回完全相同的行。
void EndForeignScan(ForeignScanState *node);
结束扫描并释放资源。通常不需要释放使用 palloc 分配的内存,但例如,应清理打开的文件和到远程服务器的连接。
如果 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 负责将重复工作降到最低。
还请注意,要应用于联接的联接子句集(作为 extra->restrictlist
传递)会根据内部关系和外部关系的组合而有所不同。为 joinrel
生成的 ForeignPath
路径必须包含它使用的联接子句集,规划器将使用该集合将 ForeignPath
路径转换为计划,如果规划器选择它作为 joinrel
的最佳路径。
如果为连接选择 ForeignPath
路径,它将代表整个连接过程;为组件表和辅助连接生成的路径将不会使用。随后对连接路径的处理过程与扫描单个外部表的路径处理过程非常相似。一个区别是,生成的 ForeignScan
计划节点的 scanrelid
应该设置为零,因为它没有代表任何单个关系;相反,ForeignScan
节点的 fs_relids
字段表示连接的集合关系。(后一个字段由核心规划器代码自动设置,不需要由 FDW 填充。)另一个区别是,由于远程连接的列列表无法从系统目录中找到,因此 FDW 必须使用适当的 TargetEntry
节点列表填充 fdw_scan_tlist
,这些节点表示它将在运行时返回的元组中提供的列集。
从 PostgreSQL 16 开始,fs_relids
包括外部连接的范围表索引,如果这些外部连接参与了此连接。新字段 fs_base_relids
仅包含基本关系索引,因此模仿了 fs_relids
的旧语义。
请参阅 第 57.4 节 以获取更多信息。
如果 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
的路径有任何直接依赖关系,因为它们的处理预计将在外部完成。但是,检查之前为先前处理步骤生成的路径有助于避免冗余的规划工作。)
请参阅 第 57.4 节 以获取更多信息。
如果 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
节点的每个目标关系子结构中,请使用它。
请参阅 第 57.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);
开始在外部表上执行插入操作。此例程在以下两种情况下被调用:将元组路由到的分区以及在 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
字段。
请参阅 第 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)
为真时,此函数不应执行任何外部可见的操作;它只应该执行使节点状态对 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 本身递增。当查询没有该子句时,FDW 必须在 EXPLAIN ANALYZE
的情况下,还为 ForeignScanState
节点递增行数。
如果 IterateDirectModify
指针被设置为 NULL
,则不会尝试在远程服务器上执行直接修改。
void EndDirectModify(ForeignScanState *node);
在远程服务器上执行直接修改后清理。通常,释放 palloc'd 内存并不重要,但例如应该清理与远程服务器的打开文件和连接。
如果 EndDirectModify
指针被设置为 NULL
,则不会尝试在远程服务器上执行直接修改。
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
,则尝试截断外部表将失败,并显示错误消息。
如果 FDW 希望支持 延迟行锁定(如 第 57.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
永远不会被调用,因此也不需要提供它。)
有关更多信息,请参阅 第 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
中设置适当的值会更方便。但是,当外部连接被下推时,即使所有必需的属性都存在,重新应用所有基础表相关的检查到结果元组也不够,因为无法匹配某些限定符可能会导致某些属性变为空,而不是没有元组返回。 RecheckForeignScan
可以重新检查限定符并在它们仍然满足时返回 true,否则返回 false,但它也可以将替换元组存储到提供的槽中。
为了实现连接下推,外部数据包装器通常会构建一个仅用于重新检查的备用本地连接计划;这将成为 ForeignScan
的外部子计划。当需要重新检查时,可以执行此子计划,并将结果元组存储在槽中。此计划不需要高效,因为没有基础表会返回多于一行;例如,它可以将所有连接实现为嵌套循环。函数 GetExistingLocalJoinPath
可用于在指定连接关系的路径列表中搜索合适的本地连接路径,该路径可用作备用本地连接计划。 GetExistingLocalJoinPath
在指定连接关系的路径列表中搜索未参数化的路径。(如果找不到这样的路径,则返回 NULL,在这种情况下,外部数据包装器可以自行构建本地路径,或者可以选择不为该连接创建访问路径。)
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
期间不会打印任何额外信息。
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
设置为零。)
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
。
一个 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 段消失之前采取某些操作的外部数据包装器应实现此方法。
一个 ForeignScan
节点可以(可选)支持 src/backend/executor/README
中描述的异步执行。以下函数都是可选的,但如果要支持异步执行,则都是必需的。
bool IsForeignPathAsyncCapable(ForeignPath *path);
测试给定的 ForeignPath
路径是否可以异步扫描底层的外部关系。此函数只会在查询计划结束时调用,当给定路径是 AppendPath
路径的直接子节点并且计划程序认为异步执行会提高性能时,如果给定路径能够异步扫描外部关系,则应返回 true。
如果未定义此函数,则假定给定路径使用 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
中的输出参数。
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
,具体取决于需要。
如果您在文档中发现任何错误,与您对特定功能的体验不符,或者需要进一步澄清,请使用 此表格 报告文档问题。