EXPLAIN output now comes out as a query result, not a NOTICE message.

Also, fix debug logging of parse/plan trees so that the messages actually
go through elog(), not directly to stdout.
This commit is contained in:
Tom Lane 2002-03-24 04:31:09 +00:00
parent a25b94c080
commit 10d3995057
14 changed files with 412 additions and 213 deletions

View File

@ -1,5 +1,5 @@
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/perform.sgml,v 1.18 2002/03/22 19:20:17 petere Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/perform.sgml,v 1.19 2002/03/24 04:31:05 tgl Exp $
--> -->
<chapter id="performance-tips"> <chapter id="performance-tips">
@ -47,8 +47,8 @@ $Header: /cvsroot/pgsql/doc/src/sgml/perform.sgml,v 1.18 2002/03/22 19:20:17 pet
<listitem> <listitem>
<para> <para>
Estimated number of rows output by this plan node (again, without Estimated number of rows output by this plan node (again, only if
regard for any LIMIT). executed to completion).
</para> </para>
</listitem> </listitem>
@ -92,13 +92,13 @@ $Header: /cvsroot/pgsql/doc/src/sgml/perform.sgml,v 1.18 2002/03/22 19:20:17 pet
<para> <para>
Here are some examples (using the regress test database after a Here are some examples (using the regress test database after a
vacuum analyze, and 7.2 development sources): vacuum analyze, and 7.3 development sources):
<programlisting> <programlisting>
regression=# EXPLAIN SELECT * FROM tenk1; regression=# EXPLAIN SELECT * FROM tenk1;
INFO: QUERY PLAN: QUERY PLAN
-------------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..333.00 rows=10000 width=148) Seq Scan on tenk1 (cost=0.00..333.00 rows=10000 width=148)
</programlisting> </programlisting>
</para> </para>
@ -120,9 +120,10 @@ SELECT * FROM pg_class WHERE relname = 'tenk1';
<programlisting> <programlisting>
regression=# EXPLAIN SELECT * FROM tenk1 WHERE unique1 &lt; 1000; regression=# EXPLAIN SELECT * FROM tenk1 WHERE unique1 &lt; 1000;
INFO: QUERY PLAN: QUERY PLAN
------------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..358.00 rows=1007 width=148) Seq Scan on tenk1 (cost=0.00..358.00 rows=1033 width=148)
Filter: (unique1 &lt; 1000)
</programlisting> </programlisting>
The estimate of output rows has gone down because of the WHERE clause. The estimate of output rows has gone down because of the WHERE clause.
@ -145,9 +146,10 @@ Seq Scan on tenk1 (cost=0.00..358.00 rows=1007 width=148)
<programlisting> <programlisting>
regression=# EXPLAIN SELECT * FROM tenk1 WHERE unique1 &lt; 50; regression=# EXPLAIN SELECT * FROM tenk1 WHERE unique1 &lt; 50;
INFO: QUERY PLAN: QUERY PLAN
-------------------------------------------------------------------------------
Index Scan using tenk1_unique1 on tenk1 (cost=0.00..181.09 rows=49 width=148) Index Scan using tenk1_unique1 on tenk1 (cost=0.00..179.33 rows=49 width=148)
Index Filter: (unique1 &lt; 50)
</programlisting> </programlisting>
and you will see that if we make the WHERE condition selective and you will see that if we make the WHERE condition selective
@ -164,13 +166,20 @@ Index Scan using tenk1_unique1 on tenk1 (cost=0.00..181.09 rows=49 width=148)
<programlisting> <programlisting>
regression=# EXPLAIN SELECT * FROM tenk1 WHERE unique1 &lt; 50 AND regression=# EXPLAIN SELECT * FROM tenk1 WHERE unique1 &lt; 50 AND
regression-# stringu1 = 'xxx'; regression-# stringu1 = 'xxx';
INFO: QUERY PLAN: QUERY PLAN
-------------------------------------------------------------------------------
Index Scan using tenk1_unique1 on tenk1 (cost=0.00..181.22 rows=1 width=148) Index Scan using tenk1_unique1 on tenk1 (cost=0.00..179.45 rows=1 width=148)
Index Filter: (unique1 &lt; 50)
Filter: (stringu1 = 'xxx'::name)
</programlisting> </programlisting>
The added clause <literal>stringu1 = 'xxx'</literal> reduces the output-rows estimate, The added clause <literal>stringu1 = 'xxx'</literal> reduces the
but not the cost because we still have to visit the same set of tuples. output-rows estimate, but not the cost because we still have to visit the
same set of tuples. Notice that the <literal>stringu1</> clause
cannot be applied as an index condition (since this index is only on
the <literal>unique1</> column). Instead it is applied as a filter on
the rows retrieved by the index. Thus the cost has actually gone up
a little bit to reflect this extra checking.
</para> </para>
<para> <para>
@ -179,13 +188,15 @@ Index Scan using tenk1_unique1 on tenk1 (cost=0.00..181.22 rows=1 width=148)
<programlisting> <programlisting>
regression=# EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 &lt; 50 regression=# EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 &lt; 50
regression-# AND t1.unique2 = t2.unique2; regression-# AND t1.unique2 = t2.unique2;
INFO: QUERY PLAN: QUERY PLAN
----------------------------------------------------------------------------
Nested Loop (cost=0.00..330.41 rows=49 width=296) Nested Loop (cost=0.00..327.02 rows=49 width=296)
-&gt; Index Scan using tenk1_unique1 on tenk1 t1 -&gt; Index Scan using tenk1_unique1 on tenk1 t1
(cost=0.00..181.09 rows=49 width=148) (cost=0.00..179.33 rows=49 width=148)
Index Filter: (unique1 &lt; 50)
-&gt; Index Scan using tenk2_unique2 on tenk2 t2 -&gt; Index Scan using tenk2_unique2 on tenk2 t2
(cost=0.00..3.01 rows=1 width=148) (cost=0.00..3.01 rows=1 width=148)
Index Filter: ("outer".unique2 = t2.unique2)
</programlisting> </programlisting>
</para> </para>
@ -227,14 +238,15 @@ regression=# set enable_nestloop = off;
SET VARIABLE SET VARIABLE
regression=# EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 &lt; 50 regression=# EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2 WHERE t1.unique1 &lt; 50
regression-# AND t1.unique2 = t2.unique2; regression-# AND t1.unique2 = t2.unique2;
INFO: QUERY PLAN: QUERY PLAN
--------------------------------------------------------------------------
Hash Join (cost=181.22..564.83 rows=49 width=296) Hash Join (cost=179.45..563.06 rows=49 width=296)
-&gt; Seq Scan on tenk2 t2 Hash Cond: ("outer".unique2 = "inner".unique2)
(cost=0.00..333.00 rows=10000 width=148) -&gt; Seq Scan on tenk2 t2 (cost=0.00..333.00 rows=10000 width=148)
-&gt; Hash (cost=181.09..181.09 rows=49 width=148) -&gt; Hash (cost=179.33..179.33 rows=49 width=148)
-&gt; Index Scan using tenk1_unique1 on tenk1 t1 -&gt; Index Scan using tenk1_unique1 on tenk1 t1
(cost=0.00..181.09 rows=49 width=148) (cost=0.00..179.33 rows=49 width=148)
Index Filter: (unique1 &lt; 50)
</programlisting> </programlisting>
This plan proposes to extract the 50 interesting rows of <classname>tenk1</classname> This plan proposes to extract the 50 interesting rows of <classname>tenk1</classname>
@ -245,7 +257,7 @@ Hash Join (cost=181.22..564.83 rows=49 width=296)
cost for the hash join, since we won't get any tuples out until we can cost for the hash join, since we won't get any tuples out until we can
start reading <classname>tenk2</classname>. The total time estimate for the join also start reading <classname>tenk2</classname>. The total time estimate for the join also
includes a hefty charge for CPU time to probe the hash table includes a hefty charge for CPU time to probe the hash table
10000 times. Note, however, that we are NOT charging 10000 times 181.09; 10000 times. Note, however, that we are NOT charging 10000 times 179.33;
the hash table setup is only done once in this plan type. the hash table setup is only done once in this plan type.
</para> </para>
@ -260,14 +272,19 @@ Hash Join (cost=181.22..564.83 rows=49 width=296)
regression=# EXPLAIN ANALYZE regression=# EXPLAIN ANALYZE
regression-# SELECT * FROM tenk1 t1, tenk2 t2 regression-# SELECT * FROM tenk1 t1, tenk2 t2
regression-# WHERE t1.unique1 &lt; 50 AND t1.unique2 = t2.unique2; regression-# WHERE t1.unique1 &lt; 50 AND t1.unique2 = t2.unique2;
INFO: QUERY PLAN: QUERY PLAN
-------------------------------------------------------------------------------
Nested Loop (cost=0.00..330.41 rows=49 width=296) (actual time=1.31..28.90 rows=50 loops=1) Nested Loop (cost=0.00..327.02 rows=49 width=296)
(actual time=1.18..29.82 rows=50 loops=1)
-&gt; Index Scan using tenk1_unique1 on tenk1 t1 -&gt; Index Scan using tenk1_unique1 on tenk1 t1
(cost=0.00..181.09 rows=49 width=148) (actual time=0.69..8.84 rows=50 loops=1) (cost=0.00..179.33 rows=49 width=148)
(actual time=0.63..8.91 rows=50 loops=1)
Index Filter: (unique1 &lt; 50)
-&gt; Index Scan using tenk2_unique2 on tenk2 t2 -&gt; Index Scan using tenk2_unique2 on tenk2 t2
(cost=0.00..3.01 rows=1 width=148) (actual time=0.28..0.31 rows=1 loops=50) (cost=0.00..3.01 rows=1 width=148)
Total runtime: 30.67 msec (actual time=0.29..0.32 rows=1 loops=50)
Index Filter: ("outer".unique2 = t2.unique2)
Total runtime: 31.60 msec
</screen> </screen>
Note that the <quote>actual time</quote> values are in milliseconds of Note that the <quote>actual time</quote> values are in milliseconds of
@ -296,7 +313,7 @@ Total runtime: 30.67 msec
little larger than the total time reported for the top-level plan node. little larger than the total time reported for the top-level plan node.
For INSERT, UPDATE, and DELETE queries, the total run time may be For INSERT, UPDATE, and DELETE queries, the total run time may be
considerably larger, because it includes the time spent processing the considerably larger, because it includes the time spent processing the
output tuples. In these queries, the time for the top plan node result tuples. In these queries, the time for the top plan node
essentially is the time spent computing the new tuples and/or locating essentially is the time spent computing the new tuples and/or locating
the old ones, but it doesn't include the time spent making the changes. the old ones, but it doesn't include the time spent making the changes.
</para> </para>

View File

@ -1,5 +1,5 @@
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/explain.sgml,v 1.16 2002/03/22 19:20:40 petere Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/ref/explain.sgml,v 1.17 2002/03/24 04:31:07 tgl Exp $
PostgreSQL documentation PostgreSQL documentation
--> -->
@ -49,7 +49,7 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="PARAMETER">query</replaceabl
<term>VERBOSE</term> <term>VERBOSE</term>
<listitem> <listitem>
<para> <para>
Flag to show detailed query plan. Flag to show detailed query plan dump.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -76,28 +76,24 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] <replaceable class="PARAMETER">query</replaceabl
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term><computeroutput> <term>Query plan</term>
INFO: QUERY PLAN:
<replaceable>plan</replaceable>
</computeroutput></term>
<listitem> <listitem>
<para> <para>
Explicit query plan from the <productname>PostgreSQL</productname> backend. Explicit query plan from the <productname>PostgreSQL</productname>
</para> planner.
</listitem>
</varlistentry>
<varlistentry>
<term><computeroutput>
EXPLAIN
</computeroutput></term>
<listitem>
<para>
Flag sent after query plan is shown.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
</variablelist> </variablelist>
</para> </para>
<note>
<para>
Prior to <application>PostgreSQL</application> 7.3, the query plan
was emitted in the form of a NOTICE message. Now it appears as a
query result (formatted like a table with a single text column).
</para>
</note>
</refsect2> </refsect2>
</refsynopsisdiv> </refsynopsisdiv>
@ -141,13 +137,6 @@ EXPLAIN
are close to reality. are close to reality.
</para> </para>
<para>
The VERBOSE option emits the full internal representation of the plan tree,
rather than just a summary (and sends it to the postmaster log file, too).
Usually this option is only useful for debugging
<application>PostgreSQL</application>.
</para>
<caution> <caution>
<para> <para>
Keep in mind that the query is actually executed when ANALYZE is used. Keep in mind that the query is actually executed when ANALYZE is used.
@ -165,6 +154,15 @@ ROLLBACK;
</para> </para>
</caution> </caution>
<para>
The VERBOSE option emits the full internal representation of the plan tree,
rather than just a summary.
Usually this option is only useful for debugging
<application>PostgreSQL</application>. The VERBOSE dump is either
pretty-printed or not, depending on the setting of the
<option>EXPLAIN_PRETTY_PRINT</option> configuration parameter.
</para>
<refsect2 id="R2-SQL-EXPLAIN-3"> <refsect2 id="R2-SQL-EXPLAIN-3">
<refsect2info> <refsect2info>
<date>1998-04-15</date> <date>1998-04-15</date>
@ -188,50 +186,48 @@ ROLLBACK;
<para> <para>
To show a query plan for a simple query on a table with a single To show a query plan for a simple query on a table with a single
<type>int4</type> column and 128 rows: <type>int4</type> column and 10000 rows:
<programlisting> <programlisting>
EXPLAIN SELECT * FROM foo; EXPLAIN SELECT * FROM foo;
<computeroutput> <computeroutput>
INFO: QUERY PLAN: QUERY PLAN
---------------------------------------------------------
Seq Scan on foo (cost=0.00..2.28 rows=128 width=4) Seq Scan on foo (cost=0.00..155.00 rows=10000 width=4)
(1 row)
EXPLAIN
</computeroutput> </computeroutput>
</programlisting> </programlisting>
</para> </para>
<para> <para>
For the same table with an index to support an If there is an index and we use a query with an indexable WHERE condition,
<firstterm>equijoin</firstterm> condition on the query,
<command>EXPLAIN</command> will show a different plan: <command>EXPLAIN</command> will show a different plan:
<programlisting> <programlisting>
EXPLAIN SELECT * FROM foo WHERE i = 4; EXPLAIN SELECT * FROM foo WHERE i = 4;
<computeroutput> <computeroutput>
INFO: QUERY PLAN: QUERY PLAN
--------------------------------------------------------------
Index Scan using fi on foo (cost=0.00..0.42 rows=1 width=4) Index Scan using fi on foo (cost=0.00..5.98 rows=1 width=4)
Index Filter: (i = 4)
EXPLAIN (2 rows)
</computeroutput> </computeroutput>
</programlisting> </programlisting>
</para> </para>
<para> <para>
And finally, for the same table with an index to support an And here is an example of a query plan for a query
<firstterm>equijoin</firstterm> condition on the query,
<command>EXPLAIN</command> will show the following for a query
using an aggregate function: using an aggregate function:
<programlisting> <programlisting>
EXPLAIN SELECT sum(i) FROM foo WHERE i = 4; EXPLAIN SELECT sum(i) FROM foo WHERE i < 4;
<computeroutput> <computeroutput>
INFO: QUERY PLAN: QUERY PLAN
---------------------------------------------------------------------
Aggregate (cost=0.42..0.42 rows=1 width=4) Aggregate (cost=23.93..23.93 rows=1 width=4)
-> Index Scan using fi on foo (cost=0.00..0.42 rows=1 width=4) -> Index Scan using fi on foo (cost=0.00..23.92 rows=6 width=4)
Index Filter: (i < 10)
(3 rows)
</computeroutput> </computeroutput>
</programlisting> </programlisting>
</para> </para>

View File

@ -1,5 +1,5 @@
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.125 2002/03/22 19:20:22 petere Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.126 2002/03/24 04:31:05 tgl Exp $
--> -->
<appendix id="release"> <appendix id="release">
@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without
worries about funny characters. worries about funny characters.
--> -->
<literallayout><![CDATA[ <literallayout><![CDATA[
EXPLAIN output comes out as a query result, not a NOTICE message
DOMAINs (types that are constrained versions of base types) DOMAINs (types that are constrained versions of base types)
Access privileges on functions Access privileges on functions
Access privileges on procedural languages Access privileges on procedural languages

View File

@ -1,5 +1,5 @@
<!-- <!--
$Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.109 2002/03/22 19:20:28 petere Exp $ $Header: /cvsroot/pgsql/doc/src/sgml/runtime.sgml,v 1.110 2002/03/24 04:31:06 tgl Exp $
--> -->
<Chapter Id="runtime"> <Chapter Id="runtime">
@ -870,6 +870,16 @@ env PGOPTIONS='-c geqo=off' psql
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>EXPLAIN_PRETTY_PRINT</varname> (<type>boolean</type>)</term>
<listitem>
<para>
Determines whether <command>EXPLAIN VERBOSE</> uses the indented
or non-indented format for displaying detailed querytree dumps.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><varname>HOSTNAME_LOOKUP</varname> (<type>boolean</type>)</term> <term><varname>HOSTNAME_LOOKUP</varname> (<type>boolean</type>)</term>
<listitem> <listitem>

View File

@ -5,12 +5,14 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California * Portions Copyright (c) 1994-5, Regents of the University of California
* *
* $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.73 2002/03/22 02:56:31 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.74 2002/03/24 04:31:07 tgl Exp $
* *
*/ */
#include "postgres.h" #include "postgres.h"
#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "commands/explain.h" #include "commands/explain.h"
#include "executor/instrument.h" #include "executor/instrument.h"
#include "lib/stringinfo.h" #include "lib/stringinfo.h"
@ -22,6 +24,7 @@
#include "rewrite/rewriteHandler.h" #include "rewrite/rewriteHandler.h"
#include "tcop/pquery.h" #include "tcop/pquery.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/relcache.h" #include "utils/relcache.h"
@ -35,9 +38,15 @@ typedef struct ExplainState
List *rtable; /* range table */ List *rtable; /* range table */
} ExplainState; } ExplainState;
typedef struct TextOutputState
{
TupleDesc tupdesc;
DestReceiver *destfunc;
} TextOutputState;
static StringInfo Explain_PlanToString(Plan *plan, ExplainState *es); static StringInfo Explain_PlanToString(Plan *plan, ExplainState *es);
static void ExplainOneQuery(Query *query, bool verbose, bool analyze, static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
CommandDest dest); TextOutputState *tstate);
static void explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan, static void explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan,
int indent, ExplainState *es); int indent, ExplainState *es);
static void show_scan_qual(List *qual, bool is_or_qual, const char *qlabel, static void show_scan_qual(List *qual, bool is_or_qual, const char *qlabel,
@ -48,6 +57,10 @@ static void show_upper_qual(List *qual, const char *qlabel,
const char *inner_name, int inner_varno, Plan *inner_plan, const char *inner_name, int inner_varno, Plan *inner_plan,
StringInfo str, int indent, ExplainState *es); StringInfo str, int indent, ExplainState *es);
static Node *make_ors_ands_explicit(List *orclauses); static Node *make_ors_ands_explicit(List *orclauses);
static TextOutputState *begin_text_output(CommandDest dest, char *title);
static void do_text_output(TextOutputState *tstate, char *aline);
static void do_text_output_multiline(TextOutputState *tstate, char *text);
static void end_text_output(TextOutputState *tstate);
/* Convert a null string pointer into "<>" */ /* Convert a null string pointer into "<>" */
#define stringStringInfo(s) (((s) == NULL) ? "<>" : (s)) #define stringStringInfo(s) (((s) == NULL) ? "<>" : (s))
@ -55,42 +68,47 @@ static Node *make_ors_ands_explicit(List *orclauses);
/* /*
* ExplainQuery - * ExplainQuery -
* print out the execution plan for a given query * execute an EXPLAIN command
*/ */
void void
ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest) ExplainQuery(ExplainStmt *stmt, CommandDest dest)
{ {
Query *query = stmt->query;
TextOutputState *tstate;
List *rewritten; List *rewritten;
List *l; List *l;
/* rewriter and planner may not work in aborted state? */ tstate = begin_text_output(dest, "QUERY PLAN");
if (IsAbortedTransactionBlockState())
{
elog(WARNING, "(transaction aborted): %s",
"queries ignored until END");
return;
}
/* rewriter will not cope with utility statements */
if (query->commandType == CMD_UTILITY) if (query->commandType == CMD_UTILITY)
{ {
elog(NOTICE, "Utility statements have no plan structure"); /* rewriter will not cope with utility statements */
return; do_text_output(tstate, "Utility statements have no plan structure");
} }
else
{
/* Rewrite through rule system */ /* Rewrite through rule system */
rewritten = QueryRewrite(query); rewritten = QueryRewrite(query);
/* In the case of an INSTEAD NOTHING, tell at least that */
if (rewritten == NIL) if (rewritten == NIL)
{ {
elog(NOTICE, "Query rewrites to nothing"); /* In the case of an INSTEAD NOTHING, tell at least that */
return; do_text_output(tstate, "Query rewrites to nothing");
} }
else
{
/* Explain every plan */ /* Explain every plan */
foreach(l, rewritten) foreach(l, rewritten)
ExplainOneQuery(lfirst(l), verbose, analyze, dest); {
ExplainOneQuery(lfirst(l), stmt, tstate);
/* put a blank line between plans */
if (lnext(l) != NIL)
do_text_output(tstate, "");
}
}
}
end_text_output(tstate);
} }
/* /*
@ -98,7 +116,7 @@ ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
* print out the execution plan for one query * print out the execution plan for one query
*/ */
static void static void
ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest) ExplainOneQuery(Query *query, ExplainStmt *stmt, TextOutputState *tstate)
{ {
Plan *plan; Plan *plan;
ExplainState *es; ExplainState *es;
@ -108,9 +126,9 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
if (query->commandType == CMD_UTILITY) if (query->commandType == CMD_UTILITY)
{ {
if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt)) if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
elog(INFO, "QUERY PLAN:\n\nNOTIFY\n"); do_text_output(tstate, "NOTIFY");
else else
elog(INFO, "QUERY PLAN:\n\nUTILITY\n"); do_text_output(tstate, "UTILITY");
return; return;
} }
@ -122,7 +140,7 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
return; return;
/* Execute the plan for statistics if asked for */ /* Execute the plan for statistics if asked for */
if (analyze) if (stmt->analyze)
{ {
struct timeval starttime; struct timeval starttime;
struct timeval endtime; struct timeval endtime;
@ -154,7 +172,7 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
es->printCost = true; /* default */ es->printCost = true; /* default */
if (verbose) if (stmt->verbose)
es->printNodes = true; es->printNodes = true;
es->rtable = query->rtable; es->rtable = query->rtable;
@ -162,12 +180,20 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
if (es->printNodes) if (es->printNodes)
{ {
char *s; char *s;
char *f;
s = nodeToString(plan); s = nodeToString(plan);
if (s) if (s)
{ {
elog(INFO, "QUERY DUMP:\n\n%s", s); if (Explain_pretty_print)
f = pretty_format_node_dump(s);
else
f = format_node_dump(s);
pfree(s); pfree(s);
do_text_output_multiline(tstate, f);
pfree(f);
if (es->printCost)
do_text_output(tstate, ""); /* separator line */
} }
} }
@ -176,17 +202,14 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
StringInfo str; StringInfo str;
str = Explain_PlanToString(plan, es); str = Explain_PlanToString(plan, es);
if (analyze) if (stmt->analyze)
appendStringInfo(str, "Total runtime: %.2f msec\n", appendStringInfo(str, "Total runtime: %.2f msec\n",
1000.0 * totaltime); 1000.0 * totaltime);
elog(INFO, "QUERY PLAN:\n\n%s", str->data); do_text_output_multiline(tstate, str->data);
pfree(str->data); pfree(str->data);
pfree(str); pfree(str);
} }
if (es->printNodes)
pprint(plan); /* display in postmaster log file */
pfree(es); pfree(es);
} }
@ -709,3 +732,78 @@ make_ors_ands_explicit(List *orclauses)
return (Node *) make_orclause(args); return (Node *) make_orclause(args);
} }
} }
/*
* Functions for sending text to the frontend (or other specified destination)
* as though it is a SELECT result.
*
* We tell the frontend that the table structure is a single TEXT column.
*/
static TextOutputState *
begin_text_output(CommandDest dest, char *title)
{
TextOutputState *tstate;
TupleDesc tupdesc;
tstate = (TextOutputState *) palloc(sizeof(TextOutputState));
/* need a tuple descriptor representing a single TEXT column */
tupdesc = CreateTemplateTupleDesc(1);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, title,
TEXTOID, -1, 0, false);
tstate->tupdesc = tupdesc;
tstate->destfunc = DestToFunction(dest);
(*tstate->destfunc->setup) (tstate->destfunc, (int) CMD_SELECT,
NULL, tupdesc);
return tstate;
}
/* write a single line of text */
static void
do_text_output(TextOutputState *tstate, char *aline)
{
HeapTuple tuple;
Datum values[1];
char nulls[1];
/* form a tuple and send it to the receiver */
values[0] = DirectFunctionCall1(textin, CStringGetDatum(aline));
nulls[0] = ' ';
tuple = heap_formtuple(tstate->tupdesc, values, nulls);
(*tstate->destfunc->receiveTuple) (tuple,
tstate->tupdesc,
tstate->destfunc);
pfree(DatumGetPointer(values[0]));
heap_freetuple(tuple);
}
/* write a chunk of text, breaking at newline characters */
/* NB: scribbles on its input! */
static void
do_text_output_multiline(TextOutputState *tstate, char *text)
{
while (*text)
{
char *eol;
eol = strchr(text, '\n');
if (eol)
*eol++ = '\0';
else
eol = text + strlen(text);
do_text_output(tstate, text);
text = eol;
}
}
static void
end_text_output(TextOutputState *tstate)
{
(*tstate->destfunc->cleanup) (tstate->destfunc);
pfree(tstate);
}

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.53 2002/03/22 02:56:32 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.54 2002/03/24 04:31:07 tgl Exp $
* *
* HISTORY * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
@ -21,6 +21,7 @@
#include "access/printtup.h" #include "access/printtup.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "lib/stringinfo.h"
#include "nodes/print.h" #include "nodes/print.h"
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "parser/parsetree.h" #include "parser/parsetree.h"
@ -37,31 +38,129 @@ void
print(void *obj) print(void *obj)
{ {
char *s; char *s;
char *f;
s = nodeToString(obj); s = nodeToString(obj);
printf("%s\n", s); f = format_node_dump(s);
fflush(stdout);
pfree(s); pfree(s);
printf("%s\n", f);
fflush(stdout);
pfree(f);
} }
/* /*
* pretty print hack extraordinaire. -ay 10/94 * pprint
* pretty-print contents of Node to stdout
*/ */
void void
pprint(void *obj) pprint(void *obj)
{ {
#define INDENTSTOP 3
#define MAXINDENT 60
#define LINELEN 80
char *s; char *s;
int i; char *f;
char line[LINELEN];
int indentLev;
int indentDist;
int j;
s = nodeToString(obj); s = nodeToString(obj);
f = pretty_format_node_dump(s);
pfree(s);
printf("%s\n", f);
fflush(stdout);
pfree(f);
}
/*
* elog_node_display
* send pretty-printed contents of Node to postmaster log
*/
void
elog_node_display(int lev, const char *title, void *obj, bool pretty)
{
char *s;
char *f;
s = nodeToString(obj);
if (pretty)
f = pretty_format_node_dump(s);
else
f = format_node_dump(s);
pfree(s);
elog(lev, "%s:\n%s", title, f);
pfree(f);
}
/*
* Format a nodeToString output for display on a terminal.
*
* The result is a palloc'd string.
*
* This version just tries to break at whitespace.
*/
char *
format_node_dump(const char *dump)
{
#define LINELEN 78
char line[LINELEN+1];
StringInfoData str;
int i;
int j;
int k;
initStringInfo(&str);
i = 0;
for (;;)
{
for (j = 0; j < LINELEN && dump[i] != '\0'; i++, j++)
line[j] = dump[i];
if (dump[i] == '\0')
break;
if (dump[i] == ' ')
{
/* ok to break at adjacent space */
i++;
}
else
{
for (k = j-1; k > 0; k--)
if (line[k] == ' ')
break;
if (k > 0)
{
/* back up; will reprint all after space */
i -= (j-k-1);
j = k;
}
}
line[j] = '\0';
appendStringInfo(&str, "%s\n", line);
}
if (j > 0)
{
line[j] = '\0';
appendStringInfo(&str, "%s\n", line);
}
return str.data;
#undef LINELEN
}
/*
* Format a nodeToString output for display on a terminal.
*
* The result is a palloc'd string.
*
* This version tries to indent intelligently.
*/
char *
pretty_format_node_dump(const char *dump)
{
#define INDENTSTOP 3
#define MAXINDENT 60
#define LINELEN 78
char line[LINELEN+1];
StringInfoData str;
int indentLev;
int indentDist;
int i;
int j;
initStringInfo(&str);
indentLev = 0; /* logical indent level */ indentLev = 0; /* logical indent level */
indentDist = 0; /* physical indent distance */ indentDist = 0; /* physical indent distance */
i = 0; i = 0;
@ -69,9 +168,9 @@ pprint(void *obj)
{ {
for (j = 0; j < indentDist; j++) for (j = 0; j < indentDist; j++)
line[j] = ' '; line[j] = ' ';
for (; j < LINELEN-1 && s[i] != '\0'; i++, j++) for (; j < LINELEN && dump[i] != '\0'; i++, j++)
{ {
line[j] = s[i]; line[j] = dump[i];
switch (line[j]) switch (line[j])
{ {
case '}': case '}':
@ -79,11 +178,12 @@ pprint(void *obj)
{ {
/* print data before the } */ /* print data before the } */
line[j] = '\0'; line[j] = '\0';
printf("%s\n", line); appendStringInfo(&str, "%s\n", line);
} }
/* print the } at indentDist */ /* print the } at indentDist */
line[indentDist] = '\0'; line[indentDist] = '}';
printf("%s}\n", line); line[indentDist+1] = '\0';
appendStringInfo(&str, "%s\n", line);
/* outdent */ /* outdent */
if (indentLev > 0) if (indentLev > 0)
{ {
@ -96,7 +196,7 @@ pprint(void *obj)
case ')': case ')':
/* force line break after ')' */ /* force line break after ')' */
line[j + 1] = '\0'; line[j + 1] = '\0';
printf("%s\n", line); appendStringInfo(&str, "%s\n", line);
j = indentDist - 1; j = indentDist - 1;
break; break;
case '{': case '{':
@ -104,36 +204,38 @@ pprint(void *obj)
if (j != indentDist) if (j != indentDist)
{ {
line[j] = '\0'; line[j] = '\0';
printf("%s\n", line); appendStringInfo(&str, "%s\n", line);
} }
/* indent */ /* indent */
indentLev++; indentLev++;
indentDist = Min(indentLev * INDENTSTOP, MAXINDENT); indentDist = Min(indentLev * INDENTSTOP, MAXINDENT);
for (j = 0; j < indentDist; j++) for (j = 0; j < indentDist; j++)
line[j] = ' '; line[j] = ' ';
line[j] = s[i]; line[j] = dump[i];
break; break;
case ':': case ':':
/* force line break before : */ /* force line break before : */
if (j != indentDist) if (j != indentDist)
{ {
line[j] = '\0'; line[j] = '\0';
printf("%s\n", line); appendStringInfo(&str, "%s\n", line);
} }
j = indentDist; j = indentDist;
line[j] = s[i]; line[j] = dump[i];
break; break;
} }
} }
line[j] = '\0'; line[j] = '\0';
if (s[i] == '\0') if (dump[i] == '\0')
break; break;
printf("%s\n", line); appendStringInfo(&str, "%s\n", line);
} }
if (j != 0) if (j > 0)
printf("%s\n", line); appendStringInfo(&str, "%s\n", line);
fflush(stdout); return str.data;
pfree(s); #undef INDENTSTOP
#undef MAXINDENT
#undef LINELEN
} }
/* /*

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.259 2002/03/22 02:56:35 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.260 2002/03/24 04:31:07 tgl Exp $
* *
* NOTES * NOTES
* this is the "main" module of the postgres backend and * this is the "main" module of the postgres backend and
@ -426,15 +426,8 @@ pg_analyze_and_rewrite(Node *parsetree)
querytree = (Query *) lfirst(list_item); querytree = (Query *) lfirst(list_item);
if (Debug_print_parse) if (Debug_print_parse)
{ elog_node_display(LOG, "parse tree", querytree,
if (Debug_pretty_print) Debug_pretty_print);
{
elog(LOG, "parse tree:");
nodeDisplay(querytree);
}
else
elog(LOG, "parse tree: %s", nodeToString(querytree));
}
if (querytree->commandType == CMD_UTILITY) if (querytree->commandType == CMD_UTILITY)
{ {
@ -470,27 +463,8 @@ pg_analyze_and_rewrite(Node *parsetree)
#endif #endif
if (Debug_print_rewritten) if (Debug_print_rewritten)
{ elog_node_display(LOG, "rewritten parse tree", querytree_list,
if (Debug_pretty_print) Debug_pretty_print);
{
elog(LOG, "rewritten parse tree:");
foreach(list_item, querytree_list)
{
querytree = (Query *) lfirst(list_item);
nodeDisplay(querytree);
printf("\n");
}
}
else
{
elog(LOG, "rewritten parse tree:");
foreach(list_item, querytree_list)
{
querytree = (Query *) lfirst(list_item);
elog(LOG, "%s", nodeToString(querytree));
}
}
}
return querytree_list; return querytree_list;
} }
@ -538,15 +512,7 @@ pg_plan_query(Query *querytree)
* Print plan if debugging. * Print plan if debugging.
*/ */
if (Debug_print_plan) if (Debug_print_plan)
{ elog_node_display(LOG, "plan", plan, Debug_pretty_print);
if (Debug_pretty_print)
{
elog(LOG, "plan:");
nodeDisplay(plan);
}
else
elog(LOG, "plan: %s", nodeToString(plan));
}
return plan; return plan;
} }
@ -1722,7 +1688,7 @@ PostgresMain(int argc, char *argv[], const char *username)
if (!IsUnderPostmaster) if (!IsUnderPostmaster)
{ {
puts("\nPOSTGRES backend interactive interface "); puts("\nPOSTGRES backend interactive interface ");
puts("$Revision: 1.259 $ $Date: 2002/03/22 02:56:35 $\n"); puts("$Revision: 1.260 $ $Date: 2002/03/24 04:31:07 $\n");
} }
/* /*

View File

@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.138 2002/03/22 02:56:35 tgl Exp $ * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.139 2002/03/24 04:31:08 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -650,11 +650,7 @@ ProcessUtility(Node *parsetree,
break; break;
case T_ExplainStmt: case T_ExplainStmt:
{ ExplainQuery((ExplainStmt *) parsetree, dest);
ExplainStmt *stmt = (ExplainStmt *) parsetree;
ExplainQuery(stmt->query, stmt->verbose, stmt->analyze, dest);
}
break; break;
#ifdef NOT_USED #ifdef NOT_USED

View File

@ -4,7 +4,7 @@
* Support for grand unified configuration scheme, including SET * Support for grand unified configuration scheme, including SET
* command, configuration file, and command line options. * command, configuration file, and command line options.
* *
* $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.62 2002/03/06 06:10:27 momjian Exp $ * $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.63 2002/03/24 04:31:08 tgl Exp $
* *
* Copyright 2000 by PostgreSQL Global Development Group * Copyright 2000 by PostgreSQL Global Development Group
* Written by Peter Eisentraut <peter_e@gmx.net>. * Written by Peter Eisentraut <peter_e@gmx.net>.
@ -82,6 +82,8 @@ bool Show_query_stats = false; /* this is sort of all three above
* together */ * together */
bool Show_btree_build_stats = false; bool Show_btree_build_stats = false;
bool Explain_pretty_print = true;
bool SQL_inheritance = true; bool SQL_inheritance = true;
bool Australian_timezones = false; bool Australian_timezones = false;
@ -293,6 +295,10 @@ static struct config_bool
}, },
#endif #endif
{
"explain_pretty_print", PGC_USERSET, PGC_S_DEFAULT, &Explain_pretty_print, true, NULL
},
{ {
"stats_start_collector", PGC_POSTMASTER, PGC_S_DEFAULT, &pgstat_collect_startcollector, true, NULL "stats_start_collector", PGC_POSTMASTER, PGC_S_DEFAULT, &pgstat_collect_startcollector, true, NULL
}, },

View File

@ -136,6 +136,8 @@
#debug_print_plan = false #debug_print_plan = false
#debug_pretty_print = false #debug_pretty_print = false
#explain_pretty_print = true
# requires USE_ASSERT_CHECKING # requires USE_ASSERT_CHECKING
#debug_assertions = true #debug_assertions = true

View File

@ -3,7 +3,7 @@
* *
* Copyright 2000 by PostgreSQL Global Development Group * Copyright 2000 by PostgreSQL Global Development Group
* *
* $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.45 2002/03/07 20:48:41 momjian Exp $ * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.46 2002/03/24 04:31:08 tgl Exp $
*/ */
/*---------------------------------------------------------------------- /*----------------------------------------------------------------------
@ -242,6 +242,7 @@ psql_completion(char *text, int start, int end)
"show_executor_stats", "show_executor_stats",
"show_query_stats", "show_query_stats",
"trace_notify", "trace_notify",
"explain_pretty_print",
"sql_inheritance", "sql_inheritance",
"australian_timezones", "australian_timezones",
"password_encryption", "password_encryption",

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California * Portions Copyright (c) 1994-5, Regents of the University of California
* *
* $Id: explain.h,v 1.15 2001/11/05 17:46:33 momjian Exp $ * $Id: explain.h,v 1.16 2002/03/24 04:31:09 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -16,6 +16,6 @@
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
#include "tcop/dest.h" #include "tcop/dest.h"
extern void ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest); extern void ExplainQuery(ExplainStmt *stmt, CommandDest dest);
#endif /* EXPLAIN_H */ #endif /* EXPLAIN_H */

View File

@ -1,13 +1,13 @@
/*------------------------------------------------------------------------- /*-------------------------------------------------------------------------
* *
* execnodes.h * print.h
* definitions for executor state nodes * definitions for nodes/print.c
* *
* *
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $Id: print.h,v 1.16 2001/11/05 17:46:34 momjian Exp $ * $Id: print.h,v 1.17 2002/03/24 04:31:09 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -17,13 +17,15 @@
#include "nodes/parsenodes.h" #include "nodes/parsenodes.h"
#include "nodes/plannodes.h" #include "nodes/plannodes.h"
/*
* nodes/{outfuncs.c,print.c}
*/
#define nodeDisplay pprint #define nodeDisplay pprint
extern void print(void *obj); extern void print(void *obj);
extern void pprint(void *obj); extern void pprint(void *obj);
extern void elog_node_display(int lev, const char *title,
void *obj, bool pretty);
extern char *format_node_dump(const char *dump);
extern char *pretty_format_node_dump(const char *dump);
extern void print_rt(List *rtable); extern void print_rt(List *rtable);
extern void print_expr(Node *expr, List *rtable); extern void print_expr(Node *expr, List *rtable);
extern void print_pathkeys(List *pathkeys, List *rtable); extern void print_pathkeys(List *pathkeys, List *rtable);

View File

@ -4,7 +4,7 @@
* External declarations pertaining to backend/utils/misc/guc.c and * External declarations pertaining to backend/utils/misc/guc.c and
* backend/utils/misc/guc-file.l * backend/utils/misc/guc-file.l
* *
* $Id: guc.h,v 1.15 2002/03/01 22:45:18 petere Exp $ * $Id: guc.h,v 1.16 2002/03/24 04:31:09 tgl Exp $
*/ */
#ifndef GUC_H #ifndef GUC_H
#define GUC_H #define GUC_H
@ -92,6 +92,8 @@ extern bool Show_executor_stats;
extern bool Show_query_stats; extern bool Show_query_stats;
extern bool Show_btree_build_stats; extern bool Show_btree_build_stats;
extern bool Explain_pretty_print;
extern bool SQL_inheritance; extern bool SQL_inheritance;
extern bool Australian_timezones; extern bool Australian_timezones;