From bee35c8a7bc09765ffd0df488c2e0205b62b1ce0 Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Thu, 15 Jan 2015 15:46:36 +0100
Subject: [PATCH] explain_sortorder v8

---
 doc/src/sgml/perform.sgml                       |  13 ++-
 src/backend/commands/explain.c                  | 109 ++++++++++++++++++++----
 src/test/regress/expected/aggregates.out        |   2 +-
 src/test/regress/expected/equivclass.out        |   4 +-
 src/test/regress/expected/explain_sortorder.out |  24 ++++++
 src/test/regress/parallel_schedule              |   9 +-
 src/test/regress/serial_schedule                |   3 +-
 src/test/regress/sql/explain_sortorder.sql      |  13 +++
 8 files changed, 151 insertions(+), 26 deletions(-)
 create mode 100644 src/test/regress/expected/explain_sortorder.out
 create mode 100644 src/test/regress/sql/explain_sortorder.sql

diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml
index 5a087fb..7723865 100644
--- a/doc/src/sgml/perform.sgml
+++ b/doc/src/sgml/perform.sgml
@@ -504,7 +504,7 @@ WHERE t1.unique1 &lt; 100 AND t1.unique2 = t2.unique2;
     (Sequential-scan-and-sort frequently beats an index scan for sorting many rows,
     because of the nonsequential disk access required by the index scan.)
    </para>
-
+   
    <para>
     One way to look at variant plans is to force the planner to disregard
     whatever strategy it thought was the cheapest, using the enable/disable
@@ -597,12 +597,12 @@ WHERE t1.unique1 &lt; 10 AND t1.unique2 = t2.unique2;
 <screen>
 EXPLAIN ANALYZE SELECT *
 FROM tenk1 t1, tenk2 t2
-WHERE t1.unique1 &lt; 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous;
+WHERE t1.unique1 &lt; 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous DESC NULLS FIRST;
 
                                                                  QUERY PLAN
 --------------------------------------------------------------------------------------------------------------------------------------------
  Sort  (cost=717.34..717.59 rows=101 width=488) (actual time=7.761..7.774 rows=100 loops=1)
-   Sort Key: t1.fivethous
+   Sort Key: t1.fivethous DESC
    Sort Method: quicksort  Memory: 77kB
    -&gt;  Hash Join  (cost=230.47..713.98 rows=101 width=488) (actual time=0.711..7.427 rows=100 loops=1)
          Hash Cond: (t2.unique2 = t1.unique2)
@@ -619,6 +619,13 @@ WHERE t1.unique1 &lt; 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous;
 
     The Sort node shows the sort method used (in particular, whether the sort
     was in-memory or on-disk) and the amount of memory or disk space needed.
+    The <quote>Sort Key</> line indicates the column(s) sorted and also
+    parts of the requested sort order, that differ from the sort order defaults 
+    (<command>ASC</>, <command>NULLS LAST</> for <command>ASC</>, <command>NULLS FIRST</> for <command>DESC</>). 
+    Therefore <command>NULLS FIRST</> is ommitted in the example above.
+   </para>
+   
+   <para>
     The Hash node shows the number of hash buckets and batches as well as the
     peak amount of memory used for the hash table.  (If the number of batches
     exceeds one, there will also be disk space usage involved, but that is not
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8a0be5d..dde7677 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -3,7 +3,7 @@
  * explain.c
  *	  Explain query execution plans
  *
- * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
  * IDENTIFICATION
@@ -14,6 +14,9 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "access/htup_details.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
 #include "commands/createas.h"
 #include "commands/defrem.h"
@@ -30,8 +33,11 @@
 #include "utils/rel.h"
 #include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 #include "utils/tuplesort.h"
+#include "utils/typcache.h"
 #include "utils/xml.h"
+#include "nodes/nodeFuncs.h"
 
 
 /* Hook for plugins to get control in ExplainOneQuery() */
@@ -84,6 +90,7 @@ static void show_group_keys(GroupState *gstate, List *ancestors,
 static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
 					 int nkeys, AttrNumber *keycols,
 					 List *ancestors, ExplainState *es);
+static char *get_sortorder_by_keyno(SortState *sortstate, ExplainState *es, int keyno);
 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,
@@ -116,7 +123,6 @@ static void ExplainYAMLLineStarting(ExplainState *es);
 static void escape_yaml(StringInfo buf, const char *str);
 
 
-
 /*
  * ExplainQuery -
  *	  execute an EXPLAIN command
@@ -1844,12 +1850,12 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
 					 int nkeys, AttrNumber *keycols,
 					 List *ancestors, ExplainState *es)
 {
-	Plan	   *plan = planstate->plan;
-	List	   *context;
-	List	   *result = NIL;
-	bool		useprefix;
-	int			keyno;
-	char	   *exprstr;
+	Plan		*plan = planstate->plan;
+	List		*context;
+	List		*result = NIL;
+	bool		 useprefix;
+	int			 keyno;
+	char		*exprstr;
 
 	if (nkeys <= 0)
 		return;
@@ -1863,23 +1869,94 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
 
 	for (keyno = 0; keyno < nkeys; keyno++)
 	{
-		/* find key expression in tlist */
-		AttrNumber	keyresno = keycols[keyno];
-		TargetEntry *target = get_tle_by_resno(plan->targetlist,
-											   keyresno);
-
+		/* find key ekeynoxpression in tlist */
+		AttrNumber	 keyresno = keycols[keyno];
+		TargetEntry	*target = get_tle_by_resno(plan->targetlist, keyresno);
+		char		*sortorder;
+		StringInfo	element_concat_sortorder;
+
+		if (nodeTag(plan) == T_Sort) {
+			sortorder	= get_sortorder_by_keyno((SortState *) planstate, es, keyno);
+		} else {
+			sortorder	= "";
+		}
 		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);
+		exprstr = deparse_expression((Node *) target->expr, context, useprefix, true);
+		element_concat_sortorder = makeStringInfo();
+		appendStringInfoString(element_concat_sortorder, exprstr);
+		appendStringInfoString(element_concat_sortorder, sortorder);
+		result = lappend(result, element_concat_sortorder->data);
 	}
 
 	ExplainPropertyList(qlabel, result, es);
 }
 
 /*
+ * In verbose mode, additional information about the collation, sort order
+ * and NULLS FIRST/LAST is printed
+ */
+static char *
+get_sortorder_by_keyno(SortState *sortstate, ExplainState *es, int keyno)
+{
+	Sort				*plan;
+	Plan				*planstatePlan;
+	Oid					 sortcoltype;
+	TypeCacheEntry		*typentry;
+	HeapTuple			 opertup;
+	Form_pg_operator	 operform;
+	char				*oprname;
+	StringInfo			 sortorderInformation;
+	Oid					 collId;
+	Oid					 operId;
+	AttrNumber			 keyresno;
+	TargetEntry			*target;
+
+	plan				= (Sort *) sortstate->ss.ps.plan;
+	planstatePlan		= (Plan *) sortstate->ss.ps.plan;
+	collId				= plan->collations[keyno];
+	operId				= plan->sortOperators[keyno];
+	keyresno			= plan->sortColIdx[keyno];
+	target				= get_tle_by_resno(planstatePlan->targetlist, keyresno);
+	sortcoltype			= exprType((const Node *) target->expr);
+	typentry			= lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
+	sortorderInformation = makeStringInfo();
+
+	if (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID)
+	{
+		char	*collname;
+
+		collname = get_collation_name(collId);
+
+		appendStringInfo(sortorderInformation, " COLLATE \"%s\"", collname);
+	}
+	if (operId == typentry->gt_opr)
+	{
+		appendStringInfoString(sortorderInformation, " DESC");
+	}
+	else if (operId != typentry->lt_opr)
+	{
+		opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId));
+		operform = (Form_pg_operator) GETSTRUCT (opertup);
+		oprname = NameStr(operform->oprname);
+		appendStringInfo(sortorderInformation, " USING %s", oprname);
+		ReleaseSysCache(opertup);
+	}
+	//if ((plan->nullsFirst[keyno]) && (operId == typentry->gt_opr))
+	if ((plan->nullsFirst[keyno]) && (operId == typentry->lt_opr))
+	{
+		appendStringInfoString(sortorderInformation, " NULLS FIRST");
+	}
+	//else if ((!plan->nullsFirst[keyno]) && (operId == typentry->lt_opr))
+	else if ((!plan->nullsFirst[keyno]) && (operId == typentry->gt_opr))
+	{
+		appendStringInfoString(sortorderInformation, " NULLS LAST");
+	}
+	return sortorderInformation->data;
+}
+
+/*
  * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
  */
 static void
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 40f5398..3e316dd 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -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
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index b312292..dfae84e 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -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)
diff --git a/src/test/regress/expected/explain_sortorder.out b/src/test/regress/expected/explain_sortorder.out
new file mode 100644
index 0000000..0195910
--- /dev/null
+++ b/src/test/regress/expected/explain_sortorder.out
@@ -0,0 +1,24 @@
+--
+-- Test explain feature: sort order
+--
+CREATE TABLE sortordertest (n1 char(1), n2 int4);
+-- Insert values by which should be ordered
+INSERT INTO sortordertest(n1, n2) VALUES ('d', 5), ('b', 3), ('a', 1), ('e', 2), ('c', 4);
+-- Display sort order when explain analyze and verbose are true.
+EXPLAIN (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "en_US" ASC, n2 DESC;
+               QUERY PLAN                
+-----------------------------------------
+ Sort
+   Sort Key: n1 COLLATE "en_US", n2 DESC
+   ->  Seq Scan on sortordertest
+(3 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC NULLS LAST, n2;
+                   QUERY PLAN                   
+------------------------------------------------
+ Sort
+   Sort Key: n1 COLLATE "C" DESC NULLS LAST, n2
+   ->  Seq Scan on sortordertest
+(3 rows)
+
+DROP TABLE sortordertest;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0ae2f2..3cf63ff 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -59,7 +59,7 @@ test: create_index create_view
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views
+test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views explain_sortorder
 
 # ----------
 # sanity_check does a vacuum, affecting the sort order of SELECT *
@@ -78,12 +78,15 @@ ignore: random
 # ----------
 # Another group of parallel tests
 # ----------
-test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete
+test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update delete namespace prepared_xacts
 
 # ----------
 # Another group of parallel tests
 # ----------
-test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address
+test: brin gin gist spgist privileges security_label collate matview lock replica_identity object_address
+
+# rowsecurity creates an event trigger, so don't run it in parallel
+test: rowsecurity
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 7f762bd..dc242f1 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -71,6 +71,7 @@ test: typed_table
 test: vacuum
 test: drop_if_exists
 test: updatable_views
+test: explain_sortorder
 test: sanity_check
 test: errors
 test: select
@@ -105,8 +106,8 @@ test: collate
 test: matview
 test: lock
 test: replica_identity
-test: rowsecurity
 test: object_address
+test: rowsecurity
 test: alter_generic
 test: misc
 test: psql
diff --git a/src/test/regress/sql/explain_sortorder.sql b/src/test/regress/sql/explain_sortorder.sql
new file mode 100644
index 0000000..18aac4b
--- /dev/null
+++ b/src/test/regress/sql/explain_sortorder.sql
@@ -0,0 +1,13 @@
+--
+-- Test explain feature: sort order
+--
+CREATE TABLE sortordertest (n1 char(1), n2 int4);
+
+-- Insert values by which should be ordered
+INSERT INTO sortordertest(n1, n2) VALUES ('d', 5), ('b', 3), ('a', 1), ('e', 2), ('c', 4);
+
+-- Display sort order when explain analyze and verbose are true.
+EXPLAIN (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "en_US" ASC, n2 DESC;
+EXPLAIN (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC NULLS LAST, n2;
+
+DROP TABLE sortordertest;
\ No newline at end of file
-- 
1.9.1

