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