1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +03:00

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

@ -5,12 +5,14 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* 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 "access/heapam.h"
#include "catalog/pg_type.h"
#include "commands/explain.h"
#include "executor/instrument.h"
#include "lib/stringinfo.h"
@ -22,6 +24,7 @@
#include "rewrite/rewriteHandler.h"
#include "tcop/pquery.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/relcache.h"
@ -35,9 +38,15 @@ typedef struct ExplainState
List *rtable; /* range table */
} ExplainState;
typedef struct TextOutputState
{
TupleDesc tupdesc;
DestReceiver *destfunc;
} TextOutputState;
static StringInfo Explain_PlanToString(Plan *plan, ExplainState *es);
static void ExplainOneQuery(Query *query, bool verbose, bool analyze,
CommandDest dest);
static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
TextOutputState *tstate);
static void explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan,
int indent, ExplainState *es);
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,
StringInfo str, int indent, ExplainState *es);
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 "<>" */
#define stringStringInfo(s) (((s) == NULL) ? "<>" : (s))
@ -55,42 +68,47 @@ static Node *make_ors_ands_explicit(List *orclauses);
/*
* ExplainQuery -
* print out the execution plan for a given query
* execute an EXPLAIN command
*/
void
ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
ExplainQuery(ExplainStmt *stmt, CommandDest dest)
{
Query *query = stmt->query;
TextOutputState *tstate;
List *rewritten;
List *l;
/* rewriter and planner may not work in aborted state? */
if (IsAbortedTransactionBlockState())
{
elog(WARNING, "(transaction aborted): %s",
"queries ignored until END");
return;
}
tstate = begin_text_output(dest, "QUERY PLAN");
/* rewriter will not cope with utility statements */
if (query->commandType == CMD_UTILITY)
{
elog(NOTICE, "Utility statements have no plan structure");
return;
/* rewriter will not cope with utility statements */
do_text_output(tstate, "Utility statements have no plan structure");
}
/* Rewrite through rule system */
rewritten = QueryRewrite(query);
/* In the case of an INSTEAD NOTHING, tell at least that */
if (rewritten == NIL)
else
{
elog(NOTICE, "Query rewrites to nothing");
return;
/* Rewrite through rule system */
rewritten = QueryRewrite(query);
if (rewritten == NIL)
{
/* In the case of an INSTEAD NOTHING, tell at least that */
do_text_output(tstate, "Query rewrites to nothing");
}
else
{
/* Explain every plan */
foreach(l, rewritten)
{
ExplainOneQuery(lfirst(l), stmt, tstate);
/* put a blank line between plans */
if (lnext(l) != NIL)
do_text_output(tstate, "");
}
}
}
/* Explain every plan */
foreach(l, rewritten)
ExplainOneQuery(lfirst(l), verbose, analyze, dest);
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
*/
static void
ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
ExplainOneQuery(Query *query, ExplainStmt *stmt, TextOutputState *tstate)
{
Plan *plan;
ExplainState *es;
@ -108,9 +126,9 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
if (query->commandType == CMD_UTILITY)
{
if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
elog(INFO, "QUERY PLAN:\n\nNOTIFY\n");
do_text_output(tstate, "NOTIFY");
else
elog(INFO, "QUERY PLAN:\n\nUTILITY\n");
do_text_output(tstate, "UTILITY");
return;
}
@ -122,7 +140,7 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
return;
/* Execute the plan for statistics if asked for */
if (analyze)
if (stmt->analyze)
{
struct timeval starttime;
struct timeval endtime;
@ -154,7 +172,7 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
es->printCost = true; /* default */
if (verbose)
if (stmt->verbose)
es->printNodes = true;
es->rtable = query->rtable;
@ -162,12 +180,20 @@ ExplainOneQuery(Query *query, bool verbose, bool analyze, CommandDest dest)
if (es->printNodes)
{
char *s;
char *f;
s = nodeToString(plan);
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);
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;
str = Explain_PlanToString(plan, es);
if (analyze)
if (stmt->analyze)
appendStringInfo(str, "Total runtime: %.2f msec\n",
1000.0 * totaltime);
elog(INFO, "QUERY PLAN:\n\n%s", str->data);
do_text_output_multiline(tstate, str->data);
pfree(str->data);
pfree(str);
}
if (es->printNodes)
pprint(plan); /* display in postmaster log file */
pfree(es);
}
@ -709,3 +732,78 @@ make_ors_ands_explicit(List *orclauses)
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
* $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
* AUTHOR DATE MAJOR EVENT
@ -21,6 +21,7 @@
#include "access/printtup.h"
#include "catalog/pg_type.h"
#include "lib/stringinfo.h"
#include "nodes/print.h"
#include "optimizer/clauses.h"
#include "parser/parsetree.h"
@ -37,31 +38,129 @@ void
print(void *obj)
{
char *s;
char *f;
s = nodeToString(obj);
printf("%s\n", s);
fflush(stdout);
f = format_node_dump(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
pprint(void *obj)
{
#define INDENTSTOP 3
#define MAXINDENT 60
#define LINELEN 80
char *s;
int i;
char line[LINELEN];
int indentLev;
int indentDist;
int j;
char *f;
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 */
indentDist = 0; /* physical indent distance */
i = 0;
@ -69,9 +168,9 @@ pprint(void *obj)
{
for (j = 0; j < indentDist; 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])
{
case '}':
@ -79,11 +178,12 @@ pprint(void *obj)
{
/* print data before the } */
line[j] = '\0';
printf("%s\n", line);
appendStringInfo(&str, "%s\n", line);
}
/* print the } at indentDist */
line[indentDist] = '\0';
printf("%s}\n", line);
line[indentDist] = '}';
line[indentDist+1] = '\0';
appendStringInfo(&str, "%s\n", line);
/* outdent */
if (indentLev > 0)
{
@ -96,7 +196,7 @@ pprint(void *obj)
case ')':
/* force line break after ')' */
line[j + 1] = '\0';
printf("%s\n", line);
appendStringInfo(&str, "%s\n", line);
j = indentDist - 1;
break;
case '{':
@ -104,36 +204,38 @@ pprint(void *obj)
if (j != indentDist)
{
line[j] = '\0';
printf("%s\n", line);
appendStringInfo(&str, "%s\n", line);
}
/* indent */
indentLev++;
indentDist = Min(indentLev * INDENTSTOP, MAXINDENT);
for (j = 0; j < indentDist; j++)
line[j] = ' ';
line[j] = s[i];
line[j] = dump[i];
break;
case ':':
/* force line break before : */
if (j != indentDist)
{
line[j] = '\0';
printf("%s\n", line);
appendStringInfo(&str, "%s\n", line);
}
j = indentDist;
line[j] = s[i];
line[j] = dump[i];
break;
}
}
line[j] = '\0';
if (s[i] == '\0')
if (dump[i] == '\0')
break;
printf("%s\n", line);
appendStringInfo(&str, "%s\n", line);
}
if (j != 0)
printf("%s\n", line);
fflush(stdout);
pfree(s);
if (j > 0)
appendStringInfo(&str, "%s\n", line);
return str.data;
#undef INDENTSTOP
#undef MAXINDENT
#undef LINELEN
}
/*

View File

@ -8,7 +8,7 @@
*
*
* 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
* 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);
if (Debug_print_parse)
{
if (Debug_pretty_print)
{
elog(LOG, "parse tree:");
nodeDisplay(querytree);
}
else
elog(LOG, "parse tree: %s", nodeToString(querytree));
}
elog_node_display(LOG, "parse tree", querytree,
Debug_pretty_print);
if (querytree->commandType == CMD_UTILITY)
{
@ -470,27 +463,8 @@ pg_analyze_and_rewrite(Node *parsetree)
#endif
if (Debug_print_rewritten)
{
if (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));
}
}
}
elog_node_display(LOG, "rewritten parse tree", querytree_list,
Debug_pretty_print);
return querytree_list;
}
@ -538,15 +512,7 @@ pg_plan_query(Query *querytree)
* Print plan if debugging.
*/
if (Debug_print_plan)
{
if (Debug_pretty_print)
{
elog(LOG, "plan:");
nodeDisplay(plan);
}
else
elog(LOG, "plan: %s", nodeToString(plan));
}
elog_node_display(LOG, "plan", plan, Debug_pretty_print);
return plan;
}
@ -1722,7 +1688,7 @@ PostgresMain(int argc, char *argv[], const char *username)
if (!IsUnderPostmaster)
{
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
* $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;
case T_ExplainStmt:
{
ExplainStmt *stmt = (ExplainStmt *) parsetree;
ExplainQuery(stmt->query, stmt->verbose, stmt->analyze, dest);
}
ExplainQuery((ExplainStmt *) parsetree, dest);
break;
#ifdef NOT_USED

View File

@ -4,7 +4,7 @@
* Support for grand unified configuration scheme, including SET
* 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
* 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 */
bool Show_btree_build_stats = false;
bool Explain_pretty_print = true;
bool SQL_inheritance = true;
bool Australian_timezones = false;
@ -293,6 +295,10 @@ static struct config_bool
},
#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
},

View File

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

View File

@ -3,7 +3,7 @@
*
* 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_query_stats",
"trace_notify",
"explain_pretty_print",
"sql_inheritance",
"australian_timezones",
"password_encryption",

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* 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 "tcop/dest.h"
extern void ExplainQuery(Query *query, bool verbose, bool analyze, CommandDest dest);
extern void ExplainQuery(ExplainStmt *stmt, CommandDest dest);
#endif /* EXPLAIN_H */

View File

@ -1,13 +1,13 @@
/*-------------------------------------------------------------------------
*
* execnodes.h
* definitions for executor state nodes
* print.h
* definitions for nodes/print.c
*
*
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* 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/plannodes.h"
/*
* nodes/{outfuncs.c,print.c}
*/
#define nodeDisplay pprint
extern void print(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_expr(Node *expr, 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
* 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
#define GUC_H
@ -92,6 +92,8 @@ extern bool Show_executor_stats;
extern bool Show_query_stats;
extern bool Show_btree_build_stats;
extern bool Explain_pretty_print;
extern bool SQL_inheritance;
extern bool Australian_timezones;