52.4. 外部数据包装器查询规划

FDW回调函数GetForeignRelSizeGetForeignPathsGetForeignPlanPlanForeignModify必须适合PostgreSQL规划器的工作。这里有一些关于它们必须做什么的注记。

rootbaserel中的信息可以被用来减少必须从外部表获得的信息量(并且因此降低代价)。baserel->baserestrictinfo是特别有趣的,因为它包含限制条件(WHERE)子句,它应该被用来过滤要被获取的行(FDW本身并不要求强制这些条件,因为核心执行器可以检查它们)。baserel->reltargetlist可以被用来决定哪些类需要被获取;但是注意它仅列出了ForeignScan计划节点所发出的列,不包含在条件计算中使用但并不被查询输出的列。

有多个私有域可以给FDW规划函数来保存信息。通常,不管你存储什么在FDW私有域中,它们都应该被palloc,这样它会在规划结束时被回收。

baserel->fdw_private是一个void指针,它可以被FDW规划函数用来存储与特定外部表相关的信息。核心规划器不会碰它除非当baserel节点被创建时把它初始化为NULL。它对从GetForeignRelSize传递信息给GetForeignPaths和/或从GetForeignPaths传递信息给GetForeignPlan非常有用,这样避免了重新计算。

GetForeignPaths可以通过在ForeignPath节点的fdw_private域中存储私有信息来标识不同的访问路径。fdw_private被声明为一个List指针,但是可能实际上包含任何东西,因为规划器不会触碰它。但是,最好是使用一种nodeToString可导出的形式,这样在后端可以用于调试支持。

GetForeignPlan可以检查选中的ForeignPath节点的fdw_private域,并且可以生成被放置于ForeignPath计划节点中的fdw_exprsfdw_private列表。这两个列表必须被表示为一种copyObject可复制的形式。fdw_private列表没有任何其他限制并且不会被核心后端以任何形式解释。非 NIL 的fdw_exprs应该包含表达式树,该树会在运行时被执行。这些树将由规划器在后期处理,以便让它们变成完全可执行的。

GetForeignPlan中,通常被传入的目标列表可以被照样复制到计划节点中。被传入的 scan_clauses 列表包含和baserel->baserestrictinfo相同的子句,但是可能为了更好的执行效率会被重新排序。在简单情况下,FDW可以只把RestrictInfo节点从 scan_clauses 列表剥离(使用extract_actual_clauses)并且把所有子句放到计划节点的条件列表中,这意味着所有子句将在运行时由执行器检查。更复杂的FDW可能可以在内部检查某些子句,着这种情况下哪些子句可以从计划节点的条件列表中删除,这样执行器就不用浪费时间去检查它们。

作为一个例子,FDW可以标识某些foreign_variable = sub_expression形式的限制子句,它决定哪些可以使用由sub_expression给出的本地计算值在远程服务器上被执行。这样一个子句的实际标识应该在GetForeignPaths期间发生,因为它可能会影响路径的代价估计。路径的fdw_private域可能包括一个已标识的子句的RestrictInfo节点。然后GetForeignPlan将从 scan_clauses 中移除该子句,但是将sub_expression加到fdw_exprs来保证它被揉成可执行的形式。它可能还将把控制信息放入到计划节点的fdw_private域来告诉执行函数在运行时要做什么。传递给远程服务器的查询将涉及类似WHERE foreign_variable = $1的东西,使用在运行时从fdw_exprs表达式树获得的参数值。

FDW应该总是只依靠表的限制子句构建至少一个路径。在连接查询中,它可能还会选择依靠连接子句构建路径,例如foreign_variable = local_variable。这样的子句将不会在baserel->baserestrictinfo中找到,但是必须出现在关系的连接列表中。使用这样一个子句的路径被称为一个"参数化路径"。它必须用一个合适的param_info值来标识其他被使用在选中的连接子句中的关系;使用get_baserel_parampathinfo来计算该值。在GetForeignPlan中,连接子句的local_variable部分将被加到fdw_exprs中,并且接着在运行时和一个普通限制子句一样工作。

在规划一个UPDATEDELETE时,PlanForeignModify能为外部表查找RelOptInfo结构,并利用之前由扫描规划函数创建的baserel->fdw_private数据。但是,在INSERT中目标表不会被扫描,因此不会有它的RelOptInfo。由PlanForeignModify返回的List具有和ForeignScan计划节点的fdw_private列表相同的限制,即它必须只包含copyObject知道怎么拷贝的结构。

对于一个支持并发更新的外部数据源的UPDATEDELETE,我们推荐让ForeignScan操作锁住它得到的行,也许通过一种等效于SELECT FOR UPDATE的方式。当外部表是在一个SELECT FOR UPDATE/SHARE中被引用时,FDW也许还会选择在获取时就锁住行;如果不是这样,FOR UPDATEFOR SHARE选项对于所关注的外部表来说在本质上是一个空操作。这个行为可能产生和本地表上操作略微不同的语义,在后者中行锁通常被尽可能延迟:即使远程行无法通过后续的本地限制或连接条件,它们也应该被锁住。不过,准确地匹配本地语义将要求对每一行有一次额外的远程访问,并且可能无法依靠外部数据源提供的锁语义。