1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-09 06:21:09 +03:00

Implement EXPLAIN EXECUTE. By Neil Conway, with some kibitzing from

Tom Lane.
This commit is contained in:
Tom Lane
2003-02-02 23:46:38 +00:00
parent 6adb475f77
commit c7bceca156
9 changed files with 275 additions and 142 deletions

View File

@@ -1,20 +1,23 @@
/*
/*-------------------------------------------------------------------------
*
* explain.c
* Explain the query execution plan
* Explain query execution plans
*
* Portions Copyright (c) 1996-2002, 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.99 2002/12/15 16:17:38 tgl Exp $
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.100 2003/02/02 23:46:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "commands/explain.h"
#include "commands/prepare.h"
#include "executor/executor.h"
#include "executor/instrument.h"
#include "lib/stringinfo.h"
@@ -81,8 +84,11 @@ ExplainQuery(ExplainStmt *stmt, CommandDest dest)
if (query->commandType == CMD_UTILITY)
{
/* rewriter will not cope with utility statements */
do_text_output_oneline(tstate, "Utility statements have no plan structure");
/* Rewriter will not cope with utility statements */
if (query->utilityStmt && IsA(query->utilityStmt, ExecuteStmt))
ExplainExecuteQuery(stmt, tstate);
else
do_text_output_oneline(tstate, "Utility statements have no plan structure");
}
else
{
@@ -119,10 +125,6 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
{
Plan *plan;
QueryDesc *queryDesc;
ExplainState *es;
StringInfo str;
double totaltime = 0;
struct timeval starttime;
/* planner will not cope with utility statements */
if (query->commandType == CMD_UTILITY)
@@ -134,6 +136,13 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
return;
}
/*
* We don't support DECLARE CURSOR in EXPLAIN, but parser will take it
* because it's an OptimizableStmt
*/
if (query->isPortal)
elog(ERROR, "EXPLAIN / DECLARE CURSOR is not supported");
/* plan the query */
plan = planner(query);
@@ -141,15 +150,34 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
if (plan == NULL)
return;
/* We don't support DECLARE CURSOR here */
Assert(!query->isPortal);
gettimeofday(&starttime, NULL);
/* Create a QueryDesc requesting no output */
queryDesc = CreateQueryDesc(query, plan, None, NULL, NULL,
stmt->analyze);
ExplainOnePlan(queryDesc, stmt, tstate);
}
/*
* ExplainOnePlan -
* given a planned query, execute it if needed, and then print
* EXPLAIN output
*
* This is exported because it's called back from prepare.c in the
* EXPLAIN EXECUTE case
*
* Note: the passed-in QueryDesc is freed when we're done with it
*/
void
ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
TupOutputState *tstate)
{
struct timeval starttime;
double totaltime = 0;
ExplainState *es;
StringInfo str;
gettimeofday(&starttime, NULL);
/* call ExecutorStart to prepare the plan for execution */
ExecutorStart(queryDesc);
@@ -160,7 +188,6 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
/* We can't clean up 'till we're done printing the stats... */
totaltime += elapsed_time(&starttime);
}
@@ -169,14 +196,14 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
es->printCost = true; /* default */
es->printNodes = stmt->verbose;
es->printAnalyze = stmt->analyze;
es->rtable = query->rtable;
es->rtable = queryDesc->parsetree->rtable;
if (es->printNodes)
{
char *s;
char *f;
s = nodeToString(plan);
s = nodeToString(queryDesc->plantree);
if (s)
{
if (Explain_pretty_print)
@@ -195,7 +222,7 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, TupOutputState *tstate)
if (es->printCost)
{
explain_outNode(str, plan, queryDesc->planstate,
explain_outNode(str, queryDesc->plantree, queryDesc->planstate,
NULL, 0, es);
}

View File

@@ -6,12 +6,13 @@
* Copyright (c) 2002, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.12 2002/12/15 21:01:34 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.13 2003/02/02 23:46:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "commands/explain.h"
#include "commands/prepare.h"
#include "executor/executor.h"
#include "utils/guc.h"
@@ -47,9 +48,10 @@ static HTAB *prepared_queries = NULL;
static void InitQueryHashTable(void);
static void StoreQuery(const char *stmt_name, List *query_list,
List *plan_list, List *argtype_list);
List *plan_list, List *argtype_list);
static QueryHashEntry *FetchQuery(const char *plan_name);
static ParamListInfo EvaluateParams(EState *estate,
List *params, List *argtypes);
/*
* Implements the 'PREPARE' utility statement.
@@ -94,7 +96,7 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
*query_list,
*plan_list;
ParamListInfo paramLI = NULL;
EState *estate;
EState *estate = NULL;
/* Look it up in the hash table */
entry = FetchQuery(stmt->name);
@@ -104,51 +106,22 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
Assert(length(query_list) == length(plan_list));
/*
* Need an EState to evaluate parameters; must not delete it till end
* of query, in case parameters are pass-by-reference.
*/
estate = CreateExecutorState();
/* Evaluate parameters, if any */
if (entry->argtype_list != NIL)
{
int nargs = length(entry->argtype_list);
int i = 0;
List *exprstates;
/* Parser should have caught this error, but check */
if (nargs != length(stmt->params))
elog(ERROR, "ExecuteQuery: wrong number of arguments");
exprstates = (List *) ExecPrepareExpr((Expr *) stmt->params, estate);
paramLI = (ParamListInfo)
palloc0((nargs + 1) * sizeof(ParamListInfoData));
foreach(l, exprstates)
{
ExprState *n = lfirst(l);
bool isNull;
paramLI[i].value = ExecEvalExprSwitchContext(n,
GetPerTupleExprContext(estate),
&isNull,
NULL);
paramLI[i].kind = PARAM_NUM;
paramLI[i].id = i + 1;
paramLI[i].isnull = isNull;
i++;
}
paramLI[i].kind = PARAM_INVALID;
/*
* Need an EState to evaluate parameters; must not delete it
* till end of query, in case parameters are pass-by-reference.
*/
estate = CreateExecutorState();
paramLI = EvaluateParams(estate, stmt->params, entry->argtype_list);
}
/* Execute each query */
foreach(l, query_list)
{
Query *query = lfirst(l);
Plan *plan = lfirst(plan_list);
Query *query = (Query *) lfirst(l);
Plan *plan = (Plan *) lfirst(plan_list);
bool is_last_query;
plan_list = lnext(plan_list);
@@ -196,11 +169,59 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
CommandCounterIncrement();
}
FreeExecutorState(estate);
if (estate)
FreeExecutorState(estate);
/* No need to pfree other memory, MemoryContext will be reset */
}
/*
* Evaluates a list of parameters, using the given executor state. It
* requires a list of the parameter values themselves, and a list of
* their types. It returns a filled-in ParamListInfo -- this can later
* be passed to CreateQueryDesc(), which allows the executor to make use
* of the parameters during query execution.
*/
static ParamListInfo
EvaluateParams(EState *estate, List *params, List *argtypes)
{
int nargs = length(argtypes);
ParamListInfo paramLI;
List *exprstates;
List *l;
int i = 0;
/* Parser should have caught this error, but check anyway */
if (length(params) != nargs)
elog(ERROR, "EvaluateParams: wrong number of arguments");
exprstates = (List *) ExecPrepareExpr((Expr *) params, estate);
paramLI = (ParamListInfo)
palloc0((nargs + 1) * sizeof(ParamListInfoData));
foreach(l, exprstates)
{
ExprState *n = lfirst(l);
bool isNull;
paramLI[i].value = ExecEvalExprSwitchContext(n,
GetPerTupleExprContext(estate),
&isNull,
NULL);
paramLI[i].kind = PARAM_NUM;
paramLI[i].id = i + 1;
paramLI[i].isnull = isNull;
i++;
}
paramLI[i].kind = PARAM_INVALID;
return paramLI;
}
/*
* Initialize query hash table upon first use.
*/
@@ -229,8 +250,8 @@ InitQueryHashTable(void)
* to the hash entry, so the caller can dispose of their copy.
*/
static void
StoreQuery(const char *stmt_name, List *query_list, List *plan_list,
List *argtype_list)
StoreQuery(const char *stmt_name, List *query_list,
List *plan_list, List *argtype_list)
{
QueryHashEntry *entry;
MemoryContext oldcxt,
@@ -278,7 +299,7 @@ StoreQuery(const char *stmt_name, List *query_list, List *plan_list,
HASH_ENTER,
&found);
/* Shouldn't get a failure, nor duplicate entry */
/* Shouldn't get a failure, nor a duplicate entry */
if (!entry || found)
elog(ERROR, "Unable to store prepared statement \"%s\"!",
stmt_name);
@@ -293,7 +314,8 @@ StoreQuery(const char *stmt_name, List *query_list, List *plan_list,
}
/*
* Lookup an existing query in the hash table.
* Lookup an existing query in the hash table. If the query does not
* actually exist, an elog(ERROR) is thrown.
*/
static QueryHashEntry *
FetchQuery(const char *plan_name)
@@ -346,52 +368,104 @@ FetchQueryParams(const char *plan_name)
/*
* Implements the 'DEALLOCATE' utility statement: deletes the
* specified plan from storage.
*
* The initial part of this routine is identical to FetchQuery(),
* but we repeat the coding because we need to use the key twice.
*/
void
DeallocateQuery(DeallocateStmt *stmt)
{
char key[HASH_KEY_LEN];
QueryHashEntry *entry;
/*
* If the hash table hasn't been initialized, it can't be storing
* anything, therefore it couldn't possibly store our plan.
*/
if (!prepared_queries)
elog(ERROR, "Prepared statement with name \"%s\" does not exist",
stmt->name);
/*
* We can't just use the statement name as supplied by the user: the
* hash package is picky enough that it needs to be NULL-padded out to
* the appropriate length to work correctly.
*/
MemSet(key, 0, sizeof(key));
strncpy(key, stmt->name, sizeof(key));
/*
* First lookup the entry, so we can release all the subsidiary memory
* it has allocated (when it's removed, hash_search() will return a
* dangling pointer, so it needs to be done prior to HASH_REMOVE).
* This requires an extra hash-table lookup, but DEALLOCATE isn't
* exactly a performance bottleneck.
*/
entry = (QueryHashEntry *) hash_search(prepared_queries,
key,
HASH_FIND,
NULL);
if (!entry)
elog(ERROR, "Prepared statement with name \"%s\" does not exist",
stmt->name);
/* Find the query's hash table entry */
entry = FetchQuery(stmt->name);
/* Flush the context holding the subsidiary data */
Assert(MemoryContextIsValid(entry->context));
MemoryContextDelete(entry->context);
/* Now we can remove the hash table entry */
hash_search(prepared_queries, key, HASH_REMOVE, NULL);
hash_search(prepared_queries, entry->key, HASH_REMOVE, NULL);
}
/*
* Implements the 'EXPLAIN EXECUTE' utility statement.
*/
void
ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
{
ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt;
QueryHashEntry *entry;
List *l,
*query_list,
*plan_list;
ParamListInfo paramLI = NULL;
EState *estate = NULL;
/* explain.c should only call me for EXECUTE stmt */
Assert(execstmt && IsA(execstmt, ExecuteStmt));
/* Look it up in the hash table */
entry = FetchQuery(execstmt->name);
query_list = entry->query_list;
plan_list = entry->plan_list;
Assert(length(query_list) == length(plan_list));
/* Evaluate parameters, if any */
if (entry->argtype_list != NIL)
{
/*
* Need an EState to evaluate parameters; must not delete it
* till end of query, in case parameters are pass-by-reference.
*/
estate = CreateExecutorState();
paramLI = EvaluateParams(estate, execstmt->params,
entry->argtype_list);
}
/* Explain each query */
foreach(l, query_list)
{
Query *query = (Query *) lfirst(l);
Plan *plan = (Plan *) lfirst(plan_list);
bool is_last_query;
plan_list = lnext(plan_list);
is_last_query = (plan_list == NIL);
if (query->commandType == CMD_UTILITY)
{
if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
do_text_output_oneline(tstate, "NOTIFY");
else
do_text_output_oneline(tstate, "UTILITY");
}
else
{
QueryDesc *qdesc;
/* Create a QueryDesc requesting no output */
qdesc = CreateQueryDesc(query, plan, None, NULL,
paramLI, stmt->analyze);
if (execstmt->into)
{
if (qdesc->operation != CMD_SELECT)
elog(ERROR, "INTO clause specified for non-SELECT query");
query->into = execstmt->into;
qdesc->dest = None;
}
ExplainOnePlan(qdesc, stmt, tstate);
}
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
/* put a blank line between plans */
if (!is_last_query)
do_text_output_oneline(tstate, "");
}
if (estate)
FreeExecutorState(estate);
}