From: Robert Haas Date: Mon, 9 Jun 2025 19:10:52 +0000 (-0400) Subject: output something for GATHER and GATHER_MERGE X-Git-Url: http://git.postgresql.org/gitweb/static/gitweb.js?a=commitdiff_plain;h=23a95d787153db54fb3c7333209bce7214315c11;p=users%2Frhaas%2Fpostgres.git output something for GATHER and GATHER_MERGE --- diff --git a/contrib/pg_plan_advice/pg_plan_advice.c b/contrib/pg_plan_advice/pg_plan_advice.c index 9a5a9daa9b..2112f7d8bc 100644 --- a/contrib/pg_plan_advice/pg_plan_advice.c +++ b/contrib/pg_plan_advice/pg_plan_advice.c @@ -54,7 +54,7 @@ pgpa_check_plan(PlannedStmt *pstmt) memset(&context, 0, sizeof(pgpa_plan_walker_context)); context.pstmt = pstmt; - pgpa_plan_walker(&context, pstmt->planTree, NULL); + pgpa_plan_walker(&context, pstmt->planTree, NULL, NULL); initStringInfo(&buf); foreach(lc, context.unrolled_joins) @@ -71,6 +71,13 @@ pgpa_check_plan(PlannedStmt *pstmt) appendStringInfoChar(&buf, ' '); pgpa_debug_out_clumped_join(&buf, cjoin); } + foreach(lc, context.gathered_joins) + { + pgpa_gathered_join *gathered_join = lfirst(lc); + + appendStringInfoChar(&buf, ' '); + pgpa_debug_out_gathered_join(&buf, gathered_join); + } elog(WARNING, "advice:%s", buf.data); } diff --git a/contrib/pg_plan_advice/pgpa_join.c b/contrib/pg_plan_advice/pgpa_join.c index f0054bd0ef..c0e7b14721 100644 --- a/contrib/pg_plan_advice/pgpa_join.c +++ b/contrib/pg_plan_advice/pgpa_join.c @@ -154,12 +154,12 @@ pgpa_unroll_join(PlannedStmt *pstmt, Plan *plan, Assert(join_unroller != NULL); /* - * We need to pass the join_unroller object down through certain types - * of plan nodes -- anything that's considered part of the join strategy, + * We need to pass the join_unroller object down through certain types of + * plan nodes -- anything that's considered part of the join strategy, * such as Hash Join's Hash node or a Materialize node inserted on the * inner side of a nested loop as part of the join strategy -- and also - * Gather and Gather Merge nodes, which can occur in a join tree but - * are not themselves scans or joins. + * Gather and Gather Merge nodes, which can occur in a join tree but are + * not themselves scans or joins. */ if (IsA(plan, Material) || IsA(plan, Memoize) || IsA(plan, Hash) || IsA(plan, Sort) || IsA(plan, Gather) || IsA(plan, GatherMerge)) @@ -456,6 +456,39 @@ pgpa_fix_scan_or_clump_member(pgpa_join_member *member) } } +/* + * Update a pgpa_gathered_join to include RTIs scanned by the provided + * plan node. + */ +void +pgpa_add_to_gathered_join(pgpa_gathered_join *gathered_join, Plan *plan) +{ + Bitmapset *relids = pgpa_relids(plan); + Index rti; + + /* + * In cases where a single node replaces a join -- such as a Result node + * that replaces a join between multiple provably-empty relations -- or + * when partitionwise join is chosen, we find multiple RTIs for a single + * Plan node and must add all of them to the pgpa_gathered_join. + */ + if (relids != NULL) + { + gathered_join->relids = bms_add_members(gathered_join->relids, + relids); + return; + } + + /* + * Otherwise, maybe there's a single RTI that this Plan node is scanning. + * If so, we should add its RTI to the pgpa_gathered_join; else, there's + * nothing to do here. + */ + rti = pgpa_scanrelid(plan); + if (rti != 0) + gathered_join->relids = bms_add_member(gathered_join->relids, rti); +} + /* * Extract the scanned RTI from a plan node. * @@ -558,6 +591,30 @@ pgpa_debug_out_unrolled_join(StringInfo buf, pgpa_unrolled_join *join) appendStringInfoChar(buf, ')'); } +void +pgpa_debug_out_gathered_join(StringInfo buf, pgpa_gathered_join *gathered_join) +{ + int rti = -1; + bool first = true; + + if (gathered_join->is_merge) + appendStringInfo(buf, "GATHER_MERGE("); + else + appendStringInfo(buf, "GATHER("); + + while ((rti = bms_next_member(gathered_join->relids, rti)) >= 0) + { + if (first) + { + first = false; + appendStringInfo(buf, "%d", rti); + } + else + appendStringInfo(buf, " %d", rti); + } + appendStringInfoChar(buf, ')'); +} + static void pgpa_debug_out_join_member(StringInfo buf, pgpa_join_member *member) { diff --git a/contrib/pg_plan_advice/pgpa_join.h b/contrib/pg_plan_advice/pgpa_join.h index f1cdd47163..5eb5b06e60 100644 --- a/contrib/pg_plan_advice/pgpa_join.h +++ b/contrib/pg_plan_advice/pgpa_join.h @@ -131,6 +131,16 @@ typedef enum PGPA_UNROLLED_JOIN } pgpa_join_class; +/* + * Relevant details about which relations are joined beneath a Gather or + * Gather Merge node. + */ +typedef struct pgpa_gathered_join +{ + bool is_merge; + Bitmapset *relids; +} pgpa_gathered_join; + extern pgpa_join_class pgpa_get_join_class(Plan *plan); extern pgpa_clumped_join *pgpa_build_clumped_join(Plan *plan, ElidedNode *elided_node); @@ -144,9 +154,14 @@ extern pgpa_unrolled_join *pgpa_build_unrolled_join(PlannedStmt *pstmt, pgpa_join_unroller *join_unroller); extern void pgpa_destroy_join_unroller(pgpa_join_unroller *join_unroller); +extern void pgpa_add_to_gathered_join(pgpa_gathered_join *gathered_join, + Plan *plan); + extern void pgpa_debug_out_clumped_join(StringInfo buf, pgpa_clumped_join *clump); extern void pgpa_debug_out_unrolled_join(StringInfo buf, pgpa_unrolled_join *join); +extern void pgpa_debug_out_gathered_join(StringInfo buf, + pgpa_gathered_join *gathered_join); #endif diff --git a/contrib/pg_plan_advice/pgpa_walker.c b/contrib/pg_plan_advice/pgpa_walker.c index 0471530208..159d2e679c 100644 --- a/contrib/pg_plan_advice/pgpa_walker.c +++ b/contrib/pg_plan_advice/pgpa_walker.c @@ -10,12 +10,13 @@ */ void pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, - pgpa_join_unroller *join_unroller) + pgpa_join_unroller *join_unroller, + pgpa_gathered_join *gathered_join) { pgpa_join_unroller *outer_join_unroller = NULL; pgpa_join_unroller *inner_join_unroller = NULL; - bool join_unroller_toplevel = false; - pgpa_join_class class; + bool join_unroller_toplevel = false; + pgpa_join_class class; ListCell *lc; List *extraplans = NIL; List *elided_nodes = NIL; @@ -30,6 +31,32 @@ pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, /* If we found any elided_nodes, handle them. */ if (elided_nodes != NIL) { + int num_elided_nodes = list_length(elided_nodes); + + /* + * If we're trying to accumulate the set of relids beneath a Gather or + * Gather Merge node, add the relids from the last elided node to the + * set. Earlier elided nodes don't need to be mentioned, because we + * only want to accumulate RTIs that are part of the same join + * problem. + * + * For instance, if a Gather node appears above a join between p and + * q, we do not really care whether p is a plain table, a partitioned + * table with children p1 and p2, or a non-inlined subquery containing + * arbitrary logic. Knowing that p was joined to q and that the Gather + * node appears above that join is enough for us to understand that + * any substructure of p and/or q must also appear beneath the Gather + * node. + */ + if (gathered_join != NULL) + { + ElidedNode *last_elided_node; + + last_elided_node = list_nth(elided_nodes, num_elided_nodes - 1); + gathered_join->relids = bms_add_members(gathered_join->relids, + last_elided_node->relids); + } + /* * If there are multiple relids for the elided node, a clumped join * should be built for it exactly once. When there's a join_unroller, @@ -37,8 +64,7 @@ pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, * clumped join for the final elided node, so throw it out. */ if (join_unroller != NULL) - elided_nodes = list_truncate(elided_nodes, - list_length(elided_nodes) - 1); + elided_nodes = list_truncate(elided_nodes, num_elided_nodes - 1); /* * Every element of elided_nodes is an ElidedNode for which any @@ -62,14 +88,34 @@ pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, * considered as part of the same join problem, nodes elided during * setrefs processing act as boundaries. * - * In more detail, if an Append or MergeAppend was elided, then - j * a partitionwise join was chosen and only a single child survived; + * In more detail, if an Append or MergeAppend was elided, then j * + * a partitionwise join was chosen and only a single child survived; * if a SubqueryScan was elided, the subquery was separately without * flattening it into the parent. In either case, the join order and * join methods beneath the elided node should be described separately * from the join order and methods above the elided node. + * + * Likewise, we only expect a ppga_gathered_join to mention the RTIs + * from the join problem considered immediately beneath the Gather or + * Gather Merge node. */ join_unroller = NULL; + gathered_join = NULL; + } + + /* + * If we've found a Gather or Gather Merge node, prepare to accumulate the + * associated RTIs in a new ppga_gathered_join object. + */ + if (IsA(plan, Gather) || IsA(plan, GatherMerge)) + { + if (gathered_join != NULL) + elog(ERROR, "nested Gather or Gather Merge nodes"); + gathered_join = palloc(sizeof(pgpa_gathered_join)); + gathered_join->is_merge = IsA(plan, GatherMerge); + gathered_join->relids = NULL; + context->gathered_joins = + lappend(context->gathered_joins, gathered_join); } /* Check whether the Plan node is a join, and if so, which kind. */ @@ -78,12 +124,12 @@ pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, /* * If join_unroller == NULL, then either (a) this join should be unrolled * but we must create a new join unroller to do so, or (b) this join is - * clumped and we must add it to the toplevel list of clumped joins - * since there's no other place to attach it, or (c) this is not a join. + * clumped and we must add it to the toplevel list of clumped joins since + * there's no other place to attach it, or (c) this is not a join. */ if (join_unroller == NULL) { - if (class == PGPA_UNROLLED_JOIN) + if (class == PGPA_UNROLLED_JOIN) { join_unroller = pgpa_create_join_unroller(); join_unroller_toplevel = true; @@ -98,23 +144,32 @@ pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, } /* - * If this join is to be unrolled, pgpa_unroll_join() will return the - * join unroller object that should be passed down when we recurse into - * the outer and inner sides of the plan. + * If this join is to be unrolled, pgpa_unroll_join() will return the join + * unroller object that should be passed down when we recurse into the + * outer and inner sides of the plan. */ if (join_unroller != NULL) pgpa_unroll_join(context->pstmt, plan, join_unroller, &outer_join_unroller, &inner_join_unroller); + /* + * If we are collecting RTIs below Gather (Merge), add any appropriate + * RTIs for this node. + */ + if (gathered_join != NULL) + pgpa_add_to_gathered_join(gathered_join, plan); + /* Recurse into the outer and inner subtrees. */ if (plan->lefttree != NULL) - pgpa_plan_walker(context, plan->lefttree, outer_join_unroller); + pgpa_plan_walker(context, plan->lefttree, outer_join_unroller, + gathered_join); if (plan->righttree != NULL) - pgpa_plan_walker(context, plan->righttree, inner_join_unroller); + pgpa_plan_walker(context, plan->righttree, inner_join_unroller, + gathered_join); /* - * If we created a join unroller up above, then it's also our join to - * use it to build the final pgpa_unrolled_join, and to destroy the object. + * If we created a join unroller up above, then it's also our join to use + * it to build the final pgpa_unrolled_join, and to destroy the object. */ if (join_unroller_toplevel) { @@ -131,9 +186,9 @@ pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, */ foreach(lc, plan->initPlan) { - Plan *subplan = lfirst(lc); + Plan *subplan = lfirst(lc); - pgpa_plan_walker(context, subplan, NULL); + pgpa_plan_walker(context, subplan, NULL, NULL); } /* @@ -156,7 +211,8 @@ pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, extraplans = ((BitmapOr *) plan)->bitmapplans; break; case T_SubqueryScan: - pgpa_plan_walker(context, ((SubqueryScan *) plan)->subplan, NULL); + pgpa_plan_walker(context, ((SubqueryScan *) plan)->subplan, + NULL, NULL); break; case T_CustomScan: extraplans = ((CustomScan *) plan)->custom_plans; @@ -168,9 +224,9 @@ pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, /* If we found a list of extra children, iterate over it. */ foreach(lc, extraplans) { - Plan *subplan = lfirst(lc); + Plan *subplan = lfirst(lc); - pgpa_plan_walker(context, subplan, NULL); + pgpa_plan_walker(context, subplan, NULL, NULL); } } diff --git a/contrib/pg_plan_advice/pgpa_walker.h b/contrib/pg_plan_advice/pgpa_walker.h index 94d3d68cb3..5d16d91ccf 100644 --- a/contrib/pg_plan_advice/pgpa_walker.h +++ b/contrib/pg_plan_advice/pgpa_walker.h @@ -19,10 +19,12 @@ typedef struct pgpa_plan_walker_context PlannedStmt *pstmt; List *unrolled_joins; List *clumped_joins; + List *gathered_joins; } pgpa_plan_walker_context; extern void pgpa_plan_walker(pgpa_plan_walker_context *context, Plan *plan, - pgpa_join_unroller *join_unroller); + pgpa_join_unroller *join_unroller, + pgpa_gathered_join *gathered_join); extern ElidedNode *pgpa_last_elided_node(PlannedStmt *pstmt, Plan *plan); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 7fbc443707..f7af532c05 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -4319,3 +4319,4 @@ pgpa_clumped_join pgpa_unrolled_join pgpa_join_unroller pgpa_plan_walker_context +pgpa_gathered_join