前言
今天在pgfans问答社区看到一个问题,使用postgres_fdw创建的外表进行查询时where没推到remote端执行。
如图:
可以看到当where条件对空间类型做函数计算匹配时,where条件没有推至remote,而是查询了remote全量数据,where条件作为local filter来过滤数据。

这是为什么呢?
原理分析
postgres_fdw是否下推expr至remote,主要取决于is_foreign_expr函数的判断。
函数中返回false的位置,就是不下推至remote的几种场景。
主要是:
1、foreign_expr_walker函数返回false;
2、表达式的有效排序规则不是由foreign var产生的;
3、包含任何可变函数的表达式不能被发送,因为它的结果不稳定。
/*
* Returns true if given expr is safe to evaluate on the foreign server.
*/
bool
is_foreign_expr(PlannerInfo *root,
RelOptInfo *baserel,
Expr *expr)
{
foreign_glob_cxt glob_cxt;
foreign_loc_cxt loc_cxt;
PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) (baserel->fdw_private);
/*
* Check that the expression consists of nodes that are safe to execute
* remotely.
*/
glob_cxt.root = root;
glob_cxt.foreignrel = baserel;
/*
* For an upper relation, use relids from its underneath scan relation,
* because the upperrel's own relids currently aren't set to anything
* meaningful by the core code. For other relation, use their own relids.
*/
if (IS_UPPER_REL(baserel))
glob_cxt.relids = fpinfo->outerrel->relids;
else
glob_cxt.relids = baserel->relids;
loc_cxt.collation = InvalidOid;
loc_cxt.state = FDW_COLLATE_NONE;
if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt, NULL))
return false;
/*
* If the expression has a valid collation that does not arise from a
* foreign var, the expression can not be sent over.
*/
if (loc_cxt.state == FDW_COLLATE_UNSAFE)
return false;
/*
* An expression which includes any mutable functions can't be sent over
* because its result is not stable. For example, sending now() remote
* side could cause confusion from clock offsets. Future versions might
* be able to make this choice with more granularity. (We check this last
* because it requires a lot of expensive catalog lookups.)
*/
if (contain_mutable_functions((Node *) expr))
return false;
/* OK to evaluate on the remote server */
return true;
}
给对应函数设置断点,debug追踪下我们这个case。
进入is_foreign_expr函数,我们当前的expr的nodetag为T_FuncExpr。

进入foreign_expr_walker函数,对应到case:T_FuncExpr,调用is_shippable函数。

进入is_shippable函数,判断fpinfo->shippable_extensions == NIL所以 return false

classifyConditions函数中:因此我们的is_foreign_expr函数也返回false了,那么我们expr就被append到local_conds而非remote_conds。

那么这样就导致我们的expr没有被下推至remote,核心问题在于为什么fpinfo->shippable_extensions == NIL,这个变量是什么?
在apply_server_options函数中可以找到答案,fpinfo->shippable_extensions取的是我们创建的server的option中的extensions配置项,如果没指定则为NULL。
我们的where条件中ST_DWithin这个函数为postgis插件的函数,因此看起来将server options extensions设置postgis就可以下推至remote了?
/*
* Parse options from foreign server and apply them to fpinfo.
*
* New options might also require tweaking merge_fdw_options().
*/
static void
apply_server_options(PgFdwRelationInfo *fpinfo)
{
ListCell *lc;
foreach(lc, fpinfo->server->options)
{
DefElem *def = (DefElem *) lfirst(lc);
if (strcmp(def->defname, "use_remote_estimate") == 0)
fpinfo->use_remote_estimate = defGetBoolean(def);
else if (strcmp(def->defname, "fdw_startup_cost") == 0)
(void) parse_real(defGetString(def), &fpinfo->fdw_startup_cost, 0,
NULL);
else if (strcmp(def->defname, "fdw_tuple_cost") == 0)
(void) parse_real(defGetString(def), &fpinfo->fdw_tuple_cost, 0,
NULL);
else if (strcmp(def->defname, "extensions") == 0)
fpinfo->shippable_extensions =
ExtractExtensionList(defGetString(def), false);
else if (strcmp(def->defname, "fetch_size") == 0)
(void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL);
else if (strcmp(def->defname, "async_capable") == 0)
fpinfo->async_capable = defGetBoolean(def);
}
}
解决方案
执行alter server testserver options (extensions ‘postgis’);
发现where条件可以下推至remote去执行。

小结
对于postgres_fdw是否会将where条件下推至remote执行有以下规则:
where条件中如果使用到了插件对应的function、operator、type等,那么首先在server的option里要配置这个extension。
满足这几种条件就不会下推至remote:
1、foreign_expr_walker函数返回false;
2、表达式的有效排序规则不是由foreign var产生的;
3、包含任何可变函数的表达式不能被发送,因为它的结果不稳定。




