*** a/contrib/postgres_fdw/deparse.c --- b/contrib/postgres_fdw/deparse.c *************** *** 48,53 **** --- 48,54 ---- #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" #include "optimizer/clauses.h" + #include "optimizer/placeholder.h" #include "optimizer/prep.h" #include "optimizer/tlist.h" #include "optimizer/var.h" *************** *** 88,93 **** typedef struct foreign_loc_cxt --- 89,107 ---- } foreign_loc_cxt; /* + * Aliased expressions delivered by subqueries in FROM of the remote query. + */ + typedef struct AliasedExprs + { + List *exprs; /* output columns of subqueries in FROM */ + int max_exprs; /* maximum number of columns stored */ + int *tabnos; /* subselect table alias numbers of columns */ + int *colnos; /* subselect column alias numbers of columns */ + int next_tabno; /* table alias number of next subquery */ + Relids relids; /* all relids of subqueries in FROM */ + } AliasedExprs; + + /* * Context for deparseExpr */ typedef struct deparse_expr_cxt *************** *** 95,104 **** typedef struct deparse_expr_cxt --- 109,121 ---- PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ StringInfo buf; /* output buffer to append to */ + AliasedExprs *aliased; /* aliased exprs of subqueries in FROM */ List **params_list; /* exprs that will become remote Params */ } deparse_expr_cxt; #define REL_ALIAS_PREFIX "r" + #define SS_TAB_ALIAS_PREFIX "ss" + #define SS_COL_ALIAS_PREFIX "c" /* Handy macro to add relation name qualification */ #define ADD_REL_QUALIFIER(buf, varno) \ appendStringInfo((buf), "%s%d.", REL_ALIAS_PREFIX, (varno)) *************** *** 148,164 **** 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, List **retrieved_attrs, deparse_expr_cxt *context); static void deparseLockingClause(deparse_expr_cxt *context); static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context); static void appendConditions(List *exprs, deparse_expr_cxt *context); static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, ! RelOptInfo *joinrel, bool use_alias, List **params_list); /* --- 165,192 ---- 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 deparsePlaceHolderExpr(PlaceHolderVar *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, ! List *remote_conds, ! List **retrieved_attrs, deparse_expr_cxt *context); static void deparseLockingClause(deparse_expr_cxt *context); static void appendOrderByClause(List *pathkeys, deparse_expr_cxt *context); static void appendConditions(List *exprs, deparse_expr_cxt *context); static void deparseFromExprForRel(StringInfo buf, PlannerInfo *root, ! RelOptInfo *foreignrel, bool add_rel_alias, ! AliasedExprs *aliased, List **params_list); ! static void deparseOperandRelation(StringInfo buf, PlannerInfo *root, ! RelOptInfo *foreignrel, JoinType jointype, ! bool add_rel_alias, AliasedExprs *aliased, ! List **params_list); ! static void appendSubselectAlias(List *exprs, deparse_expr_cxt *context); ! static void registerAliasedExpr(Expr *expr, int tabno, int colno, AliasedExprs *aliased); ! static bool is_aliased_expr(Expr *node, int *tabno, int *colno, deparse_expr_cxt *context); /* *************** *** 631,636 **** foreign_expr_walker(Node *node, --- 659,683 ---- check_type = false; } break; + case T_PlaceHolderVar: + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + PlaceHolderInfo *phinfo = find_placeholder_info(glob_cxt->root, + phv, false); + + /* + * If the PHV's contained expression is computable on the + * remote server, we consider the PHV safe to send. + */ + if (phv->phlevelsup == 0 && + bms_is_subset(phinfo->ph_eval_at, + glob_cxt->foreignrel->relids)) + return foreign_expr_walker((Node *) phv->phexpr, + glob_cxt, outer_cxt); + else + return false; + } + break; default: /* *************** *** 723,743 **** deparse_type_name(Oid type_oid, int32 typemod) * foreign server for the given relation. */ List * ! build_tlist_to_deparse(RelOptInfo *foreignrel) { ! List *tlist = NIL; PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; /* ! * We require columns specified in foreignrel->reltarget->exprs and those ! * required for evaluating the local conditions. */ ! tlist = add_to_flat_tlist(tlist, ! pull_var_clause((Node *) foreignrel->reltarget->exprs, ! PVC_RECURSE_PLACEHOLDERS)); ! tlist = add_to_flat_tlist(tlist, ! pull_var_clause((Node *) fpinfo->local_conds, ! PVC_RECURSE_PLACEHOLDERS)); return tlist; } --- 770,849 ---- * foreign server for the given relation. */ List * ! build_tlist_to_deparse(PlannerInfo *root, RelOptInfo *foreignrel) { ! List *tlist; PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; /* ! * Add expressions in foreignrel's reltarget as-is if the expressions are ! * all shippable. Otherwise add shipplable expressions in the reltarget ! * as-is plus expressions required to evaluate non-shippable expressions ! * in the reltarget. */ ! if (fpinfo->reltarget_is_shippable) ! tlist = make_tlist_from_pathtarget(foreignrel->reltarget); ! else ! { ! List *exprs = NIL; ! ListCell *lc; ! ! /* Note: we have at least one non-shippable PHV in the reltarget. */ ! foreach(lc, foreignrel->reltarget->exprs) ! { ! Node *node = (Node *) lfirst(lc); ! ! Assert(IsA(node, Var) || IsA(node, PlaceHolderVar)); ! ! if (IsA(node, Var)) ! exprs = lappend(exprs, node); ! else ! { ! PlaceHolderVar *phv = (PlaceHolderVar *) node; ! PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, ! false); ! ! if (bms_is_subset(phinfo->ph_eval_at, ! fpinfo->outerrel->relids) && ! bms_is_subset(phinfo->ph_eval_at, ! fpinfo->innerrel->relids)) ! { ! /* ! * The PHV coming from an either input should be shippable. ! */ ! exprs = lappend(exprs, node); ! } ! else ! { ! /* ! * The PHV might be shippable, but just retrieve ! * expressions required to evaluate the PHV. ! */ ! List *exprs2 = pull_var_clause(node, ! PVC_INCLUDE_PLACEHOLDERS); ! ListCell *lc2; ! ! foreach(lc2, exprs2) ! { ! Node *node2 = (Node *) lfirst(lc2); ! ! exprs = lappend(exprs, node2); ! } ! } ! } ! } ! ! tlist = NIL; ! tlist = add_to_flat_tlist(tlist, exprs); ! } ! ! /* ! * Add expressions required to evaluate local conditions, if any. ! */ ! if (fpinfo->local_conds) ! tlist = add_to_flat_tlist(tlist, ! pull_var_clause((Node *) fpinfo->local_conds, ! PVC_INCLUDE_PLACEHOLDERS)); return tlist; } *************** *** 767,772 **** deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, --- 873,879 ---- List *tlist, List *remote_conds, List *pathkeys, List **retrieved_attrs, List **params_list) { + AliasedExprs aliased; deparse_expr_cxt context; /* We handle relations for foreign tables and joins between those */ *************** *** 774,796 **** deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, 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, retrieved_attrs, &context); ! ! /* ! * Construct WHERE clause ! */ ! if (remote_conds) ! { ! appendStringInfo(buf, " WHERE "); ! appendConditions(remote_conds, &context); ! } /* Add ORDER BY clause if we found any useful pathkeys */ if (pathkeys) --- 881,903 ---- rel->reloptkind == RELOPT_BASEREL || rel->reloptkind == RELOPT_OTHER_MEMBER_REL); + /* Initialize fields of AliasedExprs */ + aliased.exprs = NIL; + aliased.max_exprs = 32; + aliased.tabnos = (int *) palloc(aliased.max_exprs * sizeof(int)); + aliased.colnos = (int *) palloc(aliased.max_exprs * sizeof(int)); + aliased.next_tabno = 1; + aliased.relids = NULL; + /* Fill portions of context common to join and base relation */ context.buf = buf; context.root = root; context.foreignrel = rel; + context.aliased = &aliased; context.params_list = params_list; ! /* Construct SELECT clause, FROM clause, and WHERE clause */ ! deparseSelectSql(tlist, remote_conds, retrieved_attrs, &context); /* Add ORDER BY clause if we found any useful pathkeys */ if (pathkeys) *************** *** 803,809 **** deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, /* * Construct a simple SELECT statement that retrieves desired columns * 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. --- 910,916 ---- /* * Construct a simple SELECT statement that retrieves desired columns * of the specified foreign table, and append it to "buf". The output ! * contains just "SELECT ... FROM ... WHERE ...". * * We also create an integer List of the columns being retrieved, which is * returned to *retrieved_attrs. *************** *** 812,832 **** deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *rel, * deparseSelectStmtForRel() for details. */ static void ! 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 "); ! if (foreignrel->reloptkind == RELOPT_JOINREL) { - /* For a join relation use the input tlist */ deparseExplicitTargetList(tlist, retrieved_attrs, context); } else --- 919,955 ---- * deparseSelectStmtForRel() for details. */ static void ! deparseSelectSql(List *tlist, ! List *remote_conds, ! 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; + StringInfoData jointree; + + /* + * Deparse a join tree expression in FROM clause first. + */ + initStringInfo(&jointree); + deparseFromExprForRel(&jointree, root, foreignrel, + (foreignrel->reloptkind == RELOPT_JOINREL), + context->aliased, context->params_list); /* * Construct SELECT list */ appendStringInfoString(buf, "SELECT "); ! /* ! * Note: tlist for a base relation may be non-NIL. For example, if the ! * base relation that is involved in a foreign join has a whole-row Var ! * in the reltarget, then tlist for the base relation would be non-NIL. ! */ ! if (tlist != NIL) { deparseExplicitTargetList(tlist, retrieved_attrs, context); } else *************** *** 843,848 **** deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context) --- 966,973 ---- */ Relation rel = heap_open(rte->relid, NoLock); + Assert(foreignrel->reloptkind != RELOPT_JOINREL); + deparseTargetList(buf, root, foreignrel->relid, rel, false, fpinfo->attrs_used, false, retrieved_attrs); heap_close(rel, NoLock); *************** *** 851,860 **** deparseSelectSql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context) /* * Construct FROM clause */ ! appendStringInfoString(buf, " FROM "); ! deparseFromExprForRel(buf, root, foreignrel, ! (foreignrel->reloptkind == RELOPT_JOINREL), ! context->params_list); } /* --- 976,991 ---- /* * Construct FROM clause */ ! appendStringInfo(buf, " FROM %s", jointree.data); ! ! /* ! * Construct WHERE clause ! */ ! if (remote_conds) ! { ! appendStringInfoString(buf, " WHERE "); ! appendConditions(remote_conds, context); ! } } /* *************** *** 965,974 **** deparseLockingClause(deparse_expr_cxt *context) --- 1096,1110 ---- StringInfo buf = context->buf; PlannerInfo *root = context->root; RelOptInfo *rel = context->foreignrel; + AliasedExprs *aliased = context->aliased; int relid = -1; while ((relid = bms_next_member(rel->relids, relid)) >= 0) { + /* Ignore if it's already deparsed in a lower subquery. */ + if (bms_is_member(relid, aliased->relids)) + continue; + /* * Add FOR UPDATE/SHARE if appropriate. We apply locking during the * initial row fetch, rather than later on as is done for local *************** *** 1132,1150 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs, Assert(IsA(tle, TargetEntry)); var = (Var *) tle->expr; ! /* We expect only Var nodes here */ ! if (!IsA(var, Var)) ! elog(ERROR, "non-Var not expected in target list"); if (i > 0) appendStringInfoString(buf, ", "); ! deparseVar(var, context); *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); i++; } if (i == 0) appendStringInfoString(buf, "NULL"); } --- 1268,1288 ---- Assert(IsA(tle, TargetEntry)); var = (Var *) tle->expr; ! /* We expect only Var or PlaceHolderVar nodes here */ ! if (!IsA(var, Var) && !IsA(var, PlaceHolderVar)) ! elog(ERROR, "unexpected node type in target list: %d", ! (int) nodeTag(var)); if (i > 0) appendStringInfoString(buf, ", "); ! deparseExpr((Expr *) var, context); *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); i++; } + /* Don't generate bad syntax if no columns */ if (i == 0) appendStringInfoString(buf, "NULL"); } *************** *** 1152,1191 **** deparseExplicitTargetList(List *tlist, List **retrieved_attrs, /* * Construct FROM clause for given relation * ! * The function constructs ... JOIN ... ON ... for join relation. For a base ! * relation it just returns schema-qualified tablename, with the appropriate ! * alias if so requested. */ static void ! deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, ! 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; /* Deparse outer relation */ ! initStringInfo(&join_sql_o); ! deparseFromExprForRel(&join_sql_o, root, rel_o, true, params_list); ! /* Deparse inner relation */ ! initStringInfo(&join_sql_i); ! 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) { deparse_expr_cxt context; --- 1290,1325 ---- /* * Construct FROM clause for given relation * ! * For a join relation the clause of the following form is appended to buf: ! * ((outer relation) (inner relation) ON (joinclauses)) ! * For a base relation the function just adds the schema-qualified tablename, ! * with the appropriate alias if so requested. */ static void ! deparseFromExprForRel(StringInfo buf, PlannerInfo *root, ! RelOptInfo *foreignrel, bool add_rel_alias, ! AliasedExprs *aliased, List **params_list) { PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; if (foreignrel->reloptkind == RELOPT_JOINREL) { ! /* Begin the FROM clause entry. */ ! appendStringInfoChar(buf, '('); /* Deparse outer relation */ ! deparseOperandRelation(buf, root, fpinfo->outerrel, fpinfo->jointype, ! true, aliased, params_list); ! /* Append join type */ ! appendStringInfo(buf, " %s JOIN ", get_jointype_name(fpinfo->jointype)); ! /* Deparse inner relation */ ! deparseOperandRelation(buf, root, fpinfo->innerrel, fpinfo->jointype, ! true, aliased, params_list); ! /* Append join conditions */ ! appendStringInfoString(buf, " ON "); if (fpinfo->joinclauses) { deparse_expr_cxt context; *************** *** 1193,1209 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, context.buf = buf; context.foreignrel = foreignrel; context.root = root; context.params_list = params_list; ! appendStringInfo(buf, "("); appendConditions(fpinfo->joinclauses, &context); ! appendStringInfo(buf, ")"); } else appendStringInfoString(buf, "(TRUE)"); /* End the FROM clause entry. */ ! appendStringInfo(buf, ")"); } else { --- 1327,1347 ---- context.buf = buf; context.foreignrel = foreignrel; context.root = root; + context.aliased = aliased; context.params_list = params_list; ! appendStringInfoChar(buf, '('); appendConditions(fpinfo->joinclauses, &context); ! appendStringInfoChar(buf, ')'); } else + { + /* No join conditions; add "(TRUE)" */ appendStringInfoString(buf, "(TRUE)"); + } /* End the FROM clause entry. */ ! appendStringInfoChar(buf, ')'); } else { *************** *** 1222,1228 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, * 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); heap_close(rel, NoLock); --- 1360,1366 ---- * pulled up subqueries in the query being built for a pushed down * join. */ ! if (add_rel_alias) appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); heap_close(rel, NoLock); *************** *** 1230,1235 **** deparseFromExprForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, --- 1368,1544 ---- } /* + * Append operand relation of foreign join to buf. + * + * If the given relation is an operand of the foreign join performing a full + * outer join and has conditions that need to be evaluated below the full + * outer join, deparse the relation as a subquery. Also, if the relation's + * reltarget has non-Var expressions, do the same, regardless of the join type + * of the foreign join. + */ + static void + deparseOperandRelation(StringInfo buf, PlannerInfo *root, + RelOptInfo *foreignrel, JoinType jointype, + bool add_rel_alias, AliasedExprs *aliased, + List **params_list) + { + PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private; + + Assert(fpinfo->local_conds == NIL); + Assert(fpinfo->reltarget_is_shippable); + + if ((jointype == JOIN_FULL && fpinfo->remote_conds) || + fpinfo->reltarget_has_non_vars) + { + List *tlist; + List *retrieved_attrs; + deparse_expr_cxt context; + + context.buf = buf; + context.root = root; + context.foreignrel = foreignrel; + context.aliased = aliased; + context.params_list = params_list; + + tlist = build_tlist_to_deparse(root, foreignrel); + appendStringInfoChar(buf, '('); + deparseSelectSql(tlist, + fpinfo->remote_conds, + &retrieved_attrs, + &context); + deparseLockingClause(&context); + appendStringInfoChar(buf, ')'); + appendSubselectAlias(foreignrel->reltarget->exprs, &context); + } + else + deparseFromExprForRel(buf, root, foreignrel, + true, aliased, params_list); + } + + /* + * Add a subselect alias to a subquery in FROM of the remote query. + */ + static void + appendSubselectAlias(List *exprs, deparse_expr_cxt *context) + { + StringInfo buf = context->buf; + RelOptInfo *foreignrel = context->foreignrel; + AliasedExprs *aliased = context->aliased; + int tabno = aliased->next_tabno; + int colno = 1; + bool first; + ListCell *lc; + + /* Append the subselect table alias */ + appendStringInfo(buf, " %s%d", SS_TAB_ALIAS_PREFIX, tabno); + + /* Append the subselect column aliases */ + first = true; + appendStringInfoChar(buf, '('); + foreach(lc, exprs) + { + Expr *expr = (Expr *) lfirst(lc); + + if (!first) + appendStringInfoString(buf, ", "); + first = false; + + appendStringInfo(buf, "%s%d", SS_COL_ALIAS_PREFIX, colno); + + registerAliasedExpr(expr, tabno, colno, aliased); + + colno++; + } + appendStringInfoChar(buf, ')'); + + /* Update next table number for next subquery */ + aliased->next_tabno++; + + /* Add foreignrel's relids to aliased->relids */ + aliased->relids = bms_add_members(aliased->relids, foreignrel->relids); + } + + /* + * Save given aliased expression into the AliasedExprs struct. + * + * Note: when an upper foreign join references the given expression, we use + * the alias for the expression saved here when deparsing the expression in + * the foreign join in deparseExpr. + */ + static void + registerAliasedExpr(Expr *expr, int tabno, int colno, AliasedExprs *aliased) + { + int num_exprs = list_length(aliased->exprs); + int max_exprs = aliased->max_exprs; + int i; + ListCell *lc; + + /* + * If we already have the expression, all we need to do is rewrite the + * corresponding table/column numbers in the tabnos/colnos arrays into + * the given table/column numbers. + */ + i = 0; + foreach(lc, aliased->exprs) + { + if (equal(lfirst(lc), (Node *) expr)) + { + aliased->tabnos[i] = tabno; + aliased->colnos[i] = colno; + return; + } + i++; + } + + /* + * This is a new one; add the given expression to the exprs list and + * write the given table/column numbers into the tabnos/colnos arrays. + */ + aliased->exprs = lappend(aliased->exprs, expr); + if (num_exprs + 1 >= max_exprs) + { + max_exprs *= 2; + aliased->tabnos = (int *) repalloc(aliased->tabnos, + max_exprs * sizeof(int)); + aliased->colnos = (int *) repalloc(aliased->colnos, + max_exprs * sizeof(int)); + aliased->max_exprs = max_exprs; + } + aliased->tabnos[num_exprs] = tabno; + aliased->colnos[num_exprs] = colno; + } + + /* + * Returns true if given expression is aliased by a subquery in FROM. + * + * If so, the table and column alias numbers of the expression are returned to + * *tabno and *colno, respectively. + */ + static bool + is_aliased_expr(Expr *node, int *tabno, int *colno, deparse_expr_cxt *context) + { + AliasedExprs *aliased = context->aliased; + int i; + ListCell *lc; + + if (!aliased) + return false; + + i = 0; + foreach(lc, aliased->exprs) + { + if (equal(lfirst(lc), (Node *) node)) + { + *tabno = aliased->tabnos[i]; + *colno = aliased->colnos[i]; + return true; + } + i++; + } + return false; + } + + /* * deparse remote INSERT statement * * The statement text is appended to buf, and we also create an integer List *************** *** 1361,1366 **** deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root, --- 1670,1676 ---- context.root = root; context.foreignrel = baserel; context.buf = buf; + context.aliased = NULL; context.params_list = params_list; appendStringInfoString(buf, "UPDATE "); *************** *** 1445,1450 **** deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root, --- 1755,1761 ---- context.root = root; context.foreignrel = baserel; context.buf = buf; + context.aliased = NULL; context.params_list = params_list; appendStringInfoString(buf, "DELETE FROM "); *************** *** 1608,1616 **** deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root, { /* * All other system attributes are fetched as 0, except for table OID, ! * which is fetched as the local table OID. However, we must be ! * careful; the table could be beneath an outer join, in which case it ! * must go to NULL whenever the rest of the row does. */ Oid fetchval = 0; --- 1919,1925 ---- { /* * All other system attributes are fetched as 0, except for table OID, ! * which is fetched as the local table OID. */ Oid fetchval = 0; *************** *** 1620,1633 **** deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root, fetchval = rte->relid; } ! if (qualify_col) ! { ! appendStringInfoString(buf, "CASE WHEN ("); ! ADD_REL_QUALIFIER(buf, varno); ! appendStringInfo(buf, "*)::text IS NOT NULL THEN %u END", fetchval); ! } ! else ! appendStringInfo(buf, "%u", fetchval); } else if (varattno == 0) { --- 1929,1935 ---- fetchval = rte->relid; } ! appendStringInfo(buf, "%u", fetchval); } else if (varattno == 0) { *************** *** 1658,1685 **** deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root, attrs_used = bms_add_member(NULL, 0 - FirstLowInvalidHeapAttributeNumber); - /* - * In case the whole-row reference is under an outer join then it has - * to go NULL whenever the rest of the row goes NULL. Deparsing a join - * query would always involve multiple relations, thus qualify_col - * would be true. - */ - if (qualify_col) - { - appendStringInfoString(buf, "CASE WHEN ("); - ADD_REL_QUALIFIER(buf, varno); - appendStringInfo(buf, "*)::text IS NOT NULL THEN "); - } - appendStringInfoString(buf, "ROW("); deparseTargetList(buf, root, varno, rel, false, attrs_used, qualify_col, &retrieved_attrs); appendStringInfoString(buf, ")"); - /* Complete the CASE WHEN statement started above. */ - if (qualify_col) - appendStringInfo(buf, " END"); - heap_close(rel, NoLock); bms_free(attrs_used); } --- 1960,1970 ---- *************** *** 1808,1816 **** deparseStringLiteral(StringInfo buf, const char *val) --- 2093,2113 ---- static void deparseExpr(Expr *node, deparse_expr_cxt *context) { + int tabno; + int colno; + if (node == NULL) return; + /* Use the alias if it's an aliased expression */ + if (is_aliased_expr(node, &tabno, &colno, context)) + { + appendStringInfo(context->buf, "%s%d.%s%d", + SS_TAB_ALIAS_PREFIX, tabno, + SS_COL_ALIAS_PREFIX, colno); + return; + } + switch (nodeTag(node)) { case T_Var: *************** *** 1849,1854 **** deparseExpr(Expr *node, deparse_expr_cxt *context) --- 2146,2154 ---- case T_ArrayExpr: deparseArrayExpr((ArrayExpr *) node, context); break; + case T_PlaceHolderVar: + deparsePlaceHolderExpr((PlaceHolderVar *) node, context); + break; default: elog(ERROR, "unsupported expression type for deparse: %d", (int) nodeTag(node)); *************** *** 2420,2425 **** deparseArrayExpr(ArrayExpr *node, deparse_expr_cxt *context) --- 2720,2734 ---- } /* + * Deparse a PlaceHolderVar's contained expression. + */ + static void + deparsePlaceHolderExpr(PlaceHolderVar *node, deparse_expr_cxt *context) + { + deparseExpr(node->phexpr, context); + } + + /* * Print the representation of a parameter to be sent to the remote side. * * Note: we always label the Param's type explicitly rather than relying on *** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** *** 456,463 **** SELECT t1.c1, t2."C 1" FROM ft2 t1 LEFT JOIN "S 1"."T 1" t2 ON (t1.c1 = t2."C 1" -- foreign join so that the local table can be joined using merge join strategy. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1."C 1" -> Merge Right Join --- 456,463 ---- -- foreign join so that the local table can be joined using merge join strategy. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1."C 1" FROM "S 1"."T 1" t1 left join ft1 t2 join ft2 t3 on (t2.c1 = t3.c1) on (t3.c1 = t1."C 1") OFFSET 100 LIMIT 10; ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1."C 1" -> Merge Right Join *************** *** 466,472 **** EXPLAIN (VERBOSE, COSTS OFF) -> Foreign Scan Output: t3.c1 Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) ! Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (((r2."C 1" = r3."C 1")))) ORDER BY r2."C 1" ASC NULLS LAST -> Index Only Scan using t1_pkey on "S 1"."T 1" t1 Output: t1."C 1" (11 rows) --- 466,472 ---- -> Foreign Scan Output: t3.c1 Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) ! Remote SQL: SELECT r3."C 1" FROM ("S 1"."T 1" r2 INNER JOIN "S 1"."T 1" r3 ON (TRUE)) WHERE ((r2."C 1" = r3."C 1")) ORDER BY r2."C 1" ASC NULLS LAST -> Index Only Scan using t1_pkey on "S 1"."T 1" t1 Output: t1."C 1" (11 rows) *************** *** 979,992 **** ANALYZE ft5; -- join two tables EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: t1.c1, t2.c1, t1.c3 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST (6 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; --- 979,992 ---- -- join two tables EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST (6 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; *************** *** 1007,1014 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -- join three tables EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c2, t3.c3, t1.c3 -> Sort --- 1007,1014 ---- -- join three tables EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c2, t3.c3, t1.c3 -> Sort *************** *** 1017,1023 **** SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3) ! Remote SQL: SELECT r1."C 1", r1.c3, r2.c2, r4.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) (9 rows) SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; --- 1017,1023 ---- -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3) ! Remote SQL: SELECT r1."C 1", r1.c3, r2.c2, r4.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) INNER JOIN "S 1"."T 3" r4 ON (TRUE)) WHERE ((r1."C 1" = r4.c1)) AND ((r1."C 1" = r2."C 1")) (9 rows) SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10; *************** *** 1223,1245 **** SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) ORDER BY t1. -- full outer join with restrictions on the joining relations EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------ ! Sort Output: ft4.c1, ft5.c1 ! Sort Key: ft4.c1, ft5.c1 ! -> Hash Full Join ! Output: ft4.c1, ft5.c1 ! Hash Cond: (ft4.c1 = ft5.c1) ! -> Foreign Scan on public.ft4 ! Output: ft4.c1, ft4.c2, ft4.c3 ! Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60)) ! -> Hash ! Output: ft5.c1 ! -> Foreign Scan on public.ft5 ! Output: ft5.c1 ! Remote SQL: SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60)) ! (14 rows) SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 --- 1223,1235 ---- -- full outer join with restrictions on the joining relations EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ! Foreign Scan Output: ft4.c1, ft5.c1 ! Relations: (public.ft4) FULL JOIN (public.ft5) ! Remote SQL: SELECT ss1.c1, ss2.c1 FROM ((SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 50)) AND ((c1 <= 60))) ss1(c1) FULL JOIN (SELECT c1 FROM "S 1"."T 4" WHERE ((c1 >= 50)) AND ((c1 <= 60))) ss2(c1) ON (((ss1.c1 = ss2.c1)))) ORDER BY ss1.c1 ASC NULLS LAST, ss2.c1 ASC NULLS LAST ! (4 rows) SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL JOIN (SELECT c1 FROM ft5 WHERE c1 between 50 and 60) t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 *************** *** 1257,1270 **** SELECT t1.c1, t2.c1 FROM (SELECT c1 FROM ft4 WHERE c1 between 50 and 60) t1 FULL -- full outer join + inner join EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t3.c1 -> Foreign Scan Output: t1.c1, t2.c1, t3.c1 Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3) ! Remote SQL: SELECT r1.c1, r2.c1, r4.c1 FROM (("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60)))) FULL JOIN "S 1"."T 3" r4 ON (((r2.c1 = r4.c1)))) ORDER BY r1.c1 ASC NULLS LAST, r2.c1 ASC NULLS LAST, r4.c1 ASC NULLS LAST (6 rows) SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; --- 1247,1260 ---- -- full outer join + inner join EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t3.c1 -> Foreign Scan Output: t1.c1, t2.c1, t3.c1 Relations: ((public.ft4 t1) INNER JOIN (public.ft5 t2)) FULL JOIN (public.ft4 t3) ! Remote SQL: SELECT ss1.c1, ss1.c2, r4.c1 FROM ((SELECT r1.c1, r2.c1 FROM ("S 1"."T 3" r1 INNER JOIN "S 1"."T 4" r2 ON (TRUE)) WHERE ((r1.c1 = (r2.c1 + 1))) AND ((r1.c1 >= 50)) AND ((r1.c1 <= 60))) ss1(c1, c2) FULL JOIN "S 1"."T 3" r4 ON (((ss1.c2 = r4.c1)))) ORDER BY ss1.c1 ASC NULLS LAST, ss1.c2 ASC NULLS LAST, r4.c1 ASC NULLS LAST (6 rows) SELECT t1.c1, t2.c1, t3.c1 FROM ft4 t1 INNER JOIN ft5 t2 ON (t1.c1 = t2.c1 + 1 and t1.c1 between 50 and 60) FULL JOIN ft4 t3 ON (t2.c1 = t3.c1) ORDER BY t1.c1, t2.c1, t3.c1 LIMIT 10; *************** *** 1453,1466 **** SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 RIGHT JOIN ft2 t2 ON (t1.c1 = t2.c1) LEFT -- left outer join + right outer join EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c2, t3.c3 -> Foreign Scan Output: t1.c1, t2.c2, t3.c3 Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2)) ! Remote SQL: SELECT r4.c3, r1."C 1", r2.c2 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ON (((r2."C 1" = r4.c1)))) (6 rows) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; --- 1443,1456 ---- -- left outer join + right outer join EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; ! QUERY PLAN ! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c2, t3.c3 -> Foreign Scan Output: t1.c1, t2.c2, t3.c3 Relations: (public.ft4 t3) LEFT JOIN ((public.ft2 t1) INNER JOIN (public.ft2 t2)) ! Remote SQL: SELECT r4.c3, r1."C 1", r2.c2 FROM ("S 1"."T 3" r4 LEFT JOIN ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ON (((r2."C 1" = r4.c1)) AND ((r1."C 1" = r2."C 1")))) (6 rows) SELECT t1.c1, t2.c2, t3.c3 FROM ft2 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN ft4 t3 ON (t2.c1 = t3.c1) OFFSET 10 LIMIT 10; *************** *** 1513,1520 **** SELECT t1.c1, t2.c1 FROM ft4 t1 FULL JOIN ft5 t2 ON (t1.c1 = t2.c1) WHERE (t1.c1 -- tests whole-row reference for row marks EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows --- 1503,1510 ---- -- tests whole-row reference for row marks EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1; ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows *************** *** 1522,1528 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 -> Merge Join Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Merge Cond: (t1.c1 = t2.c1) --- 1512,1518 ---- -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss2.c1, ss2.c2 FROM ((SELECT "C 1", c3, ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR UPDATE) ss1(c1, c2, c3) INNER JOIN (SELECT "C 1", ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1") ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c1 = ss2.c1)) ORDER BY ss1.c2 ASC NULLS LAST, ss1.c1 ASC NULLS LAST -> Merge Join Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Merge Cond: (t1.c1 = t2.c1) *************** *** 1557,1564 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; ! QUERY PLAN ! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows --- 1547,1554 ---- EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows *************** *** 1566,1572 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 -> Merge Join Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Merge Cond: (t1.c1 = t2.c1) --- 1556,1562 ---- -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss2.c1, ss2.c2 FROM ((SELECT "C 1", c3, ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR UPDATE) ss1(c1, c2, c3) INNER JOIN (SELECT "C 1", ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR UPDATE) ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c1 = ss2.c1)) ORDER BY ss1.c2 ASC NULLS LAST, ss1.c1 ASC NULLS LAST -> Merge Join Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Merge Cond: (t1.c1 = t2.c1) *************** *** 1602,1609 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -- join two tables with FOR SHARE clause EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; ! QUERY PLAN ! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows --- 1592,1599 ---- -- join two tables with FOR SHARE clause EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows *************** *** 1611,1617 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 -> Merge Join Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Merge Cond: (t1.c1 = t2.c1) --- 1601,1607 ---- -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss2.c1, ss2.c2 FROM ((SELECT "C 1", c3, ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR SHARE) ss1(c1, c2, c3) INNER JOIN (SELECT "C 1", ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1") ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c1 = ss2.c1)) ORDER BY ss1.c2 ASC NULLS LAST, ss1.c1 ASC NULLS LAST -> Merge Join Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Merge Cond: (t1.c1 = t2.c1) *************** *** 1646,1653 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows --- 1636,1643 ---- EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE; ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> LockRows *************** *** 1655,1661 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 FOR SHARE OF r2 -> Merge Join Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Merge Cond: (t1.c1 = t2.c1) --- 1645,1651 ---- -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss2.c1, ss2.c2 FROM ((SELECT "C 1", c3, ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR SHARE) ss1(c1, c2, c3) INNER JOIN (SELECT "C 1", ROW("C 1", c2, c3, c4, c5, c6, c7, c8) FROM "S 1"."T 1" FOR SHARE) ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c1 = ss2.c1)) ORDER BY ss1.c2 ASC NULLS LAST, ss1.c1 ASC NULLS LAST -> Merge Join Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Merge Cond: (t1.c1 = t2.c1) *************** *** 1691,1705 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t -- join in CTE EXPLAIN (VERBOSE, COSTS OFF) WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t.c1_1, t.c2_1, t.c1_3 CTE t -> Foreign Scan Output: t1.c1, t1.c3, t2.c1 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) -> Sort Output: t.c1_1, t.c2_1, t.c1_3 Sort Key: t.c1_3, t.c1_1 --- 1681,1695 ---- -- join in CTE EXPLAIN (VERBOSE, COSTS OFF) WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: t.c1_1, t.c2_1, t.c1_3 CTE t -> Foreign Scan Output: t1.c1, t1.c3, t2.c1 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) -> Sort Output: t.c1_1, t.c2_1, t.c1_3 Sort Key: t.c1_3, t.c1_1 *************** *** 1725,1738 **** WITH t (c1_1, c1_3, c2_1) AS (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 -- ctid with whole-row reference EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3 -> Foreign Scan Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r1."C 1", r1.c3, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST (6 rows) -- SEMI JOIN, not pushed down --- 1715,1728 ---- -- ctid with whole-row reference EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3 -> Foreign Scan Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss1.c4, ss2.c1 FROM ((SELECT ctid, ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1", c3 FROM "S 1"."T 1") ss1(c1, c2, c3, c4) INNER JOIN (SELECT ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1" FROM "S 1"."T 1") ss2(c1, c2) ON (TRUE)) WHERE ((ss1.c3 = ss2.c2)) ORDER BY ss1.c4 ASC NULLS LAST, ss1.c3 ASC NULLS LAST (6 rows) -- SEMI JOIN, not pushed down *************** *** 1955,1962 **** SELECT t1.c1, t2.c1 FROM ft1 t1 LEFT JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 -- into one of the joining sides. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, t1.c3 -> Sort --- 1945,1952 ---- -- into one of the joining sides. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: t1.c1, t2.c1, t1.c3 -> Sort *************** *** 1966,1972 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2. Output: t1.c1, t2.c1, t1.c3 Filter: (t1.c8 = t2.c8) Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) (10 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; --- 1956,1962 ---- Output: t1.c1, t2.c1, t1.c3 Filter: (t1.c8 = t2.c8) Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r1.c3, r2."C 1", r1.c8, r2.c8 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) (10 rows) SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2.c8 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10; *************** *** 1987,1994 **** SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) WHERE t1.c8 = t2. -- Aggregate after UNION, for testing setrefs EXPLAIN (VERBOSE, COSTS OFF) SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! ---------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, (avg((t1.c1 + t2.c1))) -> Sort --- 1977,1984 ---- -- Aggregate after UNION, for testing setrefs EXPLAIN (VERBOSE, COSTS OFF) SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, (avg((t1.c1 + t2.c1))) -> Sort *************** *** 2004,2014 **** SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) -> Foreign Scan Output: t1_1.c1, t2_1.c1 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) (20 rows) SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; --- 1994,2004 ---- -> Foreign Scan Output: t1.c1, t2.c1 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) -> Foreign Scan Output: t1_1.c1, t2_1.c1 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) (20 rows) SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) UNION SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) AS t (t1c1, t2c1) GROUP BY t1c1 ORDER BY t1c1 OFFSET 100 LIMIT 10; *************** *** 2029,2036 **** SELECT t1c1, avg(t1c1 + t2c1) FROM (SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 -- join with lateral reference EXPLAIN (VERBOSE, COSTS OFF) SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1."C 1" -> Nested Loop --- 2019,2026 ---- -- join with lateral reference EXPLAIN (VERBOSE, COSTS OFF) SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1."C 1" -> Nested Loop *************** *** 2043,2049 **** SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM -> Foreign Scan Output: t2.c1, t3.c1 Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) ! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer)))) (13 rows) SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; --- 2033,2039 ---- -> Foreign Scan Output: t2.c1, t3.c1 Relations: (public.ft1 t2) INNER JOIN (public.ft2 t3) ! Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r1."C 1" = r2."C 1")) AND ((r1.c2 = $1::integer)) (13 rows) SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; *************** *** 2061,2085 **** SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM 1 (10 rows) ! -- non-Var items in targelist of the nullable rel of a join preventing ! -- push-down in some cases ! -- unable to push {ft1, ft2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------- ! Nested Loop Left Join Output: (13), ft2.c1 ! Join Filter: (13 = ft2.c1) ! -> Foreign Scan on public.ft2 ! Output: ft2.c1 ! Remote SQL: SELECT "C 1" FROM "S 1"."T 1" WHERE (("C 1" >= 10)) AND (("C 1" <= 15)) ORDER BY "C 1" ASC NULLS LAST ! -> Materialize ! Output: (13) ! -> Foreign Scan on public.ft1 ! Output: 13 ! Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE (("C 1" = 13)) ! (11 rows) SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; a | c1 --- 2051,2066 ---- 1 (10 rows) ! -- check join pushdown in situations where PHVs are involved EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ! Foreign Scan Output: (13), ft2.c1 ! Relations: (public.ft2) LEFT JOIN (public.ft1) ! Remote SQL: SELECT r2."C 1", ss1.c1 FROM ("S 1"."T 1" r2 LEFT JOIN (SELECT 13 FROM "S 1"."T 1" WHERE (("C 1" = 13))) ss1(c1) ON (((13 = r2."C 1")))) WHERE ((r2."C 1" >= 10)) AND ((r2."C 1" <= 15)) ! (4 rows) SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; a | c1 *************** *** 2092,2115 **** SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 O | 15 (6 rows) ! -- ok to push {ft1, ft2} but not {ft1, ft2, ft4} EXPLAIN (VERBOSE, COSTS OFF) SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ! Nested Loop Left Join Output: ft4.c1, (13), ft1.c1, ft2.c1 ! Join Filter: (ft4.c1 = ft1.c1) ! -> Foreign Scan on public.ft4 ! Output: ft4.c1, ft4.c2, ft4.c3 ! Remote SQL: SELECT c1 FROM "S 1"."T 3" WHERE ((c1 >= 10)) AND ((c1 <= 15)) ! -> Materialize ! Output: ft1.c1, ft2.c1, (13) ! -> Foreign Scan ! Output: ft1.c1, ft2.c1, 13 ! Relations: (public.ft1) INNER JOIN (public.ft2) ! Remote SQL: SELECT r4."C 1", r5."C 1" FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (((r5."C 1" = 12)) AND ((r4."C 1" = 12)))) ORDER BY r4."C 1" ASC NULLS LAST ! (12 rows) SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; c1 | a | b | c --- 2073,2113 ---- | 15 (6 rows) ! EXPLAIN (VERBOSE, COSTS OFF) ! SELECT ft2.c1, q2.* FROM ft2 LEFT JOIN (SELECT 13, q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15) q2(a2, b2, c2) ON (ft2.c1 = q2.a2) WHERE ft2.c1 BETWEEN 10 AND 15; ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ! Foreign Scan ! Output: ft2.c1, (13), (13), ft2_1.c1 ! Relations: (public.ft2) LEFT JOIN ((public.ft2) LEFT JOIN (public.ft1)) ! Remote SQL: SELECT r1."C 1", ss2.c1, ss2.c2, ss2.c3 FROM ("S 1"."T 1" r1 LEFT JOIN (SELECT r5."C 1", 13, ss1.c1 FROM ("S 1"."T 1" r5 LEFT JOIN (SELECT 13 FROM "S 1"."T 1" WHERE (("C 1" = 13))) ss1(c1) ON (((13 = r5."C 1")))) WHERE ((r5."C 1" >= 10)) AND ((r5."C 1" <= 15))) ss2(c1, c2, c3) ON (((r1."C 1" = 13)))) WHERE ((r1."C 1" >= 10)) AND ((r1."C 1" <= 15)) ! (4 rows) ! ! SELECT ft2.c1, q2.* FROM ft2 LEFT JOIN (SELECT 13, q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15) q2(a2, b2, c2) ON (ft2.c1 = q2.a2) WHERE ft2.c1 BETWEEN 10 AND 15; ! c1 | a2 | b2 | c2 ! ----+----+----+---- ! 10 | | | ! 11 | | | ! 12 | | | ! 13 | 13 | | 10 ! 13 | 13 | | 11 ! 13 | 13 | | 12 ! 13 | 13 | 13 | 13 ! 13 | 13 | | 14 ! 13 | 13 | | 15 ! 14 | | | ! 15 | | | ! (11 rows) ! EXPLAIN (VERBOSE, COSTS OFF) SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ! Foreign Scan Output: ft4.c1, (13), ft1.c1, ft2.c1 ! Relations: (public.ft4) LEFT JOIN ((public.ft1) INNER JOIN (public.ft2)) ! Remote SQL: SELECT r1.c1, ss1.c1, ss1.c2, ss1.c3 FROM ("S 1"."T 3" r1 LEFT JOIN (SELECT r4."C 1", r5."C 1", 13 FROM ("S 1"."T 1" r4 INNER JOIN "S 1"."T 1" r5 ON (TRUE)) WHERE ((r5."C 1" = 12)) AND ((r4."C 1" = 12))) ss1(c1, c2, c3) ON (((r1.c1 = ss1.c1)))) WHERE ((r1.c1 >= 10)) AND ((r1.c1 <= 15)) ! (4 rows) SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; c1 | a | b | c *************** *** 2119,2134 **** SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT 14 | | | (3 rows) -- join with nullable side with some columns with null values UPDATE ft5 SET c3 = null where c1 % 9 = 0; EXPLAIN (VERBOSE, COSTS OFF) SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 Relations: (public.ft5) INNER JOIN (public.ft4) ! Remote SQL: SELECT CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1.c1, r1.c2, r1.c3) END, r1.c1, r1.c2, r1.c3, r2.c1, r2.c2 FROM ("S 1"."T 4" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c1 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)))) ORDER BY r1.c1 ASC NULLS LAST (4 rows) SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; --- 2117,2150 ---- 14 | | | (3 rows) + EXPLAIN (VERBOSE, COSTS OFF) + SELECT ft4.c1, q2.* FROM ft4 LEFT JOIN (SELECT (q.b IS NOT NULL), ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15) q2(a2, b2, c2, d2, e2) ON (ft4.c1 = q2.b2) WHERE ft4.c1 BETWEEN 10 AND 15; + QUERY PLAN + --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Foreign Scan + Output: ft4.c1, ((ft1.c1 IS NOT NULL)), ft4_1.c1, (13), ft1.c1, ft2.c1 + Relations: (public.ft4) LEFT JOIN ((public.ft4) LEFT JOIN ((public.ft1) INNER JOIN (public.ft2))) + Remote SQL: SELECT r1.c1, ss2.c1, ss2.c2, ss2.c3, ss2.c4, ss2.c5 FROM ("S 1"."T 3" r1 LEFT JOIN (SELECT r4.c1, ss1.c1, ss1.c2, (ss1.c1 IS NOT NULL), ss1.c3 FROM ("S 1"."T 3" r4 LEFT JOIN (SELECT r7."C 1", r8."C 1", 13 FROM ("S 1"."T 1" r7 INNER JOIN "S 1"."T 1" r8 ON (TRUE)) WHERE ((r8."C 1" = 12)) AND ((r7."C 1" = 12))) ss1(c1, c2, c3) ON (((r4.c1 = ss1.c1)))) WHERE ((r4.c1 >= 10)) AND ((r4.c1 <= 15))) ss2(c1, c2, c3, c4, c5) ON (((r1.c1 = ss2.c1)))) WHERE ((r1.c1 >= 10)) AND ((r1.c1 <= 15)) + (4 rows) + + SELECT ft4.c1, q2.* FROM ft4 LEFT JOIN (SELECT (q.b IS NOT NULL), ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15) q2(a2, b2, c2, d2, e2) ON (ft4.c1 = q2.b2) WHERE ft4.c1 BETWEEN 10 AND 15; + c1 | a2 | b2 | c2 | d2 | e2 + ----+----+----+----+----+---- + 10 | f | 10 | | | + 12 | t | 12 | 13 | 12 | 12 + 14 | f | 14 | | | + (3 rows) + -- join with nullable side with some columns with null values UPDATE ft5 SET c3 = null where c1 % 9 = 0; EXPLAIN (VERBOSE, COSTS OFF) SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; ! QUERY PLAN ! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: ft5.*, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 Relations: (public.ft5) INNER JOIN (public.ft4) ! Remote SQL: SELECT ss1.c1, ss1.c2, ss1.c3, ss1.c4, r2.c1, r2.c2 FROM ((SELECT ROW(c1, c2, c3), c1, c2, c3 FROM "S 1"."T 4") ss1(c1, c2, c3, c4) INNER JOIN "S 1"."T 3" r2 ON (TRUE)) WHERE ((ss1.c2 = r2.c1)) AND ((r2.c1 >= 10)) AND ((r2.c1 <= 30)) ORDER BY ss1.c2 ASC NULLS LAST (4 rows) SELECT ft5, ft5.c1, ft5.c2, ft5.c3, ft4.c1, ft4.c2 FROM ft5 left join ft4 on ft5.c1 = ft4.c1 WHERE ft4.c1 BETWEEN 10 and 30 ORDER BY ft5.c1, ft4.c1; *************** *** 2288,2299 **** DROP ROLE regress_view_owner; -- 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 (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------ Foreign Scan Output: t1.c3, t2.c3 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r2."C 1" = 2)) AND ((r1."C 1" = 1)))) (4 rows) EXECUTE st1(1, 1); --- 2304,2315 ---- -- 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 (VERBOSE, COSTS OFF) EXECUTE st1(1, 2); ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c3, t2.c3 Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2) ! Remote SQL: SELECT r1.c3, r2.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) WHERE ((r2."C 1" = 2)) AND ((r1."C 1" = 1)) (4 rows) EXECUTE st1(1, 1); *************** *** 2917,2930 **** UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING EXPLAIN (verbose, costs off) UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can't be pushed down ! QUERY PLAN ! --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Update on public.ft2 Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c3 = $3, c7 = $4 WHERE ctid = $1 -> Foreign Scan Output: ft2.c1, (ft2.c2 + 500), NULL::integer, (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, 'ft2 '::character(10), ft2.c8, ft2.ctid, ft1.* Relations: (public.ft2) INNER JOIN (public.ft1) ! Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c8, r1.ctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9)))) FOR UPDATE OF r1 -> Hash Join Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid, ft1.* Hash Cond: (ft2.c2 = ft1.c1) --- 2933,2946 ---- EXPLAIN (verbose, costs off) UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can't be pushed down ! QUERY PLAN ! ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Update on public.ft2 Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c3 = $3, c7 = $4 WHERE ctid = $1 -> Foreign Scan Output: ft2.c1, (ft2.c2 + 500), NULL::integer, (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, 'ft2 '::character(10), ft2.c8, ft2.ctid, ft1.* Relations: (public.ft2) INNER JOIN (public.ft1) ! Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c8, r1.ctid, ss1.c1 FROM ("S 1"."T 1" r1 INNER JOIN (SELECT ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1" FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9))) ss1(c1, c2) ON (TRUE)) WHERE ((r1.c2 = ss1.c2)) FOR UPDATE OF r1 -> Hash Join Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid, ft1.* Hash Cond: (ft2.c2 = ft1.c1) *************** *** 3060,3073 **** DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; EXPLAIN (verbose, costs off) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can't be pushed down ! QUERY PLAN ! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Delete on public.ft2 Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 -> Foreign Scan Output: ft2.ctid, ft1.* Relations: (public.ft2) INNER JOIN (public.ft1) ! Remote SQL: SELECT r1.ctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 2)))) FOR UPDATE OF r1 -> Hash Join Output: ft2.ctid, ft1.* Hash Cond: (ft2.c2 = ft1.c1) --- 3076,3089 ---- EXPLAIN (verbose, costs off) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; -- can't be pushed down ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Delete on public.ft2 Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 -> Foreign Scan Output: ft2.ctid, ft1.* Relations: (public.ft2) INNER JOIN (public.ft1) ! Remote SQL: SELECT r1.ctid, ss1.c1 FROM ("S 1"."T 1" r1 INNER JOIN (SELECT ROW("C 1", c2, c3, c4, c5, c6, c7, c8), "C 1" FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2))) ss1(c1, c2) ON (TRUE)) WHERE ((r1.c2 = ss1.c2)) FOR UPDATE OF r1 -> Hash Join Output: ft2.ctid, ft1.* Hash Cond: (ft2.c2 = ft1.c1) *** a/contrib/postgres_fdw/postgres_fdw.c --- b/contrib/postgres_fdw/postgres_fdw.c *************** *** 27,32 **** --- 27,33 ---- #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" + #include "optimizer/placeholder.h" #include "optimizer/planmain.h" #include "optimizer/restrictinfo.h" #include "optimizer/var.h" *************** *** 640,645 **** postgresGetForeignRelSize(PlannerInfo *root, --- 641,704 ---- } /* + * Detect whether the reltarget expressions are all shippable and whether + * there are any non-Var expressions in the reltarget. + * + * Note: if the relation is an appendrel child, it isn't in the main join + * tree, so the info is not used. + */ + if (baserel->reloptkind == RELOPT_BASEREL) + { + bool reltarget_is_shippable = true; + bool reltarget_has_non_vars = false; + int i; + + foreach(lc, baserel->reltarget->exprs) + { + Node *node = (Node *) lfirst(lc); + + Assert(IsA(node, Var) || IsA(node, PlaceHolderVar)); + + if (IsA(node, PlaceHolderVar)) + { + reltarget_has_non_vars = true; + if (!is_foreign_expr(root, baserel, (Expr *) node)) + { + reltarget_is_shippable = false; + break; + } + } + } + + /* + * Treat system columns other than citd and whole-row Vars like PHVs. + * + * Note: such system columns are deaprsed as 0, except for tableoid, + * which is deparsed as a valid value for the local table OID, and + * whole-row Vars as ROW() expressions. Any Vars including these in + * the reltarget are considered shippable, though. + */ + if (reltarget_is_shippable) + { + for (i = FirstLowInvalidHeapAttributeNumber + 1; i <= 0; i++) + { + if (i == SelfItemPointerAttributeNumber) + continue; + + if (bms_is_member(i - FirstLowInvalidHeapAttributeNumber, + fpinfo->attrs_used)) + { + reltarget_has_non_vars = true; + break; + } + } + } + + fpinfo->reltarget_is_shippable = reltarget_is_shippable; + fpinfo->reltarget_has_non_vars = reltarget_has_non_vars; + } + + /* * Set the name of relation in fpinfo, while we are constructing it here. * 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 *************** *** 1177,1183 **** postgresGetForeignPlan(PlannerInfo *root, 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); /* * Ensure that the outer plan produces a tuple whose descriptor --- 1236,1242 ---- local_exprs = fpinfo->local_conds; /* Build the list of columns to be fetched from the foreign server. */ ! fdw_scan_tlist = build_tlist_to_deparse(root, foreignrel); /* * Ensure that the outer plan produces a tuple whose descriptor *************** *** 2506,2512 **** estimate_path_cost_size(PlannerInfo *root, /* Build the list of columns to be fetched from the foreign server. */ if (foreignrel->reloptkind == RELOPT_JOINREL) ! fdw_scan_tlist = build_tlist_to_deparse(foreignrel); else fdw_scan_tlist = NIL; --- 2565,2571 ---- /* Build the list of columns to be fetched from the foreign server. */ if (foreignrel->reloptkind == RELOPT_JOINREL) ! fdw_scan_tlist = build_tlist_to_deparse(root, foreignrel); else fdw_scan_tlist = NIL; *************** *** 3947,3952 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, --- 4006,4013 ---- ListCell *lc; List *joinclauses; List *otherclauses; + bool reltarget_is_shippable = true; + bool reltarget_has_non_vars = false; /* * We support pushing down INNER, LEFT, RIGHT and FULL OUTER joins. *************** *** 3976,3981 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, --- 4037,4050 ---- if (fpinfo_o->local_conds || fpinfo_i->local_conds) return false; + /* + * If the reltargets of joining relations are not shippable, those are + * required to be evaluated locally, so the join can not be pushed down. + */ + if (!fpinfo_o->reltarget_is_shippable || + !fpinfo_i->reltarget_is_shippable) + return false; + /* Separate restrict list into join quals and quals on join relation */ if (IS_OUTER_JOIN(jointype)) extract_actual_join_clauses(extra->restrictlist, &joinclauses, &otherclauses); *************** *** 4001,4026 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, return false; } - /* - * deparseExplicitTargetList() isn't smart enough to handle anything other - * than a Var. In particular, if there's some PlaceHolderVar that would - * need to be evaluated within this join tree (because there's an upper - * reference to a quantity that may go to NULL as a result of an outer - * join), then we can't try to push the join down because we'll fail when - * we get to deparseExplicitTargetList(). However, a PlaceHolderVar that - * needs to be evaluated *at the top* of this join tree is OK, because we - * can do that locally after fetching the results from the remote side. - */ - foreach(lc, root->placeholder_list) - { - PlaceHolderInfo *phinfo = lfirst(lc); - Relids relids = joinrel->relids; - - if (bms_is_subset(phinfo->ph_eval_at, relids) && - bms_nonempty_difference(relids, phinfo->ph_eval_at)) - return false; - } - /* Save the join clauses, for later use. */ fpinfo->joinclauses = joinclauses; --- 4070,4075 ---- *************** *** 4049,4056 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, /* * Pull the other remote conditions from the joining relations into join * clauses or other remote clauses (remote_conds) of this relation ! * wherever possible. This avoids building subqueries at every join step, ! * which is not currently supported by the deparser logic. * * For an inner join, clauses from both the relations are added to the * other remote clauses. For LEFT and RIGHT OUTER join, the clauses from --- 4098,4104 ---- /* * Pull the other remote conditions from the joining relations into join * clauses or other remote clauses (remote_conds) of this relation ! * wherever possible. This avoids building subqueries at every join step. * * For an inner join, clauses from both the relations are added to the * other remote clauses. For LEFT and RIGHT OUTER join, the clauses from *************** *** 4061,4068 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, * * For a FULL OUTER JOIN, the other clauses from either relation can not * be added to the joinclauses or remote_conds, since each relation acts ! * as an outer relation for the other. Consider such full outer join as ! * unshippable because of the reasons mentioned above in this comment. * * The joining sides can not have local conditions, thus no need to test * shippability of the clauses being pulled up. --- 4109,4115 ---- * * For a FULL OUTER JOIN, the other clauses from either relation can not * be added to the joinclauses or remote_conds, since each relation acts ! * as an outer relation for the other. * * The joining sides can not have local conditions, thus no need to test * shippability of the clauses being pulled up. *************** *** 4070,4098 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, switch (jointype) { case JOIN_INNER: ! fpinfo->remote_conds = list_concat(fpinfo->remote_conds, list_copy(fpinfo_i->remote_conds)); ! fpinfo->remote_conds = list_concat(fpinfo->remote_conds, list_copy(fpinfo_o->remote_conds)); break; case JOIN_LEFT: ! fpinfo->joinclauses = list_concat(fpinfo->joinclauses, list_copy(fpinfo_i->remote_conds)); ! fpinfo->remote_conds = list_concat(fpinfo->remote_conds, list_copy(fpinfo_o->remote_conds)); break; case JOIN_RIGHT: ! fpinfo->joinclauses = list_concat(fpinfo->joinclauses, list_copy(fpinfo_o->remote_conds)); ! fpinfo->remote_conds = list_concat(fpinfo->remote_conds, list_copy(fpinfo_i->remote_conds)); break; case JOIN_FULL: ! if (fpinfo_i->remote_conds || fpinfo_o->remote_conds) ! return false; break; default: --- 4117,4150 ---- switch (jointype) { case JOIN_INNER: ! if (!fpinfo_i->reltarget_has_non_vars) ! fpinfo->remote_conds = list_concat(fpinfo->remote_conds, list_copy(fpinfo_i->remote_conds)); ! if (!fpinfo_o->reltarget_has_non_vars) ! fpinfo->remote_conds = list_concat(fpinfo->remote_conds, list_copy(fpinfo_o->remote_conds)); break; case JOIN_LEFT: ! if (!fpinfo_i->reltarget_has_non_vars) ! fpinfo->joinclauses = list_concat(fpinfo->joinclauses, list_copy(fpinfo_i->remote_conds)); ! if (!fpinfo_o->reltarget_has_non_vars) ! fpinfo->remote_conds = list_concat(fpinfo->remote_conds, list_copy(fpinfo_o->remote_conds)); break; case JOIN_RIGHT: ! if (!fpinfo_o->reltarget_has_non_vars) ! fpinfo->joinclauses = list_concat(fpinfo->joinclauses, list_copy(fpinfo_o->remote_conds)); ! if (!fpinfo_i->reltarget_has_non_vars) ! fpinfo->remote_conds = list_concat(fpinfo->remote_conds, list_copy(fpinfo_i->remote_conds)); break; case JOIN_FULL: ! /* nothing to do */ break; default: *************** *** 4100,4119 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, elog(ERROR, "unsupported join type %d", jointype); } /* ! * For an inner join, all restrictions can be treated alike. Treating the ! * pushed down conditions as join conditions allows a top level full outer ! * join to be deparsed without requiring subqueries. */ ! if (jointype == JOIN_INNER) { ! Assert(!fpinfo->joinclauses); ! fpinfo->joinclauses = fpinfo->remote_conds; ! fpinfo->remote_conds = NIL; ! } ! /* Mark that this join can be pushed down safely */ ! fpinfo->pushdown_safe = true; /* * If user is willing to estimate cost for a scan of either of the joining --- 4152,4190 ---- elog(ERROR, "unsupported join type %d", jointype); } + /* Mark that this join can be pushed down safely */ + fpinfo->pushdown_safe = true; + /* ! * Detect whether the reltarget expressions are all shippable and whether ! * there are any non-Var expressions in the reltarget. */ ! foreach(lc, joinrel->reltarget->exprs) { ! Node *node = (Node *) lfirst(lc); ! Assert(IsA(node, Var) || IsA(node, PlaceHolderVar)); ! ! if (IsA(node, PlaceHolderVar)) ! { ! PlaceHolderVar *phv = (PlaceHolderVar *) node; ! PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, false); ! ! /* Ignore the PHV if it has bubbled up from an either input. */ ! if (bms_is_subset(phinfo->ph_eval_at, outerrel->relids) && ! bms_is_subset(phinfo->ph_eval_at, innerrel->relids)) ! continue; ! ! reltarget_has_non_vars = true; ! if (!is_foreign_expr(root, joinrel, (Expr *) node)) ! { ! reltarget_is_shippable = false; ! break; ! } ! } ! } ! fpinfo->reltarget_is_shippable = reltarget_is_shippable; ! fpinfo->reltarget_has_non_vars = reltarget_has_non_vars; /* * If user is willing to estimate cost for a scan of either of the joining *************** *** 4526,4531 **** conversion_error_callback(void *arg) --- 4597,4603 ---- const char *attname = NULL; const char *relname = NULL; bool is_wholerow = false; + bool is_placeholder = false; ConversionLocation *errpos = (ConversionLocation *) arg; if (errpos->rel) *************** *** 4550,4580 **** conversion_error_callback(void *arg) EState *estate = fsstate->ss.ps.state; TargetEntry *tle; Var *var; - RangeTblEntry *rte; Assert(IsA(fsplan, ForeignScan)); tle = (TargetEntry *) list_nth(fsplan->fdw_scan_tlist, errpos->cur_attno - 1); Assert(IsA(tle, TargetEntry)); var = (Var *) tle->expr; - Assert(IsA(var, Var)); - - rte = rt_fetch(var->varno, estate->es_range_table); ! if (var->varattno == 0) ! is_wholerow = true; else ! attname = get_relid_attribute_name(rte->relid, var->varattno); ! relname = get_rel_name(rte->relid); } ! if (relname) { if (is_wholerow) ! errcontext("whole-row reference to foreign table \"%s\"", relname); else if (attname) ! errcontext("column \"%s\" of foreign table \"%s\"", attname, relname); } } --- 4622,4663 ---- EState *estate = fsstate->ss.ps.state; TargetEntry *tle; Var *var; Assert(IsA(fsplan, ForeignScan)); tle = (TargetEntry *) list_nth(fsplan->fdw_scan_tlist, errpos->cur_attno - 1); Assert(IsA(tle, TargetEntry)); var = (Var *) tle->expr; ! if (!IsA(var, Var)) ! { ! Assert(IsA(var, PlaceHolderVar)); ! is_placeholder = true; ! } else ! { ! RangeTblEntry *rte = rt_fetch(var->varno, estate->es_range_table); ! ! if (var->varattno == 0) ! is_wholerow = true; ! else ! attname = get_relid_attribute_name(rte->relid, var->varattno); ! relname = get_rel_name(rte->relid); ! } } ! if (is_placeholder) ! errcontext("expression at position %d in select list", ! errpos->cur_attno); ! else if (relname) { if (is_wholerow) ! errcontext("whole-row reference to foreign table \"%s\"", ! relname); else if (attname) ! errcontext("column \"%s\" of foreign table \"%s\"", ! attname, relname); } } *** a/contrib/postgres_fdw/postgres_fdw.h --- b/contrib/postgres_fdw/postgres_fdw.h *************** *** 92,97 **** typedef struct PgFdwRelationInfo --- 92,106 ---- RelOptInfo *innerrel; JoinType jointype; List *joinclauses; + + /* + * Flags on Path output tlist for the relation. These are used to detect + * whether the relation can be joined with any other foreign table (or + * join) on the remote server, and detect whether the relation has to be + * deparsed as a subquery in that case. + */ + bool reltarget_is_shippable; /* are reltarget exprs shippable? */ + bool reltarget_has_non_vars; /* are there any non-Var exprs? */ } PgFdwRelationInfo; /* in postgres_fdw.c */ *************** *** 155,161 **** 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, --- 164,170 ---- 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(PlannerInfo *root, RelOptInfo *foreignrel); extern void deparseSelectStmtForRel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, List *tlist, List *remote_conds, List *pathkeys, *** a/contrib/postgres_fdw/sql/postgres_fdw.sql --- b/contrib/postgres_fdw/sql/postgres_fdw.sql *************** *** 493,509 **** EXPLAIN (VERBOSE, COSTS OFF) SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; ! -- non-Var items in targelist of the nullable rel of a join preventing ! -- push-down in some cases ! -- unable to push {ft1, ft2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; ! ! -- ok to push {ft1, ft2} but not {ft1, ft2, ft4} EXPLAIN (VERBOSE, COSTS OFF) SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; -- join with nullable side with some columns with null values UPDATE ft5 SET c3 = null where c1 % 9 = 0; --- 493,511 ---- SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; SELECT t1."C 1" FROM "S 1"."T 1" t1, LATERAL (SELECT DISTINCT t2.c1, t3.c1 FROM ft1 t2, ft2 t3 WHERE t2.c1 = t3.c1 AND t2.c2 = t1.c2) q ORDER BY t1."C 1" OFFSET 10 LIMIT 10; ! -- check join pushdown in situations where PHVs are involved EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; SELECT q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15; ! EXPLAIN (VERBOSE, COSTS OFF) ! SELECT ft2.c1, q2.* FROM ft2 LEFT JOIN (SELECT 13, q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15) q2(a2, b2, c2) ON (ft2.c1 = q2.a2) WHERE ft2.c1 BETWEEN 10 AND 15; ! SELECT ft2.c1, q2.* FROM ft2 LEFT JOIN (SELECT 13, q.a, ft2.c1 FROM (SELECT 13 FROM ft1 WHERE c1 = 13) q(a) RIGHT JOIN ft2 ON (q.a = ft2.c1) WHERE ft2.c1 BETWEEN 10 AND 15) q2(a2, b2, c2) ON (ft2.c1 = q2.a2) WHERE ft2.c1 BETWEEN 10 AND 15; EXPLAIN (VERBOSE, COSTS OFF) SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; SELECT ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15; + EXPLAIN (VERBOSE, COSTS OFF) + SELECT ft4.c1, q2.* FROM ft4 LEFT JOIN (SELECT (q.b IS NOT NULL), ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15) q2(a2, b2, c2, d2, e2) ON (ft4.c1 = q2.b2) WHERE ft4.c1 BETWEEN 10 AND 15; + SELECT ft4.c1, q2.* FROM ft4 LEFT JOIN (SELECT (q.b IS NOT NULL), ft4.c1, q.* FROM ft4 LEFT JOIN (SELECT 13, ft1.c1, ft2.c1 FROM ft1 RIGHT JOIN ft2 ON (ft1.c1 = ft2.c1) WHERE ft1.c1 = 12) q(a, b, c) ON (ft4.c1 = q.b) WHERE ft4.c1 BETWEEN 10 AND 15) q2(a2, b2, c2, d2, e2) ON (ft4.c1 = q2.b2) WHERE ft4.c1 BETWEEN 10 AND 15; -- join with nullable side with some columns with null values UPDATE ft5 SET c3 = null where c1 % 9 = 0;