diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c index 8e79232..28fad0a 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" *************** typedef struct foreign_executable_cxt *** 35,40 **** --- 39,45 ---- { PlannerInfo *root; RelOptInfo *foreignrel; + bool has_param; } foreign_executable_cxt; /* *************** static void deparseRelation(StringInfo b *** 44,49 **** --- 49,76 ---- 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, + bool *has_param); + 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(StringInfo buf, *** 151,156 **** --- 178,284 ---- } /* + * Examine each element in the list baserestrictinfo of baserel, and sort them + * into three groups: remote_conds contains conditions which can be evaluated + * - remote_conds is push-down safe, and don't contain any Param node + * - param_conds is push-down safe, but contain some Param node + * - local_conds is not push-down safe + * + * Only remote_conds can be used in remote EXPLAIN, and remote_conds and + * param_conds can be used in final remote query. + */ + void + sortConditions(PlannerInfo *root, + RelOptInfo *baserel, + List **remote_conds, + List **param_conds, + List **local_conds) + { + ListCell *lc; + bool has_param; + + Assert(remote_conds); + Assert(param_conds); + Assert(local_conds); + + foreach(lc, baserel->baserestrictinfo) + { + RestrictInfo *ri = (RestrictInfo *) lfirst(lc); + + if (is_foreign_expr(root, baserel, ri->clause, &has_param)) + { + if (has_param) + *param_conds = lappend(*param_conds, ri); + else + *remote_conds = lappend(*remote_conds, ri); + } + else + *local_conds = lappend(*local_conds, ri); + } + } + + /* + * 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, *** 286,288 **** --- 414,1145 ---- 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 given expr is safe to evaluate on the foreign server. If + * result is true, extra information has_param tells whether given expression + * contains any Param node. This is useful to determine whether the expression + * can be used in remote EXPLAIN. + */ + static bool + is_foreign_expr(PlannerInfo *root, + RelOptInfo *baserel, + Expr *expr, + bool *has_param) + { + foreign_executable_cxt context; + context.root = root; + context.foreignrel = baserel; + context.has_param = false; + + /* + * 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; + + /* + * Tell caller whether the given expression contains any Param node, which + * can't be used in EXPLAIN statement before executor starts. + */ + *has_param = context.has_param; + + 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 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; + + /* Mark that this expression contains Param node. */ + context->has_param = 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; + + /* + * If the operator takes collatable type as operands, we push + * down only "=" and "<>" which are not affected by collation. + * Other operators might be safe about collation, but these two + * seem enogh to cover practical use cases. + */ + if (exprInputCollation(node) != InvalidOid) + { + char *opname = get_opname(oe->opno); + + if (strcmp(opname, "=") != 0 && strcmp(opname, "<>") != 0) + 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; + + /* + * If the operator takes collatable type as operands, we push + * down only "=" and "<>" which are not affected by collation. + * Other operators might be safe about collation, but these two + * seem enogh to cover practical use cases. + */ + if (exprInputCollation(node) != InvalidOid) + { + char *opname = get_opname(oe->opno); + + if (strcmp(opname, "=") != 0 && strcmp(opname, "<>") != 0) + 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 WHERE clause from given list of RestrictInfo and append them to buf. + * We assume that buf already holds a SQL statement which ends with valid WHERE + * clause. + */ + void + appendWhereClause(StringInfo buf, + bool has_where, + List *exprs, + PlannerInfo *root) + { + bool first = true; + ListCell *lc; + + if (!has_where) + appendStringInfo(buf, " WHERE "); + + foreach(lc, exprs) + { + RestrictInfo *ri = (RestrictInfo *) lfirst(lc); + + /* Connect expressions with "AND" and parenthesize whole condition. */ + if (!first) + appendStringInfo(buf, " AND "); + + appendStringInfoChar(buf, '('); + deparseExpr(buf, ri->clause, root); + appendStringInfoChar(buf, ')'); + + first = false; + } + } diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out index 67fd979..cdbdd63 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. *** 229,244 **** (10 rows) -- 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'; c1 | c2 | c3 | c4 | c5 | c6 | c7 -----+----+-------+------------------------------+--------------------------+----+------------ 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 1 --- 229,244 ---- (10 rows) -- 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.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)) AND (((c6)::text OPERATOR(pg_catalog.=) '1'::text)) (4 rows) ! SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; c1 | c2 | c3 | c4 | c5 | c6 | c7 -----+----+-------+------------------------------+--------------------------+----+------------ 101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 | 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,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: 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: SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" ! (8 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); *** 391,411 **** -- 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: 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: SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1" ! (12 rows) EXECUTE st2(10, 20); c1 | c2 | c3 | c4 | c5 | c6 | c7 --- 459,478 ---- -- 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 ! Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20)) -> Hash -> HashAggregate -> 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)) ! (11 rows) EXECUTE st2(10, 20); c1 | c2 | c3 | c4 | c5 | c6 | c7 *************** EXECUTE st1(101, 101); *** 422,442 **** -- 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: 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: SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1" ! (12 rows) EXECUTE st3(10, 20); c1 | c2 | c3 | c4 | c5 | c6 | c7 --- 489,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 ! -> Nested Loop Semi Join ! Join Filter: (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)) ! -> Materialize ! -> Foreign Scan on ft2 t2 ! Remote SQL: SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10)) AND ((pg_catalog.date_part('dow'::text, c5) OPERATOR(pg_catalog.=) 6::double precision)) ! (9 rows) EXECUTE st3(10, 20); c1 | c2 | c3 | c4 | c5 | c6 | c7 *************** EXECUTE st3(20, 30); *** 453,504 **** -- 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 61527a9..2424fb4 100644 *** a/contrib/pgsql_fdw/pgsql_fdw.c --- b/contrib/pgsql_fdw/pgsql_fdw.c *************** *** 17,23 **** #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 "optimizer/cost.h" --- 17,22 ---- *************** PG_MODULE_MAGIC; *** 60,65 **** --- 59,69 ---- #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))) + + /* * FDW-specific information for RelOptInfo.fdw_private. This is used to pass * information from pgsqlGetForeignRelSize to pgsqlGetForeignPaths. *************** typedef struct PgsqlFdwPlanState { *** 70,77 **** --- 74,85 ---- * GetForeignPaths. */ StringInfoData sql; + bool has_where; Cost startup_cost; Cost total_cost; + List *remote_conds; + List *param_conds; + List *local_conds; /* Cached catalog information. */ ForeignTable *table; *************** pgsqlGetForeignRelSize(PlannerInfo *root *** 241,246 **** --- 249,255 ---- RelOptInfo *baserel, Oid foreigntableid) { + PgsqlFdwPlanState *fpstate; StringInfo sql; ForeignTable *table; ForeignServer *server; *************** pgsqlGetForeignRelSize(PlannerInfo *root *** 250,256 **** int width; Cost startup_cost; Cost total_cost; ! PgsqlFdwPlanState *fpstate; Selectivity sel; /* --- 259,267 ---- int width; Cost startup_cost; Cost total_cost; ! List *remote_conds = NIL; ! List *param_conds = NIL; ! List *local_conds = NIL; Selectivity sel; /* *************** pgsqlGetForeignRelSize(PlannerInfo *root *** 260,265 **** --- 271,277 ---- fpstate = palloc0(sizeof(PgsqlFdwPlanState)); initStringInfo(&fpstate->sql); sql = &fpstate->sql; + fpstate->has_where = false; /* Retrieve catalog objects which are necessary to estimate rows. */ table = GetForeignTable(foreigntableid); *************** pgsqlGetForeignRelSize(PlannerInfo *root *** 267,289 **** user = GetUserMapping(GetOuterUserId(), server->serverid); /* ! * Create plain SELECT statement with no WHERE clause for this scan in ! * order to obtain meaningful rows estimation by executing EXPLAIN on ! * remote server. */ deparseSimpleSql(sql, foreigntableid, root, baserel, table); conn = GetConnection(server, user, false); get_remote_estimate(sql->data, conn, &rows, &width, &startup_cost, &total_cost); ReleaseConnection(conn); /* ! * Estimate selectivity of local filtering by calling ! * clauselist_selectivity() against baserestrictinfo, and modify rows ! * estimate with it. */ ! sel = clauselist_selectivity(root, baserel->baserestrictinfo, ! baserel->relid, JOIN_INNER, NULL); baserel->rows = rows * sel; baserel->width = width; --- 279,318 ---- user = GetUserMapping(GetOuterUserId(), server->serverid); /* ! * Construct remote query which consists of SELECT, FROM, and WHERE ! * clauses, but conditions contain any Param node are excluded because ! * placeholder can't be used in EXPLAIN statement. Such conditions are ! * appended later. */ + sortConditions(root, baserel, &remote_conds, ¶m_conds, &local_conds); deparseSimpleSql(sql, foreigntableid, root, baserel, table); + if (list_length(remote_conds) > 0) + { + appendWhereClause(sql, fpstate->has_where, remote_conds, root); + fpstate->has_where = true; + } conn = GetConnection(server, user, false); get_remote_estimate(sql->data, conn, &rows, &width, &startup_cost, &total_cost); ReleaseConnection(conn); + if (list_length(param_conds) > 0) + { + appendWhereClause(sql, fpstate->has_where, param_conds, root); + fpstate->has_where = true; + } /* ! * Estimate selectivity of conditions which are not used in remote EXPLAIN ! * by calling clauselist_selectivity(). The best we can do for ! * parameterized condition is to estimate selectivity on the basis of local ! * statistics. When we actually obtain result rows, such conditions are ! * deparsed into remote query and reduce rows transferred. */ ! sel = 1.0; ! sel *= clauselist_selectivity(root, param_conds, ! baserel->relid, JOIN_INNER, NULL); ! sel *= clauselist_selectivity(root, local_conds, ! baserel->relid, JOIN_INNER, NULL); baserel->rows = rows * sel; baserel->width = width; *************** pgsqlGetForeignRelSize(PlannerInfo *root *** 293,298 **** --- 322,330 ---- */ fpstate->startup_cost = startup_cost; fpstate->total_cost = total_cost; + fpstate->remote_conds = remote_conds; + fpstate->param_conds = param_conds; + fpstate->local_conds = local_conds; fpstate->table = table; fpstate->server = server; baserel->fdw_private = (void *) fpstate; *************** pgsqlGetForeignPlan(PlannerInfo *root, *** 373,387 **** 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 --- 405,425 ---- char *sql; ForeignTable *table; ForeignServer *server; + List *fdw_exprs = NIL; + List *local_exprs = NIL; /* ! * We need lists of Expr other than the lists of RestrictInfo. Now we can ! * merge remote_conds and param_conds into fdw_exprs, because they are ! * evaluated on remote side for actual remote query. */ ! foreach(lc, fpstate->remote_conds) ! fdw_exprs = lappend(fdw_exprs, ((RestrictInfo *) lfirst(lc))->clause); ! foreach(lc, fpstate->param_conds) ! fdw_exprs = lappend(fdw_exprs, ((RestrictInfo *) lfirst(lc))->clause); ! foreach(lc, fpstate->local_conds) ! local_exprs = lappend(local_exprs, ! ((RestrictInfo *) lfirst(lc))->clause); /* * Use specified fetch_count instead of default value, if any. Foreign *************** pgsqlGetForeignPlan(PlannerInfo *root, *** 410,418 **** fetch_count = strtol(defGetString(def), NULL, 10); elog(DEBUG1, "relid=%u fetch_count=%d", foreigntableid, fetch_count); ! /* ! * Construct cursor name with sequential value. ! */ sprintf(name, CURSOR_NAME_FORMAT, cursor_id++); /* --- 448,454 ---- fetch_count = strtol(defGetString(def), NULL, 10); elog(DEBUG1, "relid=%u fetch_count=%d", foreigntableid, fetch_count); ! /* Construct cursor name from sequential value */ sprintf(name, CURSOR_NAME_FORMAT, cursor_id++); /* *************** pgsqlGetForeignPlan(PlannerInfo *root, *** 439,449 **** 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); } --- 475,494 ---- 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 234904e..eaf2b61 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" *************** void deparseSimpleSql(StringInfo buf, *** 29,33 **** --- 30,46 ---- PlannerInfo *root, RelOptInfo *baserel, ForeignTable *table); + void appendWhereClause(StringInfo buf, + bool has_where, + List *exprs, + PlannerInfo *root); + void sortConditions(PlannerInfo *root, + RelOptInfo *baserel, + List **remote_conds, + List **param_conds, + List **local_conds); + 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..23a44df 100644 *** a/contrib/pgsql_fdw/sql/pgsql_fdw.sql --- b/contrib/pgsql_fdw/sql/pgsql_fdw.sql *************** SELECT * FROM ft1 ORDER BY c3, c1 OFFSET *** 149,156 **** EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -- with WHERE clause ! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1'; ! SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1'; -- aggregate SELECT COUNT(*) FROM ft1 t1; -- join two tables --- 149,156 ---- EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; -- with WHERE clause ! EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; ! SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1'; -- aggregate SELECT COUNT(*) FROM ft1 t1; -- join two tables *************** 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