diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 79221044c2130f6fdad7479b5752e45197d54738..a923e260edb4f6e5ffde6a3e1d6e45f254dc459f 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.47 2002/08/27 03:38:27 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.48 2002/08/27 04:55:07 tgl Exp $
 PostgreSQL documentation
 Complete list of usable sgml source files in this directory.
 -->
@@ -71,6 +71,7 @@ Complete list of usable sgml source files in this directory.
 <!entity createType         system "create_type.sgml">
 <!entity createUser         system "create_user.sgml">
 <!entity createView         system "create_view.sgml">
+<!entity deallocate         system "deallocate.sgml">
 <!entity declare            system "declare.sgml">
 <!entity delete             system "delete.sgml">
 <!entity dropAggregate      system "drop_aggregate.sgml">
@@ -93,6 +94,7 @@ Complete list of usable sgml source files in this directory.
 <!entity dropUser           system "drop_user.sgml">
 <!entity dropView           system "drop_view.sgml">
 <!entity end                system "end.sgml">
+<!entity execute            system "execute.sgml">
 <!entity explain            system "explain.sgml">
 <!entity fetch              system "fetch.sgml">
 <!entity grant              system "grant.sgml">
@@ -102,6 +104,7 @@ Complete list of usable sgml source files in this directory.
 <!entity lock               system "lock.sgml">
 <!entity move               system "move.sgml">
 <!entity notify             system "notify.sgml">
+<!entity prepare            system "prepare.sgml">
 <!entity reindex            system "reindex.sgml">
 <!entity reset              system "reset.sgml">
 <!entity revoke             system "revoke.sgml">
diff --git a/doc/src/sgml/ref/deallocate.sgml b/doc/src/sgml/ref/deallocate.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..29925adc4aa0d8348884bf466063ccab19321141
--- /dev/null
+++ b/doc/src/sgml/ref/deallocate.sgml
@@ -0,0 +1,137 @@
+<!--
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/deallocate.sgml,v 1.1 2002/08/27 04:55:07 tgl Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DEALLOCATE">
+ <refmeta>
+  <refentrytitle id="sql-deallocate-title">DEALLOCATE</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+  <refname>
+   DEALLOCATE
+  </refname>
+  <refpurpose>
+   remove a prepared query
+  </refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+  <refsynopsisdivinfo>
+   <date>2002-08-12</date>
+  </refsynopsisdivinfo>
+  <synopsis>
+   DEALLOCATE [ PREPARE ] <replaceable class="PARAMETER">plan_name</replaceable>
+  </synopsis>
+
+  <refsect2 id="R2-SQL-DEALLOCATE-1">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    Inputs
+   </title>
+
+   <para>
+    <variablelist>
+	 <varlistentry>
+	  <term>PREPARE</term>
+	  <listitem>
+	   <para>
+		This keyword is ignored.
+	   </para>
+	  </listitem>
+	  </varlistentry>
+     <varlistentry>
+      <term><replaceable class="PARAMETER">plan_name</replaceable></term>
+      <listitem>
+       <para>
+		The name of the prepared query to remove.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+  </refsect2>
+  <refsect2 id="R2-SQL-DEALLOCATE-2">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    Outputs
+   </title>
+   <para>
+
+    <variablelist>
+     <varlistentry>
+      <term><computeroutput>
+		<returnvalue>DEALLOCATE</returnvalue>
+       </computeroutput></term>
+      <listitem>
+       <para>
+		The prepared query was removed successfully.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+  </refsect2>
+ </refsynopsisdiv>
+
+ <refsect1 id="R1-SQL-DEALLOCATE-1">
+  <refsect1info>
+   <date>2002-08-12</date>
+  </refsect1info>
+  <title>
+   Description
+  </title>
+
+  <para>
+   <command>DEALLOCATE</command> is used to remove a previously
+   prepared query. If you do not explicitly
+   <command>DEALLOCATE</command> a prepared query, it is removed when
+   the session ends.
+  </para>
+
+  <para>
+   For more information on prepared queries, see <xref
+   linkend="sql-prepare" endterm="sql-prepare-title">.
+  </para>
+ </refsect1>
+
+ <refsect1 id="R1-SQL-DEALLOCATE-2">
+  <title>
+   Compatibility
+  </title>
+
+  <refsect2 id="R2-SQL-DEALLOCATE-3">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    SQL92
+   </title>
+   <para>
+	SQL92 includes a <command>DEALLOCATE</command> statement, but it is
+    only for use in embedded SQL clients.
+   </para>
+  </refsect2>
+ </refsect1>
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-omittag:nil
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:1
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:"../reference.ced"
+sgml-exposed-tags:nil
+sgml-local-catalogs:"/usr/lib/sgml/catalog"
+sgml-local-ecat-files:nil
+End:
+-->
diff --git a/doc/src/sgml/ref/execute.sgml b/doc/src/sgml/ref/execute.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..67035572e742373acd55f86af4108f12567b60e8
--- /dev/null
+++ b/doc/src/sgml/ref/execute.sgml
@@ -0,0 +1,132 @@
+<!--
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/execute.sgml,v 1.1 2002/08/27 04:55:07 tgl Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-EXECUTE">
+ <refmeta>
+  <refentrytitle id="sql-execute-title">EXECUTE</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+  <refname>
+   EXECUTE
+  </refname>
+  <refpurpose>
+   execute a prepared query
+  </refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+  <refsynopsisdivinfo>
+   <date>2002-08-12</date>
+  </refsynopsisdivinfo>
+  <synopsis>
+   EXECUTE <replaceable class="PARAMETER">plan_name</replaceable> [ (<replaceable class="PARAMETER">parameter</replaceable> [, ...] ) ]
+  </synopsis>
+
+  <refsect2 id="R2-SQL-EXECUTE-1">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    Inputs
+   </title>
+
+   <para>
+    <variablelist>
+     <varlistentry>
+      <term><replaceable class="PARAMETER">plan_name</replaceable></term>
+      <listitem>
+       <para>
+		The name of the prepared query to execute.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term><replaceable class="PARAMETER">parameter</replaceable></term>
+      <listitem>
+       <para>
+		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
+		<command>PREPARE</command> statement that created the prepared
+		query.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+  </refsect2>
+ </refsynopsisdiv>
+
+ <refsect1 id="R1-SQL-EXECUTE-1">
+  <refsect1info>
+   <date>2002-08-12</date>
+  </refsect1info>
+  <title>
+   Description
+  </title>
+
+  <para>
+   <command>EXECUTE</command> 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
+   <command>PREPARE</command> statement executed earlier in the
+   current session.
+  </para>
+
+  <para>
+   If the <command>PREPARE</command> statement that created the query
+   specified some parameters, a compatible set of parameters must be
+   passed to the <command>EXECUTE</command> 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.
+  </para>
+
+  <para>
+   For more information on the creation and usage of prepared queries,
+   see <xref linkend="sql-prepare" endterm="sql-prepare-title">.
+  </para>
+ </refsect1>
+
+ <refsect1 id="R1-SQL-EXECUTE-2">
+  <title>
+   Compatibility
+  </title>
+
+  <refsect2 id="R2-SQL-EXECUTE-2">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    SQL92
+   </title>
+   <para>
+	SQL92 includes an <command>EXECUTE</command> statement, but it is
+    only for use in embedded SQL clients. The
+    <command>EXECUTE</command> statement implemented by
+    <productname>PostgreSQL</productname> also uses a somewhat
+    different syntax.
+   </para>
+  </refsect2>
+ </refsect1>
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-omittag:nil
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:1
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:"../reference.ced"
+sgml-exposed-tags:nil
+sgml-local-catalogs:"/usr/lib/sgml/catalog"
+sgml-local-ecat-files:nil
+End:
+-->
diff --git a/doc/src/sgml/ref/prepare.sgml b/doc/src/sgml/ref/prepare.sgml
new file mode 100644
index 0000000000000000000000000000000000000000..d9fa86414bf1aabfd07a060db437a0e042b0570f
--- /dev/null
+++ b/doc/src/sgml/ref/prepare.sgml
@@ -0,0 +1,209 @@
+<!--
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/prepare.sgml,v 1.1 2002/08/27 04:55:07 tgl Exp $
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-PREPARE">
+ <refmeta>
+  <refentrytitle id="sql-prepare-title">PREPARE</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+  <refname>
+   PREPARE
+  </refname>
+  <refpurpose>
+   create a prepared query
+  </refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+  <refsynopsisdivinfo>
+   <date>2002-08-12</date>
+  </refsynopsisdivinfo>
+  <synopsis>
+   PREPARE <replaceable class="PARAMETER">plan_name</replaceable> [ (<replaceable class="PARAMETER">datatype</replaceable> [, ...] ) ] AS <replaceable class="PARAMETER">query</replaceable>
+  </synopsis>
+
+  <refsect2 id="R2-SQL-PREPARE-1">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    Inputs
+   </title>
+
+   <para>
+    <variablelist>
+     <varlistentry>
+      <term><replaceable class="PARAMETER">plan_name</replaceable></term>
+      <listitem>
+       <para>
+		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.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term><replaceable class="PARAMETER">datatype</replaceable></term>
+      <listitem>
+       <para>
+		The data-type of a parameter to the prepared query.
+		To refer to the parameters in the prepared query itself,
+		use <literal>$1</literal>, <literal>$2</literal>, etc.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+  </refsect2>
+
+  <refsect2 id="R2-SQL-PREPARE-2">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    Outputs
+   </title>
+   <para>
+
+    <variablelist>
+     <varlistentry>
+      <term><computeroutput>
+		<returnvalue>PREPARE</returnvalue>
+       </computeroutput></term>
+      <listitem>
+       <para>
+		The query has been prepared successfully.
+       </para>
+      </listitem>
+     </varlistentry>
+
+    </variablelist>
+   </para>
+  </refsect2>
+ </refsynopsisdiv>
+
+ <refsect1 id="R1-SQL-PREPARE-1">
+  <refsect1info>
+   <date>2002-08-12</date>
+  </refsect1info>
+  <title>
+   Description
+  </title>
+  <para>
+   <command>PREPARE</command> creates a prepared query. A prepared
+   query is a server-side object that can be used to optimize
+   performance. When the <command>PREPARE</command> statement is
+   executed, the specified query is parsed, rewritten, and
+   planned. When a subsequent <command>EXECUTE</command> 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.
+  </para>
+
+  <para>
+   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 <command>PREPARE</command> statement. In the query itself, you
+   can refer to the parameters by position using
+   <literal>$1</literal>, <literal>$2</literal>, etc. When executing
+   the query, specify the actual values for these parameters in the
+   <command>EXECUTE</command> statement -- refer to <xref
+   linkend="sql-execute" endterm="sql-execute-title">
+   for more information.
+  </para>
+
+  <para>
+   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.
+  </para>
+
+  <para>
+   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.
+  </para>
+
+  <refsect2 id="R2-SQL-PREPARE-3">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    Notes
+   </title>
+
+   <para>
+	In some situations, the query plan produced by
+	<productname>PostgreSQL</productname> 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. <productname>PostgreSQL</productname> 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.
+   </para>
+
+   <para>
+	For more information on query planning and the statistics
+	collected by <productname>PostgreSQL</productname> for query
+	optimization purposes, see the <xref linkend="sql-analyze"
+	endterm="sql-analyze-title"> documentation.
+   </para>
+  </refsect2>
+ </refsect1>
+
+ <refsect1 id="R1-SQL-PREPARE-3">
+  <title>
+   Compatibility
+  </title>
+
+  <refsect2 id="R2-SQL-PREPARE-4">
+   <refsect2info>
+    <date>2002-08-12</date>
+   </refsect2info>
+   <title>
+    SQL92
+   </title>
+   <para>
+	SQL92 includes a <command>PREPARE</command> statement, but it is
+    only for use in embedded SQL clients. The
+    <command>PREPARE</command> statement implemented by
+    <productname>PostgreSQL</productname> also uses a somewhat
+    different syntax.
+   </para>
+  </refsect2>
+ </refsect1>
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode: sgml
+sgml-omittag:nil
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:1
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:"../reference.ced"
+sgml-exposed-tags:nil
+sgml-local-catalogs:"/usr/lib/sgml/catalog"
+sgml-local-ecat-files:nil
+End:
+-->
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index effe495f1da38c004d76a07c1931926f766793da..22a3c07a3e69eefde9264d92ed50935d50b17eed 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -1,5 +1,5 @@
 <!-- reference.sgml
-$Header: /cvsroot/pgsql/doc/src/sgml/reference.sgml,v 1.36 2002/08/27 03:38:27 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/reference.sgml,v 1.37 2002/08/27 04:55:07 tgl Exp $
 
 PostgreSQL Reference Manual
 -->
@@ -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;
    &notify;
+   &prepare;
    &reindex;
    &reset;
    &revoke;
diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml
index 4be5868512091d62d9e0483937882c82ee68b99f..e457504ebefcf4a5352d3dac79ac6525cf7fa45a 100644
--- a/doc/src/sgml/release.sgml
+++ b/doc/src/sgml/release.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.151 2002/08/25 14:34:24 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.152 2002/08/27 04:55:07 tgl Exp $
 -->
 
 <appendix id="release">
@@ -24,6 +24,7 @@ CDATA means the content is "SGML-free", so you can write without
 worries about funny characters.
 -->
 <literallayout><![CDATA[
+PREPARE statement allows caching query plans for interactive statements
 Type OPAQUE is now deprecated in favor of pseudo-types cstring, trigger, etc
 Files larger than 2 GB are now supported (if supported by the operating system)
 SERIAL no longer implies UNIQUE; specify explicitly if index is wanted
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 92961049d775b57c8da00f9d9ecebfb51e004834..db91bf1d0e074b78a22dd91487d46771903bd367 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for backend/commands
 #
 # IDENTIFICATION
-#    $Header: /cvsroot/pgsql/src/backend/commands/Makefile,v 1.30 2002/07/29 22:14:10 tgl Exp $
+#    $Header: /cvsroot/pgsql/src/backend/commands/Makefile,v 1.31 2002/08/27 04:55:07 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -16,7 +16,7 @@ OBJS = aggregatecmds.o analyze.o async.o cluster.o comment.o  \
 	conversioncmds.o copy.o \
 	dbcommands.o define.o explain.o functioncmds.o \
 	indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
-	portalcmds.o proclang.o \
+	portalcmds.o prepare.o proclang.o \
 	schemacmds.o sequence.o tablecmds.o trigger.o typecmds.o user.o \
 	vacuum.o vacuumlazy.o variable.o view.o
 
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
new file mode 100644
index 0000000000000000000000000000000000000000..9dbe21461622909b170fc9910aa4a1828124c305
--- /dev/null
+++ b/src/backend/commands/prepare.c
@@ -0,0 +1,407 @@
+/*-------------------------------------------------------------------------
+ *
+ * prepare.c
+ *	  Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE
+ *
+ * Copyright (c) 2002, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.1 2002/08/27 04:55:07 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.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"
+
+
+#define HASH_KEY_LEN NAMEDATALEN
+
+/* All the data we need to remember about a stored query */
+typedef struct
+{
+	/* dynahash.c requires key to be first field */
+	char		key[HASH_KEY_LEN];
+	List	   *query_list;		/* list of queries */
+	List	   *plan_list;		/* list of plans */
+	List	   *argtype_list;	/* list of parameter type OIDs */
+	MemoryContext	 context;	/* context containing this query */
+} QueryHashEntry;
+
+/*
+ * 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 ("plan names"); the entries are QueryHashEntry structs.
+ */
+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);
+static QueryHashEntry *FetchQuery(const char *plan_name);
+static void RunQuery(QueryDesc *qdesc, EState *state);
+
+
+/*
+ * Implements the 'PREPARE' utility statement.
+ */
+void
+PrepareQuery(PrepareStmt *stmt)
+{
+	List *plan_list = NIL;
+	List *query_list,
+		 *query_list_item;
+
+	if (!stmt->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 4de9ba5b8dec55d6b7e9f9cbf060c1786afebb38..b3920e38b2dfbb44e73731441de537d25009ddf6 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 c96389f1e8b28e67324350d9d63eb8d53b7352b6..408a94ff1b3922f272c3e4e15ade3d2eb4a54fcb 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 01e6d867ce477135880443d1e9efa4ffaa025628..ffa371d92605d38ffacbda3b215f2075fa6be1c5 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 80aa372f9c572f21faf6685010506a10ffddfa54..0f512c7d31b32df62755d13d20ee3273ae1c8738 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 <node>	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 <range>	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 <plan_name> [(args, ...)] AS <query>
+ *
+ *****************************************************************************/
+
+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 <plan_name> [(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] <plan_name>
+ *
+ *****************************************************************************/
+
+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 2bb6772054ad26d29305bd17d9c244806fe35edb..9a3064ad661596bea9b1ce1287679dc4117f6dab 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 f4cd24e0c4ff7835a94bbc8b847a0493915bac40..8c129cb9161bd316a1e325c3f8c9b7db69d71239 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 605f44ba70b9f7ac9c5a50f598219f407f4341e8..28576b8fad610864929606cf0d96f9bda3f96777 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 1ae0a89fd6be669ff456774de8abcee36d92a5c2..b16adef54dbce3e68463c23891c3f929c4b8053f 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 0000000000000000000000000000000000000000..6af60feeae6d3d7176cc39fae8edd6c67e3d6dac
--- /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 f3437ce4cbf66adf1df958b2e9799aac0217292f..3f5f6d744998d40c868a95d727132d14c8e159b2 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 ecf59f30c10536dd695c737e50bffd08b52d8c7b..25ec8a3542b9dd0710417cb05cb64bcd6b4150d0 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 0bd00cb1b0cac29891e1fc7c99491459d1eaf29e..6af3bafbfb3d796379e3def4c7a9d69098f2f856 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 0000000000000000000000000000000000000000..166be86eefd4a40562bbb494f7e645866ff5889e
--- /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 e82d9421679cfb5d1e7045702790853ea91d89e4..94dedb2b4917d6589b5a0be7a1e196ec774b66fb 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 553df2dfe7d6939ae97789eb4dc043e8f85ec5c7..cf00eeb961cd27f3a2b7c829af3d4c6c127e04b3 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 0000000000000000000000000000000000000000..ee8df42e0e1b74cbd71f895285e72715335afa98
--- /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;