[PATCH] explain sortorder

Started by Timmer, Mariusabout 11 years ago19 messages
#1Timmer, Marius
marius.timmer@uni-muenster.de
2 attachment(s)

Hello everyone,

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 - Lukas

- This patch is meant for discussion
- It’s against the master branch
- The patch compiles successfully and one test (inherit) is affected
- There are no platform-specific items in this patch
- The patch, as described, enhances EXPLAIN VERBOSE. For an example, see
the regression test
- There is no TODO item referring to this patch

@patchname: explain_sortorder v2
@version: 2.01
@author: Marius Timmer <mtimm_01@uni-muenster.de>,
Arne Scheffer <arne.scheffer@uni-muenster.de>,
Lukas Kreft <lukaskreft@uni-muenster.de>
@description: Display sort order options in VERBOSE mode of EXPLAIN

- The situation

Currently I am able to run a EXPLAIN-Statement (verbose) for getting more
Information about a Query. But it is not enough to check in which order the
results will be sorted, what could be interesting to modify some Statements
so they can become more performant.

- What this patch does

This patch will add one more information to the result of an EXPLAIN-
Statement in verbose-mode. You will find the new property "Sort order"
which tells you the order of the used keys while sorting.
You can use it in all available Formats.

Greetings,

Marius Timmer

Attachments:

explain_sortorder-v2.patchapplication/octet-stream; name=explain_sortorder-v2.patchDownload
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

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#2Mike Blackwell
mike.blackwell@rrd.com
In reply to: Timmer, Marius (#1)
Re: [PATCH] explain sortorder

Initial review:

Patch applies cleanly to current head, although it appears to have
soft/hard tab and trailing space issues.

make check fails with the output below. The expected collation clause is
not present.

--
-- 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: ASC NULLS LAST, ASC NULLS LAST
-> Seq Scan on public.sortordertest
Output: n1, n2, n1
(6 rows)

DROP TABLE sortordertest;

__________________________________________________________________________________
*Mike Blackwell | Technical Analyst, Distribution Services/Rollout
Management | RR Donnelley*
1750 Wallace Ave | St Charles, IL 60174-3401
Office: 630.313.7818
Mike.Blackwell@rrd.com
http://www.rrdonnelley.com

#3Timmer, Marius
marius.timmer@uni-muenster.de
In reply to: Mike Blackwell (#2)
2 attachment(s)
Re: [PATCH] explain sortorder

Hi Mike,

Now I've replaced the spaces at the beginning of the lines with tabs.
Quotes(") in the expected/explain_sortorder.out-File caused failing „make check“. So I changed them to single quotes(').

I’ve added the corrected patch as attachment.

Kind regards,

Marius

Attachments:

explain_sortorder-v3.patchapplication/octet-stream; name=explain_sortorder-v3.patchDownload
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/4] 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/4] 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/4] 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


From ed962b2d49d98b9568aa225d09322321de077195 Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Tue, 16 Dec 2014 10:33:55 +0100
Subject: [PATCH 4/4] Replaced spaces with tabs (explain.c) and quotes with
 single quotes (explain_sortorder.out)

---
 src/backend/commands/explain.c                  | 161 ++++++++++++------------
 src/test/regress/expected/explain_sortorder.out |   2 +-
 2 files changed, 80 insertions(+), 83 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1874979..baade05 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1887,6 +1887,7 @@ 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
@@ -1894,88 +1895,80 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
 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;
+	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;
-
-    List *elements = NIL;
+	char			*oprname;
+	List			*elements			= NIL;
+	int				 maxItemLength		= 200;
+	char			 sort_order_values[nkeys][maxItemLength + 1];
     
 	if (nkeys <= 0)
-    {
+	{
 		return;
-    }
-    
-    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);
+	}
+
+	for (keyno = 0; keyno < nkeys; keyno++)
+	{
+		strcpy(sort_order_values[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 (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID)
+		{
+			char	*collname	= get_collation_name(collId);
+			char	*localeptr	= setlocale(LC_COLLATE, NULL);
+			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "COLLATE '%s' ", collname);
+			/* for those who use COLLATE although their default is already
+			 * the wanted */
+			if (strcmp(collname, localeptr) == 0)
+			{
+				snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " (%s is LC_COLLATE) ", collname);
+			}
+		}
+		/* print verbose information, also defaults like ASC NULLS LAST*/
+			if (operId == typentry->lt_opr)
+			{
+				snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "%s", "ASC");
+			}
+			else if (operId == typentry->gt_opr)
+			{
+				snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "%s", "DESC");
+			}
+			else
+			{
+				opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId));
+				operform = (Form_pg_operator) GETSTRUCT (opertup);
+				oprname = NameStr(operform->oprname);
+				snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "USING %s", oprname);
+				ReleaseSysCache(opertup);
+			}
+			if (plan->nullsFirst[keyno])
+			{
+				snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " NULLS FIRST");
+			}
+			else
+			{
+				snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " NULLS LAST");
+			}
+			elements = lappend(elements, sort_order_values[keyno]);
+	}
+	ExplainPropertyList("Sort Order", elements, es);
 }
+
 /*
  * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
  */
@@ -2033,14 +2026,17 @@ show_hash_info(HashState *hashstate, ExplainState *es)
 								hashtable->nbatch_original, es);
 			ExplainPropertyLong("Peak Memory Usage", spacePeakKb, es);
 		}
-		else if ((hashtable->nbatch_original != hashtable->nbatch) ||
-				 (hashtable->nbuckets_original != hashtable->nbuckets))
+		else if (hashtable->nbatch_original != hashtable->nbatch ||
+				         hashtable->nbuckets_original != hashtable->nbuckets)
 		{
 			appendStringInfoSpaces(es->str, es->indent * 2);
 			appendStringInfo(es->str,
-			"Buckets: %d (originally %d)  Batches: %d (originally %d)  Memory Usage: %ldkB\n",
-							 hashtable->nbuckets, hashtable->nbuckets_original,
-							 hashtable->nbatch, hashtable->nbatch_original, spacePeakKb);
+                                   "Buckets: %d (originally %d)  Batches: %d (originally %d)  Memory Usage: %ldkB\n",
+                                   hashtable->nbuckets,
+                                   hashtable->nbuckets_original,
+                                   hashtable->nbatch,
+                                   hashtable->nbatch_original,
+                                   spacePeakKb);
 		}
 		else
 		{
@@ -2240,6 +2236,7 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 		case T_BitmapHeapScan:
 		case T_TidScan:
 		case T_ForeignScan:
+        case T_CustomScan:
 		case T_ModifyTable:
 			/* Assert it's on a real relation */
 			Assert(rte->rtekind == RTE_RELATION);
diff --git a/src/test/regress/expected/explain_sortorder.out b/src/test/regress/expected/explain_sortorder.out
index ed39b51..8450924 100644
--- a/src/test/regress/expected/explain_sortorder.out
+++ b/src/test/regress/expected/explain_sortorder.out
@@ -11,7 +11,7 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C"
  Sort
    Output: n1, n2, ((n1)::character(1))
    Sort Key: sortordertest.n1, sortordertest.n2
-   Sort Order: COLLATE "C" DESC NULLS FIRST, ASC NULLS LAST
+   Sort Order: COLLATE 'C' DESC NULLS FIRST, ASC NULLS LAST
    ->  Seq Scan on public.sortordertest
          Output: n1, n2, n1
 (6 rows)
-- 
1.9.1

smime.p7sapplication/pkcs7-signature; name=smime.p7sDownload
#4Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Mike Blackwell (#2)
Re: [PATCH] explain sortorder

On 12/15/2014 06:49 PM, Mike Blackwell wrote:

QUERY PLAN
------------------------------------------------
Sort
Output: n1, n2, ((n1)::character(1))
Sort Key: sortordertest.n1, sortordertest.n2
Sort Order: ASC NULLS LAST, ASC NULLS LAST
-> Seq Scan on public.sortordertest
Output: n1, n2, n1
(6 rows)

I don't like this output. If there are a lot of sort keys, it gets
difficult to match the right ASC/DESC element to the sort key it applies
to. (Also, there seems to be double-spaces in the list)

I would suggest just adding the information to the Sort Key line. As
long as you don't print the modifiers when they are defaults (ASC and
NULLS LAST), we could print the information even in non-VERBOSE mode. So
it would look something like:

Sort Key: sortordertest.n1 NULLS FIRST, sortordertest.n2 DESC

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#4)
Re: [PATCH] explain sortorder

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

I would suggest just adding the information to the Sort Key line. As
long as you don't print the modifiers when they are defaults (ASC and
NULLS LAST), we could print the information even in non-VERBOSE mode.

+1. I had assumed without looking that that was what it did already,
else I'd have complained too.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Timmer, Marius
marius.timmer@uni-muenster.de
In reply to: Tom Lane (#5)
1 attachment(s)
Re: [PATCH] explain sortorder

Hi,

we have spent the last days to realize your suggestions in the patch.
It affects the result of a EXPLAIN-Statement (even in non-verbose-mode). Now you will get the order-information for every single sort-key which is not ordered by the defaults.

best regards,

Marius

---
Marius Timmer
Zentrum für Informationsverarbeitung
Westfälische Wilhelms-Universität Münster
Einsteinstraße 60

mtimm_01@uni-muenster.de

Attachments:

explain_sortorder-v6.patchapplication/octet-stream; name=explain_sortorder-v6.patchDownload
From 1e9b02e0f8cb307e824ef8ba56d5a9986fdcc023 Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Wed, 7 Jan 2015 15:32:39 +0100
Subject: [PATCH] explain_sortorder added

---
 src/backend/commands/explain.c                  | 107 +++++++++++++++++++++---
 src/test/regress/expected/aggregates.out        |   2 +-
 src/test/regress/expected/equivclass.out        |   4 +-
 src/test/regress/expected/explain_sortorder.out |  16 ++++
 src/test/regress/parallel_schedule              |   2 +-
 src/test/regress/serial_schedule                |   1 +
 src/test/regress/sql/explain_sortorder.sql      |  12 +++
 7 files changed, 130 insertions(+), 14 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 8a0be5d..ba4d17a 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
@@ -1435,6 +1441,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:
@@ -1863,23 +1871,102 @@ 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;
+		TargetEntry	*target;
+		char		*sortorder;
+		int			 maxlength;
+		char		*element_concat_sortorder;
+
+		keyresno	= keycols[keyno];
+		target		= get_tle_by_resno(plan->targetlist, keyresno);
+		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);
+		maxlength = strlen(exprstr)+1+strlen(sortorder)+1;
+		element_concat_sortorder = (char *) malloc((maxlength + 1) * sizeof(char));
+		snprintf(element_concat_sortorder, maxlength , "%s%s", exprstr, sortorder);
+		result = lappend(result, element_concat_sortorder);
 	}
 
 	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;
+	int					 maxItemLength;
+	char				*sort_order_values;
+	Oid					 collId;
+	Oid					 operId;
+	AttrNumber			 keyresno;
+	TargetEntry			*target;
+
+	plan				= (Sort *) sortstate->ss.ps.plan;
+	planstatePlan		= (Plan *) sortstate->ss.ps.plan;
+	maxItemLength		= 200;
+	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);
+	sort_order_values	= (char *) malloc((maxItemLength + 1) * sizeof(char));
+	strcpy(sort_order_values, "");
+
+	if (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID)
+	{
+		char	*collname;
+		char	*localeptr;
+
+		collname = get_collation_name(collId);
+		localeptr = setlocale(LC_COLLATE, NULL);
+
+		snprintf(sort_order_values + strlen(sort_order_values), maxItemLength - strlen(sort_order_values), " COLLATE '%s'", collname);
+		/* for those who use COLLATE although their default is already
+			* the wanted */
+		if (strcmp(collname, localeptr) == 0)
+		{
+			snprintf(sort_order_values + strlen(sort_order_values), maxItemLength - strlen(sort_order_values), " (%s is LC_COLLATE)", collname);
+		}
+	}
+	if (operId == typentry->gt_opr)
+	{
+		snprintf(sort_order_values + strlen(sort_order_values), maxItemLength - strlen(sort_order_values), "%s", " DESC");
+	}
+	else if (operId != typentry->lt_opr)
+	{
+		opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId));
+		operform = (Form_pg_operator) GETSTRUCT (opertup);
+		oprname = NameStr(operform->oprname);
+		snprintf(sort_order_values + strlen(sort_order_values), maxItemLength - strlen(sort_order_values), " USING %s", oprname);
+		ReleaseSysCache(opertup);
+	}
+	if (plan->nullsFirst[keyno])
+	{
+		snprintf(sort_order_values + strlen(sort_order_values), maxItemLength - strlen(sort_order_values), " NULLS FIRST");
+	}
+	return sort_order_values;
+}
+
+/*
  * 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..ca8aec8 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 NULLS FIRST
    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..abd8130
--- /dev/null
+++ b/src/test/regress/expected/explain_sortorder.out
@@ -0,0 +1,16 @@
+--
+-- 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 "C" DESC, n2;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: n1 COLLATE 'C' DESC NULLS FIRST, 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 62ef6ec..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 *
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index b491b97..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
diff --git a/src/test/regress/sql/explain_sortorder.sql b/src/test/regress/sql/explain_sortorder.sql
new file mode 100644
index 0000000..d1800a3
--- /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 (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC, n2;
+
+DROP TABLE sortordertest;
\ No newline at end of file
-- 
1.9.1

#7Mike Blackwell
mike.blackwell@rrd.com
In reply to: Timmer, Marius (#6)
Re: [PATCH] explain sortorder

V6 of this patch applies, builds and checks against the current HEAD. The
areas below could use some attention.

In explain.c:

malloc() should not be called directly here. palloc() would be the
correct call, I believe, but the functions in stringinfo.h are probably
your best choice as they remove the necessity for dealing with buffer size
and overflow.

There is leftover commented out code from the previous patch version in
the T_Sort case.

In show_sort_group_keys(), the splitting of the existing declaration and
initialization of the keyresno and target seems unnecessary and against the
style of surrounding code.

Multi-line comments should follow the existing format.

There are no tests for the "... is LC_COLLATE" and "COLLATE..." cases.

Section 14.1 of the documentation may need to be updated.

Mike.

__________________________________________________________________________________
*Mike Blackwell | Technical Analyst, Distribution Services/Rollout
Management | RR Donnelley*
1750 Wallace Ave | St Charles, IL 60174-3401
Office: 630.313.7818
Mike.Blackwell@rrd.com
http://www.rrdonnelley.com

<http://www.rrdonnelley.com/&gt;
* <Mike.Blackwell@rrd.com>*

On Wed, Jan 7, 2015 at 10:17 AM, Timmer, Marius <
marius.timmer@uni-muenster.de> wrote:

Show quoted text

Hi,

we have spent the last days to realize your suggestions in the patch.
It affects the result of a EXPLAIN-Statement (even in non-verbose-mode).
Now you will get the order-information for every single sort-key which is
not ordered by the defaults.

best regards,

Marius

---
Marius Timmer
Zentrum für Informationsverarbeitung
Westfälische Wilhelms-Universität Münster
Einsteinstraße 60

mtimm_01@uni-muenster.de

#8Timmer, Marius
marius.timmer@uni-muenster.de
In reply to: Mike Blackwell (#7)
1 attachment(s)
Re: [PATCH] explain sortorder

Hi,

we removed
-malloc() (StringInfo is used as suggested now).
-leftover commented out code
-the splitting of existing declaration and initialization in show_group_keys().

Missing tests and documentation are WIP and will follow with the next patch version.

Best regards

Marius

---
Marius Timmer
Zentrum für Informationsverarbeitung
Westfälische Wilhelms-Universität Münster
Einsteinstraße 60

mtimm_01@uni-muenster.de

Attachments:

explain_sortorder-v7.patchapplication/octet-stream; name=explain_sortorder-v7.patchDownload
From 1137fd62ea2a3e43ca9b9221d4e700c970fd883e Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Mon, 5 Jan 2015 15:04:16 +0100
Subject: [PATCH 1/3] Sortorder added.

---
 src/backend/commands/explain.c        | 90 +++++++++++++++++++++++++++++++++++
 src/test/regress/expected/inherit.out |  9 ++--
 src/test/regress/parallel_schedule    |  2 +-
 src/test/regress/serial_schedule      |  1 +
 4 files changed, 98 insertions(+), 4 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 064f880..611886b 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,
@@ -1435,6 +1441,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:
@@ -1880,6 +1888,88 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
 }
 
 /*
+ * 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;
+	List *elements = NIL;
+	int maxItemLength = 200;
+	char sort_order_values[nkeys][maxItemLength + 1];
+
+	if (nkeys <= 0)
+		return;
+
+	for (keyno = 0; keyno < nkeys; keyno++)
+	{
+		strcpy(sort_order_values[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 (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID)
+		{
+			char	*collname	= get_collation_name(collId);
+			char	*localeptr	= setlocale(LC_COLLATE, NULL);
+			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "COLLATE '%s' ", collname);
+			/* for those who use COLLATE although their default is already
+			 * the wanted */
+			if (strcmp(collname, localeptr) == 0)
+			{
+				snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " (%s is LC_COLLATE) ", collname);
+			}
+		}
+		/* print verbose information, also defaults like ASC NULLS LAST*/
+		if (operId == typentry->lt_opr)
+		{
+			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "%s", "ASC");
+		}
+		else if (operId == typentry->gt_opr)
+		{
+			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "%s", "DESC");
+		}
+		else
+		{
+			opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId));
+			operform = (Form_pg_operator) GETSTRUCT (opertup);
+			oprname = NameStr(operform->oprname);
+			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "USING %s", oprname);
+			ReleaseSysCache(opertup);
+		}
+		if (plan->nullsFirst[keyno])
+		{
+			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " NULLS FIRST");
+		}
+		else
+		{
+			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " NULLS LAST");
+		}
+		elements = lappend(elements, sort_order_values[keyno]);
+	}
+	ExplainPropertyList("Sort Order", elements, es);
+}
+
+/*
  * If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
  */
 static void
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 62ef6ec..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 *
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index b491b97..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
-- 
1.9.1


From 3cd1fd75654f948ff69c34df222e362cf3b59dfa Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Mon, 5 Jan 2015 15:41:39 +0100
Subject: [PATCH 2/3] added files for make check

---
 src/test/regress/expected/explain_sortorder.out | 19 +++++++++++++++++++
 src/test/regress/sql/explain_sortorder.sql      | 12 ++++++++++++
 2 files changed, 31 insertions(+)
 create mode 100644 src/test/regress/expected/explain_sortorder.out
 create mode 100644 src/test/regress/sql/explain_sortorder.sql

diff --git a/src/test/regress/expected/explain_sortorder.out b/src/test/regress/expected/explain_sortorder.out
new file mode 100644
index 0000000..8450924
--- /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/sql/explain_sortorder.sql b/src/test/regress/sql/explain_sortorder.sql
new file mode 100644
index 0000000..3d8ace8
--- /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;
\ No newline at end of file
-- 
1.9.1


From 2bb617934df2de8830e305f39d6fc976e0e64b25 Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Tue, 13 Jan 2015 11:10:59 +0100
Subject: [PATCH 3/3] replaced malloc() with StringInfo, removed comment,
 declaration and initialisation in one line

---
 src/backend/commands/explain.c                  | 165 +++++++++++-------------
 src/test/regress/expected/aggregates.out        |   2 +-
 src/test/regress/expected/equivclass.out        |   4 +-
 src/test/regress/expected/explain_sortorder.out |  15 +--
 src/test/regress/expected/inherit.out           |   9 +-
 src/test/regress/sql/explain_sortorder.sql      |   4 +-
 6 files changed, 91 insertions(+), 108 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 611886b..6f6cb6c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -37,6 +37,7 @@
 #include "utils/tuplesort.h"
 #include "utils/typcache.h"
 #include "utils/xml.h"
+#include "nodes/nodeFuncs.h"
 
 
 /* Hook for plugins to get control in ExplainOneQuery() */
@@ -89,7 +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 void show_sort_order(SortState *sortstate, 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,
@@ -122,7 +123,6 @@ static void ExplainYAMLLineStarting(ExplainState *es);
 static void escape_yaml(StringInfo buf, const char *str);
 
 
-
 /*
  * ExplainQuery -
  *	  execute an EXPLAIN command
@@ -1441,8 +1441,6 @@ 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:
@@ -1852,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;
@@ -1871,17 +1869,25 @@ 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);
@@ -1891,82 +1897,65 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel,
  * 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)
+static char *
+get_sortorder_by_keyno(SortState *sortstate, ExplainState *es, int keyno)
 {
-	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;
+	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;
+		char	*localeptr;
 
-	HeapTuple opertup;
-	Form_pg_operator operform;
-	char *oprname;
-	List *elements = NIL;
-	int maxItemLength = 200;
-	char sort_order_values[nkeys][maxItemLength + 1];
+		collname = get_collation_name(collId);
+		localeptr = setlocale(LC_COLLATE, NULL);
 
-	if (nkeys <= 0)
-		return;
-
-	for (keyno = 0; keyno < nkeys; keyno++)
-	{
-		strcpy(sort_order_values[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 (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID)
-		{
-			char	*collname	= get_collation_name(collId);
-			char	*localeptr	= setlocale(LC_COLLATE, NULL);
-			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "COLLATE '%s' ", collname);
-			/* for those who use COLLATE although their default is already
-			 * the wanted */
-			if (strcmp(collname, localeptr) == 0)
-			{
-				snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " (%s is LC_COLLATE) ", collname);
-			}
-		}
-		/* print verbose information, also defaults like ASC NULLS LAST*/
-		if (operId == typentry->lt_opr)
-		{
-			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "%s", "ASC");
-		}
-		else if (operId == typentry->gt_opr)
-		{
-			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "%s", "DESC");
-		}
-		else
-		{
-			opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId));
-			operform = (Form_pg_operator) GETSTRUCT (opertup);
-			oprname = NameStr(operform->oprname);
-			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), "USING %s", oprname);
-			ReleaseSysCache(opertup);
-		}
-		if (plan->nullsFirst[keyno])
+		appendStringInfo(sortorderInformation, " COLLATE '%s'", collname);
+		/* for those who use COLLATE although their default is already the wanted */
+		if (strcmp(collname, localeptr) == 0)
 		{
-			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " NULLS FIRST");
+			appendStringInfo(sortorderInformation, " (%s is LC_COLLATE)", collname);
 		}
-		else
-		{
-			snprintf(sort_order_values[keyno] + strlen(sort_order_values[keyno]), maxItemLength - strlen(sort_order_values[keyno]), " NULLS LAST");
-		}
-		elements = lappend(elements, sort_order_values[keyno]);
 	}
-	ExplainPropertyList("Sort Order", elements, es);
+	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])
+	{
+		//snprintf(sort_order_values + strlen(sort_order_values), maxItemLength - strlen(sort_order_values), " NULLS FIRST");
+		appendStringInfoString(sortorderInformation, " NULLS FIRST");
+	}
+	return sortorderInformation->data;
 }
 
 /*
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 40f5398..ca8aec8 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 NULLS FIRST
    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
index 8450924..abd8130 100644
--- a/src/test/regress/expected/explain_sortorder.out
+++ b/src/test/regress/expected/explain_sortorder.out
@@ -5,15 +5,12 @@ 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                         
-------------------------------------------------------------
+EXPLAIN (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)
+   Sort Key: n1 COLLATE 'C' DESC NULLS FIRST, n2
+   ->  Seq Scan on sortordertest
+(3 rows)
 
 DROP TABLE sortordertest;
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 9492066..56e2c99 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1187,7 +1187,6 @@ 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
@@ -1199,7 +1198,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
-(15 rows)
+(14 rows)
 
 select * from matest0 order by 1-id;
  id |  name  
@@ -1248,12 +1247,11 @@ 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)
-(14 rows)
+(13 rows)
 
 select * from matest0 order by 1-id;
  id |  name  
@@ -1287,7 +1285,6 @@ 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)
@@ -1295,7 +1292,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)
-(26 rows)
+(25 rows)
 
 select min(1-id) from matest0;
  min 
diff --git a/src/test/regress/sql/explain_sortorder.sql b/src/test/regress/sql/explain_sortorder.sql
index 3d8ace8..555d22f 100644
--- a/src/test/regress/sql/explain_sortorder.sql
+++ b/src/test/regress/sql/explain_sortorder.sql
@@ -7,6 +7,6 @@ CREATE TABLE sortordertest (n1 char(1), n2 int4);
 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;
+EXPLAIN (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC, n2;
 
-DROP TABLE sortordertest;
\ No newline at end of file
+DROP TABLE sortordertest;
-- 
1.9.1

#9Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Timmer, Marius (#8)
Re: [PATCH] explain sortorder

On 01/13/2015 06:04 PM, Timmer, Marius wrote:

-malloc() (StringInfo is used as suggested now).

There really shouldn't be any snprintf() calls in the patch, when
StringInfo is used correctly...

@@ -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

This patch isn't going to be committed with this output format. Please
change per my suggestion earlier:

I don't like this output. If there are a lot of sort keys, it gets
difficult to match the right ASC/DESC element to the sort key it applies
to. (Also, there seems to be double-spaces in the list)

I would suggest just adding the information to the Sort Key line. As
long as you don't print the modifiers when they are defaults (ASC and
NULLS LAST), we could print the information even in non-VERBOSE mode. So
it would look something like:

Sort Key: sortordertest.n1 NULLS FIRST, sortordertest.n2 DESC

Or if you don't agree with that, explain why.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Timmer, Marius
marius.timmer@uni-muenster.de
In reply to: Heikki Linnakangas (#9)
1 attachment(s)
Re: [PATCH] explain sortorder

Hello Heikki,

abbreviated version:
Sorry, the problem is only the unhandy patch text format, not different opinions how to proceed.

Long version:
The v7 patch file already addressed your suggestions,
but the file contained serveral (old) local commits,
the new ones at the end of the patch text/file.

v7.1 is attached and addresses this issue providing a clean patch file.

V8 will - as mentioned - add missing docs and regression tests,
Mike suggested.

VlG-Arne & Marius

---
Marius Timmer
Zentrum für Informationsverarbeitung
Westfälische Wilhelms-Universität Münster
Einsteinstraße 60

mtimm_01@uni-muenster.de

Am 13.01.2015 um 18:52 schrieb Heikki Linnakangas <hlinnakangas@vmware.com>:

Show quoted text

On 01/13/2015 06:04 PM, Timmer, Marius wrote:

-malloc() (StringInfo is used as suggested now).

There really shouldn't be any snprintf() calls in the patch, when StringInfo is used correctly...

@@ -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

This patch isn't going to be committed with this output format. Please change per my suggestion earlier:

I don't like this output. If there are a lot of sort keys, it gets
difficult to match the right ASC/DESC element to the sort key it applies
to. (Also, there seems to be double-spaces in the list)

I would suggest just adding the information to the Sort Key line. As
long as you don't print the modifiers when they are defaults (ASC and
NULLS LAST), we could print the information even in non-VERBOSE mode. So
it would look something like:

Sort Key: sortordertest.n1 NULLS FIRST, sortordertest.n2 DESC

Or if you don't agree with that, explain why.

- Heikki

Attachments:

explain_sortorder-v7_1.patchapplication/octet-stream; name=explain_sortorder-v7_1.patchDownload
From 529627b208c0a3b490b53d7676755e0c185a7451 Mon Sep 17 00:00:00 2001
From: mtimm_01 <mtimm_01@uni-muenster.de>
Date: Wed, 14 Jan 2015 15:48:29 +0100
Subject: [PATCH] Clean Patch v.7.1

---
 src/backend/commands/explain.c                  | 110 ++++++++++++++++++++----
 src/test/regress/expected/aggregates.out        |   2 +-
 src/test/regress/expected/equivclass.out        |   4 +-
 src/test/regress/expected/explain_sortorder.out |  16 ++++
 src/test/regress/parallel_schedule              |   9 +-
 src/test/regress/serial_schedule                |   3 +-
 src/test/regress/sql/explain_sortorder.sql      |  12 +++
 7 files changed, 133 insertions(+), 23 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 8a0be5d..833084c 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,95 @@ 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;
+		char	*localeptr;
+
+		collname = get_collation_name(collId);
+		localeptr = setlocale(LC_COLLATE, NULL);
+
+		appendStringInfo(sortorderInformation, " COLLATE '%s'", collname);
+		/* for those who use COLLATE although their default is already the wanted */
+		if (strcmp(collname, localeptr) == 0)
+		{
+			appendStringInfo(sortorderInformation, " (%s is LC_COLLATE)", 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])
+	{
+		appendStringInfoString(sortorderInformation, " NULLS FIRST");
+	}
+	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..ca8aec8 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 NULLS FIRST
    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..abd8130
--- /dev/null
+++ b/src/test/regress/expected/explain_sortorder.out
@@ -0,0 +1,16 @@
+--
+-- 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 "C" DESC, n2;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Sort
+   Sort Key: n1 COLLATE 'C' DESC NULLS FIRST, 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..555d22f
--- /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 (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC, n2;
+
+DROP TABLE sortordertest;
-- 
1.9.1

#11Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Timmer, Marius (#10)
Re: [PATCH] explain sortorder

On 01/14/2015 05:26 PM, Timmer, Marius wrote:

Hello Heikki,

abbreviated version:
Sorry, the problem is only the unhandy patch text format, not different opinions how to proceed.

Long version:
The v7 patch file already addressed your suggestions,
but the file contained serveral (old) local commits,
the new ones at the end of the patch text/file.

Ah, missed that. I stopped reading when I saw the old stuff there :-).

v7.1 is attached and addresses this issue providing a clean patch file.

Ok, thanks, will take a look.

V8 will - as mentioned - add missing docs and regression tests,

Great!

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Arne Scheffer
scheffa@uni-muenster.de
In reply to: Heikki Linnakangas (#11)
Re: [PATCH] explain sortorder

Hi,

we will also remove the following is lc_collate hint in the next version, showing only mandatory info as suggested.

/* for those who use COLLATE although their default is already the wanted */
if (strcmp(collname, localeptr) == 0)
{
appendStringInfo(sortorderInformation, " (%s is LC_COLLATE)", collname);
}

Anybody insisting on that?

Arne

Note: I see, at the moment we use the wrong default for DESC. We'll fix that.

On Wed, 14 Jan 2015, Heikki Linnakangas wrote:

On 01/14/2015 05:26 PM, Timmer, Marius wrote:

Hello Heikki,

abbreviated version:
Sorry, the problem is only the unhandy patch text format, not different
opinions how to proceed.

Long version:
The v7 patch file already addressed your suggestions,
but the file contained serveral (old) local commits,
the new ones at the end of the patch text/file.

Ah, missed that. I stopped reading when I saw the old stuff there :-).

v7.1 is attached and addresses this issue providing a clean patch file.

Ok, thanks, will take a look.

V8 will - as mentioned - add missing docs and regression tests,

Great!

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Timmer, Marius
marius.timmer@uni-muenster.de
In reply to: Arne Scheffer (#12)
1 attachment(s)
Re: [PATCH] explain sortorder

Hi,

attached is version 8, fixing remaining issues, adding docs and tests as requested/agreed.

Marius & Arne

---
Marius Timmer
Zentrum für Informationsverarbeitung
Westfälische Wilhelms-Universität Münster
Einsteinstraße 60

mtimm_01@uni-muenster.de

Am 14.01.2015 um 17:42 schrieb Arne Scheffer <scheffa@uni-muenster.de>:

Show quoted text

Hi,

we will also remove the following is lc_collate hint in the next version, showing only mandatory info as suggested.

/* for those who use COLLATE although their default is already the wanted */
if (strcmp(collname, localeptr) == 0)
{
appendStringInfo(sortorderInformation, " (%s is LC_COLLATE)", collname);
}

Anybody insisting on that?

Arne

Note: I see, at the moment we use the wrong default for DESC. We'll fix that.

On Wed, 14 Jan 2015, Heikki Linnakangas wrote:

On 01/14/2015 05:26 PM, Timmer, Marius wrote:

Hello Heikki,
abbreviated version:
Sorry, the problem is only the unhandy patch text format, not different opinions how to proceed.
Long version:
The v7 patch file already addressed your suggestions,
but the file contained serveral (old) local commits,
the new ones at the end of the patch text/file.

Ah, missed that. I stopped reading when I saw the old stuff there :-).

v7.1 is attached and addresses this issue providing a clean patch file.

Ok, thanks, will take a look.

V8 will - as mentioned - add missing docs and regression tests,

Great!

- Heikki

Attachments:

explain_sortorder-v8.patchapplication/octet-stream; name=explain_sortorder-v8.patchDownload
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

#14Mike Blackwell
mike.blackwell@rrd.com
In reply to: Timmer, Marius (#13)
Re: [PATCH] explain sortorder (fwd)

From: "Timmer, Marius" <marius.timmer@uni-muenster.de>

Hi,

attached is version 8, fixing remaining issues, adding docs and tests as
requested/agreed.

Marius & Arne

​This looks good to me. Test coverage seems complete. Doc updates are
included. Output format looks like it should be acceptable to He​ikki.

I'll mark this as ready for committer.

Thanks for the patch!

Mike

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Timmer, Marius (#13)
Re: [PATCH] explain sortorder

"Timmer, Marius" <marius.timmer@uni-muenster.de> writes:

attached is version 8, fixing remaining issues, adding docs and tests as requested/agreed.

I'll pick this up --- I've been a bit lax about helping with this
commitfest.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Tom Lane
tgl@sss.pgh.pa.us
In reply to: Timmer, Marius (#13)
Re: [PATCH] explain sortorder

"Timmer, Marius" <marius.timmer@uni-muenster.de> writes:

attached is version 8, fixing remaining issues, adding docs and tests as requested/agreed.

I've committed this with some rather substantial alterations, notably:

* Having get_sortorder_by_keyno dig into the plan state node by itself
seemed like a bad idea; it's certainly at variance with the existing
division of knowledge in this code, and I think it might outright do
the wrong thing if there's a Sort node underneath an Agg or Group node
(since in those cases the child Sort node, not the parent node, would
get passed in). I fixed it so that show_sort_keys and siblings are
responsible for extracting and passing in the correct data arrays.

* There were some minor bugs in the rules for when to print NULLS
FIRST/LAST too. I think the way I've got it now is a precise inverse of
what addTargetToSortList() will do.

* The proposed new regression test cases were not portable ("en_US"
isn't guaranteed to exist), and I thought adding a new regression
script file for just one test was a bit excessive. The DESC and
USING behaviors were already covered by existing test cases so I just
added something to exercise COLLATE and NULLS FIRST in collate.sql.

* I took out the change in perform.sgml. The change as proposed was
seriously confusing because it injected off-topic discussion into an
example that was meant to be just about the additional information printed
by EXPLAIN ANALYZE. I'm not really sure that this feature needs any
specific documentation (other than its eventual mention in the release
notes); but if it does, we should probably add a brand new example
someplace before the EXPLAIN ANALYZE subsection.

* Assorted cleanups such as removal of irrelevant whitespace changes.
That sort of thing just makes a reviewer's job harder, so it's best
to avoid it if you can.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Timmer, Marius
marius.timmer@uni-muenster.de
In reply to: Tom Lane (#16)
Re: [PATCH] explain sortorder

Hi Tom,

we are very happy seeing this committed.
Thank you for committing and Mike for the review!!

Your changes make sense to us, except one question:

We think, you wanted to switch to DESC behavior
(print out NULLS FIRST) in cases, where
„USING“ uses an operator which is considered to be
a DESC operator.
Great, we missed that.

But get_equality_op_for_ordering_op is called in
cases, where reverse is false, but
the part

if (reverse)
*reverse = (strategy == BTGreaterStrategyNumber);

never changes this to true?

VlG

Marius & Arne

---
Marius Timmer
Arne Scheffer
Zentrum für Informationsverarbeitung
Westfälische Wilhelms-Universität Münster
Einsteinstraße 60

mtimm_01@uni-muenster.de

Am 17.01.2015 um 00:45 schrieb Tom Lane <tgl@sss.pgh.pa.us>:

"Timmer, Marius" <marius.timmer@uni-muenster.de> writes:

attached is version 8, fixing remaining issues, adding docs and tests as requested/agreed.

I've committed this with some rather substantial alterations, notably:

* Having get_sortorder_by_keyno dig into the plan state node by itself
seemed like a bad idea; it's certainly at variance with the existing
division of knowledge in this code, and I think it might outright do
the wrong thing if there's a Sort node underneath an Agg or Group node
(since in those cases the child Sort node, not the parent node, would
get passed in). I fixed it so that show_sort_keys and siblings are
responsible for extracting and passing in the correct data arrays.

* There were some minor bugs in the rules for when to print NULLS
FIRST/LAST too. I think the way I've got it now is a precise inverse of
what addTargetToSortList() will do.

* The proposed new regression test cases were not portable ("en_US"
isn't guaranteed to exist), and I thought adding a new regression
script file for just one test was a bit excessive. The DESC and
USING behaviors were already covered by existing test cases so I just
added something to exercise COLLATE and NULLS FIRST in collate.sql.

* I took out the change in perform.sgml. The change as proposed was
seriously confusing because it injected off-topic discussion into an
example that was meant to be just about the additional information printed
by EXPLAIN ANALYZE. I'm not really sure that this feature needs any
specific documentation (other than its eventual mention in the release
notes); but if it does, we should probably add a brand new example
someplace before the EXPLAIN ANALYZE subsection.

* Assorted cleanups such as removal of irrelevant whitespace changes.
That sort of thing just makes a reviewer's job harder, so it's best
to avoid it if you can.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Tom Lane
tgl@sss.pgh.pa.us
In reply to: Timmer, Marius (#17)
Re: [PATCH] explain sortorder

"Timmer, Marius" <marius.timmer@uni-muenster.de> writes:

We think, you wanted to switch to DESC behavior
(print out NULLS FIRST) in cases, where
�USING� uses an operator which is considered to be
a DESC operator.

Right, because that's how addTargetToSortList() would parse it.

But get_equality_op_for_ordering_op is called in
cases, where reverse is false, but
the part
if (reverse)
*reverse = (strategy == BTGreaterStrategyNumber);
never changes this to true?

Sorry, not following? It's true that what I added to explain.c doesn't
worry too much about the possibility of get_ordering_op_properties()
failing --- that really shouldn't happen for something that was previously
accepted as a sorting operator. But if it does, "reverse" will just be
left as false, so the behavior will anyway be unsurprising I think.
We could alternatively make it throw a "cache lookup failed" error but
I'm not sure how that makes anyone's life better.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Mike Blackwell
mike.blackwell@rrd.com
In reply to: Tom Lane (#18)
Re: [PATCH] explain sortorder

Tom,

Thanks for the comments on what you ended up changing. It helps point out
the kind of things I should be looking for. I'll try to let less slip
through in the future.

Mike

__________________________________________________________________________________
*Mike Blackwell | Technical Analyst, Distribution Services/Rollout
Management | RR Donnelley*
1750 Wallace Ave | St Charles, IL 60174-3401
Office: 630.313.7818
Mike.Blackwell@rrd.com
http://www.rrdonnelley.com

<http://www.rrdonnelley.com/&gt;
* <Mike.Blackwell@rrd.com>*

On Mon, Jan 19, 2015 at 10:09 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Show quoted text

"Timmer, Marius" <marius.timmer@uni-muenster.de> writes:

We think, you wanted to switch to DESC behavior
(print out NULLS FIRST) in cases, where
„USING“ uses an operator which is considered to be
a DESC operator.

Right, because that's how addTargetToSortList() would parse it.

But get_equality_op_for_ordering_op is called in
cases, where reverse is false, but
the part
if (reverse)
*reverse = (strategy == BTGreaterStrategyNumber);
never changes this to true?

Sorry, not following? It's true that what I added to explain.c doesn't
worry too much about the possibility of get_ordering_op_properties()
failing --- that really shouldn't happen for something that was previously
accepted as a sorting operator. But if it does, "reverse" will just be
left as false, so the behavior will anyway be unsurprising I think.
We could alternatively make it throw a "cache lookup failed" error but
I'm not sure how that makes anyone's life better.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers