From bee35c8a7bc09765ffd0df488c2e0205b62b1ce0 Mon Sep 17 00:00:00 2001 From: mtimm_01 Date: Thu, 15 Jan 2015 15:46:36 +0100 Subject: [PATCH] explain_sortorder v8 --- doc/src/sgml/perform.sgml | 13 ++- src/backend/commands/explain.c | 109 ++++++++++++++++++++---- src/test/regress/expected/aggregates.out | 2 +- src/test/regress/expected/equivclass.out | 4 +- src/test/regress/expected/explain_sortorder.out | 24 ++++++ src/test/regress/parallel_schedule | 9 +- src/test/regress/serial_schedule | 3 +- src/test/regress/sql/explain_sortorder.sql | 13 +++ 8 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 src/test/regress/expected/explain_sortorder.out create mode 100644 src/test/regress/sql/explain_sortorder.sql diff --git a/doc/src/sgml/perform.sgml b/doc/src/sgml/perform.sgml index 5a087fb..7723865 100644 --- a/doc/src/sgml/perform.sgml +++ b/doc/src/sgml/perform.sgml @@ -504,7 +504,7 @@ WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2; (Sequential-scan-and-sort frequently beats an index scan for sorting many rows, because of the nonsequential disk access required by the index scan.) - + One way to look at variant plans is to force the planner to disregard whatever strategy it thought was the cheapest, using the enable/disable @@ -597,12 +597,12 @@ WHERE t1.unique1 < 10 AND t1.unique2 = t2.unique2; EXPLAIN ANALYZE SELECT * FROM tenk1 t1, tenk2 t2 -WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous; +WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous DESC NULLS FIRST; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------- Sort (cost=717.34..717.59 rows=101 width=488) (actual time=7.761..7.774 rows=100 loops=1) - Sort Key: t1.fivethous + Sort Key: t1.fivethous DESC Sort Method: quicksort Memory: 77kB -> Hash Join (cost=230.47..713.98 rows=101 width=488) (actual time=0.711..7.427 rows=100 loops=1) Hash Cond: (t2.unique2 = t1.unique2) @@ -619,6 +619,13 @@ WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2 ORDER BY t1.fivethous; The Sort node shows the sort method used (in particular, whether the sort was in-memory or on-disk) and the amount of memory or disk space needed. + The Sort Key line indicates the column(s) sorted and also + parts of the requested sort order, that differ from the sort order defaults + (ASC, NULLS LAST for ASC, NULLS FIRST for DESC). + Therefore NULLS FIRST is ommitted in the example above. + + + The Hash node shows the number of hash buckets and batches as well as the peak amount of memory used for the hash table. (If the number of batches exceeds one, there will also be disk space usage involved, but that is not diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 8a0be5d..dde7677 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 @@ -1844,12 +1850,12 @@ show_sort_group_keys(PlanState *planstate, const char *qlabel, int nkeys, AttrNumber *keycols, List *ancestors, ExplainState *es) { - Plan *plan = planstate->plan; - List *context; - List *result = NIL; - bool useprefix; - int keyno; - char *exprstr; + Plan *plan = planstate->plan; + List *context; + List *result = NIL; + bool useprefix; + int keyno; + char *exprstr; if (nkeys <= 0) return; @@ -1863,23 +1869,94 @@ 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 = keycols[keyno]; + TargetEntry *target = get_tle_by_resno(plan->targetlist, keyresno); + char *sortorder; + StringInfo element_concat_sortorder; + + 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); + element_concat_sortorder = makeStringInfo(); + appendStringInfoString(element_concat_sortorder, exprstr); + appendStringInfoString(element_concat_sortorder, sortorder); + result = lappend(result, element_concat_sortorder->data); } 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; + StringInfo sortorderInformation; + Oid collId; + Oid operId; + AttrNumber keyresno; + TargetEntry *target; + + plan = (Sort *) sortstate->ss.ps.plan; + planstatePlan = (Plan *) sortstate->ss.ps.plan; + 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); + sortorderInformation = makeStringInfo(); + + if (OidIsValid(collId) && collId != DEFAULT_COLLATION_OID) + { + char *collname; + + collname = get_collation_name(collId); + + appendStringInfo(sortorderInformation, " COLLATE \"%s\"", collname); + } + if (operId == typentry->gt_opr) + { + appendStringInfoString(sortorderInformation, " DESC"); + } + else if (operId != typentry->lt_opr) + { + opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operId)); + operform = (Form_pg_operator) GETSTRUCT (opertup); + oprname = NameStr(operform->oprname); + appendStringInfo(sortorderInformation, " USING %s", oprname); + ReleaseSysCache(opertup); + } + //if ((plan->nullsFirst[keyno]) && (operId == typentry->gt_opr)) + if ((plan->nullsFirst[keyno]) && (operId == typentry->lt_opr)) + { + appendStringInfoString(sortorderInformation, " NULLS FIRST"); + } + //else if ((!plan->nullsFirst[keyno]) && (operId == typentry->lt_opr)) + else if ((!plan->nullsFirst[keyno]) && (operId == typentry->gt_opr)) + { + appendStringInfoString(sortorderInformation, " NULLS LAST"); + } + return sortorderInformation->data; +} + +/* * 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..3e316dd 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 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..0195910 --- /dev/null +++ b/src/test/regress/expected/explain_sortorder.out @@ -0,0 +1,24 @@ +-- +-- 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 "en_US" ASC, n2 DESC; + QUERY PLAN +----------------------------------------- + Sort + Sort Key: n1 COLLATE "en_US", n2 DESC + -> Seq Scan on sortordertest +(3 rows) + +EXPLAIN (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC NULLS LAST, n2; + QUERY PLAN +------------------------------------------------ + Sort + Sort Key: n1 COLLATE "C" DESC NULLS LAST, 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 e0ae2f2..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 * @@ -78,12 +78,15 @@ ignore: random # ---------- # Another group of parallel tests # ---------- -test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete +test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update delete namespace prepared_xacts # ---------- # Another group of parallel tests # ---------- -test: brin gin gist spgist privileges security_label collate matview lock replica_identity rowsecurity object_address +test: brin gin gist spgist privileges security_label collate matview lock replica_identity object_address + +# rowsecurity creates an event trigger, so don't run it in parallel +test: rowsecurity # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 7f762bd..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 @@ -105,8 +106,8 @@ test: collate test: matview test: lock test: replica_identity -test: rowsecurity test: object_address +test: rowsecurity test: alter_generic test: misc test: psql diff --git a/src/test/regress/sql/explain_sortorder.sql b/src/test/regress/sql/explain_sortorder.sql new file mode 100644 index 0000000..18aac4b --- /dev/null +++ b/src/test/regress/sql/explain_sortorder.sql @@ -0,0 +1,13 @@ +-- +-- 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 "en_US" ASC, n2 DESC; +EXPLAIN (COSTS OFF) SELECT * FROM sortordertest ORDER BY n1 COLLATE "C" DESC NULLS LAST, n2; + +DROP TABLE sortordertest; \ No newline at end of file -- 1.9.1