diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 79221044c21..a923e260edb 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -1,5 +1,5 @@
@@ -71,6 +71,7 @@ Complete list of usable sgml source files in this directory.
+
@@ -93,6 +94,7 @@ Complete list of usable sgml source files in this directory.
+
@@ -102,6 +104,7 @@ Complete list of usable sgml source files in this directory.
+
diff --git a/doc/src/sgml/ref/deallocate.sgml b/doc/src/sgml/ref/deallocate.sgml
new file mode 100644
index 00000000000..29925adc4aa
--- /dev/null
+++ b/doc/src/sgml/ref/deallocate.sgml
@@ -0,0 +1,137 @@
+
+
+
+
+ DEALLOCATE
+ SQL - Language Statements
+
+
+
+ DEALLOCATE
+
+
+ remove a prepared query
+
+
+
+
+ 2002-08-12
+
+
+ DEALLOCATE [ PREPARE ] plan_name
+
+
+
+
+ 2002-08-12
+
+
+ Inputs
+
+
+
+
+
+ PREPARE
+
+
+ This keyword is ignored.
+
+
+
+
+ plan_name
+
+
+ The name of the prepared query to remove.
+
+
+
+
+
+
+
+
+ 2002-08-12
+
+
+ Outputs
+
+
+
+
+
+
+ DEALLOCATE
+
+
+
+ The prepared query was removed successfully.
+
+
+
+
+
+
+
+
+
+
+ 2002-08-12
+
+
+ Description
+
+
+
+ DEALLOCATE is used to remove a previously
+ prepared query. If you do not explicitly
+ DEALLOCATE a prepared query, it is removed when
+ the session ends.
+
+
+
+ For more information on prepared queries, see .
+
+
+
+
+
+ Compatibility
+
+
+
+
+ 2002-08-12
+
+
+ SQL92
+
+
+ SQL92 includes a DEALLOCATE statement, but it is
+ only for use in embedded SQL clients.
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/execute.sgml b/doc/src/sgml/ref/execute.sgml
new file mode 100644
index 00000000000..67035572e74
--- /dev/null
+++ b/doc/src/sgml/ref/execute.sgml
@@ -0,0 +1,132 @@
+
+
+
+
+ EXECUTE
+ SQL - Language Statements
+
+
+
+ EXECUTE
+
+
+ execute a prepared query
+
+
+
+
+ 2002-08-12
+
+
+ EXECUTE plan_name [ (parameter [, ...] ) ]
+
+
+
+
+ 2002-08-12
+
+
+ Inputs
+
+
+
+
+
+ plan_name
+
+
+ The name of the prepared query to execute.
+
+
+
+
+ parameter
+
+
+ The actual value of a parameter to the prepared query.
+ This must be an expression yielding a value of a type
+ compatible with
+ the data-type specified for this parameter position in the
+ PREPARE statement that created the prepared
+ query.
+
+
+
+
+
+
+
+
+
+
+ 2002-08-12
+
+
+ Description
+
+
+
+ EXECUTE is used to execute a previously prepared
+ query. Since prepared queries only exist for the duration of a
+ session, the prepared query must have been created by a
+ PREPARE statement executed earlier in the
+ current session.
+
+
+
+ If the PREPARE statement that created the query
+ specified some parameters, a compatible set of parameters must be
+ passed to the EXECUTE statement, or else an
+ error is raised. Note that (unlike functions) prepared queries are
+ not overloaded based on the type or number of their parameters: the
+ name of a prepared query must be unique within a database session.
+
+
+
+ For more information on the creation and usage of prepared queries,
+ see .
+
+
+
+
+
+ Compatibility
+
+
+
+
+ 2002-08-12
+
+
+ SQL92
+
+
+ SQL92 includes an EXECUTE statement, but it is
+ only for use in embedded SQL clients. The
+ EXECUTE statement implemented by
+ PostgreSQL also uses a somewhat
+ different syntax.
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/prepare.sgml b/doc/src/sgml/ref/prepare.sgml
new file mode 100644
index 00000000000..d9fa86414bf
--- /dev/null
+++ b/doc/src/sgml/ref/prepare.sgml
@@ -0,0 +1,209 @@
+
+
+
+
+ PREPARE
+ SQL - Language Statements
+
+
+
+ PREPARE
+
+
+ create a prepared query
+
+
+
+
+ 2002-08-12
+
+
+ PREPARE plan_name [ (datatype [, ...] ) ] AS query
+
+
+
+
+ 2002-08-12
+
+
+ Inputs
+
+
+
+
+
+ plan_name
+
+
+ An arbitrary name given to this particular prepared query. It
+ must be unique within a single session, and is used to execute
+ or remove a previously prepared query.
+
+
+
+
+ datatype
+
+
+ The data-type of a parameter to the prepared query.
+ To refer to the parameters in the prepared query itself,
+ use $1, $2, etc.
+
+
+
+
+
+
+
+
+
+ 2002-08-12
+
+
+ Outputs
+
+
+
+
+
+
+ PREPARE
+
+
+
+ The query has been prepared successfully.
+
+
+
+
+
+
+
+
+
+
+
+ 2002-08-12
+
+
+ Description
+
+
+ PREPARE creates a prepared query. A prepared
+ query is a server-side object that can be used to optimize
+ performance. When the PREPARE statement is
+ executed, the specified query is parsed, rewritten, and
+ planned. When a subsequent EXECUTE statement is
+ issued, the prepared query need only be executed. Thus, the
+ parsing, rewriting, and planning stages are only performed once,
+ instead of every time the query is executed.
+
+
+
+ Prepared queries can take parameters: values that are
+ substituted into the query when it is executed. To specify the
+ parameters to a prepared query, include a list of data-types with
+ the PREPARE statement. In the query itself, you
+ can refer to the parameters by position using
+ $1, $2, etc. When executing
+ the query, specify the actual values for these parameters in the
+ EXECUTE statement -- refer to
+ for more information.
+
+
+
+ Prepared queries are stored locally (in the current backend), and
+ only exist for the duration of the current database session. When
+ the client exits, the prepared query is forgotten, and so it must be
+ re-created before being used again. This also means that a single
+ prepared query cannot be used by multiple simultaneous database
+ clients; however, each client can create their own prepared query
+ to use.
+
+
+
+ Prepared queries have the largest performance advantage when a
+ single backend is being used to execute a large number of similar
+ queries. The performance difference will be particularly
+ significant if the queries are complex to plan or rewrite. For
+ example, if the query involves a join of many tables or requires
+ the application of several rules. If the query is relatively simple
+ to plan and rewrite but relatively expensive to execute, the
+ performance advantage of prepared queries will be less noticeable.
+
+
+
+
+ 2002-08-12
+
+
+ Notes
+
+
+
+ In some situations, the query plan produced by
+ PostgreSQL for a prepared query may be
+ inferior to the plan produced if the query were submitted and
+ executed normally. This is because when the query is planned (and
+ the optimizer attempts to determine the optimal query plan), the
+ actual values of any parameters specified in the query are
+ unavailable. PostgreSQL collects
+ statistics on the distribution of data in the table, and can use
+ 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.
+
+
+
+ For more information on query planning and the statistics
+ collected by PostgreSQL for query
+ optimization purposes, see the documentation.
+
+
+
+
+
+
+ Compatibility
+
+
+
+
+ 2002-08-12
+
+
+ SQL92
+
+
+ SQL92 includes a PREPARE statement, but it is
+ only for use in embedded SQL clients. The
+ PREPARE statement implemented by
+ PostgreSQL also uses a somewhat
+ different syntax.
+
+
+
+
+
+
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index effe495f1da..22a3c07a3e6 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -1,5 +1,5 @@
@@ -80,6 +80,7 @@ PostgreSQL Reference Manual
&createType;
&createUser;
&createView;
+ &deallocate;
&declare;
&delete;
&dropAggregate;
@@ -98,10 +99,11 @@ PostgreSQL Reference Manual
&dropSequence;
&dropTable;
&dropTrigger;
- &dropType
+ &dropType;
&dropUser;
&dropView;
&end;
+ &execute;
&explain;
&fetch;
&grant;
@@ -111,6 +113,7 @@ PostgreSQL Reference Manual
&lock;
&move;
¬ify;
+ &prepare;
&reindex;
&reset;
&revoke;
diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml
index 4be58685120..e457504ebef 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.
-->
name)
+ elog(ERROR, "No statement name given");
+
+ if (stmt->query->commandType == CMD_UTILITY)
+ elog(ERROR, "Utility statements cannot be prepared");
+
+ /* Rewrite the query. The result could be 0, 1, or many queries. */
+ query_list = QueryRewrite(stmt->query);
+
+ foreach(query_list_item, query_list)
+ {
+ Query *query = (Query *) lfirst(query_list_item);
+ Plan *plan;
+
+ /* We can't generate plans for utility statements. */
+ if (query->commandType == CMD_UTILITY)
+ plan = NULL;
+ else
+ {
+ /* Call the query planner to generate a plan. */
+ plan = planner(query);
+ }
+
+ plan_list = lappend(plan_list, plan);
+ }
+
+ StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids);
+}
+
+/*
+ * Implements the 'EXECUTE' utility statement.
+ */
+void
+ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
+{
+ QueryHashEntry *entry;
+ List *l,
+ *query_list,
+ *plan_list;
+ ParamListInfo paramLI = NULL;
+
+ /* Look it up in the hash table */
+ entry = FetchQuery(stmt->name);
+
+ /* Make working copies the executor can safely scribble on */
+ query_list = (List *) copyObject(entry->query_list);
+ plan_list = (List *) copyObject(entry->plan_list);
+
+ Assert(length(query_list) == length(plan_list));
+
+ /* Evaluate parameters, if any */
+ if (entry->argtype_list != NIL)
+ {
+ int nargs = length(entry->argtype_list);
+ int i = 0;
+ ExprContext *econtext = MakeExprContext(NULL, CurrentMemoryContext);
+
+ /* Parser should have caught this error, but check */
+ if (nargs != length(stmt->params))
+ elog(ERROR, "ExecuteQuery: wrong number of arguments");
+
+ paramLI = (ParamListInfo) palloc((nargs + 1) * sizeof(ParamListInfoData));
+ MemSet(paramLI, 0, (nargs + 1) * sizeof(ParamListInfoData));
+
+ foreach (l, stmt->params)
+ {
+ Node *n = lfirst(l);
+ bool isNull;
+
+ paramLI[i].value = ExecEvalExprSwitchContext(n,
+ econtext,
+ &isNull,
+ NULL);
+ paramLI[i].kind = PARAM_NUM;
+ paramLI[i].id = i + 1;
+ paramLI[i].isnull = isNull;
+
+ i++;
+ }
+ paramLI[i].kind = PARAM_INVALID;
+ }
+
+ /* Execute each query */
+ foreach(l, query_list)
+ {
+ Query *query = lfirst(l);
+ 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)
+ ProcessUtility(query->utilityStmt, outputDest, NULL);
+ else
+ {
+ QueryDesc *qdesc;
+ EState *state;
+
+ if (Show_executor_stats)
+ ResetUsage();
+
+ qdesc = CreateQueryDesc(query, plan, outputDest, NULL);
+ state = CreateExecutorState();
+
+ state->es_param_list_info = paramLI;
+
+ if (stmt->into)
+ {
+ if (qdesc->operation != CMD_SELECT)
+ elog(ERROR, "INTO clause specified for non-SELECT query");
+
+ query->into = stmt->into;
+ qdesc->dest = None;
+ }
+
+ RunQuery(qdesc, state);
+
+ if (Show_executor_stats)
+ ShowUsage("EXECUTOR STATISTICS");
+ }
+
+ /*
+ * If we're processing multiple queries, we need to increment
+ * the command counter between them. For the last query,
+ * there's no need to do this, it's done automatically.
+ */
+ if (! is_last_query)
+ CommandCounterIncrement();
+ }
+
+ /* No need to pfree memory, MemoryContext will be reset */
+}
+
+/*
+ * Initialize query hash table upon first use.
+ */
+static void
+InitQueryHashTable(void)
+{
+ HASHCTL hash_ctl;
+
+ MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+
+ hash_ctl.keysize = HASH_KEY_LEN;
+ hash_ctl.entrysize = sizeof(QueryHashEntry);
+
+ 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.
+ */
+static void
+StoreQuery(const char *stmt_name, List *query_list, List *plan_list,
+ List *argtype_list)
+{
+ QueryHashEntry *entry;
+ MemoryContext oldcxt,
+ entrycxt;
+ char key[HASH_KEY_LEN];
+ bool found;
+
+ /* Initialize the hash table, if necessary */
+ if (!prepared_queries)
+ InitQueryHashTable();
+
+ /* Check for pre-existing entry of same name */
+ /* See notes in FetchQuery */
+ 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);
+
+ /* Okay. Make a permanent memory context for the hashtable entry */
+ entrycxt = AllocSetContextCreate(TopMemoryContext,
+ stmt_name,
+ 1024,
+ 1024,
+ ALLOCSET_DEFAULT_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.
+ */
+ 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 = (QueryHashEntry *) hash_search(prepared_queries,
+ key,
+ HASH_ENTER,
+ &found);
+
+ /* Shouldn't get a failure, nor 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_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.
+ */
+static QueryHashEntry *
+FetchQuery(const char *plan_name)
+{
+ 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",
+ plan_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, plan_name, sizeof(key));
+
+ entry = (QueryHashEntry *) hash_search(prepared_queries,
+ key,
+ HASH_FIND,
+ NULL);
+
+ if (!entry)
+ elog(ERROR, "Prepared statement with name \"%s\" does not exist",
+ plan_name);
+
+ return entry;
+}
+
+/*
+ * Given a plan name, look up the stored plan (giving error if not found).
+ * If found, return the list of argument type OIDs.
+ */
+List *
+FetchQueryParams(const char *plan_name)
+{
+ QueryHashEntry *entry;
+
+ entry = FetchQuery(plan_name);
+
+ return entry->argtype_list;
+}
+
+/*
+ * Actually execute a prepared query.
+ */
+static void
+RunQuery(QueryDesc *qdesc, EState *state)
+{
+ TupleDesc tupdesc;
+
+ tupdesc = ExecutorStart(qdesc, state);
+
+ ExecutorRun(qdesc, state, state->es_direction, 0L);
+
+ ExecutorEnd(qdesc, state);
+}
+
+/*
+ * 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);
+
+ /* Flush the context holding the subsidiary data */
+ if (MemoryContextIsValid(entry->context))
+ MemoryContextDelete(entry->context);
+
+ /* Now we can remove the hash table entry */
+ hash_search(prepared_queries, key, HASH_REMOVE, NULL);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 4de9ba5b8de..b3920e38b2d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.206 2002/08/26 17:53:57 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.207 2002/08/27 04:55:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2647,6 +2647,41 @@ _copyDropCastStmt(DropCastStmt *from)
return newnode;
}
+static PrepareStmt *
+_copyPrepareStmt(PrepareStmt *from)
+{
+ PrepareStmt *newnode = makeNode(PrepareStmt);
+
+ newnode->name = pstrdup(from->name);
+ Node_Copy(from, newnode, argtypes);
+ newnode->argtype_oids = listCopy(from->argtype_oids);
+ Node_Copy(from, newnode, query);
+
+ return newnode;
+}
+
+static ExecuteStmt *
+_copyExecuteStmt(ExecuteStmt *from)
+{
+ ExecuteStmt *newnode = makeNode(ExecuteStmt);
+
+ newnode->name = pstrdup(from->name);
+ Node_Copy(from, newnode, into);
+ Node_Copy(from, newnode, params);
+
+ return newnode;
+}
+
+static DeallocateStmt *
+_copyDeallocateStmt(DeallocateStmt *from)
+{
+ DeallocateStmt *newnode = makeNode(DeallocateStmt);
+
+ newnode->name = pstrdup(from->name);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
@@ -3079,6 +3114,15 @@ copyObject(void *from)
case T_DropCastStmt:
retval = _copyDropCastStmt(from);
break;
+ case T_PrepareStmt:
+ retval = _copyPrepareStmt(from);
+ break;
+ case T_ExecuteStmt:
+ retval = _copyExecuteStmt(from);
+ break;
+ case T_DeallocateStmt:
+ retval = _copyDeallocateStmt(from);
+ break;
case T_A_Expr:
retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c96389f1e8b..408a94ff1b3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -20,7 +20,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.154 2002/08/26 17:53:57 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.155 2002/08/27 04:55:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1490,6 +1490,43 @@ _equalDropCastStmt(DropCastStmt *a, DropCastStmt *b)
return true;
}
+static bool
+_equalPrepareStmt(PrepareStmt *a, PrepareStmt *b)
+{
+ if (!equalstr(a->name, b->name))
+ return false;
+ if (!equal(a->argtypes, b->argtypes))
+ return false;
+ if (!equali(a->argtype_oids, b->argtype_oids))
+ return false;
+ if (!equal(a->query, b->query))
+ return false;
+
+ return true;
+}
+
+static bool
+_equalExecuteStmt(ExecuteStmt *a, ExecuteStmt *b)
+{
+ if (!equalstr(a->name, b->name))
+ return false;
+ if (!equal(a->into, b->into))
+ return false;
+ if (!equal(a->params, b->params))
+ return false;
+
+ return true;
+}
+
+static bool
+_equalDeallocateStmt(DeallocateStmt *a, DeallocateStmt *b)
+{
+ if (!equalstr(a->name, b->name))
+ return false;
+
+ return true;
+}
+
static bool
_equalAExpr(A_Expr *a, A_Expr *b)
{
@@ -2249,6 +2286,15 @@ equal(void *a, void *b)
case T_DropCastStmt:
retval = _equalDropCastStmt(a, b);
break;
+ case T_PrepareStmt:
+ retval = _equalPrepareStmt(a, b);
+ break;
+ case T_ExecuteStmt:
+ retval = _equalExecuteStmt(a, b);
+ break;
+ case T_DeallocateStmt:
+ retval = _equalDeallocateStmt(a, b);
+ break;
case T_A_Expr:
retval = _equalAExpr(a, b);
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 01e6d867ce4..ffa371d9260 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.243 2002/08/27 03:56:34 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.244 2002/08/27 04:55:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,7 +20,10 @@
#include "catalog/namespace.h"
#include "catalog/pg_index.h"
#include "catalog/pg_type.h"
+#include "commands/prepare.h"
#include "nodes/makefuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planmain.h"
#include "parser/analyze.h"
#include "parser/gramparse.h"
#include "parser/parsetree.h"
@@ -94,6 +97,8 @@ static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
+static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt);
+static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt);
static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
List **extras_before, List **extras_after);
static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
@@ -277,6 +282,14 @@ transformStmt(ParseState *pstate, Node *parseTree,
extras_before, extras_after);
break;
+ case T_PrepareStmt:
+ result = transformPrepareStmt(pstate, (PrepareStmt *) parseTree);
+ break;
+
+ case T_ExecuteStmt:
+ result = transformExecuteStmt(pstate, (ExecuteStmt *) parseTree);
+ break;
+
/*
* Optimizable statements
*/
@@ -2454,6 +2467,131 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
return qry;
}
+static Query *
+transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt)
+{
+ Query *result = makeNode(Query);
+ List *extras_before = NIL,
+ *extras_after = NIL;
+ List *argtype_oids = NIL; /* argtype OIDs in a list */
+ Oid *argtoids = NULL; /* as an array for parser_param_set */
+ int nargs;
+
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ /* Transform list of TypeNames to list (and array) of type OIDs */
+ nargs = length(stmt->argtypes);
+
+ if (nargs)
+ {
+ List *l;
+ int i = 0;
+
+ argtoids = (Oid *) palloc(nargs * sizeof(Oid));
+
+ foreach (l, stmt->argtypes)
+ {
+ TypeName *tn = lfirst(l);
+ Oid toid = typenameTypeId(tn);
+
+ argtype_oids = lappendi(argtype_oids, toid);
+ argtoids[i++] = toid;
+ }
+ }
+
+ stmt->argtype_oids = argtype_oids;
+
+ /*
+ * We need to adjust the parameters expected by the
+ * rest of the system, so that $1, ... $n are parsed properly.
+ *
+ * This is somewhat of a hack; however, the main parser interface
+ * only allows parameters to be specified when working with a
+ * raw query string, which is not helpful here.
+ */
+ parser_param_set(argtoids, nargs);
+
+ stmt->query = transformStmt(pstate, (Node *) stmt->query,
+ &extras_before, &extras_after);
+
+ /* Shouldn't get any extras, since grammar only allows OptimizableStmt */
+ if (extras_before || extras_after)
+ elog(ERROR, "transformPrepareStmt: internal error");
+
+ /* Remove links to our local parameters */
+ parser_param_set(NULL, 0);
+
+ return result;
+}
+
+static Query *
+transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
+{
+ Query *result = makeNode(Query);
+ List *paramtypes;
+
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ paramtypes = FetchQueryParams(stmt->name);
+
+ if (stmt->params || paramtypes)
+ {
+ int nparams = length(stmt->params);
+ int nexpected = length(paramtypes);
+ List *l;
+ int i = 1;
+
+ if (nparams != nexpected)
+ elog(ERROR, "Wrong number of parameters, expected %d but got %d",
+ nexpected, nparams);
+
+ foreach (l, stmt->params)
+ {
+ Node *expr = lfirst(l);
+ Oid expected_type_id,
+ given_type_id;
+
+ expr = transformExpr(pstate, expr);
+
+ /* Cannot contain subselects or aggregates */
+ if (contain_subplans(expr))
+ elog(ERROR, "Cannot use subselects in EXECUTE parameters");
+ if (contain_agg_clause(expr))
+ elog(ERROR, "Cannot use aggregates in EXECUTE parameters");
+
+ given_type_id = exprType(expr);
+ expected_type_id = (Oid) lfirsti(paramtypes);
+
+ if (given_type_id != expected_type_id)
+ {
+ expr = CoerceTargetExpr(pstate,
+ expr,
+ given_type_id,
+ expected_type_id,
+ -1,
+ false);
+
+ if (!expr)
+ elog(ERROR, "Parameter $%d of type %s cannot be coerced into the expected type %s"
+ "\n\tYou will need to rewrite or cast the expression",
+ i,
+ format_type_be(given_type_id),
+ format_type_be(expected_type_id));
+ }
+
+ fix_opids(expr);
+ lfirst(l) = expr;
+
+ paramtypes = lnext(paramtypes);
+ i++;
+ }
+ }
+
+ return result;
+}
+
/* exported so planner can check again after rewriting, query pullup, etc */
void
CheckSelectForUpdate(Query *qry)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 80aa372f9c5..0f512c7d31b 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.360 2002/08/19 15:08:47 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.361 2002/08/27 04:55:08 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -68,8 +68,6 @@
extern List *parsetree; /* final parse result is delivered here */
static bool QueryIsRule = FALSE;
-static Oid *param_type_info;
-static int pfunc_num_args;
/*
* If you need access to certain yacc-generated variables and find that
@@ -149,7 +147,8 @@ static void doNegateFloat(Value *v);
SelectStmt, TransactionStmt, TruncateStmt,
UnlistenStmt, UpdateStmt, VacuumStmt,
VariableResetStmt, VariableSetStmt, VariableShowStmt,
- ViewStmt, CheckPointStmt, CreateConversionStmt
+ ViewStmt, CheckPointStmt, CreateConversionStmt,
+ DeallocateStmt, PrepareStmt, ExecuteStmt
%type select_no_parens, select_with_parens, select_clause,
simple_select
@@ -218,7 +217,8 @@ static void doNegateFloat(Value *v);
group_clause, TriggerFuncArgs, select_limit,
opt_select_limit, opclass_item_list, trans_options,
TableFuncElementList, OptTableFuncElementList,
- convert_args
+ convert_args, prep_type_clause, prep_type_list,
+ execute_param_clause, execute_param_list
%type into_clause, OptTempTableName
@@ -335,7 +335,7 @@ static void doNegateFloat(Value *v);
CREATEUSER, CROSS, CURRENT_DATE, CURRENT_TIME,
CURRENT_TIMESTAMP, CURRENT_USER, CURSOR, CYCLE,
- DATABASE, DAY_P, DEC, DECIMAL, DECLARE, DEFAULT,
+ DATABASE, DAY_P, DEALLOCATE, DEC, DECIMAL, DECLARE, DEFAULT,
DEFERRABLE, DEFERRED, DEFINER, DELETE_P, DELIMITER, DELIMITERS,
DESC, DISTINCT, DO, DOMAIN_P, DOUBLE, DROP,
@@ -371,7 +371,7 @@ static void doNegateFloat(Value *v);
ORDER, OUT_P, OUTER_P, OVERLAPS, OVERLAY, OWNER,
PARTIAL, PASSWORD, PATH_P, PENDANT, PLACING, POSITION,
- PRECISION, PRIMARY, PRIOR, PRIVILEGES, PROCEDURE,
+ PRECISION, PREPARE, PRIMARY, PRIOR, PRIVILEGES, PROCEDURE,
PROCEDURAL,
READ, REAL, RECHECK, REFERENCES, REINDEX, RELATIVE, RENAME, REPLACE,
@@ -490,6 +490,7 @@ stmt :
| CreateTrigStmt
| CreateUserStmt
| ClusterStmt
+ | DeallocateStmt
| DefineStmt
| DropStmt
| TruncateStmt
@@ -502,6 +503,7 @@ stmt :
| DropTrigStmt
| DropRuleStmt
| DropUserStmt
+ | ExecuteStmt
| ExplainStmt
| FetchStmt
| GrantStmt
@@ -510,6 +512,7 @@ stmt :
| UnlistenStmt
| LockStmt
| NotifyStmt
+ | PrepareStmt
| ReindexStmt
| RemoveAggrStmt
| RemoveOperStmt
@@ -3875,6 +3878,77 @@ ExplainStmt:
}
;
+/*****************************************************************************
+ *
+ * QUERY:
+ * PREPARE [(args, ...)] AS
+ *
+ *****************************************************************************/
+
+PrepareStmt: PREPARE name prep_type_clause AS OptimizableStmt
+ {
+ PrepareStmt *n = makeNode(PrepareStmt);
+ n->name = $2;
+ n->argtypes = $3;
+ n->query = (Query *) $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+prep_type_clause: '(' prep_type_list ')' { $$ = $2; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
+prep_type_list: Typename { $$ = makeList1($1); }
+ | prep_type_list ',' Typename
+ { $$ = lappend($1, $3); }
+ ;
+
+/*****************************************************************************
+ *
+ * QUERY:
+ * EXECUTE [(params, ...)] [INTO ...]
+ *
+ *****************************************************************************/
+
+ExecuteStmt: EXECUTE name execute_param_clause into_clause
+ {
+ ExecuteStmt *n = makeNode(ExecuteStmt);
+ n->name = $2;
+ n->params = $3;
+ n->into = $4;
+ $$ = (Node *) n;
+ }
+ ;
+
+execute_param_clause: '(' execute_param_list ')' { $$ = $2; }
+ | /* EMPTY */ { $$ = NIL; }
+ ;
+
+execute_param_list: a_expr { $$ = makeList1($1); }
+ | execute_param_list ',' a_expr { $$ = lappend($1, $3); }
+ ;
+
+/*****************************************************************************
+ *
+ * QUERY:
+ * DEALLOCATE [PREPARE]
+ *
+ *****************************************************************************/
+
+DeallocateStmt: DEALLOCATE name
+ {
+ DeallocateStmt *n = makeNode(DeallocateStmt);
+ n->name = $2;
+ $$ = (Node *) n;
+ }
+ | DEALLOCATE PREPARE name
+ {
+ DeallocateStmt *n = makeNode(DeallocateStmt);
+ n->name = $3;
+ $$ = (Node *) n;
+ }
+ ;
/*****************************************************************************
* *
@@ -6947,6 +7021,7 @@ unreserved_keyword:
| CYCLE
| DATABASE
| DAY_P
+ | DEALLOCATE
| DECLARE
| DEFERRED
| DEFINER
@@ -7019,6 +7094,7 @@ unreserved_keyword:
| PATH_P
| PENDANT
| PRECISION
+ | PREPARE
| PRIOR
| PRIVILEGES
| PROCEDURAL
@@ -7589,26 +7665,9 @@ SystemTypeName(char *name)
* Initialize to parse one query string
*/
void
-parser_init(Oid *typev, int nargs)
+parser_init(void)
{
QueryIsRule = FALSE;
- /*
- * Keep enough information around to fill out the type of param nodes
- * used in postquel functions
- */
- param_type_info = typev;
- pfunc_num_args = nargs;
-}
-
-/* param_type()
- * Fetch a parameter type previously passed to parser_init
- */
-Oid
-param_type(int t)
-{
- if ((t > pfunc_num_args) || (t <= 0))
- return InvalidOid;
- return param_type_info[t - 1];
}
/* exprIsNullConstant()
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index 2bb6772054a..9a3064ad661 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.125 2002/08/18 09:36:25 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.126 2002/08/27 04:55:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -96,6 +96,7 @@ static const ScanKeyword ScanKeywords[] = {
{"cycle", CYCLE},
{"database", DATABASE},
{"day", DAY_P},
+ {"deallocate", DEALLOCATE},
{"dec", DEC},
{"decimal", DECIMAL},
{"declare", DECLARE},
@@ -229,6 +230,7 @@ static const ScanKeyword ScanKeywords[] = {
{"placing", PLACING},
{"position", POSITION},
{"precision", PRECISION},
+ {"prepare", PREPARE},
{"primary", PRIMARY},
{"prior", PRIOR},
{"privileges", PRIVILEGES},
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index f4cd24e0c4f..8c129cb9161 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -14,7 +14,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.53 2002/06/20 20:29:33 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/parser/parser.c,v 1.54 2002/08/27 04:55:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -30,6 +30,9 @@
List *parsetree; /* result of parsing is left here */
+static Oid *param_type_info; /* state for param_type() */
+static int param_count;
+
static int lookahead_token; /* one-token lookahead */
static bool have_lookahead; /* lookahead_token set? */
@@ -50,8 +53,9 @@ parser(StringInfo str, Oid *typev, int nargs)
have_lookahead = false;
scanner_init(str);
- parser_init(typev, nargs);
+ parser_init();
parse_expr_init();
+ parser_param_set(typev, nargs);
yyresult = yyparse();
@@ -65,6 +69,35 @@ parser(StringInfo str, Oid *typev, int nargs)
}
+/*
+ * Save information needed to fill out the type of Param references ($n)
+ *
+ * This is used for SQL functions, PREPARE statements, etc. It's split
+ * out from parser() setup because PREPARE needs to change the info after
+ * the grammar runs and before parse analysis is done on the preparable
+ * query.
+ */
+void
+parser_param_set(Oid *typev, int nargs)
+{
+ param_type_info = typev;
+ param_count = nargs;
+}
+
+/*
+ * param_type()
+ *
+ * Fetch a parameter type previously passed to parser_param_set
+ */
+Oid
+param_type(int t)
+{
+ if (t > param_count || t <= 0)
+ return InvalidOid;
+ return param_type_info[t - 1];
+}
+
+
/*
* Intermediate filter between parser and base lexer (base_yylex in scan.l).
*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 605f44ba70b..28576b8fad6 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.283 2002/08/17 15:12:07 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.284 2002/08/27 04:55:11 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@@ -1666,7 +1666,7 @@ PostgresMain(int argc, char *argv[], const char *username)
if (!IsUnderPostmaster)
{
puts("\nPOSTGRES backend interactive interface ");
- puts("$Revision: 1.283 $ $Date: 2002/08/17 15:12:07 $\n");
+ puts("$Revision: 1.284 $ $Date: 2002/08/27 04:55:11 $\n");
}
/*
@@ -2406,6 +2406,18 @@ CreateCommandTag(Node *parsetree)
tag = "DROP OPERATOR CLASS";
break;
+ case T_PrepareStmt:
+ tag = "PREPARE";
+ break;
+
+ case T_ExecuteStmt:
+ tag = "EXECUTE";
+ break;
+
+ case T_DeallocateStmt:
+ tag = "DEALLOCATE";
+ break;
+
default:
elog(LOG, "CreateCommandTag: unknown parse node type %d",
nodeTag(parsetree));
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1ae0a89fd6b..b16adef54db 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.172 2002/08/17 13:04:15 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.173 2002/08/27 04:55:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -30,6 +30,7 @@
#include "commands/explain.h"
#include "commands/lockcmds.h"
#include "commands/portalcmds.h"
+#include "commands/prepare.h"
#include "commands/proclang.h"
#include "commands/schemacmds.h"
#include "commands/sequence.h"
@@ -379,6 +380,18 @@ ProcessUtility(Node *parsetree,
}
break;
+ case T_PrepareStmt:
+ PrepareQuery((PrepareStmt *) parsetree);
+ break;
+
+ case T_ExecuteStmt:
+ ExecuteQuery((ExecuteStmt *) parsetree, dest);
+ break;
+
+ case T_DeallocateStmt:
+ DeallocateQuery((DeallocateStmt *) parsetree);
+ break;
+
/*
* schema
*/
@@ -541,11 +554,7 @@ ProcessUtility(Node *parsetree,
case T_GrantStmt:
- {
- GrantStmt *stmt = (GrantStmt *) parsetree;
-
- ExecuteGrantStmt(stmt);
- }
+ ExecuteGrantStmt((GrantStmt *) parsetree);
break;
/*
@@ -841,9 +850,7 @@ ProcessUtility(Node *parsetree,
break;
case T_CreateConversionStmt:
- {
- CreateConversionCommand((CreateConversionStmt *) parsetree);
- }
+ CreateConversionCommand((CreateConversionStmt *) parsetree);
break;
case T_CreateCastStmt:
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
new file mode 100644
index 00000000000..6af60feeae6
--- /dev/null
+++ b/src/include/commands/prepare.h
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * prepare.h
+ * PREPARE, EXECUTE and DEALLOCATE command prototypes
+ *
+ *
+ * Copyright (c) 2002, PostgreSQL Global Development Group
+ *
+ * $Id: prepare.h,v 1.1 2002/08/27 04:55:11 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PREPARE_H
+#define PREPARE_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);
+
+#endif /* PREPARE_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index f3437ce4cbf..3f5f6d74499 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: nodes.h,v 1.116 2002/08/19 15:08:47 tgl Exp $
+ * $Id: nodes.h,v 1.117 2002/08/27 04:55:11 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -200,6 +200,9 @@ typedef enum NodeTag
T_DropCastStmt,
T_CreateOpClassStmt,
T_RemoveOpClassStmt,
+ T_PrepareStmt,
+ T_ExecuteStmt,
+ T_DeallocateStmt,
T_A_Expr = 700,
T_ColumnRef,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ecf59f30c10..25ec8a3542b 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: parsenodes.h,v 1.201 2002/08/19 15:08:47 tgl Exp $
+ * $Id: parsenodes.h,v 1.202 2002/08/27 04:55:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1620,4 +1620,42 @@ typedef struct DropCastStmt
} DropCastStmt;
+/* ----------------------
+ * PREPARE Statement
+ * ----------------------
+ */
+typedef struct PrepareStmt
+{
+ NodeTag type;
+ char *name; /* Name of plan, arbitrary */
+ List *argtypes; /* Types of parameters (TypeNames) */
+ List *argtype_oids; /* Types of parameters (OIDs) */
+ Query *query; /* The query itself */
+} PrepareStmt;
+
+
+/* ----------------------
+ * EXECUTE Statement
+ * ----------------------
+ */
+
+typedef struct ExecuteStmt
+{
+ NodeTag type;
+ char *name; /* The name of the plan to execute */
+ RangeVar *into; /* Optional table to store results in */
+ List *params; /* Values to assign to parameters */
+} ExecuteStmt;
+
+
+/* ----------------------
+ * DEALLOCATE Statement
+ * ----------------------
+ */
+typedef struct DeallocateStmt
+{
+ NodeTag type;
+ char *name; /* The name of the plan to remove */
+} DeallocateStmt;
+
#endif /* PARSENODES_H */
diff --git a/src/include/parser/gramparse.h b/src/include/parser/gramparse.h
index 0bd00cb1b0c..6af3bafbfb3 100644
--- a/src/include/parser/gramparse.h
+++ b/src/include/parser/gramparse.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: gramparse.h,v 1.23 2002/06/20 20:29:51 momjian Exp $
+ * $Id: gramparse.h,v 1.24 2002/08/27 04:55:12 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -19,6 +19,8 @@
#include "nodes/parsenodes.h"
/* from parser.c */
+extern void parser_param_set(Oid *typev, int nargs);
+extern Oid param_type(int t);
extern int yylex(void);
/* from scan.l */
@@ -28,8 +30,7 @@ extern int base_yylex(void);
extern void yyerror(const char *message);
/* from gram.y */
-extern void parser_init(Oid *typev, int nargs);
-extern Oid param_type(int t);
+extern void parser_init(void);
extern int yyparse(void);
extern List *SystemFuncName(char *name);
extern TypeName *SystemTypeName(char *name);
diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out
new file mode 100644
index 00000000000..166be86eefd
--- /dev/null
+++ b/src/test/regress/expected/prepare.out
@@ -0,0 +1,107 @@
+-- Regression tests for prepareable statements
+PREPARE q1 AS SELECT 1;
+EXECUTE q1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- should fail
+PREPARE q1 AS SELECT 2;
+ERROR: Prepared statement with name "q1" already exists
+-- should succeed
+DEALLOCATE q1;
+PREPARE q1 AS SELECT 2;
+EXECUTE q1;
+ ?column?
+----------
+ 2
+(1 row)
+
+-- sql92 syntax
+DEALLOCATE PREPARE q1;
+-- parameterized queries
+PREPARE q2(text) AS
+ SELECT datname, datistemplate, datallowconn
+ FROM pg_database WHERE datname = $1;
+EXECUTE q2('regression');
+ datname | datistemplate | datallowconn
+------------+---------------+--------------
+ regression | f | t
+(1 row)
+
+PREPARE q3(text, int, float, boolean, oid, smallint) AS
+ SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR
+ ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int);
+EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint);
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 4502 | 412 | 0 | 2 | 2 | 2 | 2 | 502 | 502 | 4502 | 4502 | 4 | 5 | ERAAAA | WPAAAA | AAAAxx
+ 102 | 612 | 0 | 2 | 2 | 2 | 2 | 102 | 102 | 102 | 102 | 4 | 5 | YDAAAA | OXAAAA | AAAAxx
+ 7602 | 1040 | 0 | 2 | 2 | 2 | 2 | 602 | 1602 | 2602 | 7602 | 4 | 5 | KGAAAA | AOBAAA | AAAAxx
+ 902 | 1104 | 0 | 2 | 2 | 2 | 2 | 902 | 902 | 902 | 902 | 4 | 5 | SIAAAA | MQBAAA | AAAAxx
+ 4902 | 1600 | 0 | 2 | 2 | 2 | 2 | 902 | 902 | 4902 | 4902 | 4 | 5 | OGAAAA | OJCAAA | AAAAxx
+ 9502 | 1812 | 0 | 2 | 2 | 2 | 2 | 502 | 1502 | 4502 | 9502 | 4 | 5 | MBAAAA | SRCAAA | AAAAxx
+ 4702 | 2520 | 0 | 2 | 2 | 2 | 2 | 702 | 702 | 4702 | 4702 | 4 | 5 | WYAAAA | YSDAAA | AAAAxx
+ 1002 | 2580 | 0 | 2 | 2 | 2 | 2 | 2 | 1002 | 1002 | 1002 | 4 | 5 | OMAAAA | GVDAAA | AAAAxx
+ 2 | 2716 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 4 | 5 | CAAAAA | MAEAAA | AAAAxx
+ 802 | 2908 | 0 | 2 | 2 | 2 | 2 | 802 | 802 | 802 | 802 | 4 | 5 | WEAAAA | WHEAAA | AAAAxx
+ 6402 | 3808 | 0 | 2 | 2 | 2 | 2 | 402 | 402 | 1402 | 6402 | 4 | 5 | GMAAAA | MQFAAA | AAAAxx
+ 8602 | 5440 | 0 | 2 | 2 | 2 | 2 | 602 | 602 | 3602 | 8602 | 4 | 5 | WSAAAA | GBIAAA | AAAAxx
+ 8402 | 5708 | 0 | 2 | 2 | 2 | 2 | 402 | 402 | 3402 | 8402 | 4 | 5 | ELAAAA | OLIAAA | AAAAxx
+ 2102 | 6184 | 0 | 2 | 2 | 2 | 2 | 102 | 102 | 2102 | 2102 | 4 | 5 | WCAAAA | WDJAAA | AAAAxx
+ 4202 | 6628 | 0 | 2 | 2 | 2 | 2 | 202 | 202 | 4202 | 4202 | 4 | 5 | QFAAAA | YUJAAA | AAAAxx
+ 2902 | 6816 | 0 | 2 | 2 | 2 | 2 | 902 | 902 | 2902 | 2902 | 4 | 5 | QHAAAA | ECKAAA | AAAAxx
+ 2302 | 7112 | 0 | 2 | 2 | 2 | 2 | 302 | 302 | 2302 | 2302 | 4 | 5 | OKAAAA | ONKAAA | AAAAxx
+ 3202 | 7128 | 0 | 2 | 2 | 2 | 2 | 202 | 1202 | 3202 | 3202 | 4 | 5 | ETAAAA | EOKAAA | AAAAxx
+ 7802 | 7508 | 0 | 2 | 2 | 2 | 2 | 802 | 1802 | 2802 | 7802 | 4 | 5 | COAAAA | UCLAAA | AAAAxx
+ 4102 | 7676 | 0 | 2 | 2 | 2 | 2 | 102 | 102 | 4102 | 4102 | 4 | 5 | UBAAAA | GJLAAA | AAAAxx
+ 8302 | 7800 | 0 | 2 | 2 | 2 | 2 | 302 | 302 | 3302 | 8302 | 4 | 5 | IHAAAA | AOLAAA | AAAAxx
+ 1702 | 7940 | 0 | 2 | 2 | 2 | 2 | 702 | 1702 | 1702 | 1702 | 4 | 5 | MNAAAA | KTLAAA | AAAAxx
+ 2202 | 8028 | 0 | 2 | 2 | 2 | 2 | 202 | 202 | 2202 | 2202 | 4 | 5 | SGAAAA | UWLAAA | AAAAxx
+ 1602 | 8148 | 0 | 2 | 2 | 2 | 2 | 602 | 1602 | 1602 | 1602 | 4 | 5 | QJAAAA | KBMAAA | AAAAxx
+ 5602 | 8796 | 0 | 2 | 2 | 2 | 2 | 602 | 1602 | 602 | 5602 | 4 | 5 | MHAAAA | IANAAA | AAAAxx
+ 6002 | 8932 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 1002 | 6002 | 4 | 5 | WWAAAA | OFNAAA | AAAAxx
+ 3902 | 9224 | 0 | 2 | 2 | 2 | 2 | 902 | 1902 | 3902 | 3902 | 4 | 5 | CUAAAA | UQNAAA | AAAAxx
+ 9602 | 9972 | 0 | 2 | 2 | 2 | 2 | 602 | 1602 | 4602 | 9602 | 4 | 5 | IFAAAA | OTOAAA | AAAAxx
+ 8002 | 9980 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 3002 | 8002 | 4 | 5 | UVAAAA | WTOAAA | AAAAxx
+(29 rows)
+
+-- too few params
+EXECUTE q3('bool');
+ERROR: Wrong number of parameters, expected 6 but got 1
+-- too many params
+EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 500::oid, 4::bigint, true);
+ERROR: Wrong number of parameters, expected 6 but got 7
+-- wrong param types
+EXECUTE q3(5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea');
+ERROR: Parameter $2 of type double precision cannot be coerced into the expected type integer
+ You will need to rewrite or cast the expression
+-- invalid type
+PREPARE q4(nonexistenttype) AS SELECT $1;
+ERROR: Type "nonexistenttype" does not exist
+-- execute into
+PREPARE q5(int, text) AS
+ SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2;
+EXECUTE q5(200, 'DTAAAA') INTO TEMPORARY q5_prep_results;
+SELECT * FROM q5_prep_results;
+ unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4
+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------
+ 2525 | 64 | 1 | 1 | 5 | 5 | 25 | 525 | 525 | 2525 | 2525 | 50 | 51 | DTAAAA | MCAAAA | AAAAxx
+ 7257 | 1895 | 1 | 1 | 7 | 17 | 57 | 257 | 1257 | 2257 | 7257 | 114 | 115 | DTAAAA | XUCAAA | VVVVxx
+ 9961 | 2058 | 1 | 1 | 1 | 1 | 61 | 961 | 1961 | 4961 | 9961 | 122 | 123 | DTAAAA | EBDAAA | OOOOxx
+ 3877 | 4060 | 1 | 1 | 7 | 17 | 77 | 877 | 1877 | 3877 | 3877 | 154 | 155 | DTAAAA | EAGAAA | AAAAxx
+ 4553 | 4113 | 1 | 1 | 3 | 13 | 53 | 553 | 553 | 4553 | 4553 | 106 | 107 | DTAAAA | FCGAAA | HHHHxx
+ 7933 | 4514 | 1 | 1 | 3 | 13 | 33 | 933 | 1933 | 2933 | 7933 | 66 | 67 | DTAAAA | QRGAAA | OOOOxx
+ 6581 | 4686 | 1 | 1 | 1 | 1 | 81 | 581 | 581 | 1581 | 6581 | 162 | 163 | DTAAAA | GYGAAA | OOOOxx
+ 8609 | 5918 | 1 | 1 | 9 | 9 | 9 | 609 | 609 | 3609 | 8609 | 18 | 19 | DTAAAA | QTIAAA | OOOOxx
+ 5229 | 6407 | 1 | 1 | 9 | 9 | 29 | 229 | 1229 | 229 | 5229 | 58 | 59 | DTAAAA | LMJAAA | VVVVxx
+ 1173 | 6699 | 1 | 1 | 3 | 13 | 73 | 173 | 1173 | 1173 | 1173 | 146 | 147 | DTAAAA | RXJAAA | VVVVxx
+ 3201 | 7309 | 1 | 1 | 1 | 1 | 1 | 201 | 1201 | 3201 | 3201 | 2 | 3 | DTAAAA | DVKAAA | HHHHxx
+ 1849 | 8143 | 1 | 1 | 9 | 9 | 49 | 849 | 1849 | 1849 | 1849 | 98 | 99 | DTAAAA | FBMAAA | VVVVxx
+ 9285 | 8469 | 1 | 1 | 5 | 5 | 85 | 285 | 1285 | 4285 | 9285 | 170 | 171 | DTAAAA | TNMAAA | HHHHxx
+ 497 | 9092 | 1 | 1 | 7 | 17 | 97 | 497 | 497 | 497 | 497 | 194 | 195 | DTAAAA | SLNAAA | AAAAxx
+ 200 | 9441 | 0 | 0 | 0 | 0 | 0 | 200 | 200 | 200 | 200 | 0 | 1 | SHAAAA | DZNAAA | HHHHxx
+ 5905 | 9537 | 1 | 1 | 5 | 5 | 5 | 905 | 1905 | 905 | 5905 | 10 | 11 | DTAAAA | VCOAAA | HHHHxx
+(16 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e82d9421679..94dedb2b491 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -74,4 +74,4 @@ test: select_views alter_table portals_p2 rules foreign_key cluster
# The sixth group of parallel test
# ----------
# "plpgsql" cannot run concurrently with "rules"
-test: limit plpgsql temp domain rangefuncs copy2 conversion without_oid truncate
+test: limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 553df2dfe7d..cf00eeb961c 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -1,4 +1,4 @@
-# $Header: /cvsroot/pgsql/src/test/regress/serial_schedule,v 1.16 2002/08/22 04:51:06 momjian Exp $
+# $Header: /cvsroot/pgsql/src/test/regress/serial_schedule,v 1.17 2002/08/27 04:55:12 tgl Exp $
# This should probably be in an order similar to parallel_schedule.
test: boolean
test: char
@@ -86,6 +86,7 @@ test: copy2
test: temp
test: domain
test: rangefuncs
+test: prepare
test: without_oid
test: conversion
test: truncate
diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql
new file mode 100644
index 00000000000..ee8df42e0e1
--- /dev/null
+++ b/src/test/regress/sql/prepare.sql
@@ -0,0 +1,45 @@
+-- Regression tests for prepareable statements
+
+PREPARE q1 AS SELECT 1;
+EXECUTE q1;
+
+-- should fail
+PREPARE q1 AS SELECT 2;
+
+-- should succeed
+DEALLOCATE q1;
+PREPARE q1 AS SELECT 2;
+EXECUTE q1;
+
+-- sql92 syntax
+DEALLOCATE PREPARE q1;
+
+-- parameterized queries
+PREPARE q2(text) AS
+ SELECT datname, datistemplate, datallowconn
+ FROM pg_database WHERE datname = $1;
+EXECUTE q2('regression');
+
+PREPARE q3(text, int, float, boolean, oid, smallint) AS
+ SELECT * FROM tenk1 WHERE string4 = $1 AND (four = $2 OR
+ ten = $3::bigint OR true = $4 OR oid = $5 OR odd = $6::int);
+
+EXECUTE q3('AAAAxx', 5::smallint, 10.5::float, false, 500::oid, 4::bigint);
+
+-- too few params
+EXECUTE q3('bool');
+
+-- too many params
+EXECUTE q3('bytea', 5::smallint, 10.5::float, false, 500::oid, 4::bigint, true);
+
+-- wrong param types
+EXECUTE q3(5::smallint, 10.5::float, false, 500::oid, 4::bigint, 'bytea');
+
+-- invalid type
+PREPARE q4(nonexistenttype) AS SELECT $1;
+
+-- execute into
+PREPARE q5(int, text) AS
+ SELECT * FROM tenk1 WHERE unique1 = $1 OR stringu1 = $2;
+EXECUTE q5(200, 'DTAAAA') INTO TEMPORARY q5_prep_results;
+SELECT * FROM q5_prep_results;