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

