+++ /dev/null
-/*-------------------------------------------------------------------------
- *
- * create_advice.c
- * generate advice from a finished plan that can be fed back into
- * future planning cycles if desired
- *
- * Copyright (c) 2016-2024, PostgreSQL Global Development Group
- *
- * contrib/pg_plan_advice/pg_plan_advice.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "commands/explain.h"
-#include "parser/parsetree.h"
-#include "pg_plan_advice.h"
-#include "utils/builtins.h"
-#include "utils/lsyscache.h"
-
-typedef enum
-{
- JOIN_NONE,
- JOIN_FOREIGN,
- JOIN_MERGEJOIN_PLAIN,
- JOIN_MERGEJOIN_MATERIALIZE,
- JOIN_NESTLOOP_PLAIN,
- JOIN_NESTLOOP_MATERIALIZE,
- JOIN_NESTLOOP_MEMOIZE,
- JOIN_HASHJOIN,
- JOIN_PARTITIONWISE
-} join_strategy;
-
-typedef struct
-{
- PlannedStmt *stmt;
- int rtable_length;
- SubPlanRTInfo **rtable_subplans;
- char **rtable_names;
- Plan **rtable_scans;
- bool *rtable_drivingjoin;
- join_strategy *rtable_joinstrat;
- List *all_join_orders;
-} advice_context;
-
-typedef struct
-{
- bool inner_side;
- join_strategy jstrat;
- Node *join_order;
-} join_traversal;
-
-static void scan_plan_tree(advice_context *context, Plan *plan,
- join_traversal *jtraversal);
-static void scan_plan_tree_list(advice_context *context, List *list);
-
-static void
-init_advice_context(advice_context *context, PlannedStmt *stmt)
-{
- int rtable_length = list_length(stmt->rtable);
-
- context->stmt = stmt;
- context->rtable_length = list_length(stmt->rtable);
- context->rtable_subplans = palloc0_array(SubPlanRTInfo *, rtable_length);
- context->rtable_names = palloc0_array(char *, rtable_length);
- context->rtable_scans = palloc0_array(Plan *, rtable_length);
- context->rtable_drivingjoin = palloc0_array(bool, rtable_length);
- context->rtable_joinstrat = palloc0_array(join_strategy, rtable_length);
- context->all_join_orders = NIL;
-}
-
-static void
-generate_relation_identifiers(advice_context *context)
-{
- PlannedStmt *stmt = context->stmt;
- Index rti;
- Index *topparent;
-
- topparent = CreateParentRelationMap(stmt->rtable, stmt->appendRelations);
-
- /* Main loop over the entire flattened range table. */
- for (rti = 1; rti <= context->rtable_length; ++rti)
- {
- const char *result;
-
- result = MakeRelationIdentifier(stmt->rtable,
- topparent,
- stmt->subrtinfos,
- rti);
-
- /* Save the name we just generated. */
- context->rtable_names[rti - 1] = unconstify(char *, result);
- }
-}
-
-static void
-scan_plan_tree(advice_context *context, Plan *plan, join_traversal *jtraversal)
-{
- Node *join_order = NULL;
- Index rti;
-
- if (IsA(plan, NestLoop) || IsA(plan, HashJoin) || IsA(plan, MergeJoin))
- {
- join_traversal outer_join_traversal;
- join_traversal inner_join_traversal;
-
- /*
- * The outermost table in a set of joins needs no join strategy advice;
- * in the case of a bushy join, the outermost table of each clump
- * needs join strategy advice to indicate how the clump as a whole
- * should be joined.
- */
- outer_join_traversal.inner_side = false;
- outer_join_traversal.jstrat = JOIN_NONE;
- outer_join_traversal.join_order = NULL;
- if (jtraversal != NULL)
- outer_join_traversal.jstrat = jtraversal->jstrat;
- else
- outer_join_traversal.jstrat = JOIN_NONE;
- outer_join_traversal.join_order = NULL;
- scan_plan_tree(context, plan->lefttree, &outer_join_traversal);
-
- /*
- * Any table on the inner side of a join needs join strategy advice.
- */
- inner_join_traversal.inner_side = true;
- inner_join_traversal.join_order = NULL;
- if (IsA(plan, MergeJoin))
- {
- if (IsA(plan->righttree, Material))
- inner_join_traversal.jstrat = JOIN_MERGEJOIN_MATERIALIZE;
- else
- inner_join_traversal.jstrat = JOIN_MERGEJOIN_PLAIN;
- }
- else if (IsA(plan, NestLoop))
- {
- if (IsA(plan->righttree, Material))
- inner_join_traversal.jstrat = JOIN_NESTLOOP_MATERIALIZE;
- else if (IsA(plan->righttree, Memoize))
- inner_join_traversal.jstrat = JOIN_NESTLOOP_MEMOIZE;
- else
- inner_join_traversal.jstrat = JOIN_NESTLOOP_PLAIN;
- }
- else if (IsA(plan, HashJoin))
- inner_join_traversal.jstrat = JOIN_HASHJOIN;
- else
- elog(ERROR, "unknown node type: %d", nodeTag(plan));
- scan_plan_tree(context, plan->righttree, &inner_join_traversal);
-
- /*
- * We assume that left-deep (or outer-deep) join trees are the norm;
- * hence JOIN(JOIN(A,B),C) is represented as (A B C), whereas
- * JOIN(A,JOIN(B,C)) is represnted as (A (B C)).
- */
- if (IsA(outer_join_traversal.join_order, List))
- join_order = (Node *)
- lappend((List *) outer_join_traversal.join_order,
- inner_join_traversal.join_order);
- else
- join_order = (Node *)
- list_make2(outer_join_traversal.join_order,
- inner_join_traversal.join_order);
- }
- else if (jtraversal != NULL && !jtraversal->inner_side &&
- IsA(plan, Sort))
- {
- Assert(plan->lefttree != NULL && plan->righttree == NULL);
- scan_plan_tree(context, plan->lefttree, jtraversal);
- }
- else if (jtraversal != NULL && jtraversal->inner_side &&
- (IsA(plan, Material) || IsA(plan, Memoize) || IsA(plan, Hash) ||
- IsA(plan, Sort)))
- {
- Assert(plan->lefttree != NULL && plan->righttree == NULL);
- scan_plan_tree(context, plan->lefttree, jtraversal);
- }
- else
- {
- if (plan->lefttree != NULL)
- scan_plan_tree(context, plan->lefttree, NULL);
- if (plan->righttree != NULL)
- scan_plan_tree(context, plan->righttree, NULL);
-
- if (jtraversal != NULL)
- join_order = (Node *) plan;
- }
-
- if (join_order != NULL)
- {
- if (jtraversal == NULL)
- context->all_join_orders = lappend(context->all_join_orders,
- join_order);
- else
- {
- Assert(jtraversal->join_order == NULL);
- jtraversal->join_order = join_order;
- }
- }
-
- /* recurse into any special children */
- switch (nodeTag(plan))
- {
- case T_Append:
- scan_plan_tree_list(context, ((Append *) plan)->appendplans);
- break;
- case T_MergeAppend:
- scan_plan_tree_list(context, ((MergeAppend *) plan)->mergeplans);
- break;
- case T_BitmapAnd:
- scan_plan_tree_list(context, ((BitmapAnd *) plan)->bitmapplans);
- break;
- case T_BitmapOr:
- scan_plan_tree_list(context, ((BitmapOr *) plan)->bitmapplans);
- break;
- case T_SubqueryScan:
- scan_plan_tree(context, ((SubqueryScan *) plan)->subplan, NULL);
- break;
- case T_CustomScan:
- scan_plan_tree_list(context, ((CustomScan *) plan)->custom_plans);
- break;
- default:
- break;
- }
-
- rti = GetScannedRTI(context->stmt, plan);
- if (rti != 0)
- {
- if (context->rtable_scans[rti - 1] != NULL)
- elog(ERROR, "rti %d is duplicated", rti);
- context->rtable_scans[rti - 1] = plan;
- if (jtraversal != NULL)
- {
- context->rtable_drivingjoin[rti - 1] = !jtraversal->inner_side;
- context->rtable_joinstrat[rti - 1] = jtraversal->jstrat;
- }
- }
-}
-
-static void
-scan_plan_tree_list(advice_context *context, List *list)
-{
- ListCell *lc;
-
- foreach(lc, list)
- {
- scan_plan_tree(context, lfirst(lc), NULL);
- }
-}
-
-static void
-join_method_dump(StringInfo result, advice_context *context)
-{
- Index rti;
-
- for (rti = 1; rti <= context->rtable_length; ++rti)
- {
- char *name = context->rtable_names[rti - 1];
- bool drivingjoin = context->rtable_drivingjoin[rti - 1];
- join_strategy jstrat = context->rtable_joinstrat[rti - 1];
- char *jstrat_name = NULL;
-
- switch (jstrat)
- {
- case JOIN_NONE:
- break;
- case JOIN_FOREIGN:
- jstrat_name = drivingjoin ? "BUSHY_FOREIGN_JOIN" : "FOREIGN_JOIN";
- break;
- case JOIN_MERGEJOIN_PLAIN:
- jstrat_name = drivingjoin ? "BUSHY_MERGE_JOIN" : "MERGE_JOIN";
- break;
- case JOIN_MERGEJOIN_MATERIALIZE:
- jstrat_name = drivingjoin ? "BUSHY_MERGE_JOIN_MATERIALIZE" : "MERGE_JOIN_MATERIALIZE";
- break;
- case JOIN_NESTLOOP_PLAIN:
- jstrat_name = drivingjoin ? "BUSHY_NESTED_LOOP" : "NESTED_LOOP";
- break;
- case JOIN_NESTLOOP_MATERIALIZE:
- jstrat_name = drivingjoin ? "BUSHY_NESTED_LOOP_MATERIALIZE" : "NESTED_LOOP_MATERIALIZE";
- break;
- case JOIN_NESTLOOP_MEMOIZE:
- jstrat_name = drivingjoin ? "BUSHY_NESTED_LOOP_MEMOIZE" : "NESTED_LOOP_MEMOIZE";
- break;
- case JOIN_HASHJOIN:
- jstrat_name = drivingjoin ? "BUSHY_HASH_JOIN" : "HASH_JOIN";
- break;
- case JOIN_PARTITIONWISE:
- jstrat_name = drivingjoin ? "BUSHY_PARTITIONWISE" : "PARTITIONWISE";
- break;
- }
-
- if (name == NULL)
- name = "???";
- if (jstrat_name != NULL)
- appendStringInfo(result, "%s(%s)\n", jstrat_name, name);
- }
-}
-
-static void
-scan_method_dump(StringInfo result, advice_context *context)
-{
- Index rti;
-
- for (rti = 1; rti <= context->rtable_length; ++rti)
- {
- char *name = context->rtable_names[rti - 1];
- Plan *plan = context->rtable_scans[rti - 1];
- char *scan_name = NULL;
- Oid index_oid = InvalidOid;
-
- /*
- * Convert the node tag of the scan to a string. We can ignore scan
- * types for which there is no alternative, e.g. SubqueryScan,
- * FunctionScan, ValuesScan.
- */
- if (plan != NULL)
- {
- switch (nodeTag(plan))
- {
- case T_SeqScan:
- scan_name = "SEQ_SCAN";
- break;
- case T_IndexScan:
- scan_name = "INDEX_SCAN";
- index_oid = ((IndexScan *) plan)->indexid;
- break;
- case T_IndexOnlyScan:
- scan_name = "INDEX_ONLY_SCAN";
- index_oid = ((IndexOnlyScan *) plan)->indexid;
- break;
- case T_BitmapHeapScan:
- scan_name = "BITMAP_HEAP_SCAN";
- break;
- case T_TidScan:
- scan_name = "TID_SCAN";
- break;
- case T_TidRangeScan:
- scan_name = "TID_RANGE_SCAN";
- break;
- case T_CustomScan:
- scan_name = "CUSTOM_SCAN";
- break;
- default:
- break;
- }
- }
-
- if (name == NULL)
- name = "???";
- if (OidIsValid(index_oid))
- {
- char *indnsp;
- char *indrel;
-
- indnsp = get_namespace_name_or_temp(get_rel_namespace(index_oid));
- indrel = get_rel_name(index_oid);
- appendStringInfo(result, "%s(%s, %s.%s)\n", scan_name, name,
- quote_identifier(indnsp), quote_identifier(indrel));
- }
- else if (scan_name != NULL)
- appendStringInfo(result, "%s(%s)\n", scan_name, name);
- }
-}
-
-static void
-join_order_dump_recursive(StringInfo result, advice_context *context, Node *n)
-{
- if (IsA(n, List))
- {
- ListCell *lc;
- bool first = true;
-
- appendStringInfoString(result, "(");
- foreach(lc, (List *) n)
- {
- if (first)
- first = false;
- else
- appendStringInfoString(result, " ");
- join_order_dump_recursive(result, context, (Node *) lfirst(lc));
- }
- appendStringInfoString(result, ")");
- }
- else
- {
- Index rti = GetScannedRTI(context->stmt, (Plan *) n);
-
- if (rti != 0)
- appendStringInfoString(result, context->rtable_names[rti - 1]);
- else
- appendStringInfo(result, "?[rti=%d,nodetag=%d]", (int) rti,
- (int) nodeTag(n));
- }
-}
-
-static void
-join_order_dump(StringInfo result, advice_context *context)
-{
- ListCell *lc;
-
- foreach(lc, context->all_join_orders)
- {
- List *l = lfirst_node(List, lc);
- ListCell *lc2;
- bool first = true;
-
- appendStringInfoString(result, "JOIN_ORDER(");
- foreach(lc2, l)
- {
- if (first)
- first = false;
- else
- appendStringInfoString(result, " ");
- join_order_dump_recursive(result, context, (Node *) lfirst(lc2));
- }
- appendStringInfoString(result, ")\n");
- }
-}
-
-/*
- * Generate advice from a PlannedStmt, and append it to the buffer provided.
- *
- * Returns false if the PlannedStmt has no plan tree, otherwise true.
- */
-bool
-append_advice_from_plan(StringInfo buf, PlannedStmt *stmt)
-{
- advice_context context;
- ListCell *lc;
-
- /* If this is a utility statement, there's no useful work to be done. */
- if (stmt->planTree == NULL)
- return false;
-
- /* Initialization steps. */
- init_advice_context(&context, stmt);
- generate_relation_identifiers(&context);
-
- /* First, scan the main plan tree. */
- scan_plan_tree(&context, stmt->planTree, NULL);
-
- /*
- * Now, scan the list of InitPlans and SubPlans, which, confusingly,
- * are collectively known as subplans. Some subplans may have been elided
- * during planning, so skip any NULL entries in the array.
- */
- foreach(lc, stmt->subplans)
- {
- Plan *plan = lfirst(lc);
-
- if (plan != NULL)
- scan_plan_tree(&context, plan, NULL);
- }
-
- /* Finally, append advice to the output buffer. */
- join_order_dump(buf, &context);
- join_method_dump(buf, &context);
- scan_method_dump(buf, &context);
-
- return true;
-}