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 "access/xact.h" | ||||
| #include "catalog/pg_collation.h" | ||||
| #include "catalog/pg_type.h" | ||||
| #include "commands/createas.h" | ||||
| #include "commands/defrem.h" | ||||
| #include "commands/prepare.h" | ||||
| #include "executor/hashjoin.h" | ||||
| #include "foreign/fdwapi.h" | ||||
| #include "nodes/nodeFuncs.h" | ||||
| #include "optimizer/clauses.h" | ||||
| #include "parser/parsetree.h" | ||||
| #include "rewrite/rewriteHandler.h" | ||||
| @ -31,6 +33,7 @@ | ||||
| #include "utils/ruleutils.h" | ||||
| #include "utils/snapmgr.h" | ||||
| #include "utils/tuplesort.h" | ||||
| #include "utils/typcache.h" | ||||
| #include "utils/xml.h" | ||||
| 
 | ||||
| 
 | ||||
| @ -83,7 +86,10 @@ static void show_group_keys(GroupState *gstate, List *ancestors, | ||||
| 				ExplainState *es); | ||||
| static void show_sort_group_keys(PlanState *planstate, const char *qlabel, | ||||
| 					 int nkeys, AttrNumber *keycols, | ||||
| 					 Oid *sortOperators, Oid *collations, bool *nullsFirst, | ||||
| 					 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_hash_info(HashState *hashstate, ExplainState *es); | ||||
| 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", | ||||
| 						 plan->numCols, plan->sortColIdx, | ||||
| 						 plan->sortOperators, plan->collations, | ||||
| 						 plan->nullsFirst, | ||||
| 						 ancestors, es); | ||||
| } | ||||
| 
 | ||||
| @ -1795,6 +1803,8 @@ show_merge_append_keys(MergeAppendState *mstate, List *ancestors, | ||||
| 
 | ||||
| 	show_sort_group_keys((PlanState *) mstate, "Sort Key", | ||||
| 						 plan->numCols, plan->sortColIdx, | ||||
| 						 plan->sortOperators, plan->collations, | ||||
| 						 plan->nullsFirst, | ||||
| 						 ancestors, es); | ||||
| } | ||||
| 
 | ||||
| @ -1813,6 +1823,7 @@ show_agg_keys(AggState *astate, List *ancestors, | ||||
| 		ancestors = lcons(astate, ancestors); | ||||
| 		show_sort_group_keys(outerPlanState(astate), "Group Key", | ||||
| 							 plan->numCols, plan->grpColIdx, | ||||
| 							 NULL, NULL, NULL, | ||||
| 							 ancestors, es); | ||||
| 		ancestors = list_delete_first(ancestors); | ||||
| 	} | ||||
| @ -1831,29 +1842,34 @@ show_group_keys(GroupState *gstate, List *ancestors, | ||||
| 	ancestors = lcons(gstate, ancestors); | ||||
| 	show_sort_group_keys(outerPlanState(gstate), "Group Key", | ||||
| 						 plan->numCols, plan->grpColIdx, | ||||
| 						 NULL, NULL, NULL, | ||||
| 						 ancestors, es); | ||||
| 	ancestors = list_delete_first(ancestors); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * 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 | ||||
| show_sort_group_keys(PlanState *planstate, const char *qlabel, | ||||
| 					 int nkeys, AttrNumber *keycols, | ||||
| 					 Oid *sortOperators, Oid *collations, bool *nullsFirst, | ||||
| 					 List *ancestors, ExplainState *es) | ||||
| { | ||||
| 	Plan	   *plan = planstate->plan; | ||||
| 	List	   *context; | ||||
| 	List	   *result = NIL; | ||||
| 	StringInfoData sortkeybuf; | ||||
| 	bool		useprefix; | ||||
| 	int			keyno; | ||||
| 	char	   *exprstr; | ||||
| 
 | ||||
| 	if (nkeys <= 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	initStringInfo(&sortkeybuf); | ||||
| 
 | ||||
| 	/* Set up deparsing context */ | ||||
| 	context = set_deparse_context_planstate(es->deparse_cxt, | ||||
| 											(Node *) planstate, | ||||
| @ -1866,18 +1882,86 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel, | ||||
| 		AttrNumber	keyresno = keycols[keyno]; | ||||
| 		TargetEntry *target = get_tle_by_resno(plan->targetlist, | ||||
| 											   keyresno); | ||||
| 		char	   *exprstr; | ||||
| 
 | ||||
| 		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); | ||||
| 		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); | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * 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 | ||||
|  */ | ||||
|  | ||||
| @ -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 | ||||
| @ -784,7 +784,7 @@ explain (costs off) | ||||
|    InitPlan 2 (returns $1) | ||||
|      ->  Limit | ||||
|            ->  Merge Append | ||||
|                  Sort Key: minmaxtest_1.f1 | ||||
|                  Sort Key: minmaxtest_1.f1 DESC | ||||
|                  ->  Index Only Scan Backward using minmaxtesti on minmaxtest minmaxtest_1 | ||||
|                        Index Cond: (f1 IS NOT NULL) | ||||
|                  ->  Index Only Scan Backward using minmaxtest1i on minmaxtest1 minmaxtest1_1 | ||||
| @ -823,7 +823,7 @@ explain (costs off) | ||||
|    InitPlan 2 (returns $1) | ||||
|      ->  Limit | ||||
|            ->  Merge Append | ||||
|                  Sort Key: minmaxtest_1.f1 | ||||
|                  Sort Key: minmaxtest_1.f1 DESC | ||||
|                  ->  Index Only Scan Backward using minmaxtesti on minmaxtest minmaxtest_1 | ||||
|                        Index Cond: (f1 IS NOT NULL) | ||||
|                  ->  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_hashjoin; | ||||
| 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 | ||||
| CREATE TEMP TABLE vctable (f1 varchar(25)); | ||||
| INSERT INTO vctable VALUES ('foo' COLLATE "C"); | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -220,6 +220,15 @@ RESET enable_seqscan; | ||||
| RESET enable_hashjoin; | ||||
| 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 | ||||
| 
 | ||||
| CREATE TEMP TABLE vctable (f1 varchar(25)); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user