Thread: explain.c: why trace PlanState and Plan trees separately?

explain.c: why trace PlanState and Plan trees separately?

From
Tom Lane
Date:
Currently, the recursion in ExplainNode() goes to some lengths to chase
down the PlanState and Plan trees independently.  This is a bit silly:
we could just chase the PlanState tree, and use each PlanState's "plan"
link when we needed to get to the matching Plan node.  I think this is a
holdover from long ago when the code worked only with Plan trees --- the
PlanState stuff was bolted on rather than replacing that logic entirely.
But there is no capacity for EXPLAINing a Plan tree without having
constructed a PlanState tree, and I don't foresee that we'd add one
(for one reason, EXPLAIN depends on ExecutorStart to perform permissions
checking for the referenced tables).  Any objections to getting rid of
the separate Plan argument?

The reason I'm on about this at the moment is that I think I see how to
get ruleutils to print PARAM_EXEC Params as the referenced expression
rather than $N ... but it depends on having the PlanState tree at hand.
So fixing that will destroy any last shred of credibility there might
be for EXPLAINing a Plan tree without PlanState.  In fact I'm thinking
I need to change deparse_context_for_plan() to take a PlanState not a
Plan.
        regards, tom lane


Re: explain.c: why trace PlanState and Plan trees separately?

From
Yeb Havinga
Date:
Tom Lane wrote:
> The reason I'm on about this at the moment is that I think I see how to
> get ruleutils to print PARAM_EXEC Params as the referenced expression
> rather than $N ...
Wouldn't this obfuscate the plan more than printing subplan arguments at 
the call site?

regards,
Yeb Havinga



Re: explain.c: why trace PlanState and Plan trees separately?

From
Tom Lane
Date:
Yeb Havinga <yebhavinga@gmail.com> writes:
> Tom Lane wrote:
>> The reason I'm on about this at the moment is that I think I see how to
>> get ruleutils to print PARAM_EXEC Params as the referenced expression
>> rather than $N ...

> Wouldn't this obfuscate the plan more than printing subplan arguments at 
> the call site?

It would if subplans could have more than one call site, but they can't.

I do intend to force qualification of Vars that are printed as a result
of param expansion; for example consider a standard nestloop-with-
inner-indexscan plan:
NestLoop    Seq Scan on a    Index Scan on b        Index Cond: x = a.y

If y weren't qualified to show that it's not a variable of b, this could
be confusing.  But as long as we do that, it pretty much matches our
historical behavior.  Note that CVS HEAD is printing this case as
NestLoop    Seq Scan on a    Index Scan on b        Index Cond: x = $0

which is definitely not very helpful.
        regards, tom lane


Re: explain.c: why trace PlanState and Plan trees separately?

From
Yeb Havinga
Date:
Tom Lane wrote:
> Yeb Havinga <yebhavinga@gmail.com> writes:
>   
>> Tom Lane wrote:
>>     
>>> The reason I'm on about this at the moment is that I think I see how to
>>> get ruleutils to print PARAM_EXEC Params as the referenced expression
>>> rather than $N ...
>>>       
>> Wouldn't this obfuscate the plan more than printing subplan arguments at 
>> the call site?
>>     
>
> It would if subplans could have more than one call site, but they can't.
>
> I do intend to force qualification of Vars that are printed as a result
> of param expansion;
>   
Will the new referenced expression printing also be used when printing 
subplans?

If yes, I do not have to submit the latest version of a patch I made for 
subplan argument printing (discussed earlier in this thread 
http://archives.postgresql.org/pgsql-hackers/2010-02/msg01602.php)

regards,
Yeb Havinga



Re: explain.c: why trace PlanState and Plan trees separately?

From
Tom Lane
Date:
Yeb Havinga <yebhavinga@gmail.com> writes:
> Will the new referenced expression printing also be used when printing
> subplans?

> If yes, I do not have to submit the latest version of a patch I made for
> subplan argument printing (discussed earlier in this thread
> http://archives.postgresql.org/pgsql-hackers/2010-02/msg01602.php)

Oh, I had forgotten that patch was still in progress.  I think it's
unnecessary given what I'm fooling with.  The attached patch needs
some more testing, but what I get with it is for example

regression=# explain (verbose) select (select oid from pg_class a where
regression(# a.oid = b.relfilenode) from pg_class b;
                                               QUERY PLAN
--------------------------------------------------------------------------------------------------------
 Seq Scan on pg_catalog.pg_class b  (cost=0.00..5556.81 rows=669 width=4)
   Output: (SubPlan 1)
   SubPlan 1
     ->  Index Scan using pg_class_oid_index on pg_catalog.pg_class a  (cost=0.00..8.27 rows=1 width=4)
           Output: a.oid
           Index Cond: (a.oid = b.relfilenode)
(6 rows)

(this is the first example in the above-referenced thread).

            regards, tom lane

Index: src/backend/commands/explain.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/commands/explain.c,v
retrieving revision 1.206
diff -c -r1.206 explain.c
*** src/backend/commands/explain.c    10 Jun 2010 01:26:30 -0000    1.206
--- src/backend/commands/explain.c    13 Jul 2010 18:13:25 -0000
***************
*** 54,80 ****
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
                  ExplainState *es);
  static double elapsed_time(instr_time *starttime);
! static void ExplainNode(Plan *plan, PlanState *planstate,
!             Plan *outer_plan,
              const char *relationship, const char *plan_name,
              ExplainState *es);
! static void show_plan_tlist(Plan *plan, ExplainState *es);
! static void show_qual(List *qual, const char *qlabel, Plan *plan,
!           Plan *outer_plan, bool useprefix, ExplainState *es);
  static void show_scan_qual(List *qual, const char *qlabel,
!                Plan *scan_plan, Plan *outer_plan,
!                ExplainState *es);
! static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
!                 ExplainState *es);
! static void show_sort_keys(Plan *sortplan, ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
  static const char *explain_get_index_name(Oid indexId);
  static void ExplainScanTarget(Scan *plan, ExplainState *es);
! static void ExplainMemberNodes(List *plans, PlanState **planstate,
!                    Plan *outer_plan, ExplainState *es);
! static void ExplainSubPlans(List *plans, const char *relationship,
!                 ExplainState *es);
  static void ExplainPropertyList(const char *qlabel, List *data,
                      ExplainState *es);
  static void ExplainProperty(const char *qlabel, const char *value,
--- 54,83 ----
  static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
                  ExplainState *es);
  static double elapsed_time(instr_time *starttime);
! static void ExplainNode(PlanState *planstate, List *ancestors,
              const char *relationship, const char *plan_name,
              ExplainState *es);
! static void show_plan_tlist(PlanState *planstate, List *ancestors,
!                             ExplainState *es);
! static void show_qual(List *qual, const char *qlabel,
!                       PlanState *planstate, List *ancestors,
!                       bool useprefix, ExplainState *es);
  static void show_scan_qual(List *qual, const char *qlabel,
!                            PlanState *planstate, List *ancestors,
!                            ExplainState *es);
! static void show_upper_qual(List *qual, const char *qlabel,
!                             PlanState *planstate, List *ancestors,
!                             ExplainState *es);
! static void show_sort_keys(SortState *sortstate, List *ancestors,
!                            ExplainState *es);
  static void show_sort_info(SortState *sortstate, ExplainState *es);
  static void show_hash_info(HashState *hashstate, ExplainState *es);
  static const char *explain_get_index_name(Oid indexId);
  static void ExplainScanTarget(Scan *plan, ExplainState *es);
! static void ExplainMemberNodes(List *plans, PlanState **planstates,
!                    List *ancestors, ExplainState *es);
! static void ExplainSubPlans(List *plans, List *ancestors,
!                             const char *relationship, ExplainState *es);
  static void ExplainPropertyList(const char *qlabel, List *data,
                      ExplainState *es);
  static void ExplainProperty(const char *qlabel, const char *value,
***************
*** 484,491 ****
      Assert(queryDesc->plannedstmt != NULL);
      es->pstmt = queryDesc->plannedstmt;
      es->rtable = queryDesc->plannedstmt->rtable;
!     ExplainNode(queryDesc->plannedstmt->planTree, queryDesc->planstate,
!                 NULL, NULL, NULL, es);
  }

  /*
--- 487,493 ----
      Assert(queryDesc->plannedstmt != NULL);
      es->pstmt = queryDesc->plannedstmt;
      es->rtable = queryDesc->plannedstmt->rtable;
!     ExplainNode(queryDesc->planstate, NIL, NULL, NULL, es);
  }

  /*
***************
*** 585,615 ****

  /*
   * ExplainNode -
!  *      Appends a description of the Plan node to es->str
   *
!  * planstate points to the executor state node corresponding to the plan node.
!  * We need this to get at the instrumentation data (if any) as well as the
!  * list of subplans.
!  *
!  * outer_plan, if not null, references another plan node that is the outer
!  * side of a join with the current node.  This is only interesting for
!  * deciphering runtime keys of an inner indexscan.
   *
   * relationship describes the relationship of this plan node to its parent
   * (eg, "Outer", "Inner"); it can be null at top level.  plan_name is an
   * optional name to be attached to the node.
   *
   * In text format, es->indent is controlled in this function since we only
!  * want it to change at Plan-node boundaries.  In non-text formats, es->indent
   * corresponds to the nesting depth of logical output groups, and therefore
   * is controlled by ExplainOpenGroup/ExplainCloseGroup.
   */
  static void
! ExplainNode(Plan *plan, PlanState *planstate,
!             Plan *outer_plan,
              const char *relationship, const char *plan_name,
              ExplainState *es)
  {
      const char *pname;            /* node type name for text output */
      const char *sname;            /* node type name for non-text output */
      const char *strategy = NULL;
--- 587,616 ----

  /*
   * ExplainNode -
!  *      Appends a description of a plan tree to es->str
   *
!  * planstate points to the executor state node for the current plan node.
!  * We need to work from a PlanState node, not just a Plan node, in order to
!  * get at the instrumentation data (if any) as well as the list of subplans.
!  *
!  * ancestors is a list of parent PlanState nodes, most-closely-nested first.
!  * These are needed in order to interpret PARAM_EXEC Params.
   *
   * relationship describes the relationship of this plan node to its parent
   * (eg, "Outer", "Inner"); it can be null at top level.  plan_name is an
   * optional name to be attached to the node.
   *
   * In text format, es->indent is controlled in this function since we only
!  * want it to change at plan-node boundaries.  In non-text formats, es->indent
   * corresponds to the nesting depth of logical output groups, and therefore
   * is controlled by ExplainOpenGroup/ExplainCloseGroup.
   */
  static void
! ExplainNode(PlanState *planstate, List *ancestors,
              const char *relationship, const char *plan_name,
              ExplainState *es)
  {
+     Plan       *plan = planstate->plan;
      const char *pname;            /* node type name for text output */
      const char *sname;            /* node type name for non-text output */
      const char *strategy = NULL;
***************
*** 617,624 ****
      int            save_indent = es->indent;
      bool        haschildren;

-     Assert(plan);
-
      switch (nodeTag(plan))
      {
          case T_Result:
--- 618,623 ----
***************
*** 999,1021 ****

      /* target list */
      if (es->verbose)
!         show_plan_tlist(plan, es);

      /* quals, sort keys, etc */
      switch (nodeTag(plan))
      {
          case T_IndexScan:
              show_scan_qual(((IndexScan *) plan)->indexqualorig,
!                            "Index Cond", plan, outer_plan, es);
!             show_scan_qual(plan->qual, "Filter", plan, outer_plan, es);
              break;
          case T_BitmapIndexScan:
              show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
!                            "Index Cond", plan, outer_plan, es);
              break;
          case T_BitmapHeapScan:
              show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
!                            "Recheck Cond", plan, outer_plan, es);
              /* FALL THRU */
          case T_SeqScan:
          case T_FunctionScan:
--- 998,1020 ----

      /* target list */
      if (es->verbose)
!         show_plan_tlist(planstate, ancestors, es);

      /* quals, sort keys, etc */
      switch (nodeTag(plan))
      {
          case T_IndexScan:
              show_scan_qual(((IndexScan *) plan)->indexqualorig,
!                            "Index Cond", planstate, ancestors, es);
!             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
              break;
          case T_BitmapIndexScan:
              show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
!                            "Index Cond", planstate, ancestors, es);
              break;
          case T_BitmapHeapScan:
              show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
!                            "Recheck Cond", planstate, ancestors, es);
              /* FALL THRU */
          case T_SeqScan:
          case T_FunctionScan:
***************
*** 1023,1029 ****
          case T_CteScan:
          case T_WorkTableScan:
          case T_SubqueryScan:
!             show_scan_qual(plan->qual, "Filter", plan, outer_plan, es);
              break;
          case T_TidScan:
              {
--- 1022,1028 ----
          case T_CteScan:
          case T_WorkTableScan:
          case T_SubqueryScan:
!             show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
              break;
          case T_TidScan:
              {
***************
*** 1035,1075 ****

                  if (list_length(tidquals) > 1)
                      tidquals = list_make1(make_orclause(tidquals));
!                 show_scan_qual(tidquals, "TID Cond", plan, outer_plan, es);
!                 show_scan_qual(plan->qual, "Filter", plan, outer_plan, es);
              }
              break;
          case T_NestLoop:
              show_upper_qual(((NestLoop *) plan)->join.joinqual,
!                             "Join Filter", plan, es);
!             show_upper_qual(plan->qual, "Filter", plan, es);
              break;
          case T_MergeJoin:
              show_upper_qual(((MergeJoin *) plan)->mergeclauses,
!                             "Merge Cond", plan, es);
              show_upper_qual(((MergeJoin *) plan)->join.joinqual,
!                             "Join Filter", plan, es);
!             show_upper_qual(plan->qual, "Filter", plan, es);
              break;
          case T_HashJoin:
              show_upper_qual(((HashJoin *) plan)->hashclauses,
!                             "Hash Cond", plan, es);
              show_upper_qual(((HashJoin *) plan)->join.joinqual,
!                             "Join Filter", plan, es);
!             show_upper_qual(plan->qual, "Filter", plan, es);
              break;
          case T_Agg:
          case T_Group:
!             show_upper_qual(plan->qual, "Filter", plan, es);
              break;
          case T_Sort:
!             show_sort_keys(plan, es);
              show_sort_info((SortState *) planstate, es);
              break;
          case T_Result:
              show_upper_qual((List *) ((Result *) plan)->resconstantqual,
!                             "One-Time Filter", plan, es);
!             show_upper_qual(plan->qual, "Filter", plan, es);
              break;
          case T_Hash:
              show_hash_info((HashState *) planstate, es);
--- 1034,1074 ----

                  if (list_length(tidquals) > 1)
                      tidquals = list_make1(make_orclause(tidquals));
!                 show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
!                 show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
              }
              break;
          case T_NestLoop:
              show_upper_qual(((NestLoop *) plan)->join.joinqual,
!                             "Join Filter", planstate, ancestors, es);
!             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
              break;
          case T_MergeJoin:
              show_upper_qual(((MergeJoin *) plan)->mergeclauses,
!                             "Merge Cond", planstate, ancestors, es);
              show_upper_qual(((MergeJoin *) plan)->join.joinqual,
!                             "Join Filter", planstate, ancestors, es);
!             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
              break;
          case T_HashJoin:
              show_upper_qual(((HashJoin *) plan)->hashclauses,
!                             "Hash Cond", planstate, ancestors, es);
              show_upper_qual(((HashJoin *) plan)->join.joinqual,
!                             "Join Filter", planstate, ancestors, es);
!             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
              break;
          case T_Agg:
          case T_Group:
!             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
              break;
          case T_Sort:
!             show_sort_keys((SortState *) planstate, ancestors, es);
              show_sort_info((SortState *) planstate, es);
              break;
          case T_Result:
              show_upper_qual((List *) ((Result *) plan)->resconstantqual,
!                             "One-Time Filter", planstate, ancestors, es);
!             show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
              break;
          case T_Hash:
              show_hash_info((HashState *) planstate, es);
***************
*** 1169,1198 ****
      if (haschildren)
          ExplainOpenGroup("Plans", "Plans", false, es);

      /* initPlan-s */
      if (plan->initPlan)
!         ExplainSubPlans(planstate->initPlan, "InitPlan", es);

      /* lefttree */
!     if (outerPlan(plan))
!     {
!         /*
!          * Ordinarily we don't pass down our own outer_plan value to our child
!          * nodes, but in bitmap scan trees we must, since the bottom
!          * BitmapIndexScan nodes may have outer references.
!          */
!         ExplainNode(outerPlan(plan), outerPlanState(planstate),
!                     IsA(plan, BitmapHeapScan) ? outer_plan : NULL,
                      "Outer", NULL, es);
-     }

      /* righttree */
!     if (innerPlan(plan))
!     {
!         ExplainNode(innerPlan(plan), innerPlanState(planstate),
!                     outerPlan(plan),
                      "Inner", NULL, es);
-     }

      /* special child plans */
      switch (nodeTag(plan))
--- 1168,1189 ----
      if (haschildren)
          ExplainOpenGroup("Plans", "Plans", false, es);

+     /* Pass current PlanState as head of ancestors list for children */
+     ancestors = lcons(planstate, ancestors);
+
      /* initPlan-s */
      if (plan->initPlan)
!         ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);

      /* lefttree */
!     if (outerPlanState(planstate))
!         ExplainNode(outerPlanState(planstate), ancestors,
                      "Outer", NULL, es);

      /* righttree */
!     if (innerPlanState(planstate))
!         ExplainNode(innerPlanState(planstate), ancestors,
                      "Inner", NULL, es);

      /* special child plans */
      switch (nodeTag(plan))
***************
*** 1200,1231 ****
          case T_ModifyTable:
              ExplainMemberNodes(((ModifyTable *) plan)->plans,
                                 ((ModifyTableState *) planstate)->mt_plans,
!                                outer_plan, es);
              break;
          case T_Append:
              ExplainMemberNodes(((Append *) plan)->appendplans,
                                 ((AppendState *) planstate)->appendplans,
!                                outer_plan, es);
              break;
          case T_BitmapAnd:
              ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
                                 ((BitmapAndState *) planstate)->bitmapplans,
!                                outer_plan, es);
              break;
          case T_BitmapOr:
              ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
                                 ((BitmapOrState *) planstate)->bitmapplans,
!                                outer_plan, es);
              break;
          case T_SubqueryScan:
!             {
!                 SubqueryScan *subqueryscan = (SubqueryScan *) plan;
!                 SubqueryScanState *subquerystate = (SubqueryScanState *) planstate;
!
!                 ExplainNode(subqueryscan->subplan, subquerystate->subplan,
!                             NULL,
!                             "Subquery", NULL, es);
!             }
              break;
          default:
              break;
--- 1191,1216 ----
          case T_ModifyTable:
              ExplainMemberNodes(((ModifyTable *) plan)->plans,
                                 ((ModifyTableState *) planstate)->mt_plans,
!                                ancestors, es);
              break;
          case T_Append:
              ExplainMemberNodes(((Append *) plan)->appendplans,
                                 ((AppendState *) planstate)->appendplans,
!                                ancestors, es);
              break;
          case T_BitmapAnd:
              ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
                                 ((BitmapAndState *) planstate)->bitmapplans,
!                                ancestors, es);
              break;
          case T_BitmapOr:
              ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
                                 ((BitmapOrState *) planstate)->bitmapplans,
!                                ancestors, es);
              break;
          case T_SubqueryScan:
!             ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
!                         "Subquery", NULL, es);
              break;
          default:
              break;
***************
*** 1233,1241 ****

      /* subPlan-s */
      if (planstate->subPlan)
!         ExplainSubPlans(planstate->subPlan, "SubPlan", es);

      /* end of child plans */
      if (haschildren)
          ExplainCloseGroup("Plans", "Plans", false, es);

--- 1218,1227 ----

      /* subPlan-s */
      if (planstate->subPlan)
!         ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);

      /* end of child plans */
+     ancestors = list_delete_first(ancestors);
      if (haschildren)
          ExplainCloseGroup("Plans", "Plans", false, es);

***************
*** 1252,1259 ****
   * Show the targetlist of a plan node
   */
  static void
! show_plan_tlist(Plan *plan, ExplainState *es)
  {
      List       *context;
      List       *result = NIL;
      bool        useprefix;
--- 1238,1246 ----
   * Show the targetlist of a plan node
   */
  static void
! show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
  {
+     Plan       *plan = planstate->plan;
      List       *context;
      List       *result = NIL;
      bool        useprefix;
***************
*** 1271,1280 ****
          return;

      /* Set up deparsing context */
!     context = deparse_context_for_plan((Node *) plan,
!                                        NULL,
!                                        es->rtable,
!                                        es->pstmt->subplans);
      useprefix = list_length(es->rtable) > 1;

      /* Deparse each result column (we now include resjunk ones) */
--- 1258,1266 ----
          return;

      /* Set up deparsing context */
!     context = deparse_context_for_planstate((Node *) planstate,
!                                             ancestors,
!                                             es->rtable);
      useprefix = list_length(es->rtable) > 1;

      /* Deparse each result column (we now include resjunk ones) */
***************
*** 1294,1305 ****

  /*
   * Show a qualifier expression
-  *
-  * Note: outer_plan is the referent for any OUTER vars in the scan qual;
-  * this would be the outer side of a nestloop plan.  Pass NULL if none.
   */
  static void
! show_qual(List *qual, const char *qlabel, Plan *plan, Plan *outer_plan,
            bool useprefix, ExplainState *es)
  {
      List       *context;
--- 1280,1289 ----

  /*
   * Show a qualifier expression
   */
  static void
! show_qual(List *qual, const char *qlabel,
!           PlanState *planstate, List *ancestors,
            bool useprefix, ExplainState *es)
  {
      List       *context;
***************
*** 1314,1323 ****
      node = (Node *) make_ands_explicit(qual);

      /* Set up deparsing context */
!     context = deparse_context_for_plan((Node *) plan,
!                                        (Node *) outer_plan,
!                                        es->rtable,
!                                        es->pstmt->subplans);

      /* Deparse the expression */
      exprstr = deparse_expression(node, context, useprefix, false);
--- 1298,1306 ----
      node = (Node *) make_ands_explicit(qual);

      /* Set up deparsing context */
!     context = deparse_context_for_planstate((Node *) planstate,
!                                             ancestors,
!                                             es->rtable);

      /* Deparse the expression */
      exprstr = deparse_expression(node, context, useprefix, false);
***************
*** 1331,1366 ****
   */
  static void
  show_scan_qual(List *qual, const char *qlabel,
!                Plan *scan_plan, Plan *outer_plan,
                 ExplainState *es)
  {
      bool        useprefix;

!     useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan) ||
!                  es->verbose);
!     show_qual(qual, qlabel, scan_plan, outer_plan, useprefix, es);
  }

  /*
   * Show a qualifier expression for an upper-level plan node
   */
  static void
! show_upper_qual(List *qual, const char *qlabel, Plan *plan, ExplainState *es)
  {
      bool        useprefix;

      useprefix = (list_length(es->rtable) > 1 || es->verbose);
!     show_qual(qual, qlabel, plan, NULL, useprefix, es);
  }

  /*
   * Show the sort keys for a Sort node.
   */
  static void
! show_sort_keys(Plan *sortplan, ExplainState *es)
  {
!     int            nkeys = ((Sort *) sortplan)->numCols;
!     AttrNumber *keycols = ((Sort *) sortplan)->sortColIdx;
      List       *context;
      List       *result = NIL;
      bool        useprefix;
--- 1314,1351 ----
   */
  static void
  show_scan_qual(List *qual, const char *qlabel,
!                PlanState *planstate, List *ancestors,
                 ExplainState *es)
  {
      bool        useprefix;

!     useprefix = (IsA(planstate->plan, SubqueryScan) || es->verbose);
!     show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
  }

  /*
   * Show a qualifier expression for an upper-level plan node
   */
  static void
! show_upper_qual(List *qual, const char *qlabel,
!                 PlanState *planstate, List *ancestors,
!                 ExplainState *es)
  {
      bool        useprefix;

      useprefix = (list_length(es->rtable) > 1 || es->verbose);
!     show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
  }

  /*
   * Show the sort keys for a Sort node.
   */
  static void
! show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
  {
!     Sort       *plan = (Sort *) sortstate->ss.ps.plan;
!     int            nkeys = plan->numCols;
!     AttrNumber *keycols = plan->sortColIdx;
      List       *context;
      List       *result = NIL;
      bool        useprefix;
***************
*** 1371,1387 ****
          return;

      /* Set up deparsing context */
!     context = deparse_context_for_plan((Node *) sortplan,
!                                        NULL,
!                                        es->rtable,
!                                        es->pstmt->subplans);
      useprefix = (list_length(es->rtable) > 1 || es->verbose);

      for (keyno = 0; keyno < nkeys; keyno++)
      {
          /* find key expression in tlist */
          AttrNumber    keyresno = keycols[keyno];
!         TargetEntry *target = get_tle_by_resno(sortplan->targetlist, keyresno);

          if (!target)
              elog(ERROR, "no tlist entry for key %d", keyresno);
--- 1356,1372 ----
          return;

      /* Set up deparsing context */
!     context = deparse_context_for_planstate((Node *) sortstate,
!                                             ancestors,
!                                             es->rtable);
      useprefix = (list_length(es->rtable) > 1 || es->verbose);

      for (keyno = 0; keyno < nkeys; keyno++)
      {
          /* find key expression in tlist */
          AttrNumber    keyresno = keycols[keyno];
!         TargetEntry *target = get_tle_by_resno(plan->plan.targetlist,
!                                                keyresno);

          if (!target)
              elog(ERROR, "no tlist entry for key %d", keyresno);
***************
*** 1596,1629 ****
   * Explain the constituent plans of a ModifyTable, Append, BitmapAnd,
   * or BitmapOr node.
   *
!  * Ordinarily we don't pass down outer_plan to our child nodes, but in these
!  * cases we must, since the node could be an "inner indexscan" in which case
!  * outer references can appear in the child nodes.
   */
  static void
! ExplainMemberNodes(List *plans, PlanState **planstate, Plan *outer_plan,
!                    ExplainState *es)
  {
!     ListCell   *lst;
!     int            j = 0;

!     foreach(lst, plans)
!     {
!         Plan       *subnode = (Plan *) lfirst(lst);
!
!         ExplainNode(subnode, planstate[j],
!                     outer_plan,
!                     "Member", NULL,
!                     es);
!         j++;
!     }
  }

  /*
   * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
   */
  static void
! ExplainSubPlans(List *plans, const char *relationship, ExplainState *es)
  {
      ListCell   *lst;

--- 1581,1613 ----
   * Explain the constituent plans of a ModifyTable, Append, BitmapAnd,
   * or BitmapOr node.
   *
!  * The ancestors list should already contain the immediate parent of these
!  * plans.
!  *
!  * Note: we don't actually need to examine the Plan list members, but
!  * we need the list in order to determine the length of the PlanState array.
   */
  static void
! ExplainMemberNodes(List *plans, PlanState **planstates,
!                    List *ancestors, ExplainState *es)
  {
!     int            nplans = list_length(plans);
!     int            j;

!     for (j = 0; j < nplans; j++)
!         ExplainNode(planstates[j], ancestors,
!                     "Member", NULL, es);
  }

  /*
   * Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
+  *
+  * The ancestors list should already contain the immediate parent of these
+  * SubPlanStates.
   */
  static void
! ExplainSubPlans(List *plans, List *ancestors,
!                 const char *relationship, ExplainState *es)
  {
      ListCell   *lst;

***************
*** 1632,1642 ****
          SubPlanState *sps = (SubPlanState *) lfirst(lst);
          SubPlan    *sp = (SubPlan *) sps->xprstate.expr;

!         ExplainNode(exec_subplan_get_plan(es->pstmt, sp),
!                     sps->planstate,
!                     NULL,
!                     relationship, sp->plan_name,
!                     es);
      }
  }

--- 1616,1623 ----
          SubPlanState *sps = (SubPlanState *) lfirst(lst);
          SubPlan    *sp = (SubPlan *) sps->xprstate.expr;

!         ExplainNode(sps->planstate, ancestors,
!                     relationship, sp->plan_name, es);
      }
  }

Index: src/backend/utils/adt/ruleutils.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v
retrieving revision 1.327
diff -c -r1.327 ruleutils.c
*** src/backend/utils/adt/ruleutils.c    9 Jul 2010 21:11:47 -0000    1.327
--- src/backend/utils/adt/ruleutils.c    13 Jul 2010 18:13:25 -0000
***************
*** 102,117 ****
   * The rangetable is the list of actual RTEs from the query tree, and the
   * cte list is the list of actual CTEs.
   *
!  * For deparsing plan trees, we provide for outer and inner subplan nodes.
!  * The tlists of these nodes are used to resolve OUTER and INNER varnos.
!  * Also, in the plan-tree case we don't have access to the parse-time CTE
!  * list, so we need a list of subplans instead.
   */
  typedef struct
  {
      List       *rtable;            /* List of RangeTblEntry nodes */
      List       *ctes;            /* List of CommonTableExpr nodes */
!     List       *subplans;        /* List of subplans, in plan-tree case */
      Plan       *outer_plan;        /* OUTER subplan, or NULL if none */
      Plan       *inner_plan;        /* INNER subplan, or NULL if none */
  } deparse_namespace;
--- 102,124 ----
   * The rangetable is the list of actual RTEs from the query tree, and the
   * cte list is the list of actual CTEs.
   *
!  * When deparsing plan trees, there is always just a single item in the
!  * deparse_namespace list (since a plan tree never contains Vars with
!  * varlevelsup > 0).  We store the PlanState node that is the immediate
!  * parent of the expression to be deparsed, as well as a list of that
!  * PlanState's ancestors.  In addition, we store the outer and inner
!  * subplan nodes, whose targetlists are used to resolve OUTER and INNER Vars.
!  * (Note: these could be derived on-the-fly from the planstate instead.)
   */
  typedef struct
  {
      List       *rtable;            /* List of RangeTblEntry nodes */
      List       *ctes;            /* List of CommonTableExpr nodes */
!     /* Remaining fields are used only when deparsing a Plan tree: */
!     PlanState  *planstate;        /* immediate parent of current expression */
!     List       *ancestors;        /* ancestors of planstate */
!     PlanState  *outer_planstate;    /* OUTER subplan state, or NULL if none */
!     PlanState  *inner_planstate;    /* INNER subplan state, or NULL if none */
      Plan       *outer_plan;        /* OUTER subplan, or NULL if none */
      Plan       *inner_plan;        /* INNER subplan, or NULL if none */
  } deparse_namespace;
***************
*** 154,159 ****
--- 161,175 ----
  static int print_function_arguments(StringInfo buf, HeapTuple proctup,
                           bool print_table_args, bool print_defaults);
  static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+ static void set_deparse_planstate(deparse_namespace *dpns, PlanState *ps);
+ static void push_child_plan(deparse_namespace *dpns, PlanState *ps,
+                 deparse_namespace *save_dpns);
+ static void pop_child_plan(deparse_namespace *dpns,
+                            deparse_namespace *save_dpns);
+ static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
+                    deparse_namespace *save_dpns);
+ static void pop_ancestor_plan(deparse_namespace *dpns,
+                               deparse_namespace *save_dpns);
  static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
               int prettyFlags);
  static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
***************
*** 183,193 ****
  static void get_rule_windowclause(Query *query, deparse_context *context);
  static void get_rule_windowspec(WindowClause *wc, List *targetList,
                      deparse_context *context);
- static void push_plan(deparse_namespace *dpns, Plan *subplan);
  static char *get_variable(Var *var, int levelsup, bool showstar,
               deparse_context *context);
  static RangeTblEntry *find_rte_by_refname(const char *refname,
                      deparse_context *context);
  static const char *get_simple_binary_op_name(OpExpr *expr);
  static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
  static void appendContextKeyword(deparse_context *context, const char *str,
--- 199,211 ----
  static void get_rule_windowclause(Query *query, deparse_context *context);
  static void get_rule_windowspec(WindowClause *wc, List *targetList,
                      deparse_context *context);
  static char *get_variable(Var *var, int levelsup, bool showstar,
               deparse_context *context);
  static RangeTblEntry *find_rte_by_refname(const char *refname,
                      deparse_context *context);
+ static void get_parameter(Param *param, deparse_context *context);
+ static void print_parameter_expr(Node *expr, ListCell *ancestor_cell,
+                      deparse_namespace *dpns, deparse_context *context);
  static const char *get_simple_binary_op_name(OpExpr *expr);
  static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
  static void appendContextKeyword(deparse_context *context, const char *str,
***************
*** 625,634 ****
          newrte->inFromCl = true;

          /* Build two-element rtable */
          dpns.rtable = list_make2(oldrte, newrte);
          dpns.ctes = NIL;
-         dpns.subplans = NIL;
-         dpns.outer_plan = dpns.inner_plan = NULL;

          /* Set up context with one-deep namespace stack */
          context.buf = &buf;
--- 643,651 ----
          newrte->inFromCl = true;

          /* Build two-element rtable */
+         memset(&dpns, 0, sizeof(dpns));
          dpns.rtable = list_make2(oldrte, newrte);
          dpns.ctes = NIL;

          /* Set up context with one-deep namespace stack */
          context.buf = &buf;
***************
*** 2072,2078 ****
      deparse_namespace *dpns;
      RangeTblEntry *rte;

!     dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));

      /* Build a minimal RTE for the rel */
      rte = makeNode(RangeTblEntry);
--- 2089,2095 ----
      deparse_namespace *dpns;
      RangeTblEntry *rte;

!     dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));

      /* Build a minimal RTE for the rel */
      rte = makeNode(RangeTblEntry);
***************
*** 2085,2147 ****
      /* Build one-element rtable */
      dpns->rtable = list_make1(rte);
      dpns->ctes = NIL;
-     dpns->subplans = NIL;
-     dpns->outer_plan = dpns->inner_plan = NULL;

      /* Return a one-deep namespace stack */
      return list_make1(dpns);
  }

  /*
!  * deparse_context_for_plan        - Build deparse context for a plan node
   *
   * When deparsing an expression in a Plan tree, we might have to resolve
   * OUTER or INNER references.  To do this, the caller must provide the
!  * parent Plan node.  In the normal case of a join plan node, OUTER and
!  * INNER references can be resolved by drilling down into the left and
!  * right child plans.  A special case is that a nestloop inner indexscan
!  * might have OUTER Vars, but the outer side of the join is not a child
!  * plan node.  To handle such cases the outer plan node must be passed
!  * separately.    (Pass NULL for outer_plan otherwise.)
   *
!  * Note: plan and outer_plan really ought to be declared as "Plan *", but
!  * we use "Node *" to avoid having to include plannodes.h in builtins.h.
   *
   * The plan's rangetable list must also be passed.  We actually prefer to use
   * the rangetable to resolve simple Vars, but the plan inputs are necessary
   * for Vars that reference expressions computed in subplan target lists.
-  *
-  * We also need the list of subplans associated with the Plan tree; this
-  * is for resolving references to CTE subplans.
   */
  List *
! deparse_context_for_plan(Node *plan, Node *outer_plan,
!                          List *rtable, List *subplans)
  {
      deparse_namespace *dpns;

!     dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));

      dpns->rtable = rtable;
      dpns->ctes = NIL;
!     dpns->subplans = subplans;

      /*
!      * Set up outer_plan and inner_plan from the Plan node (this includes
!      * various special cases for particular Plan types).
       */
!     push_plan(dpns, (Plan *) plan);

      /*
!      * If outer_plan is given, that overrides whatever we got from the plan.
       */
-     if (outer_plan)
-         dpns->outer_plan = (Plan *) outer_plan;

!     /* Return a one-deep namespace stack */
!     return list_make1(dpns);
  }

  /* ----------
   * make_ruledef            - reconstruct the CREATE RULE command
   *                  for a given pg_rewrite tuple
--- 2102,2292 ----
      /* Build one-element rtable */
      dpns->rtable = list_make1(rte);
      dpns->ctes = NIL;

      /* Return a one-deep namespace stack */
      return list_make1(dpns);
  }

  /*
!  * deparse_context_for_planstate    - Build deparse context for a plan
   *
   * When deparsing an expression in a Plan tree, we might have to resolve
   * OUTER or INNER references.  To do this, the caller must provide the
!  * parent PlanState node.  Then OUTER and INNER references can be resolved
!  * by drilling down into the left and right child plans.
   *
!  * Note: planstate really ought to be declared as "PlanState *", but we use
!  * "Node *" to avoid having to include execnodes.h in builtins.h.
!  *
!  * The ancestors list is a list of the PlanState's parent PlanStates, the
!  * most-closely-nested first.  This is needed to resolve PARAM_EXEC Params.
!  * Note we assume that all the PlanStates share the same rtable.
   *
   * The plan's rangetable list must also be passed.  We actually prefer to use
   * the rangetable to resolve simple Vars, but the plan inputs are necessary
   * for Vars that reference expressions computed in subplan target lists.
   */
  List *
! deparse_context_for_planstate(Node *planstate, List *ancestors,
!                               List *rtable)
  {
      deparse_namespace *dpns;

!     dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));

+     /* Initialize fields that stay the same across the whole plan tree */
      dpns->rtable = rtable;
      dpns->ctes = NIL;
!
!     /* Set our attention on the specific plan node passed in */
!     set_deparse_planstate(dpns, (PlanState *) planstate);
!     dpns->ancestors = ancestors;
!
!     /* Return a one-deep namespace stack */
!     return list_make1(dpns);
! }
!
! /*
!  * set_deparse_planstate: set up deparse_namespace to parse subexpressions
!  * of a given PlanState node
!  *
!  * This sets the planstate, outer_planstate, inner_planstate, outer_plan, and
!  * inner_plan fields.  Caller is responsible for adjusting the ancestors list
!  * if necessary.  Note that the rtable and ctes fields do not need to change
!  * when shifting attention to different plan nodes in a single plan tree.
!  */
! static void
! set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
! {
!     dpns->planstate = ps;
!
!     /*
!      * We special-case Append to pretend that the first child plan is the
!      * OUTER referent; we have to interpret OUTER Vars in the Append's tlist
!      * according to one of the children, and the first one is the most natural
!      * choice.    Likewise special-case ModifyTable to pretend that the first
!      * child plan is the OUTER referent; this is to support RETURNING lists
!      * containing references to non-target relations.
!      */
!     if (IsA(ps, AppendState))
!         dpns->outer_planstate = ((AppendState *) ps)->appendplans[0];
!     else if (IsA(ps, ModifyTableState))
!         dpns->outer_planstate = ((ModifyTableState *) ps)->mt_plans[0];
!     else
!         dpns->outer_planstate = outerPlanState(ps);
!
!     if (dpns->outer_planstate)
!         dpns->outer_plan = dpns->outer_planstate->plan;
!     else
!         dpns->outer_plan = NULL;

      /*
!      * For a SubqueryScan, pretend the subplan is INNER referent.  (We don't
!      * use OUTER because that could someday conflict with the normal meaning.)
!      * Likewise, for a CteScan, pretend the subquery's plan is INNER referent.
       */
!     if (IsA(ps, SubqueryScanState))
!         dpns->inner_planstate = ((SubqueryScanState *) ps)->subplan;
!     else if (IsA(ps, CteScanState))
!         dpns->inner_planstate = ((CteScanState *) ps)->cteplanstate;
!     else
!         dpns->inner_planstate = innerPlanState(ps);
!
!     if (dpns->inner_planstate)
!         dpns->inner_plan = dpns->inner_planstate->plan;
!     else
!         dpns->inner_plan = NULL;
! }
!
! /*
!  * push_child_plan: temporarily transfer deparsing attention to a child plan
!  *
!  * When expanding an OUTER or INNER reference, we must adjust the deparse
!  * context in case the referenced expression itself uses OUTER/INNER.    We
!  * modify the top stack entry in-place to avoid affecting levelsup issues
!  * (although in a Plan tree there really shouldn't be any).
!  *
!  * Caller must provide a local deparse_namespace variable to save the
!  * previous state for pop_child_plan.
!  */
! static void
! push_child_plan(deparse_namespace *dpns, PlanState *ps,
!                 deparse_namespace *save_dpns)
! {
!     /* Save state for restoration later */
!     *save_dpns = *dpns;

      /*
!      * Currently we don't bother to adjust the ancestors list, because an
!      * OUTER or INNER reference really shouldn't contain any Params that
!      * would be set by the parent node itself.  If we did want to adjust it,
!      * lcons'ing dpns->planstate onto dpns->ancestors would be the appropriate
!      * thing --- and pop_child_plan would need to undo the change to the list.
       */

!     /* Set attention on selected child */
!     set_deparse_planstate(dpns, ps);
! }
!
! /*
!  * pop_child_plan: undo the effects of push_child_plan
!  */
! static void
! pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
! {
!     /* Restore fields changed by push_child_plan */
!     *dpns = *save_dpns;
! }
!
! /*
!  * push_ancestor_plan: temporarily transfer deparsing attention to an
!  * ancestor plan
!  *
!  * When expanding a Param reference, we must adjust the deparse context
!  * to match the plan node that contains the expression being printed;
!  * otherwise we'd fail if that expression itself contains a Param or
!  * OUTER/INNER variables.
!  *
!  * The target ancestor is conveniently identified by the ListCell holding it
!  * in dpns->ancestors.
!  *
!  * Caller must provide a local deparse_namespace variable to save the
!  * previous state for pop_ancestor_plan.
!  */
! static void
! push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
!                    deparse_namespace *save_dpns)
! {
!     PlanState  *ps = (PlanState *) lfirst(ancestor_cell);
!     List       *ancestors;
!
!     /* Save state for restoration later */
!     *save_dpns = *dpns;
!
!     /* Build a new ancestor list with just this node's ancestors */
!     ancestors = NIL;
!     while ((ancestor_cell = lnext(ancestor_cell)) != NULL)
!         ancestors = lappend(ancestors, lfirst(ancestor_cell));
!     dpns->ancestors = ancestors;
!
!     /* Set attention on selected ancestor */
!     set_deparse_planstate(dpns, ps);
! }
!
! /*
!  * pop_ancestor_plan: undo the effects of push_ancestor_plan
!  */
! static void
! pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
! {
!     /* Free the ancestor list made in push_ancestor_plan */
!     list_free(dpns->ancestors);
!
!     /* Restore fields changed by push_ancestor_plan */
!     *dpns = *save_dpns;
  }

+
  /* ----------
   * make_ruledef            - reconstruct the CREATE RULE command
   *                  for a given pg_rewrite tuple
***************
*** 2285,2294 ****
          context.varprefix = (list_length(query->rtable) != 1);
          context.prettyFlags = prettyFlags;
          context.indentLevel = PRETTYINDENT_STD;
          dpns.rtable = query->rtable;
          dpns.ctes = query->cteList;
-         dpns.subplans = NIL;
-         dpns.outer_plan = dpns.inner_plan = NULL;

          get_rule_expr(qual, &context, false);
      }
--- 2430,2439 ----
          context.varprefix = (list_length(query->rtable) != 1);
          context.prettyFlags = prettyFlags;
          context.indentLevel = PRETTYINDENT_STD;
+
+         memset(&dpns, 0, sizeof(dpns));
          dpns.rtable = query->rtable;
          dpns.ctes = query->cteList;

          get_rule_expr(qual, &context, false);
      }
***************
*** 2432,2441 ****
      context.prettyFlags = prettyFlags;
      context.indentLevel = startIndent;

      dpns.rtable = query->rtable;
      dpns.ctes = query->cteList;
-     dpns.subplans = NIL;
-     dpns.outer_plan = dpns.inner_plan = NULL;

      switch (query->commandType)
      {
--- 2577,2585 ----
      context.prettyFlags = prettyFlags;
      context.indentLevel = startIndent;

+     memset(&dpns, 0, sizeof(dpns));
      dpns.rtable = query->rtable;
      dpns.ctes = query->cteList;

      switch (query->commandType)
      {
***************
*** 3481,3537 ****


  /*
-  * push_plan: set up deparse_namespace to recurse into the tlist of a subplan
-  *
-  * When expanding an OUTER or INNER reference, we must push new outer/inner
-  * subplans in case the referenced expression itself uses OUTER/INNER.    We
-  * modify the top stack entry in-place to avoid affecting levelsup issues
-  * (although in a Plan tree there really shouldn't be any).
-  *
-  * Caller must save and restore outer_plan and inner_plan around this.
-  *
-  * We also use this to initialize the fields during deparse_context_for_plan.
-  */
- static void
- push_plan(deparse_namespace *dpns, Plan *subplan)
- {
-     /*
-      * We special-case Append to pretend that the first child plan is the
-      * OUTER referent; we have to interpret OUTER Vars in the Append's tlist
-      * according to one of the children, and the first one is the most natural
-      * choice.    Likewise special-case ModifyTable to pretend that the first
-      * child plan is the OUTER referent; this is to support RETURNING lists
-      * containing references to non-target relations.
-      */
-     if (IsA(subplan, Append))
-         dpns->outer_plan = (Plan *) linitial(((Append *) subplan)->appendplans);
-     else if (IsA(subplan, ModifyTable))
-         dpns->outer_plan = (Plan *) linitial(((ModifyTable *) subplan)->plans);
-     else
-         dpns->outer_plan = outerPlan(subplan);
-
-     /*
-      * For a SubqueryScan, pretend the subplan is INNER referent.  (We don't
-      * use OUTER because that could someday conflict with the normal meaning.)
-      * Likewise, for a CteScan, pretend the subquery's plan is INNER referent.
-      */
-     if (IsA(subplan, SubqueryScan))
-         dpns->inner_plan = ((SubqueryScan *) subplan)->subplan;
-     else if (IsA(subplan, CteScan))
-     {
-         int            ctePlanId = ((CteScan *) subplan)->ctePlanId;
-
-         if (ctePlanId > 0 && ctePlanId <= list_length(dpns->subplans))
-             dpns->inner_plan = list_nth(dpns->subplans, ctePlanId - 1);
-         else
-             dpns->inner_plan = NULL;
-     }
-     else
-         dpns->inner_plan = innerPlan(subplan);
- }
-
-
- /*
   * Display a Var appropriately.
   *
   * In some cases (currently only when recursing into an unnamed join)
--- 3625,3630 ----
***************
*** 3576,3592 ****
      else if (var->varno == OUTER && dpns->outer_plan)
      {
          TargetEntry *tle;
!         Plan       *save_outer;
!         Plan       *save_inner;

          tle = get_tle_by_resno(dpns->outer_plan->targetlist, var->varattno);
          if (!tle)
              elog(ERROR, "bogus varattno for OUTER var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         save_outer = dpns->outer_plan;
!         save_inner = dpns->inner_plan;
!         push_plan(dpns, dpns->outer_plan);

          /*
           * Force parentheses because our caller probably assumed a Var is a
--- 3669,3682 ----
      else if (var->varno == OUTER && dpns->outer_plan)
      {
          TargetEntry *tle;
!         deparse_namespace save_dpns;

          tle = get_tle_by_resno(dpns->outer_plan->targetlist, var->varattno);
          if (!tle)
              elog(ERROR, "bogus varattno for OUTER var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         push_child_plan(dpns, dpns->outer_planstate, &save_dpns);

          /*
           * Force parentheses because our caller probably assumed a Var is a
***************
*** 3598,3621 ****
          if (!IsA(tle->expr, Var))
              appendStringInfoChar(buf, ')');

!         dpns->outer_plan = save_outer;
!         dpns->inner_plan = save_inner;
          return NULL;
      }
      else if (var->varno == INNER && dpns->inner_plan)
      {
          TargetEntry *tle;
!         Plan       *save_outer;
!         Plan       *save_inner;

          tle = get_tle_by_resno(dpns->inner_plan->targetlist, var->varattno);
          if (!tle)
              elog(ERROR, "bogus varattno for INNER var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         save_outer = dpns->outer_plan;
!         save_inner = dpns->inner_plan;
!         push_plan(dpns, dpns->inner_plan);

          /*
           * Force parentheses because our caller probably assumed a Var is a
--- 3688,3707 ----
          if (!IsA(tle->expr, Var))
              appendStringInfoChar(buf, ')');

!         pop_child_plan(dpns, &save_dpns);
          return NULL;
      }
      else if (var->varno == INNER && dpns->inner_plan)
      {
          TargetEntry *tle;
!         deparse_namespace save_dpns;

          tle = get_tle_by_resno(dpns->inner_plan->targetlist, var->varattno);
          if (!tle)
              elog(ERROR, "bogus varattno for INNER var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         push_child_plan(dpns, dpns->inner_planstate, &save_dpns);

          /*
           * Force parentheses because our caller probably assumed a Var is a
***************
*** 3627,3634 ****
          if (!IsA(tle->expr, Var))
              appendStringInfoChar(buf, ')');

!         dpns->outer_plan = save_outer;
!         dpns->inner_plan = save_inner;
          return NULL;
      }
      else
--- 3713,3719 ----
          if (!IsA(tle->expr, Var))
              appendStringInfoChar(buf, ')');

!         pop_child_plan(dpns, &save_dpns);
          return NULL;
      }
      else
***************
*** 3653,3669 ****
          dpns->inner_plan)
      {
          TargetEntry *tle;
!         Plan       *save_outer;
!         Plan       *save_inner;

          tle = get_tle_by_resno(dpns->inner_plan->targetlist, var->varattno);
          if (!tle)
              elog(ERROR, "bogus varattno for subquery var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         save_outer = dpns->outer_plan;
!         save_inner = dpns->inner_plan;
!         push_plan(dpns, dpns->inner_plan);

          /*
           * Force parentheses because our caller probably assumed a Var is a
--- 3738,3751 ----
          dpns->inner_plan)
      {
          TargetEntry *tle;
!         deparse_namespace save_dpns;

          tle = get_tle_by_resno(dpns->inner_plan->targetlist, var->varattno);
          if (!tle)
              elog(ERROR, "bogus varattno for subquery var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         push_child_plan(dpns, dpns->inner_planstate, &save_dpns);

          /*
           * Force parentheses because our caller probably assumed a Var is a
***************
*** 3675,3682 ****
          if (!IsA(tle->expr, Var))
              appendStringInfoChar(buf, ')');

!         dpns->outer_plan = save_outer;
!         dpns->inner_plan = save_inner;
          return NULL;
      }

--- 3757,3763 ----
          if (!IsA(tle->expr, Var))
              appendStringInfoChar(buf, ')');

!         pop_child_plan(dpns, &save_dpns);
          return NULL;
      }

***************
*** 3827,3834 ****
      else if (var->varno == OUTER && dpns->outer_plan)
      {
          TargetEntry *tle;
!         Plan       *save_outer;
!         Plan       *save_inner;
          const char *result;

          tle = get_tle_by_resno(dpns->outer_plan->targetlist, var->varattno);
--- 3908,3914 ----
      else if (var->varno == OUTER && dpns->outer_plan)
      {
          TargetEntry *tle;
!         deparse_namespace save_dpns;
          const char *result;

          tle = get_tle_by_resno(dpns->outer_plan->targetlist, var->varattno);
***************
*** 3836,3857 ****
              elog(ERROR, "bogus varattno for OUTER var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         save_outer = dpns->outer_plan;
!         save_inner = dpns->inner_plan;
!         push_plan(dpns, dpns->outer_plan);

          result = get_name_for_var_field((Var *) tle->expr, fieldno,
                                          levelsup, context);

!         dpns->outer_plan = save_outer;
!         dpns->inner_plan = save_inner;
          return result;
      }
      else if (var->varno == INNER && dpns->inner_plan)
      {
          TargetEntry *tle;
!         Plan       *save_outer;
!         Plan       *save_inner;
          const char *result;

          tle = get_tle_by_resno(dpns->inner_plan->targetlist, var->varattno);
--- 3916,3933 ----
              elog(ERROR, "bogus varattno for OUTER var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         push_child_plan(dpns, dpns->outer_planstate, &save_dpns);

          result = get_name_for_var_field((Var *) tle->expr, fieldno,
                                          levelsup, context);

!         pop_child_plan(dpns, &save_dpns);
          return result;
      }
      else if (var->varno == INNER && dpns->inner_plan)
      {
          TargetEntry *tle;
!         deparse_namespace save_dpns;
          const char *result;

          tle = get_tle_by_resno(dpns->inner_plan->targetlist, var->varattno);
***************
*** 3859,3873 ****
              elog(ERROR, "bogus varattno for INNER var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         save_outer = dpns->outer_plan;
!         save_inner = dpns->inner_plan;
!         push_plan(dpns, dpns->inner_plan);

          result = get_name_for_var_field((Var *) tle->expr, fieldno,
                                          levelsup, context);

!         dpns->outer_plan = save_outer;
!         dpns->inner_plan = save_inner;
          return result;
      }
      else
--- 3935,3946 ----
              elog(ERROR, "bogus varattno for INNER var: %d", var->varattno);

          Assert(netlevelsup == 0);
!         push_child_plan(dpns, dpns->inner_planstate, &save_dpns);

          result = get_name_for_var_field((Var *) tle->expr, fieldno,
                                          levelsup, context);

!         pop_child_plan(dpns, &save_dpns);
          return result;
      }
      else
***************
*** 3926,3935 ****
                          deparse_namespace mydpns;
                          const char *result;

                          mydpns.rtable = rte->subquery->rtable;
                          mydpns.ctes = rte->subquery->cteList;
-                         mydpns.subplans = NIL;
-                         mydpns.outer_plan = mydpns.inner_plan = NULL;

                          context->namespaces = lcons(&mydpns,
                                                      context->namespaces);
--- 3999,4007 ----
                          deparse_namespace mydpns;
                          const char *result;

+                         memset(&mydpns, 0, sizeof(mydpns));
                          mydpns.rtable = rte->subquery->rtable;
                          mydpns.ctes = rte->subquery->cteList;

                          context->namespaces = lcons(&mydpns,
                                                      context->namespaces);
***************
*** 3954,3961 ****
                       * look into the child plan's tlist instead.
                       */
                      TargetEntry *tle;
!                     Plan       *save_outer;
!                     Plan       *save_inner;
                      const char *result;

                      if (!dpns->inner_plan)
--- 4026,4032 ----
                       * look into the child plan's tlist instead.
                       */
                      TargetEntry *tle;
!                     deparse_namespace save_dpns;
                      const char *result;

                      if (!dpns->inner_plan)
***************
*** 3967,3981 ****
                          elog(ERROR, "bogus varattno for subquery var: %d",
                               attnum);
                      Assert(netlevelsup == 0);
!                     save_outer = dpns->outer_plan;
!                     save_inner = dpns->inner_plan;
!                     push_plan(dpns, dpns->inner_plan);

                      result = get_name_for_var_field((Var *) tle->expr, fieldno,
                                                      levelsup, context);

!                     dpns->outer_plan = save_outer;
!                     dpns->inner_plan = save_inner;
                      return result;
                  }
              }
--- 4038,4049 ----
                          elog(ERROR, "bogus varattno for subquery var: %d",
                               attnum);
                      Assert(netlevelsup == 0);
!                     push_child_plan(dpns, dpns->inner_planstate, &save_dpns);

                      result = get_name_for_var_field((Var *) tle->expr, fieldno,
                                                      levelsup, context);

!                     pop_child_plan(dpns, &save_dpns);
                      return result;
                  }
              }
***************
*** 4049,4058 ****
                          deparse_namespace mydpns;
                          const char *result;

                          mydpns.rtable = ctequery->rtable;
                          mydpns.ctes = ctequery->cteList;
-                         mydpns.subplans = NIL;
-                         mydpns.outer_plan = mydpns.inner_plan = NULL;

                          new_nslist = list_copy_tail(context->namespaces,
                                                      ctelevelsup);
--- 4117,4125 ----
                          deparse_namespace mydpns;
                          const char *result;

+                         memset(&mydpns, 0, sizeof(mydpns));
                          mydpns.rtable = ctequery->rtable;
                          mydpns.ctes = ctequery->cteList;

                          new_nslist = list_copy_tail(context->namespaces,
                                                      ctelevelsup);
***************
*** 4076,4083 ****
                       * can look into the subplan's tlist instead.
                       */
                      TargetEntry *tle;
!                     Plan       *save_outer;
!                     Plan       *save_inner;
                      const char *result;

                      if (!dpns->inner_plan)
--- 4143,4149 ----
                       * can look into the subplan's tlist instead.
                       */
                      TargetEntry *tle;
!                     deparse_namespace save_dpns;
                      const char *result;

                      if (!dpns->inner_plan)
***************
*** 4089,4103 ****
                          elog(ERROR, "bogus varattno for subquery var: %d",
                               attnum);
                      Assert(netlevelsup == 0);
!                     save_outer = dpns->outer_plan;
!                     save_inner = dpns->inner_plan;
!                     push_plan(dpns, dpns->inner_plan);

                      result = get_name_for_var_field((Var *) tle->expr, fieldno,
                                                      levelsup, context);

!                     dpns->outer_plan = save_outer;
!                     dpns->inner_plan = save_inner;
                      return result;
                  }
              }
--- 4155,4166 ----
                          elog(ERROR, "bogus varattno for subquery var: %d",
                               attnum);
                      Assert(netlevelsup == 0);
!                     push_child_plan(dpns, dpns->inner_planstate, &save_dpns);

                      result = get_name_for_var_field((Var *) tle->expr, fieldno,
                                                      levelsup, context);

!                     pop_child_plan(dpns, &save_dpns);
                      return result;
                  }
              }
***************
*** 4160,4165 ****
--- 4223,4387 ----
      return result;
  }

+ /*
+  * Display a Param appropriately.
+  */
+ static void
+ get_parameter(Param *param, deparse_context *context)
+ {
+     /*
+      * If it's a PARAM_EXEC parameter, try to locate the expression from
+      * which the parameter was computed.  This will necessarily be in some
+      * ancestor of the current expression's PlanState.  Note that failing
+      * to find a referent isn't an error, since the Param might well be a
+      * subplan output rather than an input.
+      */
+     if (param->paramkind == PARAM_EXEC)
+     {
+         deparse_namespace *dpns;
+         PlanState  *child_ps;
+         bool        in_same_plan_level;
+         ListCell   *lc;
+
+         dpns = (deparse_namespace *) linitial(context->namespaces);
+         child_ps = dpns->planstate;
+         in_same_plan_level = true;
+
+         foreach(lc, dpns->ancestors)
+         {
+             PlanState  *ps = (PlanState *) lfirst(lc);
+             ListCell   *lc2;
+
+             /*
+              * NestLoops transmit params to their inner child only; also,
+              * once we've crawled up out of a subplan, this couldn't
+              * possibly be the right match.
+              */
+             if (IsA(ps, NestLoopState) &&
+                 child_ps == innerPlanState(ps) &&
+                 in_same_plan_level)
+             {
+                 NestLoop   *nl = (NestLoop *) ps->plan;
+
+                 foreach(lc2, nl->nestParams)
+                 {
+                     NestLoopParam  *nlp = (NestLoopParam *) lfirst(lc2);
+
+                     if (nlp->paramno == param->paramid)
+                     {
+                         /* Found a match, so print it */
+                         print_parameter_expr((Node *) nlp->paramval, lc,
+                                              dpns, context);
+                         return;
+                     }
+                 }
+             }
+
+             /*
+              * Check to see if we're crawling up from an initPlan.
+              */
+             foreach(lc2, ps->initPlan)
+             {
+                 SubPlanState *sstate = (SubPlanState *) lfirst(lc2);
+                 SubPlan    *subplan = (SubPlan *) sstate->xprstate.expr;
+                 ListCell   *lc3;
+                 ListCell   *lc4;
+
+                 if (child_ps != sstate->planstate)
+                     continue;
+
+                 /* Matched initPlan, so check its arguments */
+                 forboth(lc3, subplan->parParam, lc4, subplan->args)
+                 {
+                     int        paramid = lfirst_int(lc3);
+                     Node   *arg = (Node *) lfirst(lc4);
+
+                     if (paramid == param->paramid)
+                     {
+                         /* Found a match, so print it */
+                         print_parameter_expr(arg, lc, dpns, context);
+                         return;
+                     }
+                 }
+
+                 /* Keep looking, but we are emerging from an initPlan. */
+                 in_same_plan_level = false;
+                 break;
+             }
+
+             /*
+              * Likewise for subPlans.
+              */
+             foreach(lc2, ps->subPlan)
+             {
+                 SubPlanState *sstate = (SubPlanState *) lfirst(lc2);
+                 SubPlan    *subplan = (SubPlan *) sstate->xprstate.expr;
+                 ListCell   *lc3;
+                 ListCell   *lc4;
+
+                 if (child_ps != sstate->planstate)
+                     continue;
+
+                 /* Matched subPlan, so check its arguments */
+                 forboth(lc3, subplan->parParam, lc4, subplan->args)
+                 {
+                     int        paramid = lfirst_int(lc3);
+                     Node   *arg = (Node *) lfirst(lc4);
+
+                     if (paramid == param->paramid)
+                     {
+                         /* Found a match, so print it */
+                         print_parameter_expr(arg, lc, dpns, context);
+                         return;
+                     }
+                 }
+
+                 /* Keep looking, but we are emerging from a subPlan. */
+                 in_same_plan_level = false;
+                 break;
+             }
+
+             /* No luck, crawl up to next ancestor */
+             child_ps = ps;
+         }
+     }
+
+     /*
+      * Not PARAM_EXEC, or couldn't find referent: just print $N.
+      */
+     appendStringInfo(context->buf, "$%d", param->paramid);
+ }
+
+ /* Print a parameter reference expression found by get_parameter */
+ static void
+ print_parameter_expr(Node *expr, ListCell *ancestor_cell,
+                      deparse_namespace *dpns, deparse_context *context)
+ {
+     deparse_namespace save_dpns;
+     bool        save_varprefix;
+
+     /* Switch attention to the ancestor plan node */
+     push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
+
+     /*
+      * Force prefixing of Vars, since they won't belong to the relation being
+      * scanned in the original plan node.
+      */
+     save_varprefix = context->varprefix;
+     context->varprefix = true;
+
+     /*
+      * We don't need to add parentheses because a Param's expansion is
+      * (currently) always a Var or Aggref.
+      */
+     Assert(IsA(expr, Var) || IsA(expr, Aggref));
+
+     get_rule_expr(expr, context, false);
+
+     context->varprefix = save_varprefix;
+
+     pop_ancestor_plan(dpns, &save_dpns);
+ }

  /*
   * get_simple_binary_op_name
***************
*** 4496,4502 ****
              break;

          case T_Param:
!             appendStringInfo(buf, "$%d", ((Param *) node)->paramid);
              break;

          case T_Aggref:
--- 4718,4724 ----
              break;

          case T_Param:
!             get_parameter((Param *) node, context);
              break;

          case T_Aggref:
Index: src/include/utils/builtins.h
===================================================================
RCS file: /cvsroot/pgsql/src/include/utils/builtins.h,v
retrieving revision 1.350
diff -c -r1.350 builtins.h
*** src/include/utils/builtins.h    6 Jul 2010 19:19:00 -0000    1.350
--- src/include/utils/builtins.h    13 Jul 2010 18:13:25 -0000
***************
*** 609,616 ****
  extern char *deparse_expression(Node *expr, List *dpcontext,
                     bool forceprefix, bool showimplicit);
  extern List *deparse_context_for(const char *aliasname, Oid relid);
! extern List *deparse_context_for_plan(Node *plan, Node *outer_plan,
!                          List *rtable, List *subplans);
  extern const char *quote_identifier(const char *ident);
  extern char *quote_qualified_identifier(const char *qualifier,
                             const char *ident);
--- 609,616 ----
  extern char *deparse_expression(Node *expr, List *dpcontext,
                     bool forceprefix, bool showimplicit);
  extern List *deparse_context_for(const char *aliasname, Oid relid);
! extern List *deparse_context_for_planstate(Node *planstate, List *ancestors,
!                               List *rtable);
  extern const char *quote_identifier(const char *ident);
  extern char *quote_qualified_identifier(const char *qualifier,
                             const char *ident);