diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 659dfee..2f2ba19 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -144,29 +144,27 @@ static void deparseDistinctExpr(DistinctExpr *node, deparse_expr_cxt *context); static void deparseScalarArrayOpExpr(ScalarArrayOpExpr *node, deparse_expr_cxt *context); static void deparseRelabelType(RelabelType *node, deparse_expr_cxt *context); static void deparseBoolExpr(BoolExpr *node, deparse_expr_cxt *context); static void deparseNullTest(NullTest *node, deparse_expr_cxt *context); static void deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context); static void printRemoteParam(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void printRemotePlaceholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); -static void deparseSelectSql(List *tlist, StringInfo relation, - List **retrieved_attrs, deparse_expr_cxt *context); +static void deparseSelectSql(List *tlist, List **retrieved_attrs, + deparse_expr_cxt *context); static void deparseLockingClause(deparse_expr_cxt *context); static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context); -static const char *get_jointype_name(JoinType jointype); static void appendConditions(List *exprs, deparse_expr_cxt *context); static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, - RelOptInfo *joinrel, StringInfo relations, - bool use_alias, List **params_list); + RelOptInfo *joinrel, bool use_alias, List **params_list); /* * Examine each qual clause in input_conds, and classify them into two groups, * which are returned as two lists: * - remote_conds contains expressions that can be evaluated remotely * - local_conds contains expressions that can't be evaluated remotely */ void classifyConditions(PlannerInfo *root, @@ -753,46 +751,41 @@ build_tlist_to_deparse(RelOptInfo *foreignrel) * If params_list is not NULL, it receives a list of Params and other-relation * Vars used in the clauses; these values must be transmitted to the remote * server as parameter values. * * If params_list is NULL, we're generating the query for EXPLAIN purposes, * so Params and other-relation Vars should be replaced by dummy values. * * pathkeys is the list of pathkeys to order the result by. * * List of columns selected is returned in retrieved_attrs. - * - * relations is a string buffer for "Relations" portion of EXPLAIN output, - * or NULL if caller doesn't need it. Note that it should have been - * initialized by caller. */ extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, List *tlist, List *remote_conds, List *pathkeys, - List **retrieved_attrs, List **params_list, - StringInfo relations) + List **retrieved_attrs, List **params_list) { deparse_expr_cxt context; /* We handle relations for foreign tables and joins between those */ Assert(rel->reloptkind == RELOPT_JOINREL || rel->reloptkind == RELOPT_BASEREL || rel->reloptkind == RELOPT_OTHER_MEMBER_REL); /* Fill portions of context common to join and base relation */ context.buf = buf; context.root = root; context.foreignrel = rel; context.params_list = params_list; /* Construct SELECT clause and FROM clause */ - deparseSelectSql(tlist, relations, retrieved_attrs, &context); + deparseSelectSql(tlist, retrieved_attrs, &context); /* * Construct WHERE clause */ if (remote_conds) { appendStringInfo(buf, " WHERE "); appendConditions(remote_conds, &context); } @@ -809,22 +802,21 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, * of the specified foreign table, and append it to "buf". The output * contains just "SELECT ... FROM ....". * * We also create an integer List of the columns being retrieved, which is * returned to *retrieved_attrs. * * tlist is the list of desired columns. Read prologue of * deparseSelectStmtForRel() for details. */ static void -deparseSelectSql(List *tlist, StringInfo relation, List **retrieved_attrs, - deparse_expr_cxt *context) +deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context) { StringInfo buf = context->buf; RelOptInfo *foreignrel = context->foreignrel; PlannerInfo *root = context->root; PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; /* * Construct SELECT list */ appendStringInfoString(buf, "SELECT "); @@ -850,21 +842,21 @@ deparseSelectSql(List *tlist, StringInfo relation, List **retrieved_attrs, deparseTargetList(buf, root, foreignrel->relid, rel, fpinfo->attrs_used, retrieved_attrs, false); heap_close(rel, NoLock); } /* * Construct FROM clause */ appendStringInfoString(buf, " FROM "); - deparseFromExprForRel(buf, root, foreignrel, relation, + deparseFromExprForRel(buf, root, foreignrel, (foreignrel->reloptkind == RELOPT_JOINREL), context->params_list); } /* * Emit a target list that retrieves the columns specified in attrs_used. * This is used for both SELECT and RETURNING targetlists. * * The tlist text is appended to buf, and we also create an integer List * of the columns being retrieved, which is returned to *retrieved_attrs. @@ -1055,21 +1047,21 @@ appendConditions(List *exprs, deparse_expr_cxt *context) deparseExpr(expr, context); appendStringInfoChar(buf, ')'); is_first = false; } reset_transmission_modes(nestlevel); } /* Output join name for given join type */ -static const char * +extern const char * get_jointype_name(JoinType jointype) { switch (jointype) { case JOIN_INNER: return "INNER"; case JOIN_LEFT: return "LEFT"; @@ -1132,49 +1124,38 @@ deparseExplicitTargetList(List *tlist, List **retrieved_attrs, } /* * Construct FROM clause for given relation * * The function constructs ... JOIN ... ON ... for join relation. For base relation * it just returns schema-qualified tablename aliased if requested. */ void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, - StringInfo relations, bool use_alias, List **params_list) + bool use_alias, List **params_list) { PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; if (foreignrel->reloptkind == RELOPT_JOINREL) { RelOptInfo *rel_o = fpinfo->outerrel; RelOptInfo *rel_i = fpinfo->innerrel; StringInfoData join_sql_o; StringInfoData join_sql_i; - StringInfoData relations_o; - StringInfoData relations_i; /* Deparse outer relation */ initStringInfo(&join_sql_o); - initStringInfo(&relations_o); - deparseFromExprForRel(&join_sql_o, root, rel_o, &relations_o, true, - params_list); + deparseFromExprForRel(&join_sql_o, root, rel_o, true, params_list); /* Deparse inner relation */ initStringInfo(&join_sql_i); - initStringInfo(&relations_i); - deparseFromExprForRel(&join_sql_i, root, rel_i, &relations_i, true, - params_list); - - /* Let caller know what's being joined */ - appendStringInfo(relations, "(%s) %s JOIN (%s)", relations_o.data, - get_jointype_name(fpinfo->jointype), - relations_i.data); + deparseFromExprForRel(&join_sql_i, root, rel_i, true, params_list); /* * For a join relation FROM clause entry is deparsed as * ((outer relation) (inner relation) ON (joinclauses) */ appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data, get_jointype_name(fpinfo->jointype), join_sql_i.data); /* Append join clause; (TRUE) if no join clause */ if (fpinfo->joinclauses) @@ -1198,49 +1179,31 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, } else { RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); /* * Core code already has some lock on each rel being planned, so we * can use NoLock here. */ Relation rel = heap_open(rte->relid, NoLock); - const char *namespace; - const char *relname; - const char *refname; deparseRelation(buf, rel); /* * Add a unique alias to avoid any conflict in relation names due to * pulled up subqueries in the query being built for a pushed down * join. */ if (use_alias) appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); - /* - * Return local relation name for EXPLAIN output. We can't know - * whether VERBOSE option is specified or not, so always - * schema-qualify the foreign table name. - */ - namespace = get_namespace_name(get_rel_namespace(rte->relid)); - relname = get_rel_name(rte->relid); - refname = rte->eref->aliasname; - appendStringInfo(relations, "%s.%s", - quote_identifier(namespace), - quote_identifier(relname)); - if (*refname && strcmp(refname, relname) != 0) - appendStringInfo(relations, " %s", - quote_identifier(rte->eref->aliasname)); - heap_close(rel, NoLock); } return; } /* * deparse remote INSERT statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index dbee999..398c86a 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -385,20 +385,24 @@ postgres_fdw_handler(PG_FUNCTION_ARGS) * We should consider the effect of all baserestrictinfo clauses here, but * not any join clauses. */ static void postgresGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) { PgFdwRelationInfo *fpinfo; ListCell *lc; + RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); + const char *namespace; + const char *relname; + const char *refname; /* * We use PgFdwRelationInfo to pass various information to subsequent * functions. */ fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); baserel->fdw_private = (void *) fpinfo; /* Base foreign tables need to be push down always. */ fpinfo->pushdown_safe = true; @@ -444,21 +448,20 @@ postgresGetForeignRelSize(PlannerInfo *root, } /* * If the table or the server is configured to use remote estimates, * identify which user to do remote access as during planning. This * should match what ExecCheckRTEPerms() does. If we fail due to lack of * permissions, the query would have failed at runtime anyway. */ if (fpinfo->use_remote_estimate) { - RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); Oid userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid); } else fpinfo->user = NULL; /* * Identify which baserestrictinfo clauses can be sent to the remote * server and which can't. @@ -539,20 +542,37 @@ postgresGetForeignRelSize(PlannerInfo *root, } /* Estimate baserel size as best we can with local statistics. */ set_baserel_size_estimates(root, baserel); /* Fill in basically-bogus cost estimates for use later. */ estimate_path_cost_size(root, baserel, NIL, NIL, &fpinfo->rows, &fpinfo->width, &fpinfo->startup_cost, &fpinfo->total_cost); } + + /* + * Set the name of relation in fpinfo. It will be used to build the string + * describing the join relation in EXPLAIN output. We can't know whether + * VERBOSE option is specified or not, so always schema-qualify the foreign + * table name. + */ + fpinfo->relation_name = makeStringInfo(); + namespace = get_namespace_name(get_rel_namespace(foreigntableid)); + relname = get_rel_name(foreigntableid); + refname = rte->eref->aliasname; + appendStringInfo(fpinfo->relation_name, "%s.%s", + quote_identifier(namespace), + quote_identifier(relname)); + if (*refname && strcmp(refname, relname) != 0) + appendStringInfo(fpinfo->relation_name, " %s", + quote_identifier(rte->eref->aliasname)); } /* * get_useful_ecs_for_relation * Determine which EquivalenceClasses might be involved in useful * orderings of this relation. * * This function is in some respects a mirror image of the core function * pathkeys_useful_for_merging: for a regular table, we know what indexes * we have and want to test whether any of them are useful. For a foreign @@ -983,21 +1003,20 @@ postgresGetForeignPlan(PlannerInfo *root, Index scan_relid; List *fdw_private; List *remote_conds = NIL; List *remote_exprs = NIL; List *local_exprs = NIL; List *params_list = NIL; List *retrieved_attrs; StringInfoData sql; ListCell *lc; List *fdw_scan_tlist = NIL; - StringInfoData relations; /* * For base relations, set scan_relid as the relid of the relation. For * other kinds of relations set it to 0. */ if (foreignrel->reloptkind == RELOPT_BASEREL || foreignrel->reloptkind == RELOPT_OTHER_MEMBER_REL) scan_relid = foreignrel->relid; else { @@ -1063,39 +1082,38 @@ postgresGetForeignPlan(PlannerInfo *root, /* For a join relation, get the conditions from fdw_private structure */ remote_conds = fpinfo->remote_conds; local_exprs = fpinfo->local_conds; /* Build the list of columns to be fetched from the foreign server. */ fdw_scan_tlist = build_tlist_to_deparse(foreignrel); } /* * Build the query string to be sent for execution, and identify - * expressions to be sent as parameters. If the relation to scan is a join - * relation, receive constructed relations string. + * expressions to be sent as parameters. */ initStringInfo(&sql); - initStringInfo(&relations); deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist, remote_conds, best_path->path.pathkeys, - &retrieved_attrs, ¶ms_list, &relations); + &retrieved_attrs, ¶ms_list); /* * Build the fdw_private list that will be available to the executor. * Items in the list must match order in enum FdwScanPrivateIndex. */ fdw_private = list_make4(makeString(sql.data), retrieved_attrs, makeInteger(fpinfo->fetch_size), makeInteger(foreignrel->umid)); if (foreignrel->reloptkind == RELOPT_JOINREL) - fdw_private = lappend(fdw_private, makeString(relations.data)); + fdw_private = lappend(fdw_private, + makeString(fpinfo->relation_name->data)); /* * Create the ForeignScan node for the given relation. * * Note that the remote parameter expressions are stored in the fdw_exprs * field of the finished plan node; we can't keep them in private state * because then they wouldn't be subject to later planner processing. */ return make_foreignscan(tlist, local_exprs, @@ -2046,21 +2064,20 @@ estimate_path_cost_size(PlannerInfo *root, List *local_param_join_conds; StringInfoData sql; PGconn *conn; Selectivity local_sel; QualCost local_cost; List *fdw_scan_tlist = NIL; List *remote_conds; /* Required only to be passed to deparseSelectStmtForRel */ List *retrieved_attrs; - StringInfoData relations; /* * param_join_conds might contain both clauses that are safe to send * across, and clauses that aren't. */ classifyConditions(root, foreignrel, param_join_conds, &remote_param_join_conds, &local_param_join_conds); /* Build the list of columns to be fetched from the foreign server. */ if (foreignrel->reloptkind == RELOPT_JOINREL) @@ -2075,25 +2092,24 @@ estimate_path_cost_size(PlannerInfo *root, */ remote_conds = list_concat(list_copy(remote_param_join_conds), fpinfo->remote_conds); /* * Construct EXPLAIN query including the desired SELECT, FROM, and * WHERE clauses. Params and other-relation Vars are replaced by dummy * values, so don't request params_list. */ initStringInfo(&sql); - initStringInfo(&relations); appendStringInfoString(&sql, "EXPLAIN "); deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist, remote_conds, pathkeys, &retrieved_attrs, - NULL, &relations); + NULL); /* Get the remote estimate */ conn = GetConnection(fpinfo->user, false); get_remote_estimate(sql.data, conn, &rows, &width, &startup_cost, &total_cost); ReleaseConnection(conn); retrieved_rows = rows; /* Factor in the selectivity of the locally-checked quals */ @@ -3459,20 +3475,30 @@ foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, fpinfo_i->remote_conds); fpinfo->joinclauses = list_concat(fpinfo->joinclauses, fpinfo_o->remote_conds); break; default: /* Should not happen, we have just check this above */ elog(ERROR, "unsupported join type %d", jointype); } + /* + * Set the string describing this join relation to be used in EXPLAIN output + * of corresponding ForeignScan. + */ + fpinfo->relation_name = makeStringInfo(); + appendStringInfo(fpinfo->relation_name, "(%s) %s JOIN (%s)", + fpinfo_o->relation_name->data, + get_jointype_name(fpinfo->jointype), + fpinfo_i->relation_name->data); + return true; } /* * postgresGetForeignJoinPaths * Add possible ForeignPath to joinrel, if join is safe to push down. */ static void postgresGetForeignJoinPaths(PlannerInfo *root, RelOptInfo *joinrel, diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index dda1087..a671b8c 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -73,20 +73,27 @@ typedef struct PgFdwRelationInfo Cost fdw_tuple_cost; List *shippable_extensions; /* OIDs of whitelisted extensions */ /* Cached catalog information. */ ForeignTable *table; ForeignServer *server; UserMapping *user; /* only set in use_remote_estimate mode */ int fetch_size; /* fetch size for this remote table */ + /* + * Name of the relation while EXPLAINing ForeignScan. It is used for join + * relations but is set for all relations. For join relation, the name + * indicates which foreign tables are being joined and the join type used. + */ + StringInfo relation_name; + /* Join information */ RelOptInfo *outerrel; RelOptInfo *innerrel; JoinType jointype; List *joinclauses; } PgFdwRelationInfo; /* in postgres_fdw.c */ extern int set_transmission_modes(void); extern void reset_transmission_modes(int nestlevel); @@ -129,17 +136,18 @@ extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root, List **retrieved_attrs); extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel); extern void deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs); extern void deparseStringLiteral(StringInfo buf, const char *val); extern Expr *find_em_expr_for_rel(EquivalenceClass *ec, RelOptInfo *rel); extern List *build_tlist_to_deparse(RelOptInfo *foreign_rel); extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, List *tlist, List *remote_conds, List *pathkeys, List **retrieved_attrs, - List **params_list, StringInfo relations); + List **params_list); /* in shippable.c */ extern bool is_builtin(Oid objectId); extern bool is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo); +extern const char *get_jointype_name(JoinType jointype); #endif /* POSTGRES_FDW_H */