1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-17 06:41:24 +03:00
Tom Lane 79913910d4 Restructure command destination handling so that we pass around
DestReceiver pointers instead of just CommandDest values.  The DestReceiver
is made at the point where the destination is selected, rather than
deep inside the executor.  This cleans up the original kluge implementation
of tstoreReceiver.c, and makes it easy to support retrieving results
from utility statements inside portals.  Thus, you can now do fun things
like Bind and Execute a FETCH or EXPLAIN command, and it'll all work
as expected (e.g., you can Describe the portal, or use Execute's count
parameter to suspend the output partway through).  Implementation involves
stuffing the utility command's output into a Tuplestore, which would be
kind of annoying for huge output sets, but should be quite acceptable
for typical uses of utility commands.
2003-05-06 20:26:28 +00:00

519 lines
13 KiB
C

/*-------------------------------------------------------------------------
*
* prepare.c
* Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE
*
* This module also implements storage of prepared statements that are
* accessed via the extended FE/BE query protocol.
*
*
* Copyright (c) 2002-2003, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.16 2003/05/06 20:26:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "commands/explain.h"
#include "commands/prepare.h"
#include "executor/executor.h"
#include "utils/guc.h"
#include "optimizer/planner.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
#include "tcop/utility.h"
#include "utils/hsearch.h"
#include "utils/memutils.h"
/*
* The hash table in which prepared queries are stored. This is
* per-backend: query plans are not shared between backends.
* The keys for this hash table are the arguments to PREPARE and EXECUTE
* (statement names); the entries are PreparedStatement structs.
*/
static HTAB *prepared_queries = NULL;
static void InitQueryHashTable(void);
static ParamListInfo EvaluateParams(EState *estate,
List *params, List *argtypes);
/*
* Implements the 'PREPARE' utility statement.
*/
void
PrepareQuery(PrepareStmt *stmt)
{
const char *commandTag;
List *query_list,
*plan_list;
/*
* Disallow empty-string statement name (conflicts with protocol-level
* unnamed statement).
*/
if (!stmt->name || stmt->name[0] == '\0')
elog(ERROR, "Invalid statement name: must not be empty");
switch (stmt->query->commandType)
{
case CMD_SELECT:
commandTag = "SELECT";
break;
case CMD_INSERT:
commandTag = "INSERT";
break;
case CMD_UPDATE:
commandTag = "UPDATE";
break;
case CMD_DELETE:
commandTag = "DELETE";
break;
default:
elog(ERROR, "Utility statements cannot be prepared");
commandTag = NULL; /* keep compiler quiet */
break;
}
/*
* Parse analysis is already done, but we must still rewrite and plan
* the query.
*/
/* Rewrite the query. The result could be 0, 1, or many queries. */
query_list = QueryRewrite(stmt->query);
/* Generate plans for queries. Snapshot is already set. */
plan_list = pg_plan_queries(query_list, false);
/* Save the results. */
StorePreparedStatement(stmt->name,
NULL, /* text form not available */
commandTag,
query_list,
plan_list,
stmt->argtype_oids);
}
/*
* Implements the 'EXECUTE' utility statement.
*/
void
ExecuteQuery(ExecuteStmt *stmt, DestReceiver *dest)
{
PreparedStatement *entry;
char *query_string;
List *query_list,
*plan_list;
MemoryContext qcontext;
ParamListInfo paramLI = NULL;
EState *estate = NULL;
Portal portal;
/* Look it up in the hash table */
entry = FetchPreparedStatement(stmt->name, true);
query_string = entry->query_string;
query_list = entry->query_list;
plan_list = entry->plan_list;
qcontext = entry->context;
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, stmt->params, entry->argtype_list);
}
/*
* Create a new portal to run the query in
*/
portal = CreateNewPortal();
/*
* For EXECUTE INTO, make a copy of the stored query so that we can
* modify its destination (yech, but INTO has always been ugly).
* For regular EXECUTE we can just use the stored query where it sits,
* since the executor is read-only.
*/
if (stmt->into)
{
MemoryContext oldContext;
Query *query;
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
if (query_string)
query_string = pstrdup(query_string);
query_list = copyObject(query_list);
plan_list = copyObject(plan_list);
qcontext = PortalGetHeapMemory(portal);
if (length(query_list) != 1)
elog(ERROR, "INTO clause specified for non-SELECT query");
query = (Query *) lfirst(query_list);
if (query->commandType != CMD_SELECT)
elog(ERROR, "INTO clause specified for non-SELECT query");
query->into = copyObject(stmt->into);
MemoryContextSwitchTo(oldContext);
}
PortalDefineQuery(portal,
query_string,
entry->commandTag,
query_list,
plan_list,
qcontext);
/*
* Run the portal to completion.
*/
PortalStart(portal, paramLI);
(void) PortalRun(portal, FETCH_ALL, dest, dest, NULL);
PortalDrop(portal, false);
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.
*/
static void
InitQueryHashTable(void)
{
HASHCTL hash_ctl;
MemSet(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = NAMEDATALEN;
hash_ctl.entrysize = sizeof(PreparedStatement);
prepared_queries = hash_create("Prepared Queries",
32,
&hash_ctl,
HASH_ELEM);
if (!prepared_queries)
elog(ERROR, "InitQueryHashTable: unable to create hash table");
}
/*
* Store all the data pertaining to a query in the hash table using
* the specified key. A copy of the data is made in a memory context belonging
* to the hash entry, so the caller can dispose of their copy.
*
* Exception: commandTag is presumed to be a pointer to a constant string,
* or possibly NULL, so it need not be copied. Note that commandTag should
* be NULL only if the original query (before rewriting) was empty.
*/
void
StorePreparedStatement(const char *stmt_name,
const char *query_string,
const char *commandTag,
List *query_list,
List *plan_list,
List *argtype_list)
{
PreparedStatement *entry;
MemoryContext oldcxt,
entrycxt;
char *qstring;
char key[NAMEDATALEN];
bool found;
/* Initialize the hash table, if necessary */
if (!prepared_queries)
InitQueryHashTable();
/* Check for pre-existing entry of same name */
/* See notes in FetchPreparedStatement */
MemSet(key, 0, sizeof(key));
strncpy(key, stmt_name, sizeof(key));
hash_search(prepared_queries, key, HASH_FIND, &found);
if (found)
elog(ERROR, "Prepared statement with name \"%s\" already exists",
stmt_name);
/* Make a permanent memory context for the hashtable entry */
entrycxt = AllocSetContextCreate(TopMemoryContext,
stmt_name,
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
oldcxt = MemoryContextSwitchTo(entrycxt);
/*
* We need to copy the data so that it is stored in the correct memory
* context. Do this before making hashtable entry, so that an
* out-of-memory failure only wastes memory and doesn't leave us with
* an incomplete (ie corrupt) hashtable entry.
*/
qstring = query_string ? pstrdup(query_string) : (char *) NULL;
query_list = (List *) copyObject(query_list);
plan_list = (List *) copyObject(plan_list);
argtype_list = listCopy(argtype_list);
/* Now we can add entry to hash table */
entry = (PreparedStatement *) hash_search(prepared_queries,
key,
HASH_ENTER,
&found);
/* Shouldn't get a failure, nor a duplicate entry */
if (!entry || found)
elog(ERROR, "Unable to store prepared statement \"%s\"!",
stmt_name);
/* Fill in the hash table entry with copied data */
entry->query_string = qstring;
entry->commandTag = commandTag;
entry->query_list = query_list;
entry->plan_list = plan_list;
entry->argtype_list = argtype_list;
entry->context = entrycxt;
MemoryContextSwitchTo(oldcxt);
}
/*
* Lookup an existing query in the hash table. If the query does not
* actually exist, throw elog(ERROR) or return NULL per second parameter.
*/
PreparedStatement *
FetchPreparedStatement(const char *stmt_name, bool throwError)
{
char key[NAMEDATALEN];
PreparedStatement *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)
{
/*
* 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));
entry = (PreparedStatement *) hash_search(prepared_queries,
key,
HASH_FIND,
NULL);
}
else
entry = NULL;
if (!entry && throwError)
elog(ERROR, "Prepared statement with name \"%s\" does not exist",
stmt_name);
return entry;
}
/*
* Look up a prepared statement given the name (giving error if not found).
* If found, return the list of argument type OIDs.
*/
List *
FetchPreparedStatementParams(const char *stmt_name)
{
PreparedStatement *entry;
entry = FetchPreparedStatement(stmt_name, true);
return entry->argtype_list;
}
/*
* Implements the 'DEALLOCATE' utility statement: deletes the
* specified plan from storage.
*/
void
DeallocateQuery(DeallocateStmt *stmt)
{
DropPreparedStatement(stmt->name, true);
}
/*
* Internal version of DEALLOCATE
*
* If showError is false, dropping a nonexistent statement is a no-op.
*/
void
DropPreparedStatement(const char *stmt_name, bool showError)
{
PreparedStatement *entry;
/* Find the query's hash table entry; raise error if wanted */
entry = FetchPreparedStatement(stmt_name, showError);
if (entry)
{
/* Drop any open portals that depend on this prepared statement */
Assert(MemoryContextIsValid(entry->context));
DropDependentPortals(entry->context);
/* Flush the context holding the subsidiary data */
MemoryContextDelete(entry->context);
/* Now we can remove the hash table entry */
hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
}
}
/*
* Implements the 'EXPLAIN EXECUTE' utility statement.
*/
void
ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
{
ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt;
PreparedStatement *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 = FetchPreparedStatement(execstmt->name, true);
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;
if (execstmt->into)
{
if (query->commandType != CMD_SELECT)
elog(ERROR, "INTO clause specified for non-SELECT query");
/* Copy the query so we can modify it */
query = copyObject(query);
query->into = execstmt->into;
}
/* Create a QueryDesc requesting no output */
qdesc = CreateQueryDesc(query, plan, None_Receiver, NULL,
paramLI, stmt->analyze);
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);
}