FDW 回调函数 GetForeignRelSize
、GetForeignPaths
、GetForeignPlan
、PlanForeignModify
、GetForeignJoinPaths
、GetForeignUpperPaths
和 PlanDirectModify
必须符合 PostgreSQL 计划程序的工作原理。下面是说明它们必须执行哪些操作的一些注解。
可以利用 root
和 baserel
中的信息来减少必须从外部表中获取的信息量(从而降低成本)。baserel->baserestrictinfo
特别有趣,因为它包含用于过滤要获取的行 的限制条件(WHERE
从句)。(FDW 本身不需要强制执行这些条件,因为核心执行程序可以检查它们。)可以利用 baserel->reltarget->exprs
来确定需要获取哪些列;但请注意,它只列出 ForeignScan
计划节点必须输出的列,而不是那些用于条件评估、但未由查询输出的列。
可供 FDW 计划函数使用来保留信息的各种私有字段。通常,你存储在 FDW 私有字段中的任何内容都应调用 palloc,以便在计划结束时将其回收。
baserel->fdw_private
为 void
指针,供 FDW 计划函数将与特定外部表相关的信息存储其中。核心计划程序不接触它,除了在创建 RelOptInfo
节点时将其初始化为 NULL。它可用于将信息从 GetForeignRelSize
传递至 GetForeignPaths
,和/或从 GetForeignPaths
传递至 GetForeignPlan
,从而避免重新计算。
GetForeignPaths
可以通过将私有信息存储到 ForeignPath
节点的 fdw_private
字段中来识别不同访问路径的含义。fdw_private
声明为 List
指针,但实际上可以包含任何内容,因为核心计划程序不接触它。不过,最佳做法是使用可以由 nodeToString
转储的表示形式,以与后端中提供的调试支持一起使用。
GetForeignPlan
可以检查所选 ForeignPath
节点的 fdw_private
字段,并生成 fdw_exprs
和 fdw_private
列表,置于 ForeignScan
计划节点,供执行时使用。这两个列表都必须表示为 copyObject
了解如何进行复制的形式。fdw_private
列表没有其他限制,并且不受核心的后端以任何方式解释。如果 fdw_exprs
列表不为 NIL,则预期它将包含旨在在运行时执行的表达式树。这些树将经过计划程序的后处理,以使其完全可执行。
在 GetForeignPlan
中,传入的目标列表通常可以原样复制到计划节点中。传入的 scan_clauses
列表包含与 baserel->baserestrictinfo
相同的子句,但可能会重新排序以提高执行效率。在简单的情况下,FDW 可以在 scan_clauses
列表(使用 extract_actual_clauses
)中剥离 RestrictInfo
节点,并将所有子句放入计划节点的 qual 列表中,这意味着执行器将在运行时检查所有子句。更复杂的 FDW 可能能够在内部检查一些子句,在这种情况下,可以从计划节点的 qual 列表中删除这些子句,以便执行器不会浪费时间重新检查它们。
作为一个示例,FDW 可能会识别一些限制子句的形式 foreign_variable
=
sub_expression
,它确定可以在远程服务器上执行给定 sub_expression
的本地求值之后获得的子句。此类子句的实际标识应发生在 GetForeignPaths
期间,因为它会影响路径的成本估算。路径的 fdw_private
字段可能包含指向已识别的子句的 RestrictInfo
节点的指针。然后,GetForeignPlan
会从 scan_clauses
中删除该子句,但将 sub_expression
添加到 fdw_exprs
,以确保将其调整为可执行形式。它可能还会将控制信息放入计划节点的 fdw_private
字段中,以告知执行函数在运行时执行的操作。传输到远程服务器的查询将包含类似于 WHERE
的内容,并且参数值是从计算 foreign_variable
= $1fdw_exprs
表达式树中在运行时获得的。
必须将从计划节点的 qual 列表中移除的任何子句添加到 fdw_recheck_quals
,或者在 READ COMMITTED
隔离级别重新检查 RecheckForeignScan
,以确保正确执行。当查询中涉及的其他一些表发生并发更新时,执行器可能需要确认某个元组的所有原始 qual 均仍得到满足,这可能会针对另一组参数值执行。使用 fdw_recheck_quals
通常比在 RecheckForeignScan
中执行检查更为轻松,但当联接从属后,这种方法将会显得不足,因为在这种情况下,联接元组的某些域可能会变为 NULL,但不会全面拒绝该元组。
FDW 可以填充的另一 ForeignScan
域是 fdw_scan_tlist
,它描述了此计划节点的 FDW 返回的元组。对于简单的外键表扫描,可以将其设置为 NIL
,这意味着返回的元组具有为外键表声明的行类型。非 NIL
值必须是目标列表(由 TargetEntry
组成的列表),其中包含代表所返回列的 Var 和/或表达式。例如此方法可用于显示 FDW 遗漏了它注意到不需要用于查询的一些列。此外,如果 FDW 可以比本地更低成本地计算查询使用的表达式,则它可以将这些表达式添加到 fdw_scan_tlist
。请注意,联接计划(通过 GetForeignJoinPaths
所建的路径创建)必须始终提供 fdw_scan_tlist
来描述它们将返回的列集。
FDW 应始终构建至少一个仅取决于表限制子句的路径。在联接查询中,它还可以选择构建取决于联接子句的路径,例如 foreign_variable
=
local_variable
。此类子句不会在 baserel->baserestrictinfo
中找到,但必须在关联的联接列表中查找。使用此类子句的路径称为“参数化路径”。它必须使用合适的 param_info
值标识所选联接子句中使用的其他关联;使用 get_baserel_parampathinfo
计算该值。在 GetForeignPlan
中,联接子句的 local_variable
部分将被添加到 fdw_exprs
中,然后在运行时,此案例的工作方式与普通限制子句相同。
如果 FDW 支持远程联接,那么 GetForeignJoinPaths
应以与 GetForeignPaths
对基表的工作方式非常相似的方式为潜在的远程联接生成 ForeignPath
。有关预期联接的信息可以用上面描述的相同方式传送到 GetForeignPlan
。不过,baserestrictinfo
与联接关联不相关;相反,特定联接的相关联接子句会作为一个单独的参数(extra->restrictlist
)传递给 GetForeignJoinPaths
。
FDW 还可以附带执行某些计划操作,这些操作高于扫描和联接的级别,例如分组或聚合。要提供此类选项,FDW 应生成路径并将其插入到适当的上级关联中。例如,应使用 add_path
将代表远程聚合的路径插入到 UPPERREL_GROUP_AGG
关联中。此路径将根据成本基础与通过为外键关联读取简单扫描路径而执行的本地聚合进行比较(请注意,也必须提供此类路径,否则在计划时会出错)。如果远程聚合路径获胜(通常情况下会获胜),则通过调用 GetForeignPlan
将其转换为计划,方式与通常的方法相同。生成此类路径的建议位置是在 GetForeignUpperPaths
回调函数中,如果查询的所有基关联都来自同一 FDW,则会为每个上级关联(即,每个扫描/联接后处理步骤)调用此函数。
PlanForeignModify
及 第 57.2.4 节 中描述的其他回调,其设计基于如下假设:将以常规方式扫描外部关系,而非特定行更新将由 ModifyTable
本地计划节点驱动。对于常规情况,这是必要的,这种情况下,更新需要读取本地表和外部表。但是,如果可以通过外来服务器完全执行操作,则 FDW 可以生成表示该操作的路径,并将其插入 UPPERREL_FINAL
上层关系,在该关系中,它将与 ModifyTable
方法相竞争。此方法还可以用于实现远程 SELECT FOR UPDATE
,而非使用 第 57.2.6 节 中描述的行锁定回调。请记住,插入 UPPERREL_FINAL
的路径负责实现查询的所有行为。
计划 UPDATE
或 DELETE
时,PlanForeignModify
和 PlanDirectModify
可以查找外部表的 RelOptInfo
结构,并利用由扫描计划函数之前创建的 baserel->fdw_private
数据。但在 INSERT
中,目标表并未被扫描,因此没有 RelOptInfo
。由 PlanForeignModify
返回的 List
具有与 ForeignScan
计划节点的 fdw_private
列表相同的限制,即它只能包含 copyObject
知道如何复制的结构。
INSERT
带有 ON CONFLICT
子句不支持指定冲突目标,因为远程表上的唯一性约束或排除约束在本地未知。这反过来意味着不支持 ON CONFLICT DO UPDATE
,因为该规范在其中是强制性的。