*** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** *** 8215,8222 **** ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false'); ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false'); INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i; INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i; ! CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (0) TO (250) SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true'); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true'); ANALYZE fprt2; --- 8215,8223 ---- ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false'); INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i; INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i; ! CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int) SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true'); + ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true'); ANALYZE fprt2; *************** *** 8269,8294 **** SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) -- with whole-row reference EXPLAIN (COSTS OFF) ! SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2; ! QUERY PLAN ! --------------------------------------------------------------------------------- Sort Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2) -> Append -> Foreign Scan ! Relations: (public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2) -> Foreign Scan ! Relations: (public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2) (7 rows) ! SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2; ! t1 | t2 ----------------+---------------- (0,0,0000) | (0,0,0000) (150,150,0003) | (150,150,0003) (250,250,0005) | (250,250,0005) (400,400,0008) | (400,400,0008) ! (4 rows) -- join with lateral reference EXPLAIN (COSTS OFF) --- 8270,8305 ---- -- with whole-row reference EXPLAIN (COSTS OFF) ! SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; ! QUERY PLAN ! -------------------------------------------------------------------------------- Sort Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2) -> Append -> Foreign Scan ! Relations: (public.ftprt1_p1 t1) FULL JOIN (public.ftprt2_p1 t2) -> Foreign Scan ! Relations: (public.ftprt1_p2 t1) FULL JOIN (public.ftprt2_p2 t2) (7 rows) ! SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; ! wr | wr ----------------+---------------- (0,0,0000) | (0,0,0000) + (50,50,0001) | + (100,100,0002) | (150,150,0003) | (150,150,0003) + (200,200,0004) | (250,250,0005) | (250,250,0005) + (300,300,0006) | + (350,350,0007) | (400,400,0008) | (400,400,0008) ! (450,450,0009) | ! | (75,75,0001) ! | (225,225,0004) ! | (325,325,0006) ! | (475,475,0009) ! (14 rows) -- join with lateral reference EXPLAIN (COSTS OFF) *************** *** 8352,8357 **** SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE --- 8363,8398 ---- | | 475 | t2_phv (14 rows) + -- test FOR UPDATE + EXPLAIN (COSTS OFF) + SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + QUERY PLAN + --------------------------------------------------------------------------------------- + LockRows + -> Sort + Sort Key: t1.a + -> Append + -> Foreign Scan + Relations: (public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2) + -> Nested Loop + -> Foreign Scan on ftprt1_p1 t1 + -> Foreign Scan on ftprt2_p1 t2 + -> Foreign Scan + Relations: (public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2) + -> Nested Loop + -> Foreign Scan on ftprt1_p2 t1_1 + -> Foreign Scan on ftprt2_p2 t2_1 + (14 rows) + + SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + a | b + -----+----- + 0 | 0 + 150 | 150 + 250 | 250 + 400 | 400 + (4 rows) + RESET enable_partitionwise_join; -- =================================================================== -- test partitionwise aggregates *** a/contrib/postgres_fdw/sql/postgres_fdw.sql --- b/contrib/postgres_fdw/sql/postgres_fdw.sql *************** *** 2216,2223 **** ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false'); ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false'); INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i; INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i; ! CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (0) TO (250) SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true'); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true'); ANALYZE fprt2; --- 2216,2224 ---- ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false'); INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i; INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i; ! CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int) SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true'); + ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500) SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true'); ANALYZE fprt2; *************** *** 2236,2243 **** SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10) -- with whole-row reference EXPLAIN (COSTS OFF) ! SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2; ! SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2; -- join with lateral reference EXPLAIN (COSTS OFF) --- 2237,2244 ---- -- with whole-row reference EXPLAIN (COSTS OFF) ! SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; ! SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2; -- join with lateral reference EXPLAIN (COSTS OFF) *************** *** 2249,2254 **** EXPLAIN (COSTS OFF) --- 2250,2260 ---- SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b; + -- test FOR UPDATE + EXPLAIN (COSTS OFF) + SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1; + RESET enable_partitionwise_join; *** a/src/backend/optimizer/path/allpaths.c --- b/src/backend/optimizer/path/allpaths.c *************** *** 956,1027 **** set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); ! if (rel->part_scheme) { - AttrNumber attno; - /* ! * We need attr_needed data for building targetlist of a join ! * relation representing join between matching partitions for ! * partitionwise join. A given attribute of a child will be needed ! * in the same highest joinrel where the corresponding attribute ! * of parent is needed. Hence it suffices to use the same Relids ! * set for parent and child. */ ! for (attno = rel->min_attr; attno <= rel->max_attr; attno++) ! { ! int index = attno - rel->min_attr; ! Relids attr_needed = rel->attr_needed[index]; ! ! /* System attributes do not need translation. */ ! if (attno <= 0) ! { ! Assert(rel->min_attr == childrel->min_attr); ! childrel->attr_needed[index] = attr_needed; ! } ! else ! { ! Var *var = list_nth_node(Var, ! appinfo->translated_vars, ! attno - 1); ! int child_index; ! ! /* ! * Ignore any column dropped from the parent. ! * Corresponding Var won't have any translation. It won't ! * have attr_needed information, since it can not be ! * referenced in the query. ! */ ! if (var == NULL) ! { ! Assert(attr_needed == NULL); ! continue; ! } ! ! child_index = var->varattno - childrel->min_attr; ! childrel->attr_needed[child_index] = attr_needed; ! } ! } } /* - * Copy/Modify targetlist. Even if this child is deemed empty, we need - * its targetlist in case it falls on nullable side in a child-join - * because of partitionwise join. - * - * NB: the resulting childrel->reltarget->exprs may contain arbitrary - * expressions, which otherwise would not occur in a rel's targetlist. - * Code that might be looking at an appendrel child must cope with - * such. (Normally, a rel's targetlist would only include Vars and - * PlaceHolderVars.) XXX we do not bother to update the cost or width - * fields of childrel->reltarget; not clear if that would be useful. - */ - childrel->reltarget->exprs = (List *) - adjust_appendrel_attrs(root, - (Node *) rel->reltarget->exprs, - 1, &appinfo); - - /* * We have to make child entries in the EquivalenceClass data * structures as well. This is needed either if the parent * participates in some eclass joins (because we will want to consider --- 956,996 ---- childrel = find_base_rel(root, childRTindex); Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL); ! /* ! * We have to copy the parent's targetlist to the child, with ! * appropriate substitution of variables. ! * ! * We need special handling for partitioned inheritance cases if ! * partitionwise join technique is enabled: if the entry in the parent ! * rel's targetlist is a whole-row Var for the parent rel, map it into ! * a whole-row Var for the child rel (we convert the tuple layout back ! * to the parent's rowtype at plan creation time); otherwise, map ! * expressions in the targetlist with adjust_appendrel_attrs. Note: ! * even if the child is deemed empty, we need the targetlist for it in ! * case it falls on nullable side in a child-join. ! */ ! if (enable_partitionwise_join && rel->top_parent_is_partitioned) ! { ! build_childrel_tlist(root, rel, childrel, 1, &appinfo); ! } ! else { /* ! * NB: the resulting childrel->reltarget->exprs may contain ! * arbitrary expressions, which otherwise would not occur in a ! * rel's targetlist. Code that might be looking at an appendrel ! * child must cope with such. (Normally, a rel's targetlist would ! * only include Vars and PlaceHolderVars.) XXX we do not bother ! * to update the cost or width fields of childrel->reltarget; not ! * clear if that would be useful. */ ! childrel->reltarget->exprs = (List *) ! adjust_appendrel_attrs(root, ! (Node *) rel->reltarget->exprs, ! 1, &appinfo); } /* * We have to make child entries in the EquivalenceClass data * structures as well. This is needed either if the parent * participates in some eclass joins (because we will want to consider *************** *** 1181,1186 **** set_append_rel_size(PlannerInfo *root, RelOptInfo *rel, --- 1150,1163 ---- 1, &appinfo); /* + * Note: we could compute appropriate attr_needed data for the child's + * variables, by transforming the parent's attr_needed through the + * translated_vars mapping. However, currently there's no need + * because attr_needed is only examined for base relations not + * otherrels. So we just leave the child's attr_needed empty. + */ + + /* * If parallelism is allowable for this query in general, see whether * it's allowable for this childrel in particular. But if we've * already decided the appendrel is not parallel-safe as a whole, *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 29,34 **** --- 29,35 ---- #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" + #include "optimizer/pathnode.h" #include "optimizer/paths.h" #include "optimizer/placeholder.h" #include "optimizer/plancat.h" *************** *** 171,176 **** static void copy_generic_path_info(Plan *dest, Path *src); --- 172,178 ---- static void copy_plan_costsize(Plan *dest, Plan *src); static void label_sort_with_costsize(PlannerInfo *root, Sort *plan, double limit_tuples); + static void adjust_subplan_tlist(PlannerInfo *root, RelOptInfo *rel, Plan *subplan); static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid); static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid, TableSampleClause *tsc); *************** *** 1074,1079 **** create_append_plan(PlannerInfo *root, AppendPath *best_path) --- 1076,1084 ---- /* Must insist that all children return the same tlist */ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST); + /* Adjust the plan's targetlist (if necessary) */ + adjust_subplan_tlist(root, subpath->parent, subplan); + subplans = lappend(subplans, subplan); } *************** *** 1185,1190 **** create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path) --- 1190,1198 ---- /* Must insist that all children return the same tlist */ subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST); + /* Adjust the plan's targetlist (if necessary) */ + adjust_subplan_tlist(root, subpath->parent, subplan); + /* Compute sort column info, and adjust subplan's tlist as needed */ subplan = prepare_sort_from_pathkeys(subplan, pathkeys, subpath->parent->relids, *************** *** 5074,5079 **** bitmap_subplan_mark_shared(Plan *plan) --- 5082,5144 ---- elog(ERROR, "unrecognized node type: %d", nodeTag(plan)); } + /* + * adjust_subplan_tlist + * If the child plan's targetlist has a whole-row Var as an entry of the + * list, add a ConvertRowtypeExpr above the Var to convert the tuple layout + * back to the top parent's rowtype. + */ + static void + adjust_subplan_tlist(PlannerInfo *root, RelOptInfo *rel, Plan *subplan) + { + List *newtlist; + ListCell *l; + + /* No work if the given rel's tlist doesn't have whole-row Vars */ + if (!rel->has_child_wholerow) + return; + + /* The given rel should be a scan or join relation */ + Assert(rel->reloptkind == RELOPT_OTHER_MEMBER_REL || + rel->reloptkind == RELOPT_OTHER_JOINREL); + + /* No work if the child plan is an Append or MergeAppend */ + if (IsA(subplan, Append) || IsA(subplan, MergeAppend)) + return; + + /* The child plan should be able to do projection */ + Assert(is_projection_capable_plan(subplan)); + + newtlist = NIL; + foreach(l, subplan->targetlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + Var *var = (Var *) tle->expr; + + if (IsA(var, Var) && var->varattno == 0) + { + RelOptInfo *partrel = find_base_rel(root, var->varno); + ConvertRowtypeExpr *cre = makeNode(ConvertRowtypeExpr); + + /* The rel should be a partition */ + Assert(partrel->reloptkind == RELOPT_OTHER_MEMBER_REL); + + /* The top parent rel should be a partitioned table */ + Assert(partrel->top_parent_is_partitioned); + + cre->arg = (Expr *) var; + cre->resulttype = partrel->top_parent_rowtype; + cre->convertformat = COERCE_IMPLICIT_CAST; + cre->location = -1; + + tle = flatCopyTargetEntry(tle); + tle->expr = (Expr *) cre; + } + newtlist = lappend(newtlist, tle); + } + subplan->targetlist = newtlist; + } + /***************************************************************************** * * PLAN NODE BUILDING ROUTINES *** a/src/backend/optimizer/plan/setrefs.c --- b/src/backend/optimizer/plan/setrefs.c *************** *** 41,49 **** typedef struct int num_vars; /* number of plain Var tlist entries */ bool has_ph_vars; /* are there PlaceHolderVar entries? */ bool has_non_vars; /* are there other entries? */ - bool has_conv_whole_rows; /* are there ConvertRowtypeExpr - * entries encapsulating a whole-row - * Var? */ tlist_vinfo vars[FLEXIBLE_ARRAY_MEMBER]; /* has num_vars entries */ } indexed_tlist; --- 41,46 ---- *************** *** 143,149 **** static List *set_returning_clause_references(PlannerInfo *root, int rtoffset); static bool extract_query_dependencies_walker(Node *node, PlannerInfo *context); - static bool is_converted_whole_row_reference(Node *node); /***************************************************************************** * --- 140,145 ---- *************** *** 1997,2003 **** build_tlist_index(List *tlist) itlist->tlist = tlist; itlist->has_ph_vars = false; itlist->has_non_vars = false; - itlist->has_conv_whole_rows = false; /* Find the Vars and fill in the index array */ vinfo = itlist->vars; --- 1993,1998 ---- *************** *** 2016,2023 **** build_tlist_index(List *tlist) } else if (tle->expr && IsA(tle->expr, PlaceHolderVar)) itlist->has_ph_vars = true; - else if (is_converted_whole_row_reference((Node *) tle->expr)) - itlist->has_conv_whole_rows = true; else itlist->has_non_vars = true; } --- 2011,2016 ---- *************** *** 2033,2042 **** build_tlist_index(List *tlist) * This is like build_tlist_index, but we only index tlist entries that * are Vars belonging to some rel other than the one specified. We will set * has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars ! * (so nothing other than Vars and PlaceHolderVars can be matched). In case of ! * DML, where this function will be used, returning lists from child relations ! * will be appended similar to a simple append relation. That does not require ! * fixing ConvertRowtypeExpr references. So, those are not considered here. */ static indexed_tlist * build_tlist_index_other_vars(List *tlist, Index ignore_rel) --- 2026,2032 ---- * This is like build_tlist_index, but we only index tlist entries that * are Vars belonging to some rel other than the one specified. We will set * has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars ! * (so nothing other than Vars and PlaceHolderVars can be matched). */ static indexed_tlist * build_tlist_index_other_vars(List *tlist, Index ignore_rel) *************** *** 2053,2059 **** build_tlist_index_other_vars(List *tlist, Index ignore_rel) itlist->tlist = tlist; itlist->has_ph_vars = false; itlist->has_non_vars = false; - itlist->has_conv_whole_rows = false; /* Find the desired Vars and fill in the index array */ vinfo = itlist->vars; --- 2043,2048 ---- *************** *** 2256,2262 **** static Node * fix_join_expr_mutator(Node *node, fix_join_expr_context *context) { Var *newvar; - bool converted_whole_row; if (node == NULL) return NULL; --- 2245,2250 ---- *************** *** 2326,2337 **** fix_join_expr_mutator(Node *node, fix_join_expr_context *context) } if (IsA(node, Param)) return fix_param_node(context->root, (Param *) node); - /* Try matching more complex expressions too, if tlists have any */ ! converted_whole_row = is_converted_whole_row_reference(node); ! if (context->outer_itlist && ! (context->outer_itlist->has_non_vars || ! (context->outer_itlist->has_conv_whole_rows && converted_whole_row))) { newvar = search_indexed_tlist_for_non_var((Expr *) node, context->outer_itlist, --- 2314,2321 ---- } if (IsA(node, Param)) return fix_param_node(context->root, (Param *) node); /* Try matching more complex expressions too, if tlists have any */ ! if (context->outer_itlist && context->outer_itlist->has_non_vars) { newvar = search_indexed_tlist_for_non_var((Expr *) node, context->outer_itlist, *************** *** 2339,2347 **** fix_join_expr_mutator(Node *node, fix_join_expr_context *context) if (newvar) return (Node *) newvar; } ! if (context->inner_itlist && ! (context->inner_itlist->has_non_vars || ! (context->inner_itlist->has_conv_whole_rows && converted_whole_row))) { newvar = search_indexed_tlist_for_non_var((Expr *) node, context->inner_itlist, --- 2323,2329 ---- if (newvar) return (Node *) newvar; } ! if (context->inner_itlist && context->inner_itlist->has_non_vars) { newvar = search_indexed_tlist_for_non_var((Expr *) node, context->inner_itlist, *************** *** 2461,2469 **** fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context) /* If no match, just fall through to process it normally */ } /* Try matching more complex expressions too, if tlist has any */ ! if (context->subplan_itlist->has_non_vars || ! (context->subplan_itlist->has_conv_whole_rows && ! is_converted_whole_row_reference(node))) { newvar = search_indexed_tlist_for_non_var((Expr *) node, context->subplan_itlist, --- 2443,2449 ---- /* If no match, just fall through to process it normally */ } /* Try matching more complex expressions too, if tlist has any */ ! if (context->subplan_itlist->has_non_vars) { newvar = search_indexed_tlist_for_non_var((Expr *) node, context->subplan_itlist, *************** *** 2670,2702 **** extract_query_dependencies_walker(Node *node, PlannerInfo *context) return expression_tree_walker(node, extract_query_dependencies_walker, (void *) context); } - - /* - * is_converted_whole_row_reference - * If the given node is a ConvertRowtypeExpr encapsulating a whole-row - * reference as implicit cast, return true. Otherwise return false. - */ - static bool - is_converted_whole_row_reference(Node *node) - { - ConvertRowtypeExpr *convexpr; - - if (!node || !IsA(node, ConvertRowtypeExpr)) - return false; - - /* Traverse nested ConvertRowtypeExpr's. */ - convexpr = castNode(ConvertRowtypeExpr, node); - while (convexpr->convertformat == COERCE_IMPLICIT_CAST && - IsA(convexpr->arg, ConvertRowtypeExpr)) - convexpr = castNode(ConvertRowtypeExpr, convexpr->arg); - - if (IsA(convexpr->arg, Var)) - { - Var *var = castNode(Var, convexpr->arg); - - if (var->varattno == 0) - return true; - } - - return false; - } --- 2650,2652 ---- *** a/src/backend/optimizer/util/placeholder.c --- b/src/backend/optimizer/util/placeholder.c *************** *** 20,26 **** #include "optimizer/pathnode.h" #include "optimizer/placeholder.h" #include "optimizer/planmain.h" - #include "optimizer/prep.h" #include "optimizer/var.h" #include "utils/lsyscache.h" --- 20,25 ---- *************** *** 415,424 **** add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel, Relids relids = joinrel->relids; ListCell *lc; - /* This function is called only on the parent relations. */ - Assert(!IS_OTHER_REL(joinrel) && !IS_OTHER_REL(outer_rel) && - !IS_OTHER_REL(inner_rel)); - foreach(lc, root->placeholder_list) { PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc); --- 414,419 ---- *************** *** 464,519 **** add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel, } } } - - /* - * add_placeholders_to_child_joinrel - * Translate the PHVs in parent's targetlist and add them to the child's - * targetlist. Also adjust the cost - */ - void - add_placeholders_to_child_joinrel(PlannerInfo *root, RelOptInfo *childrel, - RelOptInfo *parentrel) - { - ListCell *lc; - AppendRelInfo **appinfos; - int nappinfos; - - Assert(IS_JOIN_REL(childrel) && IS_JOIN_REL(parentrel)); - Assert(IS_OTHER_REL(childrel)); - - /* Nothing to do if no PHVs. */ - if (root->placeholder_list == NIL) - return; - - appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos); - foreach(lc, parentrel->reltarget->exprs) - { - PlaceHolderVar *phv = lfirst(lc); - - if (IsA(phv, PlaceHolderVar)) - { - /* - * In case the placeholder Var refers to any of the parent - * relations, translate it to refer to the corresponding child. - */ - if (bms_overlap(phv->phrels, parentrel->relids) && - childrel->reloptkind == RELOPT_OTHER_JOINREL) - { - phv = (PlaceHolderVar *) adjust_appendrel_attrs(root, - (Node *) phv, - nappinfos, - appinfos); - } - - childrel->reltarget->exprs = lappend(childrel->reltarget->exprs, - phv); - } - } - - /* Adjust the cost and width of child targetlist. */ - childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup; - childrel->reltarget->cost.per_tuple = parentrel->reltarget->cost.per_tuple; - childrel->reltarget->width = parentrel->reltarget->width; - - pfree(appinfos); - } --- 459,461 ---- *** a/src/backend/optimizer/util/relnode.c --- b/src/backend/optimizer/util/relnode.c *************** *** 16,21 **** --- 16,22 ---- #include + #include "catalog/pg_class.h" #include "miscadmin.h" #include "optimizer/clauses.h" #include "optimizer/cost.h" *************** *** 28,33 **** --- 29,35 ---- #include "optimizer/tlist.h" #include "partitioning/partbounds.h" #include "utils/hsearch.h" + #include "utils/lsyscache.h" typedef struct JoinHashEntry *************** *** 57,62 **** static void add_join_rel(PlannerInfo *root, RelOptInfo *joinrel); --- 59,69 ---- static void build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, RelOptInfo *inner_rel, List *restrictlist, JoinType jointype); + static void build_child_join_reltarget(PlannerInfo *root, + RelOptInfo *parentrel, + RelOptInfo *childrel, + int nappinfos, + AppendRelInfo **appinfos); /* *************** *** 151,156 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) --- 158,166 ---- rel->baserestrict_min_security = UINT_MAX; rel->joininfo = NIL; rel->has_eclass_joins = false; + rel->top_parent_is_partitioned = false; + rel->top_parent_rowtype = InvalidOid; + rel->has_child_wholerow = false; rel->part_scheme = NULL; rel->nparts = 0; rel->boundinfo = NULL; *************** *** 165,170 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) --- 175,183 ---- * has top_parent_relids set, it's a direct or an indirect child of the * top parent indicated by top_parent_relids. By extension this child is * also an indirect child of that parent. + * + * Also pass the flag to indicate whether the top parent is a partitioned + * table and the OID of the top parent rowtype down the hierarchy. */ if (parent) { *************** *** 172,181 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent) --- 185,205 ---- rel->top_parent_relids = parent->top_parent_relids; else rel->top_parent_relids = bms_copy(parent->relids); + + rel->top_parent_is_partitioned = parent->top_parent_is_partitioned; + rel->top_parent_rowtype = parent->top_parent_rowtype; } else + { rel->top_parent_relids = NULL; + if (rte->relkind == RELKIND_PARTITIONED_TABLE) + { + rel->top_parent_is_partitioned = true; + rel->top_parent_rowtype = get_rel_type_id(rte->relid); + } + } + /* Check type of rtable entry */ switch (rte->rtekind) { *************** *** 565,571 **** build_join_rel(PlannerInfo *root, --- 589,598 ---- joinrel->baserestrict_min_security = UINT_MAX; joinrel->joininfo = NIL; joinrel->has_eclass_joins = false; + joinrel->top_parent_is_partitioned = false; + joinrel->top_parent_rowtype = InvalidOid; joinrel->top_parent_relids = NULL; + joinrel->has_child_wholerow = false; joinrel->part_scheme = NULL; joinrel->nparts = 0; joinrel->boundinfo = NULL; *************** *** 736,742 **** build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, --- 763,772 ---- joinrel->baserestrictcost.per_tuple = 0; joinrel->joininfo = NIL; joinrel->has_eclass_joins = false; + joinrel->top_parent_is_partitioned = false; + joinrel->top_parent_rowtype = InvalidOid; joinrel->top_parent_relids = NULL; + joinrel->has_child_wholerow = false; joinrel->part_scheme = NULL; joinrel->nparts = 0; joinrel->boundinfo = NULL; *************** *** 752,765 **** build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, /* Compute information relevant to foreign relations. */ set_foreign_rel_properties(joinrel, outer_rel, inner_rel); ! /* Build targetlist */ ! build_joinrel_tlist(root, joinrel, outer_rel); ! build_joinrel_tlist(root, joinrel, inner_rel); ! /* Add placeholder variables. */ ! add_placeholders_to_child_joinrel(root, joinrel, parent_joinrel); /* Construct joininfo list. */ - appinfos = find_appinfos_by_relids(root, joinrel->relids, &nappinfos); joinrel->joininfo = (List *) adjust_appendrel_attrs(root, (Node *) parent_joinrel->joininfo, nappinfos, --- 782,794 ---- /* Compute information relevant to foreign relations. */ set_foreign_rel_properties(joinrel, outer_rel, inner_rel); ! appinfos = find_appinfos_by_relids(root, joinrel->relids, &nappinfos); ! ! /* Set up reltarget struct */ ! build_child_join_reltarget(root, parent_joinrel, joinrel, ! nappinfos, appinfos); /* Construct joininfo list. */ joinrel->joininfo = (List *) adjust_appendrel_attrs(root, (Node *) parent_joinrel->joininfo, nappinfos, *************** *** 858,872 **** static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *input_rel) { ! Relids relids; ListCell *vars; - /* attrs_needed refers to parent relids and not those of a child. */ - if (joinrel->top_parent_relids) - relids = joinrel->top_parent_relids; - else - relids = joinrel->relids; - foreach(vars, input_rel->reltarget->exprs) { Var *var = (Var *) lfirst(vars); --- 887,895 ---- build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *input_rel) { ! Relids relids = joinrel->relids; ListCell *vars; foreach(vars, input_rel->reltarget->exprs) { Var *var = (Var *) lfirst(vars); *************** *** 882,935 **** build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel, /* * Otherwise, anything in a baserel or joinrel targetlist ought to be ! * a Var. Children of a partitioned table may have ConvertRowtypeExpr ! * translating whole-row Var of a child to that of the parent. ! * Children of an inherited table or subquery child rels can not ! * directly participate in a join, so other kinds of nodes here. */ ! if (IsA(var, Var)) ! { ! baserel = find_base_rel(root, var->varno); ! ndx = var->varattno - baserel->min_attr; ! } ! else if (IsA(var, ConvertRowtypeExpr)) ! { ! ConvertRowtypeExpr *child_expr = (ConvertRowtypeExpr *) var; ! Var *childvar = (Var *) child_expr->arg; ! ! /* ! * Child's whole-row references are converted to look like those ! * of parent using ConvertRowtypeExpr. There can be as many ! * ConvertRowtypeExpr decorations as the depth of partition tree. ! * The argument to the deepest ConvertRowtypeExpr is expected to ! * be a whole-row reference of the child. ! */ ! while (IsA(childvar, ConvertRowtypeExpr)) ! { ! child_expr = (ConvertRowtypeExpr *) childvar; ! childvar = (Var *) child_expr->arg; ! } ! Assert(IsA(childvar, Var) &&childvar->varattno == 0); ! ! baserel = find_base_rel(root, childvar->varno); ! ndx = 0 - baserel->min_attr; ! } ! else elog(ERROR, "unexpected node type in rel targetlist: %d", (int) nodeTag(var)); ! /* Is the target expression still needed above this joinrel? */ if (bms_nonempty_difference(baserel->attr_needed[ndx], relids)) { /* Yup, add it to the output */ joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var); ! ! /* ! * Vars have cost zero, so no need to adjust reltarget->cost. Even ! * if it's a ConvertRowtypeExpr, it will be computed only for the ! * base relation, costing nothing for a join. ! */ joinrel->reltarget->width += baserel->attr_widths[ndx]; } } --- 905,927 ---- /* * Otherwise, anything in a baserel or joinrel targetlist ought to be ! * a Var. (More general cases can only appear in appendrel child ! * rels, which will never be seen here.) */ ! if (!IsA(var, Var)) elog(ERROR, "unexpected node type in rel targetlist: %d", (int) nodeTag(var)); + /* Get the Var's original base rel */ + baserel = find_base_rel(root, var->varno); ! /* Is it still needed above this joinrel? */ ! ndx = var->varattno - baserel->min_attr; if (bms_nonempty_difference(baserel->attr_needed[ndx], relids)) { /* Yup, add it to the output */ joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var); ! /* Vars have cost zero, so no need to adjust reltarget->cost */ joinrel->reltarget->width += baserel->attr_widths[ndx]; } } *************** *** 1178,1183 **** fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids) --- 1170,1177 ---- upperrel->cheapest_unique_path = NULL; upperrel->cheapest_parameterized_paths = NIL; + upperrel->has_child_wholerow = false; + root->upper_rels[kind] = lappend(root->upper_rels[kind], upperrel); return upperrel; *************** *** 1760,1762 **** build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel, --- 1754,1834 ---- joinrel->nullable_partexprs[cnt] = nullable_partexpr; } } + + /* + * build_childrel_tlist + * Build a child relation's targetlist from a parent relation. + */ + void + build_childrel_tlist(PlannerInfo *root, + RelOptInfo *parentrel, + RelOptInfo *childrel, + int nappinfos, + AppendRelInfo **appinfos) + { + ListCell *lc; + + foreach(lc, parentrel->reltarget->exprs) + { + Node *node = (Node *) lfirst(lc); + + /* Should be a simple Var or PlaceHolderVar ... */ + Assert(IsA(node, Var) || IsA(node, PlaceHolderVar)); + + if (IsA(node, Var) && ((Var *) node)->varattno == 0) + { + Var *var = (Var *) copyObject(node); + AppendRelInfo *appinfo = NULL; + int cnt; + + /* Find the AppendRelInfo associated with the parent */ + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (var->varno == appinfos[cnt]->parent_relid) + { + appinfo = appinfos[cnt]; + break; + } + } + + /* Should have found the entry ... */ + Assert(appinfo); + + var->varno = appinfo->child_relid; + var->varnoold = appinfo->child_relid; + var->vartype = appinfo->child_reltype; + + childrel->reltarget->exprs = + lappend(childrel->reltarget->exprs, var); + childrel->has_child_wholerow = true; + } + else + { + childrel->reltarget->exprs = + lappend(childrel->reltarget->exprs, + adjust_appendrel_attrs(root, node, + nappinfos, + appinfos)); + } + } + } + + /* + * build_child_join_reltarget + * Set up a child-join relation's reltarget from a parent-join relation. + */ + static void + build_child_join_reltarget(PlannerInfo *root, + RelOptInfo *parentrel, + RelOptInfo *childrel, + int nappinfos, + AppendRelInfo **appinfos) + { + /* Build the targetlist */ + build_childrel_tlist(root, parentrel, childrel, nappinfos, appinfos); + + /* Set the cost and width fields */ + childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup; + childrel->reltarget->cost.per_tuple = parentrel->reltarget->cost.per_tuple; + childrel->reltarget->width = parentrel->reltarget->width; + } *** a/src/include/nodes/relation.h --- b/src/include/nodes/relation.h *************** *** 678,684 **** typedef struct RelOptInfo --- 678,689 ---- bool has_eclass_joins; /* T means joininfo is incomplete */ /* used by "other" relations */ + bool top_parent_is_partitioned; /* is topmost parent a partitioned + * table? */ + Oid top_parent_rowtype; /* OID of topmost parent's rowtype */ Relids top_parent_relids; /* Relids of topmost parents */ + bool has_child_wholerow; /* does reltarget->exprs have child + * whole-row Vars? */ /* used for partitioned relations */ PartitionScheme part_scheme; /* Partitioning scheme. */ *** a/src/include/optimizer/pathnode.h --- b/src/include/optimizer/pathnode.h *************** *** 299,303 **** extern RelOptInfo *build_child_join_rel(PlannerInfo *root, --- 299,308 ---- RelOptInfo *outer_rel, RelOptInfo *inner_rel, RelOptInfo *parent_joinrel, List *restrictlist, SpecialJoinInfo *sjinfo, JoinType jointype); + extern void build_childrel_tlist(PlannerInfo *root, + RelOptInfo *parentrel, + RelOptInfo *childrel, + int nappinfos, + AppendRelInfo **appinfos); #endif /* PATHNODE_H */ *** a/src/test/regress/expected/partition_join.out --- b/src/test/regress/expected/partition_join.out *************** *** 1042,1047 **** SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * --- 1042,1090 ---- 400 | (9 rows) + -- merge join when expression with whole-row reference needs to be sorted + EXPLAIN (COSTS OFF) + SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; + QUERY PLAN + ----------------------------------------------------------------------------------- + Merge Append + Sort Key: t1.a + -> Merge Join + Merge Cond: ((t1.a = t2.b) AND (((t1.*)::text) = ((t2.*)::text))) + -> Sort + Sort Key: t1.a, ((t1.*)::text) + -> Seq Scan on prt1_p1 t1 + -> Sort + Sort Key: t2.b, ((t2.*)::text) + -> Seq Scan on prt2_p1 t2 + -> Merge Join + Merge Cond: ((t1_1.a = t2_1.b) AND (((t1_1.*)::text) = ((t2_1.*)::text))) + -> Sort + Sort Key: t1_1.a, ((t1_1.*)::text) + -> Seq Scan on prt1_p2 t1_1 + -> Sort + Sort Key: t2_1.b, ((t2_1.*)::text) + -> Seq Scan on prt2_p2 t2_1 + -> Merge Join + Merge Cond: ((t1_2.a = t2_2.b) AND (((t1_2.*)::text) = ((t2_2.*)::text))) + -> Sort + Sort Key: t1_2.a, ((t1_2.*)::text) + -> Seq Scan on prt1_p3 t1_2 + -> Sort + Sort Key: t2_2.b, ((t2_2.*)::text) + -> Seq Scan on prt2_p3 t2_2 + (26 rows) + + SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; + a | b + ----+---- + 0 | 0 + 6 | 6 + 12 | 12 + 18 | 18 + 24 | 24 + (5 rows) + RESET enable_hashjoin; RESET enable_nestloop; -- *** a/src/test/regress/sql/partition_join.sql --- b/src/test/regress/sql/partition_join.sql *************** *** 160,165 **** EXPLAIN (COSTS OFF) --- 160,170 ---- SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b; + -- merge join when expression with whole-row reference needs to be sorted + EXPLAIN (COSTS OFF) + SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; + SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a; + RESET enable_hashjoin; RESET enable_nestloop;