pgsql_fdw_pushdown_v10.patch
text/plain
Filename: pgsql_fdw_pushdown_v10.patch
Type: text/plain
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v10
| File | + | − |
|---|---|---|
| contrib/pgsql_fdw/deparse.c | 0 | 0 |
| contrib/pgsql_fdw/expected/pgsql_fdw.out | 0 | 0 |
| contrib/pgsql_fdw/pgsql_fdw.c | 0 | 0 |
| contrib/pgsql_fdw/pgsql_fdw.h | 0 | 0 |
| contrib/pgsql_fdw/sql/pgsql_fdw.sql | 0 | 0 |
diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c
index ca3356d..e7a6303 100644
*** a/contrib/pgsql_fdw/deparse.c
--- b/contrib/pgsql_fdw/deparse.c
***************
*** 14,19 ****
--- 14,21 ----
#include "access/transam.h"
#include "catalog/pg_class.h"
+ #include "catalog/pg_operator.h"
+ #include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "foreign/foreign.h"
#include "lib/stringinfo.h"
***************
*** 22,30 ****
--- 24,34 ----
#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h"
+ #include "parser/parser.h"
#include "parser/parsetree.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+ #include "utils/syscache.h"
#include "pgsql_fdw.h"
*************** static void deparseRelation(StringInfo b
*** 44,49 ****
--- 48,74 ----
bool need_prefix);
static void deparseVar(StringInfo buf, Var *node, PlannerInfo *root,
bool need_prefix);
+ static void deparseConst(StringInfo buf, Const *node, PlannerInfo *root);
+ static void deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root);
+ static void deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root);
+ static void deparseDistinctExpr(StringInfo buf, DistinctExpr *node,
+ PlannerInfo *root);
+ static void deparseRelabelType(StringInfo buf, RelabelType *node,
+ PlannerInfo *root);
+ static void deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root);
+ static void deparseParam(StringInfo buf, Param *node, PlannerInfo *root);
+ static void deparseScalarArrayOpExpr(StringInfo buf, ScalarArrayOpExpr *node,
+ PlannerInfo *root);
+ static void deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root);
+ static void deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root);
+ static void deparseArrayExpr(StringInfo buf, ArrayExpr *node, PlannerInfo *root);
+
+ /*
+ * Determine whether an expression can be evaluated on remote side safely.
+ */
+ static bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
+ static bool foreign_expr_walker(Node *node, foreign_executable_cxt *context);
+ static bool is_builtin(Oid procid);
/*
* Deparse query representation into SQL statement which suits for remote
*************** deparseSimpleSql(Oid relid,
*** 152,157 ****
--- 177,273 ----
}
/*
+ * Examine baserestrictinfo of baserel, and extract expressions which can be
+ * evaluated on remote side safely. This function never changes
+ * baserestrictinfo.
+ */
+ List *
+ extractRemoteExprs(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List **local_exprs)
+ {
+ ListCell *lc;
+ List *exprs = NIL;
+
+ foreach(lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+ if (is_foreign_expr(root, baserel, ri->clause))
+ exprs = lappend(exprs, ri->clause);
+ else
+ {
+ if (local_exprs != NULL)
+ *local_exprs = lappend(*local_exprs, ri->clause);
+ }
+
+ }
+
+ return exprs;
+ }
+
+ /*
+ * Deparse given expression into buf. Actual string operation is delegated to
+ * node-type-specific functions.
+ *
+ * Note that switch statement of this function MUST match the one in
+ * foreign_expr_walker to avoid unsupported error..
+ */
+ void
+ deparseExpr(StringInfo buf, Expr *node, PlannerInfo *root)
+ {
+ /*
+ * This part must be match foreign_expr_walker.
+ */
+ switch (nodeTag(node))
+ {
+ case T_Const:
+ deparseConst(buf, (Const *) node, root);
+ break;
+ case T_BoolExpr:
+ deparseBoolExpr(buf, (BoolExpr *) node, root);
+ break;
+ case T_NullTest:
+ deparseNullTest(buf, (NullTest *) node, root);
+ break;
+ case T_DistinctExpr:
+ deparseDistinctExpr(buf, (DistinctExpr *) node, root);
+ break;
+ case T_RelabelType:
+ deparseRelabelType(buf, (RelabelType *) node, root);
+ break;
+ case T_FuncExpr:
+ deparseFuncExpr(buf, (FuncExpr *) node, root);
+ break;
+ case T_Param:
+ deparseParam(buf, (Param *) node, root);
+ break;
+ case T_ScalarArrayOpExpr:
+ deparseScalarArrayOpExpr(buf, (ScalarArrayOpExpr *) node, root);
+ break;
+ case T_OpExpr:
+ deparseOpExpr(buf, (OpExpr *) node, root);
+ break;
+ case T_Var:
+ deparseVar(buf, (Var *) node, root, false);
+ break;
+ case T_ArrayRef:
+ deparseArrayRef(buf, (ArrayRef *) node, root);
+ break;
+ case T_ArrayExpr:
+ deparseArrayExpr(buf, (ArrayExpr *) node, root);
+ break;
+ default:
+ {
+ ereport(ERROR,
+ (errmsg("unsupported expression for deparse"),
+ errdetail("%s", nodeToString(node))));
+ }
+ break;
+ }
+ }
+
+ /*
* Deparse node into buf, with relation qualifier if need_prefix was true. If
* node is a column of a foreign table, use value of colname FDW option (if any)
* instead of attribute name.
*************** deparseRelation(StringInfo buf,
*** 287,289 ****
--- 403,1093 ----
appendStringInfo(buf, "%s.", q_nspname);
appendStringInfo(buf, "%s", q_relname);
}
+
+ /*
+ * Deparse given constant value into buf. This function have to be kept in
+ * sync with get_const_expr.
+ */
+ static void
+ deparseConst(StringInfo buf,
+ Const *node,
+ PlannerInfo *root)
+ {
+ Oid typoutput;
+ bool typIsVarlena;
+ char *extval;
+ bool isfloat = false;
+ bool needlabel;
+
+ if (node->constisnull)
+ {
+ appendStringInfo(buf, "NULL");
+ return;
+ }
+
+ getTypeOutputInfo(node->consttype,
+ &typoutput, &typIsVarlena);
+ extval = OidOutputFunctionCall(typoutput, node->constvalue);
+
+ switch (node->consttype)
+ {
+ case ANYARRAYOID:
+ case ANYNONARRAYOID:
+ elog(ERROR, "anyarray and anyenum are not supported");
+ break;
+ case INT2OID:
+ case INT4OID:
+ case INT8OID:
+ case OIDOID:
+ case FLOAT4OID:
+ case FLOAT8OID:
+ case NUMERICOID:
+ {
+ /*
+ * No need to quote unless they contain special values such as
+ * 'Nan'.
+ */
+ if (strspn(extval, "0123456789+-eE.") == strlen(extval))
+ {
+ if (extval[0] == '+' || extval[0] == '-')
+ appendStringInfo(buf, "(%s)", extval);
+ else
+ appendStringInfoString(buf, extval);
+ if (strcspn(extval, "eE.") != strlen(extval))
+ isfloat = true; /* it looks like a float */
+ }
+ else
+ appendStringInfo(buf, "'%s'", extval);
+ }
+ break;
+ case BITOID:
+ case VARBITOID:
+ appendStringInfo(buf, "B'%s'", extval);
+ break;
+ case BOOLOID:
+ if (strcmp(extval, "t") == 0)
+ appendStringInfoString(buf, "true");
+ else
+ appendStringInfoString(buf, "false");
+ break;
+
+ default:
+ {
+ const char *valptr;
+
+ appendStringInfoChar(buf, '\'');
+ for (valptr = extval; *valptr; valptr++)
+ {
+ char ch = *valptr;
+
+ /*
+ * standard_conforming_strings of remote session should be
+ * set to similar value as local session.
+ */
+ if (SQL_STR_DOUBLE(ch, !standard_conforming_strings))
+ appendStringInfoChar(buf, ch);
+ appendStringInfoChar(buf, ch);
+ }
+ appendStringInfoChar(buf, '\'');
+ }
+ break;
+ }
+
+ /*
+ * Append ::typename unless the constant will be implicitly typed as the
+ * right type when it is read in.
+ *
+ * XXX this code has to be kept in sync with the behavior of the parser,
+ * especially make_const.
+ */
+ switch (node->consttype)
+ {
+ case BOOLOID:
+ case INT4OID:
+ case UNKNOWNOID:
+ needlabel = false;
+ break;
+ case NUMERICOID:
+ needlabel = !isfloat || (node->consttypmod >= 0);
+ break;
+ default:
+ needlabel = true;
+ break;
+ }
+ if (needlabel)
+ {
+ appendStringInfo(buf, "::%s",
+ format_type_with_typemod(node->consttype,
+ node->consttypmod));
+ }
+ }
+
+ static void
+ deparseBoolExpr(StringInfo buf,
+ BoolExpr *node,
+ PlannerInfo *root)
+ {
+ ListCell *lc;
+ char *op;
+ bool first;
+
+ switch (node->boolop)
+ {
+ case AND_EXPR:
+ op = "AND";
+ break;
+ case OR_EXPR:
+ op = "OR";
+ break;
+ case NOT_EXPR:
+ appendStringInfo(buf, "(NOT ");
+ deparseExpr(buf, list_nth(node->args, 0), root);
+ appendStringInfo(buf, ")");
+ return;
+ }
+
+ first = true;
+ appendStringInfo(buf, "(");
+ foreach(lc, node->args)
+ {
+ if (!first)
+ appendStringInfo(buf, " %s ", op);
+ deparseExpr(buf, (Expr *) lfirst(lc), root);
+ first = false;
+ }
+ appendStringInfo(buf, ")");
+ }
+
+ /*
+ * Deparse given IS [NOT] NULL test expression into buf.
+ */
+ static void
+ deparseNullTest(StringInfo buf,
+ NullTest *node,
+ PlannerInfo *root)
+ {
+ appendStringInfoChar(buf, '(');
+ deparseExpr(buf, node->arg, root);
+ if (node->nulltesttype == IS_NULL)
+ appendStringInfo(buf, " IS NULL)");
+ else
+ appendStringInfo(buf, " IS NOT NULL)");
+ }
+
+ static void
+ deparseDistinctExpr(StringInfo buf,
+ DistinctExpr *node,
+ PlannerInfo *root)
+ {
+ Assert(list_length(node->args) == 2);
+
+ deparseExpr(buf, linitial(node->args), root);
+ appendStringInfo(buf, " IS DISTINCT FROM ");
+ deparseExpr(buf, lsecond(node->args), root);
+ }
+
+ static void
+ deparseRelabelType(StringInfo buf,
+ RelabelType *node,
+ PlannerInfo *root)
+ {
+ char *typname;
+
+ Assert(node->arg);
+
+ /* We don't need to deparse cast when argument has same type as result. */
+ if (IsA(node->arg, Const) &&
+ ((Const *) node->arg)->consttype == node->resulttype &&
+ ((Const *) node->arg)->consttypmod == -1)
+ {
+ deparseExpr(buf, node->arg, root);
+ return;
+ }
+
+ typname = format_type_with_typemod(node->resulttype, node->resulttypmod);
+ appendStringInfoChar(buf, '(');
+ deparseExpr(buf, node->arg, root);
+ appendStringInfo(buf, ")::%s", typname);
+ }
+
+ /*
+ * Deparse given node which represents a function call into buf. We treat only
+ * explicit function call and explicit cast (coerce), because others are
+ * processed on remote side if necessary.
+ *
+ * Function name (and type name) is always qualified by schema name to avoid
+ * problems caused by different setting of search_path on remote side.
+ */
+ static void
+ deparseFuncExpr(StringInfo buf,
+ FuncExpr *node,
+ PlannerInfo *root)
+ {
+ Oid pronamespace;
+ const char *schemaname;
+ const char *funcname;
+ ListCell *arg;
+ bool first;
+
+ pronamespace = get_func_namespace(node->funcid);
+ schemaname = quote_identifier(get_namespace_name(pronamespace));
+ funcname = quote_identifier(get_func_name(node->funcid));
+
+ if (node->funcformat == COERCE_EXPLICIT_CALL)
+ {
+ /* Function call, deparse all arguments recursively. */
+ appendStringInfo(buf, "%s.%s(", schemaname, funcname);
+ first = true;
+ foreach(arg, node->args)
+ {
+ if (!first)
+ appendStringInfo(buf, ", ");
+ deparseExpr(buf, lfirst(arg), root);
+ first = false;
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ else if (node->funcformat == COERCE_EXPLICIT_CAST)
+ {
+ /* Explicit cast, deparse only first argument. */
+ appendStringInfoChar(buf, '(');
+ deparseExpr(buf, linitial(node->args), root);
+ appendStringInfo(buf, ")::%s", funcname);
+ }
+ else
+ {
+ /* Implicit cast, deparse only first argument. */
+ deparseExpr(buf, linitial(node->args), root);
+ }
+ }
+
+ /*
+ * Deparse given Param node into buf.
+ *
+ * We don't renumber parameter id, because skipping $1 is not cause problem
+ * as far as we pass through all arguments.
+ */
+ static void
+ deparseParam(StringInfo buf,
+ Param *node,
+ PlannerInfo *root)
+ {
+ Assert(node->paramkind == PARAM_EXTERN);
+
+ appendStringInfo(buf, "$%d", node->paramid);
+ }
+
+ /*
+ * Deparse given ScalarArrayOpExpr expression into buf. To avoid problems
+ * around priority of operations, we always parenthesize the arguments. Also we
+ * use OPERATOR(schema.operator) notation to determine remote operator exactly.
+ */
+ static void
+ deparseScalarArrayOpExpr(StringInfo buf,
+ ScalarArrayOpExpr *node,
+ PlannerInfo *root)
+ {
+ HeapTuple tuple;
+ Form_pg_operator form;
+ const char *opnspname;
+ char *opname;
+ Expr *arg1;
+ Expr *arg2;
+
+ /* Retrieve necessary information about the operator from system catalog. */
+ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for operator %u", node->opno);
+ form = (Form_pg_operator) GETSTRUCT(tuple);
+ /* opname is not a SQL identifier, so we don't need to quote it. */
+ opname = NameStr(form->oprname);
+ opnspname = quote_identifier(get_namespace_name(form->oprnamespace));
+ ReleaseSysCache(tuple);
+
+ /* Sanity check. */
+ Assert(list_length(node->args) == 2);
+
+ /* Always parenthesize the expression. */
+ appendStringInfoChar(buf, '(');
+
+ /* Extract operands. */
+ arg1 = linitial(node->args);
+ arg2 = lsecond(node->args);
+
+ /* Deparse fully qualified operator name. */
+ deparseExpr(buf, arg1, root);
+ appendStringInfo(buf, " OPERATOR(%s.%s) %s (",
+ opnspname, opname, node->useOr ? "ANY" : "ALL");
+ deparseExpr(buf, arg2, root);
+ appendStringInfoChar(buf, ')');
+
+ /* Always parenthesize the expression. */
+ appendStringInfoChar(buf, ')');
+ }
+
+ /*
+ * Deparse given operator expression into buf. To avoid problems around
+ * priority of operations, we always parenthesize the arguments. Also we use
+ * OPERATOR(schema.operator) notation to determine remote operator exactly.
+ */
+ static void
+ deparseOpExpr(StringInfo buf,
+ OpExpr *node,
+ PlannerInfo *root)
+ {
+ HeapTuple tuple;
+ Form_pg_operator form;
+ const char *opnspname;
+ char *opname;
+ char oprkind;
+ ListCell *arg;
+
+ /* Retrieve necessary information about the operator from system catalog. */
+ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for operator %u", node->opno);
+ form = (Form_pg_operator) GETSTRUCT(tuple);
+ opnspname = quote_identifier(get_namespace_name(form->oprnamespace));
+ /* opname is not a SQL identifier, so we don't need to quote it. */
+ opname = NameStr(form->oprname);
+ oprkind = form->oprkind;
+ ReleaseSysCache(tuple);
+
+ /* Sanity check. */
+ Assert((oprkind == 'r' && list_length(node->args) == 1) ||
+ (oprkind == 'l' && list_length(node->args) == 1) ||
+ (oprkind == 'b' && list_length(node->args) == 2));
+
+ /* Always parenthesize the expression. */
+ appendStringInfoChar(buf, '(');
+
+ /* Deparse first operand. */
+ arg = list_head(node->args);
+ if (oprkind == 'r' || oprkind == 'b')
+ {
+ deparseExpr(buf, lfirst(arg), root);
+ appendStringInfoChar(buf, ' ');
+ }
+
+ /* Deparse fully qualified operator name. */
+ appendStringInfo(buf, "OPERATOR(%s.%s)", opnspname, opname);
+
+ /* Deparse last operand. */
+ arg = list_tail(node->args);
+ if (oprkind == 'l' || oprkind == 'b')
+ {
+ appendStringInfoChar(buf, ' ');
+ deparseExpr(buf, lfirst(arg), root);
+ }
+
+ appendStringInfoChar(buf, ')');
+ }
+
+ static void
+ deparseArrayRef(StringInfo buf,
+ ArrayRef *node,
+ PlannerInfo *root)
+ {
+ ListCell *lowlist_item;
+ ListCell *uplist_item;
+
+ /* Always parenthesize the expression. */
+ appendStringInfoChar(buf, '(');
+
+ /* Deparse referenced array expression first. */
+ appendStringInfoChar(buf, '(');
+ deparseExpr(buf, node->refexpr, root);
+ appendStringInfoChar(buf, ')');
+
+ /* Deparse subscripts expression. */
+ lowlist_item = list_head(node->reflowerindexpr); /* could be NULL */
+ foreach(uplist_item, node->refupperindexpr)
+ {
+ appendStringInfoChar(buf, '[');
+ if (lowlist_item)
+ {
+ deparseExpr(buf, lfirst(lowlist_item), root);
+ appendStringInfoChar(buf, ':');
+ lowlist_item = lnext(lowlist_item);
+ }
+ deparseExpr(buf, lfirst(uplist_item), root);
+ appendStringInfoChar(buf, ']');
+ }
+
+ appendStringInfoChar(buf, ')');
+ }
+
+
+ /*
+ * Deparse given array of something into buf.
+ */
+ static void
+ deparseArrayExpr(StringInfo buf,
+ ArrayExpr *node,
+ PlannerInfo *root)
+ {
+ ListCell *lc;
+ bool first = true;
+
+ appendStringInfo(buf, "ARRAY[");
+ foreach(lc, node->elements)
+ {
+ if (!first)
+ appendStringInfo(buf, ", ");
+ deparseExpr(buf, lfirst(lc), root);
+
+ first = false;
+ }
+ appendStringInfoChar(buf, ']');
+
+ /* If the array is empty, we need explicit cast to the array type. */
+ if (node->elements == NIL)
+ {
+ char *typname;
+
+ typname = format_type_with_typemod(node->array_typeid, -1);
+ appendStringInfo(buf, "::%s", typname);
+ }
+ }
+
+ /*
+ * Returns true if expr is safe to be evaluated on the foreign server.
+ */
+ static bool
+ is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
+ {
+ foreign_executable_cxt context;
+ context.root = root;
+ context.foreignrel = baserel;
+
+ /*
+ * An expression which includes any mutable function can't be pushed down
+ * because it's result is not stable. For example, pushing now() down to
+ * remote side would cause confusion from the clock offset.
+ * If we have routine mapping infrastructure in future release, we will be
+ * able to choose function to be pushed down in finer granularity.
+ */
+ if (contain_mutable_functions((Node *) expr))
+ return false;
+
+ /*
+ * Check that the expression consists of nodes which are known as safe to
+ * be pushed down.
+ */
+ if (foreign_expr_walker((Node *) expr, &context))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Return true if node includes any node which is not known as safe to be
+ * pushed down.
+ */
+ static bool
+ foreign_expr_walker(Node *node, foreign_executable_cxt *context)
+ {
+ if (node == NULL)
+ return false;
+
+ /*
+ * Special case handling for List; expression_tree_walker handles List as
+ * well as other Expr nodes. For instance, List is used in RestrictInfo
+ * for args of FuncExpr node.
+ *
+ * Although the comments of expression_tree_walker mention that
+ * RangeTblRef, FromExpr, JoinExpr, and SetOperationStmt are handled as
+ * well, but we don't care them because they are not used in RestrictInfo.
+ * If one of them was passed into, default label catches it and give up
+ * traversing.
+ */
+ if (IsA(node, List))
+ {
+ ListCell *lc;
+
+ foreach(lc, (List *) node)
+ {
+ if (foreign_expr_walker(lfirst(lc), context))
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * If given expression has valid collation, it can't be pushed down because
+ * it might has incompatible semantics on remote side.
+ */
+ if (exprCollation(node) != InvalidOid ||
+ exprInputCollation(node) != InvalidOid)
+ return true;
+
+ /*
+ * If return type of given expression is not built-in, it can't be pushed
+ * down because it might has incompatible semantics on remote side.
+ */
+ if (!is_builtin(exprType(node)))
+ return true;
+
+ switch (nodeTag(node))
+ {
+ case T_Const:
+ /*
+ * Using anyarray and/or anyenum in remote query is not supported.
+ */
+ if (((Const *) node)->consttype == ANYARRAYOID ||
+ ((Const *) node)->consttype == ANYNONARRAYOID)
+ return true;
+ break;
+ case T_BoolExpr:
+ case T_NullTest:
+ case T_DistinctExpr:
+ case T_RelabelType:
+ /*
+ * 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 at the tail of this function.
+ */
+ break;
+ /*
+ * If function used by the expression is not built-in, it can't be
+ * pushed down because it might has incompatible semantics on remote
+ * side.
+ */
+ case T_FuncExpr:
+ {
+ FuncExpr *fe = (FuncExpr *) node;
+ if (!is_builtin(fe->funcid))
+ return true;
+ }
+ break;
+ case T_Param:
+ /*
+ * Only external parameters can be pushed down.:
+ */
+ {
+ if (((Param *) node)->paramkind != PARAM_EXTERN)
+ return true;
+ }
+ break;
+ case T_ScalarArrayOpExpr:
+ /*
+ * Only built-in operators can be pushed down. In addition,
+ * underlying function must be built-in and immutable, but we don't
+ * check volatility here; such check must be done already with
+ * contain_mutable_functions.
+ */
+ {
+ ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node;
+
+ if (!is_builtin(oe->opno) || !is_builtin(oe->opfuncid))
+ return true;
+
+ /* operands are checked later */
+ }
+ break;
+ case T_OpExpr:
+ /*
+ * Only built-in operators can be pushed down. In addition,
+ * underlying function must be built-in and immutable, but we don't
+ * check volatility here; such check must be done already with
+ * contain_mutable_functions.
+ */
+ {
+ OpExpr *oe = (OpExpr *) node;
+
+ if (!is_builtin(oe->opno) || !is_builtin(oe->opfuncid))
+ return true;
+
+ /* operands are checked later */
+ }
+ 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;
+ foreign_executable_cxt *f_context;
+
+ f_context = (foreign_executable_cxt *) context;
+ if (var->varno != f_context->foreignrel->relid ||
+ var->varlevelsup != 0)
+ return true;
+ }
+ break;
+ case T_ArrayRef:
+ /*
+ * ArrayRef which holds non-built-in typed elements can't be pushed
+ * down.
+ */
+ {
+ ArrayRef *ar = (ArrayRef *) node;;
+
+ if (!is_builtin(ar->refelemtype))
+ return true;
+
+ /* Assignment should not be in restrictions. */
+ if (ar->refassgnexpr != NULL)
+ return true;
+ }
+ break;
+ case T_ArrayExpr:
+ /*
+ * ArrayExpr which holds non-built-in typed elements can't be pushed
+ * down.
+ */
+ {
+ if (!is_builtin(((ArrayExpr *) node)->element_typeid))
+ return true;
+ }
+ break;
+ default:
+ {
+ ereport(DEBUG3,
+ (errmsg("expression is too complex"),
+ errdetail("%s", nodeToString(node))));
+ return true;
+ }
+ break;
+ }
+
+ return expression_tree_walker(node, foreign_expr_walker, context);
+ }
+
+ /*
+ * Return true if given object is one of built-in objects.
+ */
+ static bool
+ is_builtin(Oid oid)
+ {
+ return (oid < FirstNormalObjectId);
+ }
+
+ /*
+ * Deparse exprs to WHERE clause string and append it into buf.
+ *
+ * Each element in exprs must be a boolean expression.
+ */
+ void
+ deparseWhereClause(StringInfo buf,
+ List *exprs,
+ PlannerInfo *root)
+ {
+ ListCell *lc;
+ bool first = true;
+
+ appendStringInfo(buf, " WHERE (");
+ foreach(lc, exprs)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+
+ /* Connect expressions with "AND". */
+ if (!first)
+ appendStringInfo(buf, ") AND (");
+
+ deparseExpr(buf, expr, root);
+ first = false;
+ }
+ appendStringInfoChar(buf, ')');
+ }
diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out
index fdd908c..5560931 100644
*** a/contrib/pgsql_fdw/expected/pgsql_fdw.out
--- b/contrib/pgsql_fdw/expected/pgsql_fdw.out
*************** SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.
*** 230,241 ****
-- with WHERE clause
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1';
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------
Foreign Scan on public.ft1 t1
Output: c1, c2, c3, c4, c5, c6, c7
! Filter: ((t1.c1 = 101) AND ((t1.c6)::text = '1'::text) AND (t1.c7 = '1'::bpchar))
! Remote SQL: DECLARE pgsql_fdw_cursor_4 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
(4 rows)
SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1';
--- 230,241 ----
-- with WHERE clause
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1';
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on public.ft1 t1
Output: c1, c2, c3, c4, c5, c6, c7
! Filter: (((t1.c6)::text = '1'::text) AND (t1.c7 = '1'::bpchar))
! Remote SQL: DECLARE pgsql_fdw_cursor_4 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 101))
(4 rows)
SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1';
*************** EXPLAIN (COSTS false) SELECT * FROM ft1
*** 343,362 ****
(3 rows)
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
! QUERY PLAN
! ---------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = abs(c2))
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
! QUERY PLAN
! ---------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = c2)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
-- ===================================================================
-- parameterized queries
--- 343,433 ----
(3 rows)
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) pg_catalog.abs(c2)))
! (2 rows)
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) c2))
! (2 rows)
!
! -- ===================================================================
! -- WHERE push down
! -- ===================================================================
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 100)) AND ((c2 OPERATOR(pg_catalog.=) 0))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" IS NULL))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest
! QUERY PLAN
! -------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ((pg_catalog.round(pg_catalog.abs("C 1"), 0) OPERATOR(pg_catalog.=) 1::numeric))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l)
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) (OPERATOR(pg_catalog.-) "C 1")))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r)
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ((1::numeric OPERATOR(pg_catalog.=) ("C 1" OPERATOR(pg_catalog.!))))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) ANY (ARRAY[c2, 1, ("C 1" OPERATOR(pg_catalog.+) 0)])))
! (2 rows)
!
! EXPLAIN (COSTS false) SELECT * FROM ft1 ft WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on ft1 ft
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) ((ARRAY["C 1", c2, 3])[1])))
! (2 rows)
-- ===================================================================
-- parameterized queries
*************** EXPLAIN (COSTS false) SELECT * FROM ft1
*** 364,379 ****
-- simple join
PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
EXPLAIN (COSTS false) EXECUTE st1(1, 2);
! QUERY PLAN
! -------------------------------------------------------------------------------------
Nested Loop
-> Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1"
-> Foreign Scan on ft2 t2
! Filter: (c1 = 2)
! Remote SQL: SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1"
! (7 rows)
EXECUTE st1(1, 1);
c3 | c3
--- 435,448 ----
-- simple join
PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
EXPLAIN (COSTS false) EXECUTE st1(1, 2);
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------
Nested Loop
-> Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
-> Foreign Scan on ft2 t2
! Remote SQL: SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 2))
! (5 rows)
EXECUTE st1(1, 1);
c3 | c3
*************** EXECUTE st1(101, 101);
*** 390,409 ****
-- subquery using stable function (can't be pushed down)
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1;
EXPLAIN (COSTS false) EXECUTE st2(10, 20);
! QUERY PLAN
! ------------------------------------------------------------------------------------------------
Sort
Sort Key: t1.c1
-> Hash Semi Join
Hash Cond: (t1.c3 = t2.c3)
-> Foreign Scan on ft1 t1
! Filter: (c1 < 20)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-> Hash
-> Foreign Scan on ft2 t2
! Filter: ((c1 > 10) AND (date_part('dow'::text, c4) = 6::double precision))
! Remote SQL: SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1"
! (11 rows)
EXECUTE st2(10, 20);
c1 | c2 | c3 | c4 | c5 | c6 | c7
--- 459,477 ----
-- subquery using stable function (can't be pushed down)
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c4) = 6) ORDER BY c1;
EXPLAIN (COSTS false) EXECUTE st2(10, 20);
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------
Sort
Sort Key: t1.c1
-> Hash Semi Join
Hash Cond: (t1.c3 = t2.c3)
-> Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20))
-> Hash
-> Foreign Scan on ft2 t2
! Filter: (date_part('dow'::text, c4) = 6::double precision)
! Remote SQL: SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10))
! (10 rows)
EXECUTE st2(10, 20);
c1 | c2 | c3 | c4 | c5 | c6 | c7
*************** EXECUTE st1(101, 101);
*** 420,439 ****
-- subquery using immutable function (can be pushed down)
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c5) = 6) ORDER BY c1;
EXPLAIN (COSTS false) EXECUTE st3(10, 20);
! QUERY PLAN
! ------------------------------------------------------------------------------------------------
Sort
Sort Key: t1.c1
-> Hash Semi Join
Hash Cond: (t1.c3 = t2.c3)
-> Foreign Scan on ft1 t1
! Filter: (c1 < 20)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-> Hash
-> Foreign Scan on ft2 t2
! Filter: ((c1 > 10) AND (date_part('dow'::text, c5) = 6::double precision))
! Remote SQL: SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1"
! (11 rows)
EXECUTE st3(10, 20);
c1 | c2 | c3 | c4 | c5 | c6 | c7
--- 488,506 ----
-- subquery using immutable function (can be pushed down)
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND EXTRACT(dow FROM c5) = 6) ORDER BY c1;
EXPLAIN (COSTS false) EXECUTE st3(10, 20);
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------
Sort
Sort Key: t1.c1
-> Hash Semi Join
Hash Cond: (t1.c3 = t2.c3)
-> Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20))
-> Hash
-> Foreign Scan on ft2 t2
! Filter: (date_part('dow'::text, c5) = 6::double precision)
! Remote SQL: SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10))
! (10 rows)
EXECUTE st3(10, 20);
c1 | c2 | c3 | c4 | c5 | c6 | c7
*************** EXECUTE st3(20, 30);
*** 450,501 ****
-- custom plan should be chosen
PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = $1)
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
-- cleanup
DEALLOCATE st1;
--- 517,562 ----
-- custom plan should be chosen
PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) $1))
! (2 rows)
-- cleanup
DEALLOCATE st1;
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
index df7f407..4916a45 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.c
--- b/contrib/pgsql_fdw/pgsql_fdw.c
***************
*** 17,26 ****
#include "catalog/pg_foreign_table.h"
#include "commands/defrem.h"
#include "commands/explain.h"
! #include "foreign/fdwapi.h"
#include "funcapi.h"
#include "miscadmin.h"
- #include "nodes/nodeFuncs.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
--- 17,25 ----
#include "catalog/pg_foreign_table.h"
#include "commands/defrem.h"
#include "commands/explain.h"
! #include "foreign/foreign.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
*************** PG_MODULE_MAGIC;
*** 61,66 ****
--- 60,70 ----
#define CURSOR_NAME_FORMAT "pgsql_fdw_cursor_%u"
static uint32 cursor_id = 0;
+ /* Convenient macros for accessing the first record of PGresult. */
+ #define PGRES_VAL0(col) (PQgetvalue(res, 0, (col)))
+ #define PGRES_NULL0(col) (PQgetisnull(res, 0, (col)))
+
+
/*
* Index of FDW-private information stored in fdw_private list.
*
*************** static uint32 cursor_id = 0;
*** 68,78 ****
* the boundary between planner and executor. Finally FdwPlan using cursor
* would hold items below:
*
! * 1) plain SELECT statement
! * 2) SQL statement used to declare cursor
! * 3) SQL statement used to fetch rows from cursor
* 5) SQL statement used to reset cursor
! * 5) SQL statement used to close cursor
*
* These items are indexed with the enum FdwPrivateIndex, so an item
* can be accessed directly via list_nth(). For example of FETCH
--- 72,83 ----
* the boundary between planner and executor. Finally FdwPlan using cursor
* would hold items below:
*
! * 1) expressions which are pushed down
! * 2) plain SELECT statement
! * 3) SQL statement used to declare cursor
! * 4) SQL statement used to fetch rows from cursor
* 5) SQL statement used to reset cursor
! * 6) SQL statement used to close cursor
*
* These items are indexed with the enum FdwPrivateIndex, so an item
* can be accessed directly via list_nth(). For example of FETCH
*************** static uint32 cursor_id = 0;
*** 80,85 ****
--- 85,93 ----
* list_nth(fdw_private, FdwPrivateFetchSql)
*/
enum FdwPrivateIndex {
+ /* Planning information */
+ FdwPrivateFdwExprs,
+
/* SQL statements */
FdwPrivateSelectSql,
FdwPrivateDeclareSql,
*************** pgsqlGetForeignPlan(PlannerInfo *root,
*** 282,298 ****
DefElem *def;
int fetch_count = DEFAULT_FETCH_COUNT;
char *sql;
ForeignTable *table;
ForeignServer *server;
/*
! * We have no native ability to evaluate restriction clauses, so we just
! * put all the scan_clauses into the plan node's qual list for the
! * executor to check. So all we have to do here is strip RestrictInfo
! * nodes from the clauses and ignore pseudoconstants (which will be
! * handled elsewhere).
*/
scan_clauses = extract_actual_clauses(scan_clauses, false);
/*
* Use specified fetch_count instead of default value, if any. Foreign
--- 290,312 ----
DefElem *def;
int fetch_count = DEFAULT_FETCH_COUNT;
char *sql;
+ List *fdw_exprs = NIL;
+ List *local_exprs = NIL;
ForeignTable *table;
ForeignServer *server;
/*
! * We have native ability to evaluate restriction clauses on remote side,
! * so we split clauses into local portion and remote portion. Before
! * that, we strip RestrictInfo nodes from the clauses and ignore
! * pseudoconstants (which will be handled elsewhere).
! *
! * Extracted list of expressions is stored in fdw_private of ForeignScan
! * for possible reference in executor handlers.
*/
scan_clauses = extract_actual_clauses(scan_clauses, false);
+ fdw_exprs = extractRemoteExprs(root, baserel, &local_exprs);
+ fdw_private = lappend(fdw_private, fdw_exprs);
/*
* Use specified fetch_count instead of default value, if any. Foreign
*************** pgsqlGetForeignPlan(PlannerInfo *root,
*** 322,330 ****
elog(DEBUG1, "relid=%u fetch_count=%d", foreigntableid, fetch_count);
/*
! * Construct simple remote query which has no WHERE clause.
*/
sql = deparseSimpleSql(foreigntableid, root, baserel, table);
fdw_private = lappend(fdw_private, makeString(sql));
/* Construct cursor name from sequential value */
--- 336,354 ----
elog(DEBUG1, "relid=%u fetch_count=%d", foreigntableid, fetch_count);
/*
! * Construct remote query, and add WHERE clause if we have any expression
! * which can be pushed down to remote side safely.
*/
sql = deparseSimpleSql(foreigntableid, root, baserel, table);
+ if (list_length(fdw_exprs) > 0)
+ {
+ StringInfoData buf;
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "%s", sql);
+ deparseWhereClause(&buf, fdw_exprs, root);
+ sql = buf.data;
+ }
fdw_private = lappend(fdw_private, makeString(sql));
/* Construct cursor name from sequential value */
*************** pgsqlGetForeignPlan(PlannerInfo *root,
*** 350,360 ****
appendStringInfo(&cursor, "CLOSE %s", name);
fdw_private = lappend(fdw_private, makeString(cursor.data));
! /* Create the ForeignScan node with fdw_private of selected path. */
return make_foreignscan(tlist,
! scan_clauses,
scan_relid,
! NIL,
fdw_private);
}
--- 374,393 ----
appendStringInfo(&cursor, "CLOSE %s", name);
fdw_private = lappend(fdw_private, makeString(cursor.data));
! /*
! * Create the ForeignScan node from target list, local filtering
! * expressions, remote filtering expressions, and FDW private information.
! *
! * We remove expressions which are evaluated on remote side from qual of
! * the scan node to avoid redundant filtering. Such filter reduction
! * can be done only here, done after choosing best path, because
! * baserestrictinfo in RelOptInfo is shared by all possible paths until
! * best path is chosen.
! */
return make_foreignscan(tlist,
! local_exprs,
scan_relid,
! fdw_exprs,
fdw_private);
}
diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h
index 6eb99d5..5bd672a 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.h
--- b/contrib/pgsql_fdw/pgsql_fdw.h
***************
*** 15,20 ****
--- 15,21 ----
#define PGSQL_FDW_H
#include "postgres.h"
+ #include "foreign/fdwapi.h"
#include "foreign/foreign.h"
#include "nodes/relation.h"
*************** char *deparseSimpleSql(Oid relid,
*** 28,32 ****
--- 29,42 ----
PlannerInfo *root,
RelOptInfo *baserel,
ForeignTable *table);
+ void deparseWhereClause(StringInfo buf,
+ List *exprs,
+ PlannerInfo *root);
+ List *extractRemoteExprs(PlannerInfo *root,
+ RelOptInfo *baserel,
+ List **local_exprs);
+ void deparseExpr(StringInfo buf,
+ Expr *expr,
+ PlannerInfo *root);
#endif /* PGSQL_FDW_H */
diff --git a/contrib/pgsql_fdw/sql/pgsql_fdw.sql b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
index b3fac96..b7cd71d 100644
*** a/contrib/pgsql_fdw/sql/pgsql_fdw.sql
--- b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
*************** EXPLAIN (COSTS false) SELECT * FROM ft1
*** 182,187 ****
--- 182,201 ----
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
-- ===================================================================
+ -- WHERE push down
+ -- ===================================================================
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1; -- Var, OpExpr(b), Const
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL; -- NullTest
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL; -- NullTest
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1; -- OpExpr(l)
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!; -- OpExpr(r)
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
+ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
+ EXPLAIN (COSTS false) SELECT * FROM ft1 ft WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
+
+ -- ===================================================================
-- parameterized queries
-- ===================================================================
-- simple join