mirror of
https://github.com/postgres/postgres.git
synced 2025-05-11 05:41:32 +03: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
src
@ -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