diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 2b84ac8af3724535dbc4140aa606e2ca15514b6a..7cfc9bb612a83f3b4ee938bb835508ab19daa5e4 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -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 */ diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out index 40f5398b72c268133a266354b3dafa3eff999ef0..8852051e9325f4eec89a01def3f0673fbc27a0c6 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 @@ -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 diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out index 91d574dbe4c77a79692eac54a6e470eaa37a422d..f076a4dbae183fdd41ecf5765c86e6d207cc3206 100644 --- a/src/test/regress/expected/collate.out +++ b/src/test/regress/expected/collate.out @@ -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"); diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out index b312292c6f337ae7c0f00eca5bd3ed0b00243026..dfae84e1a5f7c0da47b94c1f6f3d34d720a311bc 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/sql/collate.sql b/src/test/regress/sql/collate.sql index 63ab590f3a4153d49e4e3299c067fff4df4a67f8..e8ca0856e34bf5bfbb7943fafc8a510ffaf687e2 100644 --- a/src/test/regress/sql/collate.sql +++ b/src/test/regress/sql/collate.sql @@ -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));