From a864cfd1974d1fdf4f888434e963403782dca490 Mon Sep 17 00:00:00 2001
From: Arne Scheffer <scheffa@uni-muenster.de>
Date: Sun, 9 Nov 2014 18:21:18 +0100
Subject: [PATCH 1/3] explain sortorder

Display sort order options in VERBOSE mode of EXPLAIN

For creating indexes on more than one column, it is useful to know
the sort order of each sort key. So now, if you run EXPLAIN in
VERBOSE mode, you get the sort order information in the order
the sort keys are displayed.

Signed-off-by: Marius Timmer <mtimm_01@uni-muenster.de>
---
 src/backend/commands/explain.c                  | 80 +++++++++++++++++++++++++
 src/test/regress/expected/explain_sortorder.out | 19 ++++++
 src/test/regress/expected/inherit.out           |  9 ++-
 src/test/regress/parallel_schedule              |  2 +-
 src/test/regress/serial_schedule                |  1 +
 src/test/regress/sql/explain_sortorder.sql      | 12 ++++
 6 files changed, 119 insertions(+), 4 deletions(-)
 create mode 100644 src/test/regress/expected/explain_sortorder.out
 create mode 100644 src/test/regress/sql/explain_sortorder.sql

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 99aa0f0..c1be1ef 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -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,7 +33,9 @@
 #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"
 
 
@@ -84,6 +89,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 void show_sort_order(SortState *sortstate, ExplainState *es);
 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,
@@ -1436,6 +1442,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			break;
 		case T_Sort:
 			show_sort_keys((SortState *) planstate, ancestors, es);
+			if (es->verbose)
+				show_sort_order((SortState *)planstate, es);
 			show_sort_info((SortState *) planstate, es);
 			break;
 		case T_MergeAppend:
@@ -1879,7 +1887,79 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
 
 	ExplainPropertyList(qlabel, result, es);
 }
+/*
+ * In verbose mode, additional information about the collation, sort order
+ * and NULLS FIRST/LAST is printed
+ */
+static void
+show_sort_order(SortState *sortstate, ExplainState *es)
+{
+	Sort *plan = (Sort *) sortstate->ss.ps.plan;
+	Plan *planstatePlan = (Plan *) sortstate->ss.ps.plan;
+
+	int nkeys = plan->numCols;
+	AttrNumber *keycols = plan->sortColIdx;
+	int keyno;
+
+	Oid sortcoltype;
+	TypeCacheEntry *typentry;
+
+	HeapTuple opertup;
+	Form_pg_operator operform;
+	char *oprname;
+
+	if (nkeys <= 0)
+		return;
+
+	appendStringInfoSpaces(es->str, es->indent * 2);
+	appendStringInfoString(es->str, "Sort Order:");
+
+	for (keyno = 0; keyno < nkeys; keyno++)
+	{
+		Oid collId = plan->collations[keyno];
+		Oid operId = plan->sortOperators[keyno];
+
+		AttrNumber keyresno = keycols[keyno];
+		TargetEntry *target = get_tle_by_resno(planstatePlan->targetlist,
+											   keyresno);
+
+		sortcoltype = exprType(target->expr);
+		typentry = lookup_type_cache(sortcoltype,
+									 TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
 
+		if (keyno!=0)
+			appendStringInfoString(es->str, ",");
+
+		if (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID)
+		{
+			char *collname = get_collation_name(collId);
+			char *localeptr = setlocale(LC_COLLATE, NULL);
+			appendStringInfo(es->str, " COLLATE \"%s\"", collname);
+			/* for those who use COLLATE although their default is already
+			 * the wanted */
+			if (strcmp(collname, localeptr) == 0)
+				appendStringInfo(es->str, " (%s is LC_COLLATE)", collname);
+		}
+		/* print verbose information, also defaults like ASC NULLS LAST*/
+		if (operId == typentry->lt_opr)
+			appendStringInfoString(es->str, " ASC");
+		else if (operId == typentry->gt_opr)
+			appendStringInfoString(es->str, " DESC");
+		else
+		{
+			opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId));
+			operform = (Form_pg_operator) GETSTRUCT (opertup);
+			oprname = NameStr(operform->oprname);
+			appendStringInfo(es->str, " USING %s", oprname);
+			ReleaseSysCache(opertup);
+		}
+		if (plan->nullsFirst[keyno])
+			appendStringInfoString(es->str, " NULLS FIRST");
+		else
+			appendStringInfoString(es->str, " NULLS LAST");
+	}
+	appendStringInfoChar(es->str, '\n');
+}
 /*
  * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
  */
diff --git a/src/test/regress/expected/explain_sortorder.out b/src/test/regress/expected/explain_sortorder.out
new file mode 100644
index 0000000..ed39b51
--- /dev/null
+++ b/src/test/regress/expected/explain_sortorder.out
@@ -0,0 +1,19 @@
+--
+-- 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 (VERBOSE, COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC, n2;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Sort
+   Output: n1, n2, ((n1)::character(1))
+   Sort Key: sortordertest.n1, sortordertest.n2
+   Sort Order: COLLATE "C" DESC NULLS FIRST, ASC NULLS LAST
+   ->  Seq Scan on public.sortordertest
+         Output: n1, n2, n1
+(6 rows)
+
+DROP TABLE sortordertest;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 56e2c99..9492066 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1187,6 +1187,7 @@ explain (verbose, costs off) select * from matest0 order by 1-id;
  Sort
    Output: matest0.id, matest0.name, ((1 - matest0.id))
    Sort Key: ((1 - matest0.id))
+   Sort Order: ASC NULLS LAST
    ->  Result
          Output: matest0.id, matest0.name, (1 - matest0.id)
          ->  Append
@@ -1198,7 +1199,7 @@ explain (verbose, costs off) select * from matest0 order by 1-id;
                      Output: matest2.id, matest2.name
                ->  Seq Scan on public.matest3
                      Output: matest3.id, matest3.name
-(14 rows)
+(15 rows)
 
 select * from matest0 order by 1-id;
  id |  name  
@@ -1247,11 +1248,12 @@ explain (verbose, costs off) select * from matest0 order by 1-id;
    ->  Sort
          Output: matest2.id, matest2.name, ((1 - matest2.id))
          Sort Key: ((1 - matest2.id))
+         Sort Order: ASC NULLS LAST
          ->  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)
-(13 rows)
+(14 rows)
 
 select * from matest0 order by 1-id;
  id |  name  
@@ -1285,6 +1287,7 @@ explain (verbose, costs off) select min(1-id) from matest0;
                        ->  Sort
                              Output: matest2.id, ((1 - matest2.id))
                              Sort Key: ((1 - matest2.id))
+                             Sort Order: ASC NULLS LAST
                              ->  Bitmap Heap Scan on public.matest2
                                    Output: matest2.id, (1 - matest2.id)
                                    Filter: ((1 - matest2.id) IS NOT NULL)
@@ -1292,7 +1295,7 @@ explain (verbose, costs off) select min(1-id) from matest0;
                        ->  Index Scan using matest3i on public.matest3
                              Output: matest3.id, (1 - matest3.id)
                              Index Cond: ((1 - matest3.id) IS NOT NULL)
-(25 rows)
+(26 rows)
 
 select min(1-id) from matest0;
  min 
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d4f02e5..7d12852 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 *
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 611b0a8..bbca2c6 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
diff --git a/src/test/regress/sql/explain_sortorder.sql b/src/test/regress/sql/explain_sortorder.sql
new file mode 100644
index 0000000..2600316
--- /dev/null
+++ b/src/test/regress/sql/explain_sortorder.sql
@@ -0,0 +1,12 @@
+--
+-- 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 (VERBOSE, COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC, n2;
+
+DROP TABLE sortordertest;
-- 
1.9.1


From f144abdf5e529ae131927fee1f0865f5288512fa Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Thu, 20 Nov 2014 12:55:30 +0100
Subject: [PATCH 2/3] Included Testline

---
 src/backend/commands/explain.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index c1be1ef..3dc402d 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1937,6 +1937,7 @@ show_sort_order(SortState *sortstate, ExplainState *es)
 			appendStringInfo(es->str, " COLLATE \"%s\"", collname);
 			/* for those who use COLLATE although their default is already
 			 * the wanted */
+            /* This is a testline made by mtimm_01 */
 			if (strcmp(collname, localeptr) == 0)
 				appendStringInfo(es->str, " (%s is LC_COLLATE)", collname);
 		}
-- 
1.9.1


From 3b2fdc73b7034511e59d57671ed37429c5b62cca Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Thu, 20 Nov 2014 18:27:16 +0100
Subject: [PATCH 3/3] Output now in correct format

---
 src/backend/commands/explain.c | 115 +++++++++++++++++++++++------------------
 1 file changed, 65 insertions(+), 50 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 3dc402d..1874979 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1908,58 +1908,73 @@ show_sort_order(SortState *sortstate, ExplainState *es)
 	Form_pg_operator operform;
 	char *oprname;
 
+    List *elements = NIL;
+    
 	if (nkeys <= 0)
+    {
 		return;
-
-	appendStringInfoSpaces(es->str, es->indent * 2);
-	appendStringInfoString(es->str, "Sort Order:");
-
-	for (keyno = 0; keyno < nkeys; keyno++)
-	{
-		Oid collId = plan->collations[keyno];
-		Oid operId = plan->sortOperators[keyno];
-
-		AttrNumber keyresno = keycols[keyno];
-		TargetEntry *target = get_tle_by_resno(planstatePlan->targetlist,
-											   keyresno);
-
-		sortcoltype = exprType(target->expr);
-		typentry = lookup_type_cache(sortcoltype,
-									 TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
-
-		if (keyno!=0)
-			appendStringInfoString(es->str, ",");
-
-		if (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID)
-		{
-			char *collname = get_collation_name(collId);
-			char *localeptr = setlocale(LC_COLLATE, NULL);
-			appendStringInfo(es->str, " COLLATE \"%s\"", collname);
-			/* for those who use COLLATE although their default is already
-			 * the wanted */
-            /* This is a testline made by mtimm_01 */
-			if (strcmp(collname, localeptr) == 0)
-				appendStringInfo(es->str, " (%s is LC_COLLATE)", collname);
-		}
-		/* print verbose information, also defaults like ASC NULLS LAST*/
-		if (operId == typentry->lt_opr)
-			appendStringInfoString(es->str, " ASC");
-		else if (operId == typentry->gt_opr)
-			appendStringInfoString(es->str, " DESC");
-		else
-		{
-			opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId));
-			operform = (Form_pg_operator) GETSTRUCT (opertup);
-			oprname = NameStr(operform->oprname);
-			appendStringInfo(es->str, " USING %s", oprname);
-			ReleaseSysCache(opertup);
-		}
-		if (plan->nullsFirst[keyno])
-			appendStringInfoString(es->str, " NULLS FIRST");
-		else
-			appendStringInfoString(es->str, " NULLS LAST");
-	}
-	appendStringInfoChar(es->str, '\n');
+    }
+    
+    for (keyno = 0; keyno < nkeys; keyno++)
+    {
+        char sort_order_value[200] = "";
+        Oid collId = plan->collations[keyno];
+        Oid operId = plan->sortOperators[keyno];
+
+        AttrNumber keyresno = keycols[keyno];
+        TargetEntry *target = get_tle_by_resno(planstatePlan->targetlist,
+                                            keyresno);
+
+        sortcoltype = exprType(target->expr);
+        typentry = lookup_type_cache(sortcoltype,
+                                    TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
+
+        if (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID)
+        {
+            char *collname = get_collation_name(collId);
+            char *localeptr = setlocale(LC_COLLATE, NULL);
+            strcat(sort_order_value, "COLLATE \"");
+            strcat(sort_order_value, collname);
+            strcat(sort_order_value, "\"");
+            /* for those who use COLLATE although their default is already
+             * the wanted */
+            if (strcmp(collname, localeptr) == 0)
+            {
+                strcat(sort_order_value, " (");
+                strcat(sort_order_value, collname);
+                strcat(sort_order_value, " is LC_COLLATE)");
+            }
+        }
+        /* print verbose information, also defaults like ASC NULLS LAST*/
+        if (operId == typentry->lt_opr)
+        {
+            strcat(sort_order_value, " ASC");
+        }
+        else if (operId == typentry->gt_opr)
+        {
+            strcat(sort_order_value, " DESC");
+        }
+        else 
+        {
+            opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId));
+            operform = (Form_pg_operator) GETSTRUCT (opertup);
+            oprname = NameStr(operform->oprname);
+            strcat(sort_order_value, " USING ");
+            strcat(sort_order_value, oprname);
+            ReleaseSysCache(opertup);
+        }
+        if (plan->nullsFirst[keyno])
+        {
+            strcat(sort_order_value, " NULLS FIRST");
+        }
+        else
+        {
+            strcat(sort_order_value, " NULLS LAST");
+        }
+        elements = lappend(elements, sort_order_value);
+    }
+    
+    ExplainPropertyList("Sort Order", elements, es);
 }
 /*
  * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
-- 
1.9.1

