static bool is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
static bool foreign_qual_walker(Node *node, void *context);
-/*
- * return true if node can NOT be evaluatated in foreign server.
- */
-static bool
-foreign_qual_walker(Node *node, void *context)
-{
- if (node == NULL)
- return false;
-
- switch (nodeTag(node))
- {
- case T_Param:
- /* TODO: pass internal parameters to the foreign server */
- {
- ParamKind paramkind = ((Param *) node)->paramkind;
- elog(DEBUG1, "%s() param=%s", __FUNCTION__,
- paramkind == PARAM_EXTERN ? "PARAM_EXTERN" :
- paramkind == PARAM_EXEC ? "PARAM_EXEC" :
- paramkind == PARAM_SUBLINK ? "PARAM_SUBLINK" : "unkown");
- }
- if (((Param *) node)->paramkind != PARAM_EXTERN)
- return true;
- break;
- case T_DistinctExpr:
- case T_OpExpr:
- case T_ScalarArrayOpExpr:
- case T_FuncExpr:
- /*
- * If the qual contains any mutable (STABLE or VOLATILE) function,
- * the whole expression should be evaluated on local side.
- */
- if (contain_mutable_functions(node))
- return true;
- break;
- case T_TargetEntry:
- case T_PlaceHolderVar:
- case T_AppendRelInfo:
- case T_PlaceHolderInfo:
- /* TODO: research whether those complex nodes are evaluatable. */
- return true;
- default:
- break;
- }
-
- return expression_tree_walker(node, foreign_qual_walker, context);
-}
-
/*
* Deparse a var into column name, result is quoted if necessary.
*/
/*
* assume that only built-in functions can be pushed down.
* TODO routine mapping should be used to determine whether the function
- * can be executed in foreign server.
+ * can be pushed down.
*/
if (get_func_namespace(procid) != PG_CATALOG_NAMESPACE)
return false;
/*
* return true if node can NOT be evaluatated in foreign server.
+ *
+ * An expression which consists of expressions below can be evaluated in
+ * the foreign server.
+ * - constant value
+ * - variable (foreign table column)
+ * - external parameter (parameter of prepared statement)
+ * - array
+ * - bool expression (AND/OR/NOT)
+ * - NULL test (IS [NOT] NULL)
+ * - operator
+ * - IMMUTABLE or STABLE only
+ * - It is required that the meaning of the operator be the same as the
+ * local server in the foreign server.
+ * - function
+ * - IMMUTABLE or STABLE only
+ * - It is required that the meaning of the operator be the same as the
+ * local server in the foreign server.
+ * - scalar array operator (ANY/ALL)
*/
static bool
-is_not_remotely_executable_walker(Node *node, remotely_executable_cxt *context)
+foreign_qual_walker(Node *node, void *context)
{
if (node == NULL)
return false;
switch (nodeTag(node))
{
+ case T_Const:
+ case T_ArrayExpr:
+ case T_BoolExpr:
+ case T_NullTest:
+ case T_DistinctExpr:
+ case T_ScalarArrayOpExpr:
+ /*
+ * These type of nodes are known as safe to be pushed down.
+ * Of course the subtree of the node, if any, should be checked
+ * continuously.
+ */
+ break;
case T_Param:
- /* TODO: pass internal parameters to the foreign server */
+ /*
+ * External parameter can be pushed down.
+ * TODO: Pass internal parameters to the foreign server. It needs
+ * index renumbering.
+ */
{
- ParamKind paramkind = ((Param *) node)->paramkind;
- elog(DEBUG1, "%s() param=%s", __FUNCTION__,
- paramkind == PARAM_EXTERN ? "PARAM_EXTERN" :
- paramkind == PARAM_EXEC ? "PARAM_EXEC" :
- paramkind == PARAM_SUBLINK ? "PARAM_SUBLINK" : "unkown");
+ if (((Param *) node)->paramkind != PARAM_EXTERN)
+ return true;
}
- if (((Param *) node)->paramkind != PARAM_EXTERN)
- return true;
break;
case T_OpExpr:
{
OpExpr *op = (OpExpr *) node;
- ListCell *lc;
if (!is_proc_remotely_executable(op->opfuncid))
return true;
-
- /*
- * In addition to operator itself, every arguments should be
- * exacutable on remote side.
- */
- foreach (lc, op->args)
- {
- Node *arg = lfirst(lc);
- if (is_not_remotely_executable_walker(arg, context))
- return true;
- }
-
- return false;
+ /* arguments are checked later via walker */
}
+ break;
case T_FuncExpr:
{
FuncExpr *fe = (FuncExpr *) node;
- ListCell *lc;
if (!is_proc_remotely_executable(fe->funcid))
return true;
-
- /*
- * In addition to function itself, every arguments should be
- * exacutable on remote side.
- */
- foreach (lc, fe->args)
- {
- Node *arg = lfirst(lc);
- if (is_not_remotely_executable_walker(arg, context))
- return true;
- }
-
- return false;
+ /* arguments are checked later via walker */
}
- case T_TargetEntry:
- case T_PlaceHolderVar:
- case T_AppendRelInfo:
- case T_PlaceHolderInfo:
- /* TODO: research whether those complex nodes are evaluatable. */
- return true;
+ break;
case T_Var:
{
+ /*
+ * Var can be pushed down if it is in the foreign table.
+ * XXX Var of other relation can be here?
+ */
Var *var = (Var *) node;
- if (var->varno != context->foreignrel->relid || var->varlevelsup != 0)
+ remotely_executable_cxt *r_context;
+
+ r_context = (remotely_executable_cxt *) context;
+ if (var->varno != r_context->foreignrel->relid ||
+ var->varlevelsup != 0)
return true;
- else
- return false;
}
-
- default:
break;
+ default:
+ /* Other expression can't be pushed down */
+ elog(DEBUG3, "node is too complex");
+ elog(DEBUG3, "%s", nodeToString(node));
+ return true;
}
return expression_tree_walker(node, foreign_qual_walker, context);
/*
* Check whether the ExprState node can be evaluated in foreign server.
*
- * An expression which consists of expressions below can be evaluated in
- * the foreign server.
- * - constant value
- * - variable (foreign table column)
- * - external parameter (parameter of prepared statement)
- * - array
- * - bool expression (AND/OR/NOT)
- * - NULL test (IS [NOT] NULL)
- * - operator
- * - IMMUTABLE only
- * - It is required that the meaning of the operator be the same as the
- * local server in the foreign server.
- * - function
- * - IMMUTABLE only
- * - It is required that the meaning of the operator be the same as the
- * local server in the foreign server.
- * - scalar array operator (ANY/ALL)
+ * Actual check is implemented in foreign_qual_walker.
*/
static bool
is_foreign_qual(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
context.root = root;
context.foreignrel = baserel;
- if (is_not_remotely_executable_walker((Node *) expr, &context))
+
+ /*
+ * Check that the expression consists of nodes which are known as safe to
+ * be pushed down.
+ */
+ if (foreign_qual_walker((Node *) expr, &context))
return false;
+
+ /* Check that the expression doesn't include any volatile function. */
if (contain_volatile_functions((Node *) expr))
return false;
AttrNumber attr;
List *attr_used = NIL;
List *context;
+ List *foreign_expr = NIL;
StringInfoData sql;
ForeignTable *table = GetForeignTable(foreigntableid);
ListCell *lc;
context = deparse_context_for("foreigntable", foreigntableid);
+ /*
+ * Determine which qual can be pushed down.
+ *
+ * The expressions which satisfy is_foreign_qual() are deparsed into WHERE
+ * clause of result SQL string, and they could be removed from qual of
+ * PlanState to avoid duplicate evaluation at ExecScan().
+ *
+ * We never change the qual in the Plan node which was made by PREPARE
+ * statement to make following EXECUTE statements work properly. The Plan
+ * node is used repeatedly to create PlanState for each EXECUTE statement.
+ *
+ * We do this before deparsing SELECT clause because attributes which
+ * aren't used in neither reltargetlist nor baserel->baserestrictinfo,
+ * quals evaluated on local, can be replaced with NULL in the SELECT
+ * clause.
+ */
+ if (baserel->baserestrictinfo)
+ {
+ List *local_qual = NIL;
+ ListCell *lc;
+
+ /*
+ * Divide qual of PlanState into two lists, one for local evaluation
+ * and one for foreign evaluation.
+ */
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+ if (is_foreign_qual(root, baserel, ri->clause))
+ {
+ /* XXX: deparse and add to sql here */
+ foreign_expr = lappend(foreign_expr, ri->clause);
+ }
+ else
+ local_qual = lappend(local_qual, ri);
+ }
+ /*
+ * XXX: If the remote side is not reliable enough, we can keep the qual
+ * in PlanState as is and evaluate them on local side too. If so, just
+ * omit replacement below.
+ */
+ baserel->baserestrictinfo = local_qual;
+
+ }
+
/* Collect used columns from restrict information and target list */
attr_used = list_union(attr_used, baserel->reltargetlist);
foreach (lc, baserel->baserestrictinfo)
if (relname == NULL)
relname = get_rel_name(foreigntableid);
appendStringInfo(&sql, " FROM %s.%s",
- quote_identifier(nspname),
+ quote_identifier(nspname),
quote_identifier(relname));
-
/*
- * deparse WHERE cluase
- *
- * The expressions which satisfy is_foreign_qual() are deparsed into WHERE
- * clause of result SQL string, and they could be removed from qual of
- * PlanState to avoid duplicate evaluation at ExecScan().
- *
- * We never change the qual in the Plan node which was made by PREPARE
- * statement to make following EXECUTE statements work properly. The Plan
- * node is used repeatedly to create PlanState for each EXECUTE statement.
+ * deparse WHERE if some quals can be pushed down
*/
- if (baserel->baserestrictinfo)
+ if (foreign_expr != NIL)
{
- List *local_qual = NIL;
- List *foreign_expr = NIL;
- ListCell *lc;
-
- /*
- * Divide qual of PlanState into two lists, one for local evaluation
- * and one for foreign evaluation.
- */
- foreach (lc, baserel->baserestrictinfo)
- {
- RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
-
- if (is_foreign_qual(root, baserel, ri->clause))
- {
- /* XXX: deparse and add to sql here */
- foreign_expr = lappend(foreign_expr, ri->clause);
- }
- else
- local_qual = lappend(local_qual, ri);
- }
- /*
- * XXX: If the remote side is not reliable enough, we can keep the qual
- * in PlanState as is and evaluate them on local side too. If so, just
- * omit replacement below.
- */
- baserel->baserestrictinfo = local_qual;
-
/*
* Deparse quals to be evaluated in the foreign server if any.
* TODO: modify deparse_expression() to deparse conditions which use