From c7bceca156cc7ffd744b17669b7839b69eb1c9d3 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sun, 2 Feb 2003 23:46:38 +0000 Subject: [PATCH] Implement EXPLAIN EXECUTE. By Neil Conway, with some kibitzing from Tom Lane. --- doc/src/sgml/ref/execute.sgml | 8 +- doc/src/sgml/ref/explain.sgml | 51 +++++-- doc/src/sgml/ref/prepare.sgml | 6 +- doc/src/sgml/release.sgml | 3 +- src/backend/commands/explain.c | 65 ++++++--- src/backend/commands/prepare.c | 238 +++++++++++++++++++++------------ src/backend/parser/gram.y | 24 ++-- src/include/commands/explain.h | 6 +- src/include/commands/prepare.h | 8 +- 9 files changed, 271 insertions(+), 138 deletions(-) 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,30 +3953,34 @@ opt_name_list: /***************************************************************************** * * QUERY: - * EXPLAIN query - * EXPLAIN ANALYZE query + * EXPLAIN [ANALYZE] [VERBOSE] query * *****************************************************************************/ ExplainStmt: - EXPLAIN opt_verbose OptimizableStmt + EXPLAIN opt_analyze opt_verbose OptimizableStmt { ExplainStmt *n = makeNode(ExplainStmt); - n->verbose = $2; - n->analyze = FALSE; - n->query = (Query*)$3; + n->analyze = $2; + n->verbose = $3; + n->query = (Query*)$4; $$ = (Node *)n; } - | EXPLAIN analyze_keyword opt_verbose OptimizableStmt + | EXPLAIN opt_analyze opt_verbose ExecuteStmt { ExplainStmt *n = makeNode(ExplainStmt); + n->analyze = $2; n->verbose = $3; - n->analyze = TRUE; n->query = (Query*)$4; $$ = (Node *)n; } ; +opt_analyze: + analyze_keyword { $$ = TRUE; } + | /* EMPTY */ { $$ = FALSE; } + ; + /***************************************************************************** * * QUERY: 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 */ -- 2.40.0