diff --git a/doc/src/sgml/ref/execute.sgml b/doc/src/sgml/ref/execute.sgml
index 2302ef99a3..d62209797b 100644
--- a/doc/src/sgml/ref/execute.sgml
+++ b/doc/src/sgml/ref/execute.sgml
@@ -1,5 +1,5 @@
@@ -99,9 +99,9 @@ PostgreSQL documentation
Like SELECT INTO, EXECUTE can
- be used to store the results of executing the query in a table by
- specifying an INTO clause. For more information on this behabior,
- consult the reference for .
+ store the results of executing the query into a newly-created
+ table, by specifying an INTO clause. For more information on this behavior,
+ see .
diff --git a/doc/src/sgml/ref/explain.sgml b/doc/src/sgml/ref/explain.sgml
index 7e7f43f50e..14639cc268 100644
--- a/doc/src/sgml/ref/explain.sgml
+++ b/doc/src/sgml/ref/explain.sgml
@@ -1,5 +1,5 @@
@@ -55,7 +55,8 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] queryquery
- Any query.
+ Any SELECT>, INSERT>, UPDATE>,
+ DELETE>, or EXECUTE> query.
@@ -132,13 +133,13 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] query
In order to allow the PostgreSQL query
planner to make reasonably informed decisions when optimizing
- queries, the ANALYZE statement should be used
+ queries, the ANALYZE statement should be run
to record statistics about the distribution of data within the
- table. If you have not done this (or the statistical distribution
+ table. If you have not done this (or if the statistical distribution
of the data in the table has changed significantly since the last
- time ANALYZE was run), the estimated costs and
- the resulting query plan displayed by EXPLAIN
- are unlikely to conform to the real properties of the query.
+ time ANALYZE was run), the estimated costs
+ are unlikely to conform to the real properties of the query,
+ and consequently an inferior query plan may be chosen.
@@ -147,7 +148,7 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] query
@@ -157,8 +158,8 @@ EXPLAIN [ ANALYZE ] [ VERBOSE ] queryEXPLAIN ANALYZE on an INSERT,
- UPDATE, or DELETE query without letting the query affect your data,
- use this approach:
+ UPDATE, DELETE, or EXECUTE query without letting the query affect your
+ data, use this approach:
BEGIN;
EXPLAIN ANALYZE ...;
@@ -244,13 +245,35 @@ EXPLAIN SELECT sum(i) FROM foo WHERE i < 10;
+
+ Here is an example of using EXPLAIN EXECUTE to display the query
+ plan for a prepared query:
+
+
+PREPARE query(int, int) AS SELECT sum(bar) FROM test
+ WHERE id > $1 AND id < $2
+ GROUP BY foo;
+
+EXPLAIN ANALYZE EXECUTE query(100, 200);
+
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------
+ HashAggregate (cost=39.53..39.53 rows=1 width=8) (actual time=0.66..0.67 rows=7 loops=1)
+ -> Index Scan using test_pkey on test (cost=0.00..32.97 rows=1311 width=8) (actual time=0.05..0.39 rows=99 loops=1)
+ Index Cond: ((id > $1) AND (id < $2))
+ Total runtime: 0.85 msec
+(4 rows)
+
+
+
+
Note that the specific numbers shown, and even the selected query
strategy, may vary between PostgreSQL
- releases due to planner improvements. In addition, the algorithm
- used by ANALYZE to generate statistics is not
- completely deterministic; therefore, it is possible (although not
- likely) for cost estimations to change between runs of
+ releases due to planner improvements. In addition, the
+ ANALYZE command uses random sampling to estimate
+ data statistics; therefore, it is possible
+ for cost estimates to change after a fresh run of
ANALYZE, even if the actual distribution of data
in the table has not changed.
diff --git a/doc/src/sgml/ref/prepare.sgml b/doc/src/sgml/ref/prepare.sgml
index d9fa86414b..418bd83ace 100644
--- a/doc/src/sgml/ref/prepare.sgml
+++ b/doc/src/sgml/ref/prepare.sgml
@@ -1,5 +1,5 @@
@@ -156,7 +156,9 @@ PostgreSQL documentation
constant values in a query to make guesses about the likely
result of executing the query. Since this data is unavailable when
planning prepared queries with parameters, the chosen plan may be
- sub-optimal.
+ sub-optimal. To examine the query plan
+ PostgreSQL has chosen for a prepared
+ query, use EXPLAIN EXECUTE.
diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml
index fa1cac9f57..354b70cc07 100644
--- a/doc/src/sgml/release.sgml
+++ b/doc/src/sgml/release.sgml
@@ -1,5 +1,5 @@
@@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without
worries about funny characters.
-->
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);
}
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 19d64c3e1c..f6cd5d0a80 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -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);
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ff79e7a39f..2d3c444520 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.396 2003/01/23 23:38:56 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.397 2003/02/02 23:46:38 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -241,7 +241,7 @@ static void doNegateFloat(Value *v);
%type opt_interval
%type overlay_placing substr_from substr_for
-%type opt_instead opt_cursor
+%type opt_instead opt_cursor opt_analyze
%type index_opt_unique opt_verbose opt_full
%type opt_freeze opt_default opt_recheck
%type opt_binary opt_oids copy_delimiter
@@ -3953,28 +3953,32 @@ opt_name_list:
/*****************************************************************************
*
* QUERY:
- * EXPLAIN query
- * EXPLAIN ANALYZE query
+ * EXPLAIN [ANALYZE] [VERBOSE] query
*
*****************************************************************************/
ExplainStmt:
- EXPLAIN opt_verbose OptimizableStmt
- {
- ExplainStmt *n = makeNode(ExplainStmt);
- n->verbose = $2;
- n->analyze = FALSE;
- n->query = (Query*)$3;
- $$ = (Node *)n;
- }
- | EXPLAIN analyze_keyword opt_verbose OptimizableStmt
+ EXPLAIN opt_analyze opt_verbose OptimizableStmt
{
ExplainStmt *n = makeNode(ExplainStmt);
+ n->analyze = $2;
n->verbose = $3;
- n->analyze = TRUE;
n->query = (Query*)$4;
$$ = (Node *)n;
}
+ | EXPLAIN opt_analyze opt_verbose ExecuteStmt
+ {
+ ExplainStmt *n = makeNode(ExplainStmt);
+ n->analyze = $2;
+ n->verbose = $3;
+ n->query = (Query*)$4;
+ $$ = (Node *)n;
+ }
+ ;
+
+opt_analyze:
+ analyze_keyword { $$ = TRUE; }
+ | /* EMPTY */ { $$ = FALSE; }
;
/*****************************************************************************
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 7889ccccfb..040740490b 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -6,16 +6,20 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
- * $Id: explain.h,v 1.17 2002/06/20 20:29:49 momjian Exp $
+ * $Id: explain.h,v 1.18 2003/02/02 23:46:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef EXPLAIN_H
#define EXPLAIN_H
+#include "executor/executor.h"
#include "nodes/parsenodes.h"
#include "tcop/dest.h"
+
extern void ExplainQuery(ExplainStmt *stmt, CommandDest dest);
+extern void ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
+ TupOutputState *tstate);
#endif /* EXPLAIN_H */
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index 884236ff99..aad6416675 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -6,24 +6,22 @@
*
* Copyright (c) 2002, PostgreSQL Global Development Group
*
- * $Id: prepare.h,v 1.2 2002/09/04 20:31:42 momjian Exp $
+ * $Id: prepare.h,v 1.3 2003/02/02 23:46:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
-
#ifndef PREPARE_H
#define PREPARE_H
+#include "executor/executor.h"
#include "nodes/parsenodes.h"
#include "tcop/dest.h"
extern void PrepareQuery(PrepareStmt *stmt);
-
extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest);
-
extern void DeallocateQuery(DeallocateStmt *stmt);
-
extern List *FetchQueryParams(const char *plan_name);
+extern void ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate);
#endif /* PREPARE_H */