57.4. 外部数据包装器查询计划

FDW 回调函数GetForeignRelSizeGetForeignPathsGetForeignPlanPlanForeignModifyGetForeignJoinPathsGetForeignUpperPathsPlanDirectModify必须适合 PostgreSQL 计划程序的工作。以下是有关他们必须执行的操作的一些注意事项。

rootbaserel中的信息可用于减少必须从外部表中获取的信息量(从而降低成本)。 baserel->baserestrictinfo特别有趣,因为它包含限制条件(WHERE子句),该限制条件应用于过滤要提取的行。 (FDW 本身不需要强制执行这些限定符,因为核心 Actuator 可以代替检查它们.)baserel->reltarget->exprs可用于确定需要提取哪些列;但请注意,它仅列出ForeignScan计划节点必须发出的列,而不列出用于质量评估但查询没有输出的列。

FDW 计划功能可以使用各种专用字段来保留信息。通常,存储在 FDW 专用字段中的任何内容都应进行 palloc 分配,以便在计划结束时将其回收。

baserel->fdw_privatevoid指针,可用于 FDW 计划功能来存储与特定外部表有关的信息。创建RelOptInfo节点时,核心计划器不会触摸它,只是将其初始化为 NULL。对于将信息从GetForeignRelSize传递到GetForeignPaths和/或GetForeignPaths传递到GetForeignPlan很有用,从而避免了重新计算。

GetForeignPaths可以通过将私有信息存储在ForeignPath个节点的fdw_private字段中来标识不同访问路径的含义。 fdw_private被声明为List指针,但实际上可以包含任何内容,因为核心计划程序不会触摸它。但是,最佳实践是使用nodeToString可转储的表示形式,以与后端中可用的调试支持一起使用。

GetForeignPlan可以检查所选ForeignPath节点的fdw_private字段,并可以生成fdw_exprsfdw_private列表以放置在ForeignScan计划节点中,这些列表在执行时将可用。这两个列表都必须以copyObject知道如何复制的形式表示。 fdw_private列表没有其他限制,并且核心后端不会以任何方式对其进行解释。 fdw_exprs列表(如果不是 NIL)应该包含要在运行时执行的表达式树。这些树将由计划者进行后期处理,以使其完全可执行。

GetForeignPlan中,通常可以按原样将传入的目标列表复制到计划节点中。传递的scan_clauses列表包含与baserel->baserestrictinfo相同的子句,但可以重新排序以提高执行效率。在简单的情况下,FDW 只需从scan_clauses列表中剥离RestrictInfo个节点(使用extract_actual_clauses),然后将所有子句放入计划节点的 qual 列表中,这意味着执行者将在运行时检查所有子句。更复杂的 FDW 可能能够在内部检查某些子句,在这种情况下,可以从计划节点的质量列表中删除这些子句,以使执行者不会浪费时间重新检查它们。

例如,FDW 可能会标识* foreign_variable * = * sub_expression 形式的某些限制子句,只要确定了 sub_expression 的本地评估值,它就可以确定可以在远程服务器上执行。此类子句的实际标识应在GetForeignPaths期间进行,因为这会影响路径的成本估算。路径的fdw_private字段可能包含指向所标识子句的RestrictInfo节点的指针。然后GetForeignPlan将从scan_clauses中删除该子句,但在fdw_exprs上添加 sub_expression *以确保它被压缩为可执行形式。可能还会将控制信息放入计划节点的fdw_private字段中,以告诉执行功能在运行时做什么。传输到远程服务器的查询将涉及WHERE foreign_variable = $1之类的内容,并在运行时从fdw_exprs表达式树的评估中获取参数值。

必须将从计划节点的质量列表中删除的所有子句添加到fdw_recheck_quals或由RecheckForeignScan重新检查,以确保READ COMMITTED隔离级别的正确行为。当查询中涉及的某些其他表发生并发更新时,执行程序可能需要验证 Tuples 是否仍然满足所有原始质量,可能是针对一组不同的参数值。使用fdw_recheck_quals通常比在RecheckForeignScan内部实现检查更容易,但是当按下外部联接时,此方法将不够用,因为在这种情况下,联接 Tuples 可能会将某些字段设置为 NULL 而不完全拒绝该 Tuples。

FDW 可以填充的另一个ForeignScan字段是fdw_scan_tlist,它描述了 FDW 为该计划节点返回的 Tuples。对于简单的外部表扫描,可以将其设置为NIL,这意味着返回的 Tuples 具有为外部表声明的行类型。非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中,join 子句的 local_variable *部分将添加到fdw_exprs,然后在运行时,该案例的工作原理与普通限制子句相同。

如果 FDW 支持远程联接,则GetForeignJoinPaths应该为潜在的远程联接产生ForeignPath,其方式与GetForeignPaths对于基表的工作方式几乎相同。可以按照与上述相同的方式将有关预期联接的信息转发到GetForeignPlan。但是,baserestrictinfo与联接关系无关;而是将特定联接的相关联接子句作为单独的参数(extra->restrictlist)传递给GetForeignJoinPaths

FDW 可能还支持直接执行超出扫描和联接级别的某些计划操作,例如分组或聚合。要提供此类选项,FDW 应生成路径并将其插入相应的*“上关系” *中。例如,应该使用add_path将表示远程聚合的路径插入到UPPERREL_GROUP_AGG关系中。该路径将根据成本与通过读取外部关系的简单扫描路径进行的本地聚合进行比较(请注意,还必须提供该路径,否则在计划时会出现错误)。如果远程聚合路径(通常会获胜),则将通过调用GetForeignPlan以通常的方式将其转换为计划。如果要查询的所有基本关系都来自同一 FDW,则建议在GetForeignUpperPaths回调函数中生成此 Classpath 的建议位置,该函数针对每个上层关系(即每个后扫描/联接处理步骤)调用。

PlanForeignModifySection 57.2.4中描述的其他回调是在以下假设下设计的:将以常规方式扫描外部关系,然后由本地ModifyTable计划节点驱动单独的行更新。对于更新需要读取本地表和外部表的一般情况,此方法是必需的。但是,如果该操作可以完全由外部服务器执行,则 FDW 可以生成表示该操作的路径,并将其插入UPPERREL_FINAL上级关系中,从而与ModifyTable方法竞争。此方法也可以用于实现远程SELECT FOR UPDATE,而不是使用Section 57.2.5中描述的行锁定回调。请记住,插入UPPERREL_FINAL的路径负责实现查询的* all *行为。

在计划UPDATEDELETE时,PlanForeignModifyPlanDirectModify可以查找外表的RelOptInfo结构,并利用先前由扫描计划功能创建的baserel->fdw_private数据。但是,在INSERT中未扫描目标表,因此没有RelOptInfoPlanForeignModify返回的ListForeignScan计划节点的fdw_private列表具有相同的限制,即它必须仅包含copyObject知道如何复制的结构。

带有ON CONFLICT子句的INSERT不支持指定冲突目标,因为本地不知道远程表的唯一约束或排除约束。这反过来意味着不支持ON CONFLICT DO UPDATE,因为在那里规范是强制性的。