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