Re: Confusing EXPLAIN output in case of inherited tables - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Confusing EXPLAIN output in case of inherited tables
Date
Msg-id 3138.1348167308@sss.pgh.pa.us
Whole thread Raw
In response to Re: Confusing EXPLAIN output in case of inherited tables  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Confusing EXPLAIN output in case of inherited tables
List pgsql-hackers
I wrote:
> I got interested in this problem again now that we have a user complaint
> about it (bug #7553).
> ...
> As far as the relation-rename problem goes, I propose that what we
> should do is have ruleutils.c invent nonconflicting fake aliases for
> each RTE in the query tree.  This would allow getting rid of some of the
> dubious heuristics in get_variable: it should just print the chosen
> alias and be done.  (It still has to do something different for unnamed
> joins, but we can leave that part alone I think.)

Attached is a draft patch for this.  It fixes the view-dumping problems
that I exhibited in
http://archives.postgresql.org/message-id/29791.1327718297@sss.pgh.pa.us
as well as nicely cleaning up Ashutosh's original complaint at
http://archives.postgresql.org/pgsql-hackers/2012-01/msg00505.php
There are quite a few more changes in the regression-test plan printouts
than was originally discussed, but they seem to be generally for the
better IMO: for instance there is no longer any problem with different
RTEs being printed with identical names in EXPLAIN.

One thing I found while working on this is that some of the
inconsistency is not really EXPLAIN's fault but the planner's: the
planner does not take any trouble to avoid duplicate RTE aliases when it
manufactures additional RTEs, which it does in at least two places
(inheritance expansion and min/max aggregate optimization).  In my first
version of the patch I was getting EXPLAIN printouts like this for
inheritance append-plans:

   Nested Loop
     ->  Limit
           ->  Seq Scan on int4_tbl
     ->  Append
!          ->  Index Scan using patest0i on patest0 patest0_1
                 Index Cond: (id = int4_tbl.f1)
!          ->  Index Scan using patest1i on patest1
                 Index Cond: (id = int4_tbl.f1)
!          ->  Index Scan using patest2i on patest2
                 Index Cond: (id = int4_tbl.f1)

That happened because the original inheritance-root RTE got the
"patest0" alias, and then the inheritance-child RTE for the parent
relation got stuck with "patest0_1".  This isn't terribly desirable
since the inheritance-root RTE isn't actually visible anywhere in
the EXPLAIN printout, so giving it the preferred name isn't ideal.

In the attached I've hacked around this by causing the planner to
assign new aliases to RTEs that it replaces in this way (see planagg.c
and prepunion.c diffs).  This seems like a bit of a kluge, but it
doesn't take much code.  An alternative that I'm considering is to
have EXPLAIN make a pre-pass over the plan tree to identify which
RTEs will actually be referenced, and then consider only those RTEs
while assigning aliases.  This would be a great deal more code though,
and code which would require maintenance every time we add plan node
types etc.  So I'm not sure it's really a better answer.  Thoughts?

            regards, tom lane

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1e8f618a3476b9e171658900f92532b87b3cde97..ab39c197c07f44c486104fbfe0f22c513413a716 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** ExplainPrintPlan(ExplainState *es, Query
*** 542,547 ****
--- 542,548 ----
      Assert(queryDesc->plannedstmt != NULL);
      es->pstmt = queryDesc->plannedstmt;
      es->rtable = queryDesc->plannedstmt->rtable;
+     es->rtable_names = select_rtable_names_for_explain(es->rtable);
      ExplainNode(queryDesc->planstate, NIL, NULL, NULL, es);
  }

*************** show_plan_tlist(PlanState *planstate, Li
*** 1440,1446 ****
      /* 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) */
--- 1441,1448 ----
      /* Set up deparsing context */
      context = deparse_context_for_planstate((Node *) planstate,
                                              ancestors,
!                                             es->rtable,
!                                             es->rtable_names);
      useprefix = list_length(es->rtable) > 1;

      /* Deparse each result column (we now include resjunk ones) */
*************** show_expression(Node *node, const char *
*** 1471,1477 ****
      /* Set up deparsing context */
      context = deparse_context_for_planstate((Node *) planstate,
                                              ancestors,
!                                             es->rtable);

      /* Deparse the expression */
      exprstr = deparse_expression(node, context, useprefix, false);
--- 1473,1480 ----
      /* Set up deparsing context */
      context = deparse_context_for_planstate((Node *) planstate,
                                              ancestors,
!                                             es->rtable,
!                                             es->rtable_names);

      /* Deparse the expression */
      exprstr = deparse_expression(node, context, useprefix, false);
*************** show_sort_keys_common(PlanState *plansta
*** 1573,1579 ****
      /* Set up deparsing context */
      context = deparse_context_for_planstate((Node *) planstate,
                                              ancestors,
!                                             es->rtable);
      useprefix = (list_length(es->rtable) > 1 || es->verbose);

      for (keyno = 0; keyno < nkeys; keyno++)
--- 1576,1583 ----
      /* Set up deparsing context */
      context = deparse_context_for_planstate((Node *) planstate,
                                              ancestors,
!                                             es->rtable,
!                                             es->rtable_names);
      useprefix = (list_length(es->rtable) > 1 || es->verbose);

      for (keyno = 0; keyno < nkeys; keyno++)
*************** ExplainTargetRel(Plan *plan, Index rti,
*** 1813,1820 ****
--- 1817,1826 ----
      char       *namespace = NULL;
      const char *objecttag = NULL;
      RangeTblEntry *rte;
+     char       *refname;

      rte = rt_fetch(rti, es->rtable);
+     refname = (char *) list_nth(es->rtable_names, rti - 1);

      switch (nodeTag(plan))
      {
*************** ExplainTargetRel(Plan *plan, Index rti,
*** 1887,1896 ****
                               quote_identifier(objectname));
          else if (objectname != NULL)
              appendStringInfo(es->str, " %s", quote_identifier(objectname));
!         if (objectname == NULL ||
!             strcmp(rte->eref->aliasname, objectname) != 0)
!             appendStringInfo(es->str, " %s",
!                              quote_identifier(rte->eref->aliasname));
      }
      else
      {
--- 1893,1901 ----
                               quote_identifier(objectname));
          else if (objectname != NULL)
              appendStringInfo(es->str, " %s", quote_identifier(objectname));
!         if (refname != NULL &&
!             (objectname == NULL || strcmp(refname, objectname) != 0))
!             appendStringInfo(es->str, " %s", quote_identifier(refname));
      }
      else
      {
*************** ExplainTargetRel(Plan *plan, Index rti,
*** 1898,1904 ****
              ExplainPropertyText(objecttag, objectname, es);
          if (namespace != NULL)
              ExplainPropertyText("Schema", namespace, es);
!         ExplainPropertyText("Alias", rte->eref->aliasname, es);
      }
  }

--- 1903,1910 ----
              ExplainPropertyText(objecttag, objectname, es);
          if (namespace != NULL)
              ExplainPropertyText("Schema", namespace, es);
!         if (refname != NULL)
!             ExplainPropertyText("Alias", refname, es);
      }
  }

diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 55a5ed7b4c6320886e1e0d36d7a6aa857efc25e2..61211d7c30001befc202f190f14944b97755c4e2 100644
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** optimize_minmax_aggregates(PlannerInfo *
*** 268,273 ****
--- 268,291 ----
                                (void *) root);

      /*
+      * Modify all the original RTEs to have aliases different from those in
+      * the copied subplan(s).  This allows EXPLAIN's heuristics for alias
+      * assignment to work nicely.  (Otherwise, EXPLAIN would see the original
+      * RTE first and preferentially give it the base relation name, which we
+      * don't want since the original RTE doesn't have any actual role in the
+      * finished plan.)
+      */
+     foreach(lc, parse->rtable)
+     {
+         RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+         char       *aliasname;
+
+         aliasname = (char *) palloc(strlen(rte->eref->aliasname) + 10);
+         sprintf(aliasname, "%s original", rte->eref->aliasname);
+         rte->alias = makeAlias(aliasname, NIL);
+     }
+
+     /*
       * Generate the output plan --- basically just a Result
       */
      plan = (Plan *) make_result(root, tlist, hqual, NULL);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 9e154e1120bed7cddd9e5827d5a5b566210c86af..0e1dad5e3a4451ab60fb5fe3fafcb75bc17780b3 100644
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1234,1239 ****
--- 1234,1240 ----
      PlanRowMark *oldrc;
      Relation    oldrelation;
      LOCKMODE    lockmode;
+     char       *aliasname;
      List       *inhOIDs;
      List       *appinfos;
      ListCell   *l;
*************** expand_inherited_rtentry(PlannerInfo *ro
*** 1415,1420 ****
--- 1416,1433 ----

      /* Otherwise, OK to add to root->append_rel_list */
      root->append_rel_list = list_concat(root->append_rel_list, appinfos);
+
+     /*
+      * Force the parent RTE to have an alias that marks it as a parent.  This
+      * is convenient for planner debugging purposes and also allows EXPLAIN's
+      * heuristics for alias assignment to work nicely.  (Otherwise, EXPLAIN
+      * would see the parent RTE first and preferentially give it the base
+      * relation name, which we don't want since the parent RTE doesn't have
+      * any actual role in the finished plan.)
+      */
+     aliasname = (char *) palloc(strlen(rte->eref->aliasname) + 10);
+     sprintf(aliasname, "%s parent", rte->eref->aliasname);
+     rte->alias = makeAlias(aliasname, NIL);
  }

  /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 08396431384a8f8581492304c8c916c41e4b9516..aef2f5e4c23dcc134a52d4ab5aaa88b020746bee 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** typedef struct
*** 104,110 ****
   * the current context's namespaces list.
   *
   * 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
--- 104,112 ----
   * the current context's namespaces list.
   *
   * The rangetable is the list of actual RTEs from the query tree, and the
!  * cte list is the list of actual CTEs.  rtable_names holds the alias name
!  * to be used for each RTE (either a C string, or NULL for nameless RTEs
!  * such as unnamed joins).
   *
   * When deparsing plan trees, there is always just a single item in the
   * deparse_namespace list (since a plan tree never contains Vars with
*************** typedef struct
*** 119,124 ****
--- 121,127 ----
  typedef struct
  {
      List       *rtable;            /* List of RangeTblEntry nodes */
+     List       *rtable_names;    /* Parallel list of names for RTEs */
      List       *ctes;            /* List of CommonTableExpr nodes */
      /* Remaining fields are used only when deparsing a Plan tree: */
      PlanState  *planstate;        /* immediate parent of current expression */
*************** static text *pg_get_expr_worker(text *ex
*** 172,177 ****
--- 175,184 ----
  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_rtable_names(deparse_namespace *dpns, List *parent_namespaces);
+ static bool refname_is_unique(char *refname, deparse_namespace *dpns,
+                   List *parent_namespaces);
+ static char *get_rtable_name(int rtindex, deparse_context *context);
  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 get_rule_windowspec(WindowCl
*** 212,219 ****
                      deparse_context *context);
  static char *get_variable(Var *var, int levelsup, bool istoplevel,
               deparse_context *context);
- static RangeTblEntry *find_rte_by_refname(const char *refname,
-                     deparse_context *context);
  static Node *find_param_referent(Param *param, deparse_context *context,
                      deparse_namespace **dpns_p, ListCell **ancestor_cell_p);
  static void get_parameter(Param *param, deparse_context *context);
--- 219,224 ----
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 676,682 ****
          oldrte->rtekind = RTE_RELATION;
          oldrte->relid = trigrec->tgrelid;
          oldrte->relkind = relkind;
!         oldrte->eref = makeAlias("old", NIL);
          oldrte->lateral = false;
          oldrte->inh = false;
          oldrte->inFromCl = true;
--- 681,688 ----
          oldrte->rtekind = RTE_RELATION;
          oldrte->relid = trigrec->tgrelid;
          oldrte->relkind = relkind;
!         oldrte->alias = makeAlias("old", NIL);
!         oldrte->eref = oldrte->alias;
          oldrte->lateral = false;
          oldrte->inh = false;
          oldrte->inFromCl = true;
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 685,691 ****
          newrte->rtekind = RTE_RELATION;
          newrte->relid = trigrec->tgrelid;
          newrte->relkind = relkind;
!         newrte->eref = makeAlias("new", NIL);
          newrte->lateral = false;
          newrte->inh = false;
          newrte->inFromCl = true;
--- 691,698 ----
          newrte->rtekind = RTE_RELATION;
          newrte->relid = trigrec->tgrelid;
          newrte->relkind = relkind;
!         newrte->alias = makeAlias("new", NIL);
!         newrte->eref = newrte->alias;
          newrte->lateral = false;
          newrte->inh = false;
          newrte->inFromCl = true;
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 694,699 ****
--- 701,707 ----
          memset(&dpns, 0, sizeof(dpns));
          dpns.rtable = list_make2(oldrte, newrte);
          dpns.ctes = NIL;
+         set_rtable_names(&dpns, NIL);

          /* Set up context with one-deep namespace stack */
          context.buf = &buf;
*************** deparse_context_for(const char *aliasnam
*** 2176,2182 ****
      rte->rtekind = RTE_RELATION;
      rte->relid = relid;
      rte->relkind = RELKIND_RELATION;    /* no need for exactness here */
!     rte->eref = makeAlias(aliasname, NIL);
      rte->lateral = false;
      rte->inh = false;
      rte->inFromCl = true;
--- 2184,2191 ----
      rte->rtekind = RTE_RELATION;
      rte->relid = relid;
      rte->relkind = RELKIND_RELATION;    /* no need for exactness here */
!     rte->alias = makeAlias(aliasname, NIL);
!     rte->eref = rte->alias;
      rte->lateral = false;
      rte->inh = false;
      rte->inFromCl = true;
*************** deparse_context_for(const char *aliasnam
*** 2184,2189 ****
--- 2193,2199 ----
      /* Build one-element rtable */
      dpns->rtable = list_make1(rte);
      dpns->ctes = NIL;
+     set_rtable_names(dpns, NIL);

      /* Return a one-deep namespace stack */
      return list_make1(dpns);
*************** deparse_context_for(const char *aliasnam
*** 2209,2221 ****
   * 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 with special varnos.
   */
  List *
  deparse_context_for_planstate(Node *planstate, List *ancestors,
!                               List *rtable)
  {
      deparse_namespace *dpns;

--- 2219,2232 ----
   * 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, along with the per-RTE
!  * alias names assigned by a previous call to select_rtable_names_for_explain.
!  * (We use the rangetable to resolve simple Vars, but the plan inputs are
!  * necessary for Vars with special varnos.)
   */
  List *
  deparse_context_for_planstate(Node *planstate, List *ancestors,
!                               List *rtable, List *rtable_names)
  {
      deparse_namespace *dpns;

*************** deparse_context_for_planstate(Node *plan
*** 2223,2228 ****
--- 2234,2240 ----

      /* Initialize fields that stay the same across the whole plan tree */
      dpns->rtable = rtable;
+     dpns->rtable_names = rtable_names;
      dpns->ctes = NIL;

      /* Set our attention on the specific plan node passed in */
*************** deparse_context_for_planstate(Node *plan
*** 2234,2239 ****
--- 2246,2377 ----
  }

  /*
+  * select_rtable_names_for_explain    - Select RTE aliases for EXPLAIN
+  *
+  * Determine the aliases we'll use during an EXPLAIN operation.  This is
+  * just a frontend to set_rtable_names.  We have to expose this to EXPLAIN
+  * because EXPLAIN needs to know the right alias names to print.
+  */
+ List *
+ select_rtable_names_for_explain(List *rtable)
+ {
+     deparse_namespace dpns;
+
+     memset(&dpns, 0, sizeof(dpns));
+     dpns.rtable = rtable;
+     dpns.ctes = NIL;
+     set_rtable_names(&dpns, NIL);
+
+     return dpns.rtable_names;
+ }
+
+ /*
+  * set_rtable_names: select RTE aliases to be used in printing variables
+  *
+  * We fill in dpns->rtable_names with a list of names that is one-for-one with
+  * the already-filled dpns->rtable list.  Each RTE name is unique among those
+  * in the new namespace plus any ancestor namespaces listed in
+  * parent_namespaces.
+  */
+ static void
+ set_rtable_names(deparse_namespace *dpns, List *parent_namespaces)
+ {
+     ListCell   *lc;
+
+     dpns->rtable_names = NIL;
+     foreach(lc, dpns->rtable)
+     {
+         RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+         char       *refname;
+
+         if (rte->alias)
+         {
+             /* If RTE has a user-defined alias, prefer that */
+             refname = rte->alias->aliasname;
+         }
+         else if (rte->rtekind == RTE_RELATION)
+         {
+             /* Use the current actual name of the relation */
+             refname = get_rel_name(rte->relid);
+         }
+         else if (rte->rtekind == RTE_JOIN)
+         {
+             /* Unnamed join has no refname */
+             refname = NULL;
+         }
+         else
+         {
+             /* Otherwise use whatever the parser assigned */
+             refname = rte->eref->aliasname;
+         }
+
+         /*
+          * If the selected name isn't unique, append digits to make it so
+          */
+         if (refname &&
+             !refname_is_unique(refname, dpns, parent_namespaces))
+         {
+             char       *modname = (char *) palloc(strlen(refname) + 32);
+             int            i = 0;
+
+             do
+             {
+                 sprintf(modname, "%s_%d", refname, ++i);
+             } while (!refname_is_unique(modname, dpns, parent_namespaces));
+             refname = modname;
+         }
+
+         dpns->rtable_names = lappend(dpns->rtable_names, refname);
+     }
+ }
+
+ /*
+  * refname_is_unique: is refname distinct from all already-chosen RTE names?
+  */
+ static bool
+ refname_is_unique(char *refname, deparse_namespace *dpns,
+                   List *parent_namespaces)
+ {
+     ListCell   *lc;
+
+     foreach(lc, dpns->rtable_names)
+     {
+         char       *oldname = (char *) lfirst(lc);
+
+         if (oldname && strcmp(oldname, refname) == 0)
+             return false;
+     }
+     foreach(lc, parent_namespaces)
+     {
+         deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc);
+         ListCell   *lc2;
+
+         foreach(lc2, olddpns->rtable_names)
+         {
+             char       *oldname = (char *) lfirst(lc2);
+
+             if (oldname && strcmp(oldname, refname) == 0)
+                 return false;
+         }
+     }
+     return true;
+ }
+
+ /*
+  * get_rtable_name: convenience function to get a previously assigned RTE alias
+  *
+  * The RTE must belong to the topmost namespace level in "context".
+  */
+ static char *
+ get_rtable_name(int rtindex, deparse_context *context)
+ {
+     deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
+
+     Assert(rtindex > 0 && rtindex <= list_length(dpns->rtable_names));
+     return (char *) list_nth(dpns->rtable_names, rtindex - 1);
+ }
+
+ /*
   * set_deparse_planstate: set up deparse_namespace to parse subexpressions
   * of a given PlanState node
   *
*************** make_ruledef(StringInfo buf, HeapTuple r
*** 2534,2539 ****
--- 2672,2678 ----
          memset(&dpns, 0, sizeof(dpns));
          dpns.rtable = query->rtable;
          dpns.ctes = query->cteList;
+         set_rtable_names(&dpns, NIL);

          get_rule_expr(qual, &context, false);
      }
*************** get_query_def(Query *query, StringInfo b
*** 2680,2685 ****
--- 2819,2825 ----
      memset(&dpns, 0, sizeof(dpns));
      dpns.rtable = query->rtable;
      dpns.ctes = query->cteList;
+     set_rtable_names(&dpns, parentnamespace);

      switch (query->commandType)
      {
*************** get_select_query_def(Query *query, depar
*** 2899,2905 ****
          foreach(l, query->rowMarks)
          {
              RowMarkClause *rc = (RowMarkClause *) lfirst(l);
-             RangeTblEntry *rte = rt_fetch(rc->rti, query->rtable);

              /* don't print implicit clauses */
              if (rc->pushedDown)
--- 3039,3044 ----
*************** get_select_query_def(Query *query, depar
*** 2912,2918 ****
                  appendContextKeyword(context, " FOR SHARE",
                                       -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
              appendStringInfo(buf, " OF %s",
!                              quote_identifier(rte->eref->aliasname));
              if (rc->noWait)
                  appendStringInfo(buf, " NOWAIT");
          }
--- 3051,3058 ----
                  appendContextKeyword(context, " FOR SHARE",
                                       -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
              appendStringInfo(buf, " OF %s",
!                              quote_identifier(get_rtable_name(rc->rti,
!                                                               context)));
              if (rc->noWait)
                  appendStringInfo(buf, " NOWAIT");
          }
*************** get_variable(Var *var, int levelsup, boo
*** 3854,3860 ****
      AttrNumber    attnum;
      int            netlevelsup;
      deparse_namespace *dpns;
-     char       *schemaname;
      char       *refname;
      char       *attname;

--- 3994,3999 ----
*************** get_variable(Var *var, int levelsup, boo
*** 3874,3879 ****
--- 4013,4019 ----
      if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
      {
          rte = rt_fetch(var->varno, dpns->rtable);
+         refname = (char *) list_nth(dpns->rtable_names, var->varno - 1);
          attnum = var->varattno;
      }
      else if (var->varno == OUTER_VAR && dpns->outer_tlist)
*************** get_variable(Var *var, int levelsup, boo
*** 3993,4053 ****
          return NULL;
      }

!     /* Identify names to use */
!     schemaname = NULL;            /* default assumptions */
!     refname = rte->eref->aliasname;
!
!     /* Exceptions occur only if the RTE is alias-less */
!     if (rte->alias == NULL)
      {
!         if (rte->rtekind == RTE_RELATION)
!         {
!             /*
!              * It's possible that use of the bare refname would find another
!              * more-closely-nested RTE, or be ambiguous, in which case we need
!              * to specify the schemaname to avoid these errors.
!              */
!             if (find_rte_by_refname(rte->eref->aliasname, context) != rte)
!                 schemaname = get_namespace_name(get_rel_namespace(rte->relid));
!         }
!         else if (rte->rtekind == RTE_JOIN)
          {
!             /*
!              * If it's an unnamed join, look at the expansion of the alias
!              * variable.  If it's a simple reference to one of the input vars
!              * then recursively print the name of that var, instead. (This
!              * allows correct decompiling of cases where there are identically
!              * named columns on both sides of the join.) When it's not a
!              * simple reference, we have to just print the unqualified
!              * variable name (this can only happen with columns that were
!              * merged by USING or NATURAL clauses).
!              *
!              * This wouldn't work in decompiling plan trees, because we don't
!              * store joinaliasvars lists after planning; but a plan tree
!              * should never contain a join alias variable.
!              */
!             if (rte->joinaliasvars == NIL)
!                 elog(ERROR, "cannot decompile join alias var in plan tree");
!             if (attnum > 0)
!             {
!                 Var           *aliasvar;

!                 aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
!                 if (IsA(aliasvar, Var))
!                 {
!                     return get_variable(aliasvar, var->varlevelsup + levelsup,
!                                         istoplevel, context);
!                 }
              }
-
-             /*
-              * Unnamed join has neither schemaname nor refname.  (Note: since
-              * it's unnamed, there is no way the user could have referenced it
-              * to create a whole-row Var for it.  So we don't have to cover
-              * that case below.)
-              */
-             refname = NULL;
          }
      }

      if (attnum == InvalidAttrNumber)
--- 4133,4173 ----
          return NULL;
      }

!     /*
!      * If it's an unnamed join, look at the expansion of the alias variable.
!      * If it's a simple reference to one of the input vars, then recursively
!      * print the name of that var instead.    (This allows correct decompiling
!      * of cases where there are identically named columns on both sides of the
!      * join.)  When it's not a simple reference, we have to just print the
!      * unqualified variable name (this can only happen with columns that were
!      * merged by USING or NATURAL clauses).
!      *
!      * This wouldn't work in decompiling plan trees, because we don't store
!      * joinaliasvars lists after planning; but a plan tree should never
!      * contain a join alias variable.
!      */
!     if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
      {
!         if (rte->joinaliasvars == NIL)
!             elog(ERROR, "cannot decompile join alias var in plan tree");
!         if (attnum > 0)
          {
!             Var           *aliasvar;

!             aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
!             if (IsA(aliasvar, Var))
!             {
!                 return get_variable(aliasvar, var->varlevelsup + levelsup,
!                                     istoplevel, context);
              }
          }
+
+         /*
+          * Unnamed join has no refname.  (Note: since it's unnamed, there is
+          * no way the user could have referenced it to create a whole-row Var
+          * for it.    So we don't have to cover that case below.)
+          */
+         Assert(refname == NULL);
      }

      if (attnum == InvalidAttrNumber)
*************** get_variable(Var *var, int levelsup, boo
*** 4057,4065 ****

      if (refname && (context->varprefix || attname == NULL))
      {
-         if (schemaname)
-             appendStringInfo(buf, "%s.",
-                              quote_identifier(schemaname));
          appendStringInfoString(buf, quote_identifier(refname));
          appendStringInfoChar(buf, '.');
      }
--- 4177,4182 ----
*************** get_name_for_var_field(Var *var, int fie
*** 4289,4294 ****
--- 4406,4412 ----
                          memset(&mydpns, 0, sizeof(mydpns));
                          mydpns.rtable = rte->subquery->rtable;
                          mydpns.ctes = rte->subquery->cteList;
+                         set_rtable_names(&mydpns, context->namespaces);

                          context->namespaces = lcons(&mydpns,
                                                      context->namespaces);
*************** get_name_for_var_field(Var *var, int fie
*** 4406,4411 ****
--- 4524,4530 ----
                          memset(&mydpns, 0, sizeof(mydpns));
                          mydpns.rtable = ctequery->rtable;
                          mydpns.ctes = ctequery->cteList;
+                         set_rtable_names(&mydpns, context->namespaces);

                          new_nslist = list_copy_tail(context->namespaces,
                                                      ctelevelsup);
*************** get_name_for_var_field(Var *var, int fie
*** 4467,4513 ****
      return NameStr(tupleDesc->attrs[fieldno - 1]->attname);
  }

-
- /*
-  * find_rte_by_refname        - look up an RTE by refname in a deparse context
-  *
-  * Returns NULL if there is no matching RTE or the refname is ambiguous.
-  *
-  * NOTE: this code is not really correct since it does not take account of
-  * the fact that not all the RTEs in a rangetable may be visible from the
-  * point where a Var reference appears.  For the purposes we need, however,
-  * the only consequence of a false match is that we might stick a schema
-  * qualifier on a Var that doesn't really need it.  So it seems close
-  * enough.
-  */
- static RangeTblEntry *
- find_rte_by_refname(const char *refname, deparse_context *context)
- {
-     RangeTblEntry *result = NULL;
-     ListCell   *nslist;
-
-     foreach(nslist, context->namespaces)
-     {
-         deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
-         ListCell   *rtlist;
-
-         foreach(rtlist, dpns->rtable)
-         {
-             RangeTblEntry *rte = (RangeTblEntry *) lfirst(rtlist);
-
-             if (strcmp(rte->eref->aliasname, refname) == 0)
-             {
-                 if (result)
-                     return NULL;    /* it's ambiguous */
-                 result = rte;
-             }
-         }
-         if (result)
-             break;
-     }
-     return result;
- }
-
  /*
   * Try to find the referenced expression for a PARAM_EXEC Param that might
   * reference a parameter supplied by an upper NestLoop or SubPlan plan node.
--- 4586,4591 ----
*************** get_from_clause_item(Node *jtnode, Query
*** 6649,6654 ****
--- 6727,6733 ----
      {
          int            varno = ((RangeTblRef *) jtnode)->rtindex;
          RangeTblEntry *rte = rt_fetch(varno, query->rtable);
+         char       *refname = get_rtable_name(varno, context);
          bool        gavealias = false;

          if (rte->lateral)
*************** get_from_clause_item(Node *jtnode, Query
*** 6688,6719 ****

          if (rte->alias != NULL)
          {
!             appendStringInfo(buf, " %s",
!                              quote_identifier(rte->alias->aliasname));
              gavealias = true;
          }
!         else if (rte->rtekind == RTE_RELATION &&
!             strcmp(rte->eref->aliasname, get_relation_name(rte->relid)) != 0)
          {
              /*
!              * Apparently the rel has been renamed since the rule was made.
!              * Emit a fake alias clause so that variable references will still
!              * work.  This is not a 100% solution but should work in most
!              * reasonable situations.
               */
!             appendStringInfo(buf, " %s",
!                              quote_identifier(rte->eref->aliasname));
!             gavealias = true;
          }
          else if (rte->rtekind == RTE_FUNCTION)
          {
              /*
!              * For a function RTE, always give an alias. This covers possible
               * renaming of the function and/or instability of the
               * FigureColname rules for things that aren't simple functions.
               */
!             appendStringInfo(buf, " %s",
!                              quote_identifier(rte->eref->aliasname));
              gavealias = true;
          }

--- 6767,6797 ----

          if (rte->alias != NULL)
          {
!             /* Always print alias if user provided one */
!             appendStringInfo(buf, " %s", quote_identifier(refname));
              gavealias = true;
          }
!         else if (rte->rtekind == RTE_RELATION)
          {
              /*
!              * No need to print alias if it's same as relation name (this
!              * would normally be the case, but not if set_rtable_names had to
!              * resolve a conflict).
               */
!             if (strcmp(refname, get_relation_name(rte->relid)) != 0)
!             {
!                 appendStringInfo(buf, " %s", quote_identifier(refname));
!                 gavealias = true;
!             }
          }
          else if (rte->rtekind == RTE_FUNCTION)
          {
              /*
!              * For a function RTE, always print alias.  This covers possible
               * renaming of the function and/or instability of the
               * FigureColname rules for things that aren't simple functions.
               */
!             appendStringInfo(buf, " %s", quote_identifier(refname));
              gavealias = true;
          }

diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index c4215da1e329102df3289f9c88a732f51f90503e..4227f4e59c3d07cee8d0fc95660010705ce5e13b 100644
*** a/src/include/commands/explain.h
--- b/src/include/commands/explain.h
*************** typedef struct ExplainState
*** 37,42 ****
--- 37,43 ----
      /* other states */
      PlannedStmt *pstmt;            /* top of plan */
      List       *rtable;            /* range table */
+     List       *rtable_names;    /* alias names for RTEs */
      int            indent;            /* current indentation level */
      List       *grouping_stack; /* format-specific grouping state */
  } ExplainState;
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index c9c665dae09c08a726c04ed58591d63e3b1b3347..2ff9460d0c7d58b0ef922490823cd1dd34fc192d 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern char *deparse_expression(Node *ex
*** 654,660 ****
                     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);
--- 654,661 ----
                     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, List *rtable_names);
! extern List *select_rtable_names_for_explain(List *rtable);
  extern const char *quote_identifier(const char *ident);
  extern char *quote_qualified_identifier(const char *qualifier,
                             const char *ident);
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 6ca73a0ed73fc9c2fa18701f17fa34b7f12db131..7286f1aa446ffc0f3b0d0527daf2a5bbba34713e 100644
*** a/src/test/regress/expected/aggregates.out
--- b/src/test/regress/expected/aggregates.out
*************** insert into minmaxtest2 values(15), (16)
*** 705,736 ****
  insert into minmaxtest3 values(17), (18);
  explain (costs off)
    select min(f1), max(f1) from minmaxtest;
!                                         QUERY PLAN
! -------------------------------------------------------------------------------------------
   Result
     InitPlan 1 (returns $0)
       ->  Limit
             ->  Merge Append
!                  Sort Key: public.minmaxtest.f1
                   ->  Index Only Scan using minmaxtesti on minmaxtest
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan using minmaxtest1i on minmaxtest1 minmaxtest
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan Backward using minmaxtest2i on minmaxtest2 minmaxtest
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan using minmaxtest3i on minmaxtest3 minmaxtest
                         Index Cond: (f1 IS NOT NULL)
     InitPlan 2 (returns $1)
       ->  Limit
             ->  Merge Append
!                  Sort Key: public.minmaxtest.f1
!                  ->  Index Only Scan Backward using minmaxtesti on minmaxtest
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan Backward using minmaxtest1i on minmaxtest1 minmaxtest
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest
                         Index Cond: (f1 IS NOT NULL)
  (25 rows)

--- 705,736 ----
  insert into minmaxtest3 values(17), (18);
  explain (costs off)
    select min(f1), max(f1) from minmaxtest;
!                                           QUERY PLAN
! ----------------------------------------------------------------------------------------------
   Result
     InitPlan 1 (returns $0)
       ->  Limit
             ->  Merge Append
!                  Sort Key: minmaxtest.f1
                   ->  Index Only Scan using minmaxtesti on minmaxtest
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan using minmaxtest1i on minmaxtest1
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan Backward using minmaxtest2i on minmaxtest2
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan using minmaxtest3i on minmaxtest3
                         Index Cond: (f1 IS NOT NULL)
     InitPlan 2 (returns $1)
       ->  Limit
             ->  Merge Append
!                  Sort Key: minmaxtest_1.f1
!                  ->  Index Only Scan Backward using minmaxtesti on minmaxtest minmaxtest_1
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan Backward using minmaxtest1i on minmaxtest1 minmaxtest1_1
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest2_1
                         Index Cond: (f1 IS NOT NULL)
!                  ->  Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest3_1
                         Index Cond: (f1 IS NOT NULL)
  (25 rows)

diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 453a3894b202873b31dada5727e7e09ae3f2f142..c22d74c7b562e86d6e839b4ab3a87d3cf7a29149 100644
*** a/src/test/regress/expected/alter_table.out
--- b/src/test/regress/expected/alter_table.out
*************** explain (costs off) select * from nv_par
*** 391,399 ****
     ->  Append
           ->  Seq Scan on nv_parent
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!          ->  Seq Scan on nv_child_2010 nv_parent
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!          ->  Seq Scan on nv_child_2011 nv_parent
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
  (8 rows)

--- 391,399 ----
     ->  Append
           ->  Seq Scan on nv_parent
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!          ->  Seq Scan on nv_child_2010
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!          ->  Seq Scan on nv_child_2011
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
  (8 rows)

*************** explain (costs off) select * from nv_par
*** 405,413 ****
     ->  Append
           ->  Seq Scan on nv_parent
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!          ->  Seq Scan on nv_child_2010 nv_parent
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!          ->  Seq Scan on nv_child_2011 nv_parent
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
  (8 rows)

--- 405,413 ----
     ->  Append
           ->  Seq Scan on nv_parent
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!          ->  Seq Scan on nv_child_2010
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
!          ->  Seq Scan on nv_child_2011
                 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
  (8 rows)

*************** explain (costs off) select * from nv_par
*** 418,428 ****
     ->  Append
           ->  Seq Scan on nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2010 nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2011 nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2009 nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
  (10 rows)

--- 418,428 ----
     ->  Append
           ->  Seq Scan on nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2010
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2011
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2009
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
  (10 rows)

*************** explain (costs off) select * from nv_par
*** 435,443 ****
     ->  Append
           ->  Seq Scan on nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2010 nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2009 nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
  (8 rows)

--- 435,443 ----
     ->  Append
           ->  Seq Scan on nv_parent
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2010
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
!          ->  Seq Scan on nv_child_2009
                 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
  (8 rows)

diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 25adcd2346118853ef57943bd22d9b80667ca560..906a928b0c0a44ff1dcac5b3b9803cd508ec7533 100644
*** a/src/test/regress/expected/inherit.out
--- b/src/test/regress/expected/inherit.out
*************** analyze patest1;
*** 1105,1121 ****
  analyze patest2;
  explain (costs off)
  select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
!                         QUERY PLAN
! ----------------------------------------------------------
   Nested Loop
     ->  Limit
           ->  Seq Scan on int4_tbl
     ->  Append
           ->  Index Scan using patest0i on patest0
                 Index Cond: (id = int4_tbl.f1)
!          ->  Index Scan using patest1i on patest1 patest0
                 Index Cond: (id = int4_tbl.f1)
!          ->  Index Scan using patest2i on patest2 patest0
                 Index Cond: (id = int4_tbl.f1)
  (10 rows)

--- 1105,1121 ----
  analyze patest2;
  explain (costs off)
  select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
!                     QUERY PLAN
! --------------------------------------------------
   Nested Loop
     ->  Limit
           ->  Seq Scan on int4_tbl
     ->  Append
           ->  Index Scan using patest0i on patest0
                 Index Cond: (id = int4_tbl.f1)
!          ->  Index Scan using patest1i on patest1
                 Index Cond: (id = int4_tbl.f1)
!          ->  Index Scan using patest2i on patest2
                 Index Cond: (id = int4_tbl.f1)
  (10 rows)

*************** select * from patest0 join (select f1 fr
*** 1130,1146 ****
  drop index patest2i;
  explain (costs off)
  select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
!                         QUERY PLAN
! ----------------------------------------------------------
   Nested Loop
     ->  Limit
           ->  Seq Scan on int4_tbl
     ->  Append
           ->  Index Scan using patest0i on patest0
                 Index Cond: (id = int4_tbl.f1)
!          ->  Index Scan using patest1i on patest1 patest0
                 Index Cond: (id = int4_tbl.f1)
!          ->  Seq Scan on patest2 patest0
                 Filter: (int4_tbl.f1 = id)
  (10 rows)

--- 1130,1146 ----
  drop index patest2i;
  explain (costs off)
  select * from patest0 join (select f1 from int4_tbl limit 1) ss on id = f1;
!                     QUERY PLAN
! --------------------------------------------------
   Nested Loop
     ->  Limit
           ->  Seq Scan on int4_tbl
     ->  Append
           ->  Index Scan using patest0i on patest0
                 Index Cond: (id = int4_tbl.f1)
!          ->  Index Scan using patest1i on patest1
                 Index Cond: (id = int4_tbl.f1)
!          ->  Seq Scan on patest2
                 Filter: (int4_tbl.f1 = id)
  (10 rows)

*************** insert into matest3 (name) values ('Test
*** 1178,1199 ****
  insert into matest3 (name) values ('Test 6');
  set enable_indexscan = off;  -- force use of seqscan/sort, so no merge
  explain (verbose, costs off) select * from matest0 order by 1-id;
!                                    QUERY PLAN
! ---------------------------------------------------------------------------------
   Sort
!    Output: public.matest0.id, public.matest0.name, ((1 - public.matest0.id))
!    Sort Key: ((1 - public.matest0.id))
     ->  Result
!          Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
           ->  Append
                 ->  Seq Scan on public.matest0
!                      Output: public.matest0.id, public.matest0.name
!                ->  Seq Scan on public.matest1 matest0
!                      Output: public.matest0.id, public.matest0.name
!                ->  Seq Scan on public.matest2 matest0
!                      Output: public.matest0.id, public.matest0.name
!                ->  Seq Scan on public.matest3 matest0
!                      Output: public.matest0.id, public.matest0.name
  (14 rows)

  select * from matest0 order by 1-id;
--- 1178,1199 ----
  insert into matest3 (name) values ('Test 6');
  set enable_indexscan = off;  -- force use of seqscan/sort, so no merge
  explain (verbose, costs off) select * from matest0 order by 1-id;
!                          QUERY PLAN
! ------------------------------------------------------------
   Sort
!    Output: matest0.id, matest0.name, ((1 - matest0.id))
!    Sort Key: ((1 - matest0.id))
     ->  Result
!          Output: matest0.id, matest0.name, (1 - matest0.id)
           ->  Append
                 ->  Seq Scan on public.matest0
!                      Output: matest0.id, matest0.name
!                ->  Seq Scan on public.matest1
!                      Output: matest1.id, matest1.name
!                ->  Seq Scan on public.matest2
!                      Output: matest2.id, matest2.name
!                ->  Seq Scan on public.matest3
!                      Output: matest3.id, matest3.name
  (14 rows)

  select * from matest0 order by 1-id;
*************** select * from matest0 order by 1-id;
*** 1210,1232 ****
  reset enable_indexscan;
  set enable_seqscan = off;  -- plan with fewest seqscans should be merge
  explain (verbose, costs off) select * from matest0 order by 1-id;
!                                          QUERY PLAN
! ---------------------------------------------------------------------------------------------
   Result
!    Output: public.matest0.id, public.matest0.name, ((1 - public.matest0.id))
     ->  Merge Append
!          Sort Key: ((1 - public.matest0.id))
           ->  Index Scan using matest0i on public.matest0
!                Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
!          ->  Index Scan using matest1i on public.matest1 matest0
!                Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
           ->  Sort
!                Output: public.matest0.id, public.matest0.name, ((1 - public.matest0.id))
!                Sort Key: ((1 - public.matest0.id))
!                ->  Seq Scan on public.matest2 matest0
!                      Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
!          ->  Index Scan using matest3i on public.matest3 matest0
!                Output: public.matest0.id, public.matest0.name, (1 - public.matest0.id)
  (15 rows)

  select * from matest0 order by 1-id;
--- 1210,1232 ----
  reset enable_indexscan;
  set enable_seqscan = off;  -- plan with fewest seqscans should be merge
  explain (verbose, costs off) select * from matest0 order by 1-id;
!                                QUERY PLAN
! ------------------------------------------------------------------------
   Result
!    Output: matest0.id, matest0.name, ((1 - matest0.id))
     ->  Merge Append
!          Sort Key: ((1 - matest0.id))
           ->  Index Scan using matest0i on public.matest0
!                Output: matest0.id, matest0.name, (1 - matest0.id)
!          ->  Index Scan using matest1i on public.matest1
!                Output: matest1.id, matest1.name, (1 - matest1.id)
           ->  Sort
!                Output: matest2.id, matest2.name, ((1 - matest2.id))
!                Sort Key: ((1 - matest2.id))
!                ->  Seq Scan on public.matest2
!                      Output: matest2.id, matest2.name, (1 - matest2.id)
!          ->  Index Scan using matest3i on public.matest3
!                Output: matest3.id, matest3.name, (1 - matest3.id)
  (15 rows)

  select * from matest0 order by 1-id;
*************** SELECT thousand, tenthous FROM tenk1
*** 1258,1272 ****
  UNION ALL
  SELECT thousand, thousand FROM tenk1
  ORDER BY thousand, tenthous;
!                               QUERY PLAN
! -----------------------------------------------------------------------
   Result
     ->  Merge Append
!          Sort Key: public.tenk1.thousand, public.tenk1.tenthous
           ->  Index Only Scan using tenk1_thous_tenthous on tenk1
           ->  Sort
!                Sort Key: public.tenk1.thousand, public.tenk1.thousand
!                ->  Index Only Scan using tenk1_thous_tenthous on tenk1
  (7 rows)

  explain (costs off)
--- 1258,1272 ----
  UNION ALL
  SELECT thousand, thousand FROM tenk1
  ORDER BY thousand, tenthous;
!                                   QUERY PLAN
! -------------------------------------------------------------------------------
   Result
     ->  Merge Append
!          Sort Key: tenk1.thousand, tenk1.tenthous
           ->  Index Only Scan using tenk1_thous_tenthous on tenk1
           ->  Sort
!                Sort Key: tenk1_1.thousand, tenk1_1.thousand
!                ->  Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1
  (7 rows)

  explain (costs off)
*************** SELECT thousand, tenthous, thousand+tent
*** 1274,1288 ****
  UNION ALL
  SELECT 42, 42, hundred FROM tenk1
  ORDER BY thousand, tenthous;
!                            QUERY PLAN
! -----------------------------------------------------------------
   Result
     ->  Merge Append
!          Sort Key: public.tenk1.thousand, public.tenk1.tenthous
           ->  Index Only Scan using tenk1_thous_tenthous on tenk1
           ->  Sort
                 Sort Key: (42), (42)
!                ->  Index Only Scan using tenk1_hundred on tenk1
  (7 rows)

  explain (costs off)
--- 1274,1288 ----
  UNION ALL
  SELECT 42, 42, hundred FROM tenk1
  ORDER BY thousand, tenthous;
!                                QUERY PLAN
! ------------------------------------------------------------------------
   Result
     ->  Merge Append
!          Sort Key: tenk1.thousand, tenk1.tenthous
           ->  Index Only Scan using tenk1_thous_tenthous on tenk1
           ->  Sort
                 Sort Key: (42), (42)
!                ->  Index Only Scan using tenk1_hundred on tenk1 tenk1_1
  (7 rows)

  explain (costs off)
*************** SELECT thousand, tenthous FROM tenk1
*** 1290,1304 ****
  UNION ALL
  SELECT thousand, random()::integer FROM tenk1
  ORDER BY thousand, tenthous;
!                               QUERY PLAN
! -----------------------------------------------------------------------
   Result
     ->  Merge Append
!          Sort Key: public.tenk1.thousand, public.tenk1.tenthous
           ->  Index Only Scan using tenk1_thous_tenthous on tenk1
           ->  Sort
!                Sort Key: public.tenk1.thousand, ((random())::integer)
!                ->  Index Only Scan using tenk1_thous_tenthous on tenk1
  (7 rows)

  -- Check min/max aggregate optimization
--- 1290,1304 ----
  UNION ALL
  SELECT thousand, random()::integer FROM tenk1
  ORDER BY thousand, tenthous;
!                                   QUERY PLAN
! -------------------------------------------------------------------------------
   Result
     ->  Merge Append
!          Sort Key: tenk1.thousand, tenk1.tenthous
           ->  Index Only Scan using tenk1_thous_tenthous on tenk1
           ->  Sort
!                Sort Key: tenk1_1.thousand, ((random())::integer)
!                ->  Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1
  (7 rows)

  -- Check min/max aggregate optimization
diff --git a/src/test/regress/expected/select_views.out b/src/test/regress/expected/select_views.out
index e4eba1ae36c8715d2f414db413feb68e71c1c575..6a00c17d3f6a5f949b3fb88352cd970e2af0d380 100644
*** a/src/test/regress/expected/select_views.out
--- b/src/test/regress/expected/select_views.out
*************** EXPLAIN (COSTS OFF) SELECT * FROM my_cre
*** 1421,1430 ****
           ->  Subquery Scan on l
                 Filter: f_leak(l.cnum)
                 ->  Hash Join
!                      Hash Cond: (r.cid = l.cid)
!                      ->  Seq Scan on credit_card r
                       ->  Hash
!                            ->  Seq Scan on customer l
                                   Filter: (name = ("current_user"())::text)
  (13 rows)

--- 1421,1430 ----
           ->  Subquery Scan on l
                 Filter: f_leak(l.cnum)
                 ->  Hash Join
!                      Hash Cond: (r_1.cid = l_1.cid)
!                      ->  Seq Scan on credit_card r_1
                       ->  Hash
!                            ->  Seq Scan on customer l_1
                                   Filter: (name = ("current_user"())::text)
  (13 rows)

*************** EXPLAIN (COSTS OFF) SELECT * FROM my_cre
*** 1447,1461 ****
   Subquery Scan on my_credit_card_usage_secure
     Filter: f_leak(my_credit_card_usage_secure.cnum)
     ->  Nested Loop
!          Join Filter: (l.cid = r.cid)
           ->  Seq Scan on credit_usage r
                 Filter: ((ymd >= '10-01-2011'::date) AND (ymd < '11-01-2011'::date))
           ->  Materialize
                 ->  Hash Join
!                      Hash Cond: (r.cid = l.cid)
!                      ->  Seq Scan on credit_card r
                       ->  Hash
!                            ->  Seq Scan on customer l
                                   Filter: (name = ("current_user"())::text)
  (13 rows)

--- 1447,1461 ----
   Subquery Scan on my_credit_card_usage_secure
     Filter: f_leak(my_credit_card_usage_secure.cnum)
     ->  Nested Loop
!          Join Filter: (l_1.cid = r.cid)
           ->  Seq Scan on credit_usage r
                 Filter: ((ymd >= '10-01-2011'::date) AND (ymd < '11-01-2011'::date))
           ->  Materialize
                 ->  Hash Join
!                      Hash Cond: (r_1.cid = l_1.cid)
!                      ->  Seq Scan on credit_card r_1
                       ->  Hash
!                            ->  Seq Scan on customer l_1
                                   Filter: (name = ("current_user"())::text)
  (13 rows)

diff --git a/src/test/regress/expected/select_views_1.out b/src/test/regress/expected/select_views_1.out
index 94b439825c49b25651a7722f301d203f9d6c958f..0b509f4ee6ba9a58d125f223bdb85350976d3596 100644
*** a/src/test/regress/expected/select_views_1.out
--- b/src/test/regress/expected/select_views_1.out
*************** EXPLAIN (COSTS OFF) SELECT * FROM my_cre
*** 1421,1430 ****
           ->  Subquery Scan on l
                 Filter: f_leak(l.cnum)
                 ->  Hash Join
!                      Hash Cond: (r.cid = l.cid)
!                      ->  Seq Scan on credit_card r
                       ->  Hash
!                            ->  Seq Scan on customer l
                                   Filter: (name = ("current_user"())::text)
  (13 rows)

--- 1421,1430 ----
           ->  Subquery Scan on l
                 Filter: f_leak(l.cnum)
                 ->  Hash Join
!                      Hash Cond: (r_1.cid = l_1.cid)
!                      ->  Seq Scan on credit_card r_1
                       ->  Hash
!                            ->  Seq Scan on customer l_1
                                   Filter: (name = ("current_user"())::text)
  (13 rows)

*************** EXPLAIN (COSTS OFF) SELECT * FROM my_cre
*** 1447,1461 ****
   Subquery Scan on my_credit_card_usage_secure
     Filter: f_leak(my_credit_card_usage_secure.cnum)
     ->  Nested Loop
!          Join Filter: (l.cid = r.cid)
           ->  Seq Scan on credit_usage r
                 Filter: ((ymd >= '10-01-2011'::date) AND (ymd < '11-01-2011'::date))
           ->  Materialize
                 ->  Hash Join
!                      Hash Cond: (r.cid = l.cid)
!                      ->  Seq Scan on credit_card r
                       ->  Hash
!                            ->  Seq Scan on customer l
                                   Filter: (name = ("current_user"())::text)
  (13 rows)

--- 1447,1461 ----
   Subquery Scan on my_credit_card_usage_secure
     Filter: f_leak(my_credit_card_usage_secure.cnum)
     ->  Nested Loop
!          Join Filter: (l_1.cid = r.cid)
           ->  Seq Scan on credit_usage r
                 Filter: ((ymd >= '10-01-2011'::date) AND (ymd < '11-01-2011'::date))
           ->  Materialize
                 ->  Hash Join
!                      Hash Cond: (r_1.cid = l_1.cid)
!                      ->  Seq Scan on credit_card r_1
                       ->  Hash
!                            ->  Seq Scan on customer l_1
                                   Filter: (name = ("current_user"())::text)
  (13 rows)

diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 38cfb8c7276c9310a60375f0f70d56b8c14eb970..671f293b68de95617b6f953573a31fda3fde176f 100644
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
*************** SELECT * FROM parent;
*** 2006,2013 ****
  EXPLAIN (VERBOSE, COSTS OFF)
  WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
  DELETE FROM a USING wcte WHERE aa = q2;
!                     QUERY PLAN
! --------------------------------------------------
   Delete on public.a
     CTE wcte
       ->  Insert on public.int8_tbl
--- 2006,2013 ----
  EXPLAIN (VERBOSE, COSTS OFF)
  WITH wcte AS ( INSERT INTO int8_tbl VALUES ( 42, 47 ) RETURNING q2 )
  DELETE FROM a USING wcte WHERE aa = q2;
!                    QUERY PLAN
! ------------------------------------------------
   Delete on public.a
     CTE wcte
       ->  Insert on public.int8_tbl
*************** DELETE FROM a USING wcte WHERE aa = q2;
*** 2015,2045 ****
             ->  Result
                   Output: 42::bigint, 47::bigint
     ->  Nested Loop
!          Output: public.a.ctid, wcte.*
!          Join Filter: (public.a.aa = wcte.q2)
           ->  Seq Scan on public.a
!                Output: public.a.ctid, public.a.aa
           ->  CTE Scan on wcte
                 Output: wcte.*, wcte.q2
     ->  Nested Loop
!          Output: public.a.ctid, wcte.*
!          Join Filter: (public.a.aa = wcte.q2)
!          ->  Seq Scan on public.b a
!                Output: public.a.ctid, public.a.aa
           ->  CTE Scan on wcte
                 Output: wcte.*, wcte.q2
     ->  Nested Loop
!          Output: public.a.ctid, wcte.*
!          Join Filter: (public.a.aa = wcte.q2)
!          ->  Seq Scan on public.c a
!                Output: public.a.ctid, public.a.aa
           ->  CTE Scan on wcte
                 Output: wcte.*, wcte.q2
     ->  Nested Loop
!          Output: public.a.ctid, wcte.*
!          Join Filter: (public.a.aa = wcte.q2)
!          ->  Seq Scan on public.d a
!                Output: public.a.ctid, public.a.aa
           ->  CTE Scan on wcte
                 Output: wcte.*, wcte.q2
  (34 rows)
--- 2015,2045 ----
             ->  Result
                   Output: 42::bigint, 47::bigint
     ->  Nested Loop
!          Output: a.ctid, wcte.*
!          Join Filter: (a.aa = wcte.q2)
           ->  Seq Scan on public.a
!                Output: a.ctid, a.aa
           ->  CTE Scan on wcte
                 Output: wcte.*, wcte.q2
     ->  Nested Loop
!          Output: b.ctid, wcte.*
!          Join Filter: (b.aa = wcte.q2)
!          ->  Seq Scan on public.b
!                Output: b.ctid, b.aa
           ->  CTE Scan on wcte
                 Output: wcte.*, wcte.q2
     ->  Nested Loop
!          Output: c.ctid, wcte.*
!          Join Filter: (c.aa = wcte.q2)
!          ->  Seq Scan on public.c
!                Output: c.ctid, c.aa
           ->  CTE Scan on wcte
                 Output: wcte.*, wcte.q2
     ->  Nested Loop
!          Output: d.ctid, wcte.*
!          Join Filter: (d.aa = wcte.q2)
!          ->  Seq Scan on public.d
!                Output: d.ctid, d.aa
           ->  CTE Scan on wcte
                 Output: wcte.*, wcte.q2
  (34 rows)

pgsql-hackers by date:

Previous
From: "Karl O. Pinc"
Date:
Subject: Suggestion for --truncate-tables to pg_restore
Next
From: Kohei KaiGai
Date:
Subject: Re: Move postgresql_fdw_validator into dblink