diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c index beb62fe..a9585e9 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,81 ---- bool need_prefix); static void deparseVar(StringInfo buf, Var *node, PlannerInfo *root, RelOptInfo *baserel, bool need_prefix); + static void deparseConst(StringInfo buf, Const *node, PlannerInfo *root, + RelOptInfo *baserel); + static void deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root, + RelOptInfo *baserel); + static void deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root, + RelOptInfo *baserel); + static void deparseDistinctExpr(StringInfo buf, DistinctExpr *node, + PlannerInfo *root, RelOptInfo *baserel); + static void deparseRelabelType(StringInfo buf, RelabelType *node, + PlannerInfo *root, RelOptInfo *baserel); + static void deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root, + RelOptInfo *baserel); + static void deparseParam(StringInfo buf, Param *node, PlannerInfo *root, + RelOptInfo *baserel); + static void deparseScalarArrayOpExpr(StringInfo buf, ScalarArrayOpExpr *node, + PlannerInfo *root, RelOptInfo *baserel); + static void deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root, + RelOptInfo *baserel); + static void deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root, + RelOptInfo *baserel); + static void deparseArrayExpr(StringInfo buf, ArrayExpr *node, PlannerInfo *root, RelOptInfo *baserel); + + /* + * 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 **** --- 184,268 ---- } /* + * Examine baserestrictinfo of baserel, and extract expressions which can be + * evaluated on remote side safely. This function never changes + * baserestrictinfo. + */ + List * + extractRemoteExprs(Oid relid, + PlannerInfo *root, + RelOptInfo *baserel) + { + 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); + } + + return exprs; + } + + void + deparseExpr(StringInfo buf, Expr *node, PlannerInfo *root, RelOptInfo *baserel) + { + /* + * This part must be match foreign_expr_walker. + */ + switch (nodeTag(node)) + { + case T_Const: + deparseConst(buf, (Const *) node, root, baserel); + break; + case T_BoolExpr: + deparseBoolExpr(buf, (BoolExpr *) node, root, baserel); + break; + case T_NullTest: + deparseNullTest(buf, (NullTest *) node, root, baserel); + break; + case T_DistinctExpr: + deparseDistinctExpr(buf, (DistinctExpr *) node, root, baserel); + break; + case T_RelabelType: + deparseRelabelType(buf, (RelabelType *) node, root, baserel); + break; + case T_FuncExpr: + deparseFuncExpr(buf, (FuncExpr *) node, root, baserel); + break; + case T_Param: + deparseParam(buf, (Param *) node, root, baserel); + break; + case T_ScalarArrayOpExpr: + deparseScalarArrayOpExpr(buf, (ScalarArrayOpExpr *) node, root, + baserel); + break; + case T_OpExpr: + deparseOpExpr(buf, (OpExpr *) node, root, baserel); + break; + case T_Var: + deparseVar(buf, (Var *) node, root, baserel, false); + break; + case T_ArrayRef: + deparseArrayRef(buf, (ArrayRef *) node, root, baserel); + break; + case T_ArrayExpr: + deparseArrayExpr(buf, (ArrayExpr *) node, root, baserel); + 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, *** 288,290 **** --- 399,811 ---- appendStringInfo(buf, "%s.", q_nspname); appendStringInfo(buf, "%s", q_relname); } + + static void + deparseConst(StringInfo buf, + Const *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + Oid typoutput; + bool typIsVarlena; + char *extval; + + if (node->constisnull) + { + appendStringInfo(buf, "NULL"); + return; + } + + getTypeOutputInfo(node->consttype, + &typoutput, &typIsVarlena); + extval = OidOutputFunctionCall(typoutput, node->constvalue); + + switch (node->consttype) + { + 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); + } + 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; + } + } + + static void + deparseBoolExpr(StringInfo buf, + BoolExpr *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + 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, baserel); + appendStringInfo(buf, ")"); + return; + } + + first = true; + appendStringInfo(buf, "("); + foreach(lc, node->args) + { + if (!first) + appendStringInfo(buf, " %s ", op); + deparseExpr(buf, (Expr *) lfirst(lc), root, baserel); + first = false; + } + appendStringInfo(buf, ")"); + } + + static void + deparseNullTest(StringInfo buf, + NullTest *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + elog(ERROR, "NullTest is not supported"); + } + + static void + deparseDistinctExpr(StringInfo buf, + DistinctExpr *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + elog(ERROR, "DistinctExpr not supported"); + } + + static void + deparseRelabelType(StringInfo buf, + RelabelType *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + elog(ERROR, "RelabelType is not supported"); + } + + static void + deparseFuncExpr(StringInfo buf, + FuncExpr *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + elog(ERROR, "FuncExpr is not supported"); + } + + static void + deparseParam(StringInfo buf, + Param *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + elog(ERROR, "Param is not supported"); + } + + static void + deparseScalarArrayOpExpr(StringInfo buf, + ScalarArrayOpExpr *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + elog(ERROR, "ScalarArrayOpExpr is not supported"); + } + + static void + deparseOpExpr(StringInfo buf, + OpExpr *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + HeapTuple tuple; + Form_pg_operator form; + char *opname; + char oprkind; + ListCell *arg = list_head(node->args); + + /* Retieve 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 = NameStr(form->oprname); + oprkind = form->oprkind; + ReleaseSysCache(tuple); + + Assert((oprkind == 'r' && list_length(node->args) == 1) || + (oprkind == 'l' && list_length(node->args) == 1) || + (oprkind == 'b' && list_length(node->args) == 2)); + + appendStringInfo(buf, "("); + if (oprkind == 'b' || oprkind == 'r') + { + deparseExpr(buf, lfirst(arg), root, baserel); + arg = lnext(arg); + } + + appendStringInfo(buf, " %s ", opname); + + if (oprkind == 'b' || oprkind == 'r') + deparseExpr(buf, lfirst(arg), root, baserel); + appendStringInfo(buf, ")"); + } + + static void + deparseArrayRef(StringInfo buf, + ArrayRef *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + elog(ERROR, "ArrayRef is not supported"); + } + + static void + deparseArrayExpr(StringInfo buf, + ArrayExpr *node, + PlannerInfo *root, + RelOptInfo *baserel) + { + elog(ERROR, "ArrayExpr is not supported"); + } + + /* + * 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; + + /* + * 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: + case T_BoolExpr: + break; + #ifdef NOT_SUPPORTED + 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; + #endif + 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; + #ifdef NOT_SUPPORTED + case T_ArrayRef: + /* + * ArrayRef which holds non-built-in typed elements can't be pushed + * down. + */ + { + if (!is_builtin(((ArrayRef *) node)->refelemtype)) + 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; + #endif + 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); + } diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out index 5bac241..d60c768 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.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" WHERE (("C 1" = 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 *** 351,361 **** (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: DECLARE pgsql_fdw_cursor_19 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" (3 rows) -- =================================================================== --- 351,361 ---- (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: DECLARE pgsql_fdw_cursor_19 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" = c2)) (3 rows) -- =================================================================== *************** EXPLAIN (COSTS false) SELECT * FROM ft1 *** 364,380 **** -- 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: DECLARE pgsql_fdw_cursor_20 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" ! -> Materialize ! -> Foreign Scan on ft2 t2 ! Filter: (c1 = 2) ! Remote SQL: DECLARE pgsql_fdw_cursor_21 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" ! (8 rows) EXECUTE st1(1, 1); c3 | c3 --- 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: DECLARE pgsql_fdw_cursor_20 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" = 1)) ! -> Foreign Scan on ft2 t2 ! Filter: (c1 = 2) ! Remote SQL: DECLARE pgsql_fdw_cursor_21 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" = 2)) ! (7 rows) EXECUTE st1(1, 1); c3 | c3 *************** EXECUTE st1(101, 101); *** 391,410 **** -- 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 Join Hash Cond: (t1.c3 = t2.c3) -> Foreign Scan on ft1 t1 Filter: (c1 < 20) ! Remote SQL: DECLARE pgsql_fdw_cursor_26 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" -> Hash -> HashAggregate -> Foreign Scan on ft2 t2 Filter: ((c1 > 10) AND (date_part('dow'::text, c4) = 6::double precision)) ! Remote SQL: DECLARE pgsql_fdw_cursor_27 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1" (12 rows) EXECUTE st2(10, 20); --- 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 Join Hash Cond: (t1.c3 = t2.c3) -> Foreign Scan on ft1 t1 Filter: (c1 < 20) ! Remote SQL: DECLARE pgsql_fdw_cursor_26 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" < 20)) -> Hash -> HashAggregate -> Foreign Scan on ft2 t2 Filter: ((c1 > 10) AND (date_part('dow'::text, c4) = 6::double precision)) ! Remote SQL: DECLARE pgsql_fdw_cursor_27 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10)) (12 rows) EXECUTE st2(10, 20); *************** EXECUTE st1(101, 101); *** 422,441 **** -- 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 Join Hash Cond: (t1.c3 = t2.c3) -> Foreign Scan on ft1 t1 Filter: (c1 < 20) ! Remote SQL: DECLARE pgsql_fdw_cursor_32 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" -> Hash -> HashAggregate -> Foreign Scan on ft2 t2 Filter: ((c1 > 10) AND (date_part('dow'::text, c5) = 6::double precision)) ! Remote SQL: DECLARE pgsql_fdw_cursor_33 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1" (12 rows) EXECUTE st3(10, 20); --- 421,440 ---- -- 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 Join Hash Cond: (t1.c3 = t2.c3) -> Foreign Scan on ft1 t1 Filter: (c1 < 20) ! Remote SQL: DECLARE pgsql_fdw_cursor_32 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" < 20)) -> Hash -> HashAggregate -> Foreign Scan on ft2 t2 Filter: ((c1 > 10) AND (date_part('dow'::text, c5) = 6::double precision)) ! Remote SQL: DECLARE pgsql_fdw_cursor_33 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10)) (12 rows) EXECUTE st3(10, 20); *************** EXECUTE st3(20, 30); *** 453,503 **** -- 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: DECLARE pgsql_fdw_cursor_38 SCROLL CURSOR FOR 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: DECLARE pgsql_fdw_cursor_39 SCROLL CURSOR FOR 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: DECLARE pgsql_fdw_cursor_40 SCROLL CURSOR FOR 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: DECLARE pgsql_fdw_cursor_41 SCROLL CURSOR FOR 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: DECLARE pgsql_fdw_cursor_42 SCROLL CURSOR FOR 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: DECLARE pgsql_fdw_cursor_43 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" (3 rows) -- cleanup --- 452,502 ---- -- 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: DECLARE pgsql_fdw_cursor_38 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" = 1)) (3 rows) EXPLAIN (COSTS false) EXECUTE st4(1); ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on ft1 t1 Filter: (c1 = 1) ! Remote SQL: DECLARE pgsql_fdw_cursor_39 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" = 1)) (3 rows) EXPLAIN (COSTS false) EXECUTE st4(1); ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on ft1 t1 Filter: (c1 = 1) ! Remote SQL: DECLARE pgsql_fdw_cursor_40 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" = 1)) (3 rows) EXPLAIN (COSTS false) EXECUTE st4(1); ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on ft1 t1 Filter: (c1 = 1) ! Remote SQL: DECLARE pgsql_fdw_cursor_41 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" = 1)) (3 rows) EXPLAIN (COSTS false) EXECUTE st4(1); ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on ft1 t1 Filter: (c1 = 1) ! Remote SQL: DECLARE pgsql_fdw_cursor_42 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" = 1)) (3 rows) EXPLAIN (COSTS false) EXECUTE st4(1); ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on ft1 t1 ! Filter: (c1 = 1) ! Remote SQL: DECLARE pgsql_fdw_cursor_44 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" = 1)) (3 rows) -- cleanup diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c index 370d240..7746ab9 100644 *** a/contrib/pgsql_fdw/pgsql_fdw.c --- b/contrib/pgsql_fdw/pgsql_fdw.c *************** typedef struct PgsqlFdwPlanState { *** 75,80 **** --- 75,84 ---- char *sql; Cost startup_cost; Cost total_cost; + + ForeignTable *table; + ForeignServer *server; + UserMapping *user; } PgsqlFdwPlanState; /* *************** typedef struct PgsqlFdwPlanState { *** 82,87 **** --- 86,92 ---- * stored in fdw_private list. */ enum FdwPrivateIndex { + FdwPrivateFdwExprs, FdwPrivateSelectSql, FdwPrivateDeclareSql, FdwPrivateFetchSql, *************** pgsqlGetForeignRelSize(PlannerInfo *root *** 268,273 **** --- 273,281 ---- planstate->sql = sql; planstate->startup_cost = startup_cost; planstate->total_cost = total_cost; + planstate->table = table; + planstate->server = server; + planstate->user = user; baserel->fdw_private = (void *) planstate; } *************** pgsqlGetForeignPaths(PlannerInfo *root, *** 283,288 **** --- 291,305 ---- PgsqlFdwPlanState *planstate = (PgsqlFdwPlanState *) baserel->fdw_private; List *fdw_private = NIL; ForeignPath *path; + StringInfoData buf; + PGconn *conn; + double rows; + int width; + Cost startup_cost; + Cost total_cost; + bool first = true; + ListCell *lc; + List *fdw_exprs; /* * Cost estimation should be modified to respect cost of establishing *************** pgsqlGetForeignPaths(PlannerInfo *root, *** 297,303 **** * Create simplest ForeignScan path node, counterpart of SeqScan for * regular tables, for this scan, and add it to baserel. */ ! fdw_private = list_make1(makeString(planstate->sql)); path = create_foreignscan_path(root, baserel, baserel->rows, planstate->startup_cost, --- 314,321 ---- * Create simplest ForeignScan path node, counterpart of SeqScan for * regular tables, for this scan, and add it to baserel. */ ! fdw_private = lappend(fdw_private, NIL); /* no fdw_exprs */ ! fdw_private = lappend(fdw_private, makeString(planstate->sql)); path = create_foreignscan_path(root, baserel, baserel->rows, planstate->startup_cost, *************** pgsqlGetForeignPaths(PlannerInfo *root, *** 309,314 **** --- 327,389 ---- add_path(baserel, (Path *) path); /* + * Create a ForeignScan path with WHERE push-down. + */ + fdw_exprs = extractRemoteExprs(foreigntableid, root, baserel); + if (fdw_exprs != NIL) + { + initStringInfo(&buf); + appendStringInfo(&buf, "%s WHERE (", planstate->sql); + + foreach(lc, fdw_exprs) + { + Expr *expr = (Expr *) lfirst(lc); + + /* Connect expressions with "AND". */ + if (!first) + appendStringInfo(&buf, ") AND ("); + + deparseExpr(&buf, expr, root, baserel); + first = false; + } + appendStringInfoChar(&buf, ')'); + + /* + * Cost estimation should be modified to respect cost of establishing + * connection and transferring data. + */ + conn = GetConnection(planstate->server, planstate->user, false); + get_remote_estimate(buf.data, + conn, + &rows, + &width, + &startup_cost, + &total_cost); + ReleaseConnection(conn); + adjust_costs(baserel->rows, + baserel->width, + &planstate->startup_cost, + &planstate->total_cost); + + /* + * Create simplest ForeignScan path node, counterpart of SeqScan for + * regular tables, for this scan, and add it to baserel. + */ + fdw_private = NIL; + fdw_private = lappend(fdw_private, fdw_exprs); + fdw_private = lappend(fdw_private, makeString(buf.data)); + path = create_foreignscan_path(root, baserel, + rows, + startup_cost, + total_cost, + NIL, /* no pathkeys */ + NULL, /* no outer rel either */ + NIL, /* no param clause */ + fdw_private); /* pass SQL */ + add_path(baserel, (Path *) path); + } + + /* * XXX We can consider sorted path here if we know that foreign table is * indexed on remote end. For this purpose, should we support FOREIGN * INDEX to represent possible sets of sort keys which are relatively *************** pgsqlGetForeignPlan(PlannerInfo *root, *** 338,343 **** --- 413,419 ---- DefElem *def; int fetch_count = DEFAULT_FETCH_COUNT; char *sql; + List *fdw_exprs = NIL; /* * We have no native ability to evaluate restriction clauses, so we just *************** pgsqlGetForeignPlan(PlannerInfo *root, *** 380,396 **** * boundary between planner and executor. Finally FdwPlan using cursor * would hold items below: * ! * 1) plain SELECT statement (already added above) ! * 2) SQL statement used to declare cursor ! * 3) SQL statement used to fetch rows from cursor ! * 4) 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 * statement: * list_nth(fdw_private, FdwPrivateFetchSql) */ sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql)); /* Construct cursor name from sequential value */ --- 456,474 ---- * boundary between planner and executor. Finally FdwPlan using cursor * would hold items below: * ! * 1) expressions which are pushed down ! * 2) plain SELECT statement (already added above) ! * 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 * statement: * list_nth(fdw_private, FdwPrivateFetchSql) */ + fdw_exprs = list_nth(fdw_private, FdwPrivateFdwExprs); sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql)); /* Construct cursor name from sequential value */ *************** pgsqlGetForeignPlan(PlannerInfo *root, *** 420,426 **** return make_foreignscan(tlist, scan_clauses, scan_relid, ! NIL, fdw_private); } --- 498,504 ---- return make_foreignscan(tlist, scan_clauses, scan_relid, ! fdw_exprs, fdw_private); } diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h index 6eb99d5..b7d1310 100644 *** a/contrib/pgsql_fdw/pgsql_fdw.h --- b/contrib/pgsql_fdw/pgsql_fdw.h *************** char *deparseSimpleSql(Oid relid, *** 28,32 **** --- 28,39 ---- PlannerInfo *root, RelOptInfo *baserel, ForeignTable *table); + List *extractRemoteExprs(Oid relid, + PlannerInfo *root, + RelOptInfo *baserel); + void deparseExpr(StringInfo buf, + Expr *expr, + PlannerInfo *root, + RelOptInfo *baserel); #endif /* PGSQL_FDW_H */