]> granicus.if.org Git - postgresql/commitdiff
Show sort ordering options in EXPLAIN output.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 16 Jan 2015 23:18:52 +0000 (18:18 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 16 Jan 2015 23:19:00 +0000 (18:19 -0500)
Up to now, EXPLAIN has contented itself with printing the sort expressions
in a Sort or Merge Append plan node.  This patch improves that by
annotating the sort keys with COLLATE, DESC, USING, and/or NULLS FIRST/LAST
whenever nondefault sort ordering options are used.  The output is now a
reasonably close approximation of an ORDER BY clause equivalent to the
plan's ordering.

Marius Timmer, Lukas Kreft, and Arne Scheffer; reviewed by Mike Blackwell.
Some additional hacking by me.

src/backend/commands/explain.c
src/test/regress/expected/aggregates.out
src/test/regress/expected/collate.out
src/test/regress/expected/equivclass.out
src/test/regress/sql/collate.sql

index 2b84ac8af3724535dbc4140aa606e2ca15514b6a..7cfc9bb612a83f3b4ee938bb835508ab19daa5e4 100644 (file)
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "commands/createas.h"
 #include "commands/defrem.h"
 #include "commands/prepare.h"
 #include "executor/hashjoin.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
@@ -31,6 +33,7 @@
 #include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 #include "utils/tuplesort.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
 
 
@@ -83,7 +86,10 @@ static void show_group_keys(GroupState *gstate, List *ancestors,
                                ExplainState *es);
 static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
                                         int nkeys, AttrNumber *keycols,
+                                        Oid *sortOperators, Oid *collations, bool *nullsFirst,
                                         List *ancestors, ExplainState *es);
+static void show_sortorder_options(StringInfo buf, Node *sortexpr,
+                                          Oid sortOperator, Oid collation, bool nullsFirst);
 static void show_sort_info(SortState *sortstate, ExplainState *es);
 static void show_hash_info(HashState *hashstate, ExplainState *es);
 static void show_tidbitmap_info(BitmapHeapScanState *planstate,
@@ -1781,6 +1787,8 @@ show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
 
        show_sort_group_keys((PlanState *) sortstate, "Sort Key",
                                                 plan->numCols, plan->sortColIdx,
+                                                plan->sortOperators, plan->collations,
+                                                plan->nullsFirst,
                                                 ancestors, es);
 }
 
@@ -1795,6 +1803,8 @@ show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
 
        show_sort_group_keys((PlanState *) mstate, "Sort Key",
                                                 plan->numCols, plan->sortColIdx,
+                                                plan->sortOperators, plan->collations,
+                                                plan->nullsFirst,
                                                 ancestors, es);
 }
 
@@ -1813,6 +1823,7 @@ show_agg_keys(AggState *astate, List *ancestors,
                ancestors = lcons(astate, ancestors);
                show_sort_group_keys(outerPlanState(astate), "Group Key",
                                                         plan->numCols, plan->grpColIdx,
+                                                        NULL, NULL, NULL,
                                                         ancestors, es);
                ancestors = list_delete_first(ancestors);
        }
@@ -1831,29 +1842,34 @@ show_group_keys(GroupState *gstate, List *ancestors,
        ancestors = lcons(gstate, ancestors);
        show_sort_group_keys(outerPlanState(gstate), "Group Key",
                                                 plan->numCols, plan->grpColIdx,
+                                                NULL, NULL, NULL,
                                                 ancestors, es);
        ancestors = list_delete_first(ancestors);
 }
 
 /*
  * Common code to show sort/group keys, which are represented in plan nodes
- * as arrays of targetlist indexes
+ * as arrays of targetlist indexes.  If it's a sort key rather than a group
+ * key, also pass sort operators/collations/nullsFirst arrays.
  */
 static void
 show_sort_group_keys(PlanState *planstate, const char *qlabel,
                                         int nkeys, AttrNumber *keycols,
+                                        Oid *sortOperators, Oid *collations, bool *nullsFirst,
                                         List *ancestors, ExplainState *es)
 {
        Plan       *plan = planstate->plan;
        List       *context;
        List       *result = NIL;
+       StringInfoData sortkeybuf;
        bool            useprefix;
        int                     keyno;
-       char       *exprstr;
 
        if (nkeys <= 0)
                return;
 
+       initStringInfo(&sortkeybuf);
+
        /* Set up deparsing context */
        context = set_deparse_context_planstate(es->deparse_cxt,
                                                                                        (Node *) planstate,
@@ -1866,18 +1882,86 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
                AttrNumber      keyresno = keycols[keyno];
                TargetEntry *target = get_tle_by_resno(plan->targetlist,
                                                                                           keyresno);
+               char       *exprstr;
 
                if (!target)
                        elog(ERROR, "no tlist entry for key %d", keyresno);
                /* Deparse the expression, showing any top-level cast */
                exprstr = deparse_expression((Node *) target->expr, context,
                                                                         useprefix, true);
-               result = lappend(result, exprstr);
+               resetStringInfo(&sortkeybuf);
+               appendStringInfoString(&sortkeybuf, exprstr);
+               /* Append sort order information, if relevant */
+               if (sortOperators != NULL)
+                       show_sortorder_options(&sortkeybuf,
+                                                                  (Node *) target->expr,
+                                                                  sortOperators[keyno],
+                                                                  collations[keyno],
+                                                                  nullsFirst[keyno]);
+               /* Emit one property-list item per sort key */
+               result = lappend(result, pstrdup(sortkeybuf.data));
        }
 
        ExplainPropertyList(qlabel, result, es);
 }
 
+/*
+ * Append nondefault characteristics of the sort ordering of a column to buf
+ * (collation, direction, NULLS FIRST/LAST)
+ */
+static void
+show_sortorder_options(StringInfo buf, Node *sortexpr,
+                                          Oid sortOperator, Oid collation, bool nullsFirst)
+{
+       Oid                     sortcoltype = exprType(sortexpr);
+       bool            reverse = false;
+       TypeCacheEntry *typentry;
+
+       typentry = lookup_type_cache(sortcoltype,
+                                                                TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
+
+       /*
+        * Print COLLATE if it's not default.  There are some cases where this is
+        * redundant, eg if expression is a column whose declared collation is
+        * that collation, but it's hard to distinguish that here.
+        */
+       if (OidIsValid(collation) && collation != DEFAULT_COLLATION_OID)
+       {
+               char       *collname = get_collation_name(collation);
+
+               if (collname == NULL)
+                       elog(ERROR, "cache lookup failed for collation %u", collation);
+               appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));
+       }
+
+       /* Print direction if not ASC, or USING if non-default sort operator */
+       if (sortOperator == typentry->gt_opr)
+       {
+               appendStringInfoString(buf, " DESC");
+               reverse = true;
+       }
+       else if (sortOperator != typentry->lt_opr)
+       {
+               char       *opname = get_opname(sortOperator);
+
+               if (opname == NULL)
+                       elog(ERROR, "cache lookup failed for operator %u", sortOperator);
+               appendStringInfo(buf, " USING %s", opname);
+               /* Determine whether operator would be considered ASC or DESC */
+               (void) get_equality_op_for_ordering_op(sortOperator, &reverse);
+       }
+
+       /* Add NULLS FIRST/LAST only if it wouldn't be default */
+       if (nullsFirst && !reverse)
+       {
+               appendStringInfoString(buf, " NULLS FIRST");
+       }
+       else if (!nullsFirst && reverse)
+       {
+               appendStringInfoString(buf, " NULLS LAST");
+       }
+}
+
 /*
  * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
  */
index 40f5398b72c268133a266354b3dafa3eff999ef0..8852051e9325f4eec89a01def3f0673fbc27a0c6 100644 (file)
@@ -735,7 +735,7 @@ explain (costs off)
                              QUERY PLAN                              
 ---------------------------------------------------------------------
  Sort
-   Sort Key: (generate_series(1, 3))
+   Sort Key: (generate_series(1, 3)) DESC
    InitPlan 1 (returns $0)
      ->  Limit
            ->  Index Only Scan Backward using tenk1_unique2 on tenk1
@@ -784,7 +784,7 @@ explain (costs off)
    InitPlan 2 (returns $1)
      ->  Limit
            ->  Merge Append
-                 Sort Key: minmaxtest_1.f1
+                 Sort Key: minmaxtest_1.f1 DESC
                  ->  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
@@ -823,7 +823,7 @@ explain (costs off)
    InitPlan 2 (returns $1)
      ->  Limit
            ->  Merge Append
-                 Sort Key: minmaxtest_1.f1
+                 Sort Key: minmaxtest_1.f1 DESC
                  ->  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 91d574dbe4c77a79692eac54a6e470eaa37a422d..f076a4dbae183fdd41ecf5765c86e6d207cc3206 100644 (file)
@@ -603,6 +603,25 @@ ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20;
 RESET enable_seqscan;
 RESET enable_hashjoin;
 RESET enable_nestloop;
+-- EXPLAIN
+EXPLAIN (COSTS OFF)
+  SELECT * FROM collate_test10 ORDER BY x, y;
+                  QUERY PLAN                  
+----------------------------------------------
+ Sort
+   Sort Key: x COLLATE "C", y COLLATE "POSIX"
+   ->  Seq Scan on collate_test10
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+  SELECT * FROM collate_test10 ORDER BY x DESC, y COLLATE "C" ASC NULLS FIRST;
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Sort
+   Sort Key: x COLLATE "C" DESC, y COLLATE "C" NULLS FIRST
+   ->  Seq Scan on collate_test10
+(3 rows)
+
 -- 9.1 bug with useless COLLATE in an expression subject to length coercion
 CREATE TEMP TABLE vctable (f1 varchar(25));
 INSERT INTO vctable VALUES ('foo' COLLATE "C");
index b312292c6f337ae7c0f00eca5bd3ed0b00243026..dfae84e1a5f7c0da47b94c1f6f3d34d720a311bc 100644 (file)
@@ -319,7 +319,7 @@ explain (costs off)
                      ->  Index Scan using ec1_expr4 on ec1 ec1_3
                ->  Materialize
                      ->  Sort
-                           Sort Key: ec1.f1
+                           Sort Key: ec1.f1 USING <
                            ->  Index Scan using ec1_pkey on ec1
                                  Index Cond: (ff = 42::bigint)
 (20 rows)
@@ -376,7 +376,7 @@ explain (costs off)
          ->  Index Scan using ec1_expr4 on ec1 ec1_3
    ->  Materialize
          ->  Sort
-               Sort Key: ec1.f1
+               Sort Key: ec1.f1 USING <
                ->  Index Scan using ec1_pkey on ec1
                      Index Cond: (ff = 42::bigint)
 (14 rows)
index 63ab590f3a4153d49e4e3299c067fff4df4a67f8..e8ca0856e34bf5bfbb7943fafc8a510ffaf687e2 100644 (file)
@@ -220,6 +220,15 @@ RESET enable_seqscan;
 RESET enable_hashjoin;
 RESET enable_nestloop;
 
+
+-- EXPLAIN
+
+EXPLAIN (COSTS OFF)
+  SELECT * FROM collate_test10 ORDER BY x, y;
+EXPLAIN (COSTS OFF)
+  SELECT * FROM collate_test10 ORDER BY x DESC, y COLLATE "C" ASC NULLS FIRST;
+
+
 -- 9.1 bug with useless COLLATE in an expression subject to length coercion
 
 CREATE TEMP TABLE vctable (f1 varchar(25));