From 16503e6fa4a13051debe09698b6db9ce0d509af8 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 5 May 2003 00:44:56 +0000
Subject: [PATCH] Extended query protocol: parse, bind, execute, describe FE/BE
 messages. Only lightly tested as yet, since libpq doesn't know anything about
 'em.

---
 doc/src/sgml/protocol.sgml           |  94 ++-
 src/backend/access/common/printtup.c |  84 ++-
 src/backend/commands/portalcmds.c    |  24 +-
 src/backend/commands/prepare.c       | 204 +++---
 src/backend/parser/analyze.c         |  63 +-
 src/backend/tcop/dest.c              |  39 +-
 src/backend/tcop/fastpath.c          |  18 +-
 src/backend/tcop/postgres.c          | 959 ++++++++++++++++++++++++---
 src/backend/utils/mmgr/portalmem.c   |  15 +-
 src/include/access/printtup.h        |   6 +-
 src/include/commands/portalcmds.h    |   4 +-
 src/include/commands/prepare.h       |  42 +-
 src/include/libpq/pqcomm.h           |   4 +-
 src/include/tcop/dest.h              |  10 +-
 src/include/tcop/tcopprot.h          |   7 +-
 src/interfaces/libpq/fe-connect.c    |  13 +-
 src/interfaces/libpq/libpq-int.h     |   4 +-
 17 files changed, 1329 insertions(+), 261 deletions(-)

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 4a01e310b30..48ba74edb54 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1,4 +1,4 @@
-<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.33 2003/04/28 05:17:31 tgl Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/protocol.sgml,v 1.34 2003/05/05 00:44:55 tgl Exp $ -->
 
 <chapter id="protocol">
  <title>Frontend/Backend Protocol</title>
@@ -595,7 +595,11 @@
    <para>
     If successfully created, a named prepared-statement object lasts till
     the end of the current session, unless explicitly destroyed.  An unnamed
-    prepared statement lasts only until the next Parse message is issued.
+    prepared statement lasts only until the next Parse statement specifying
+    the unnamed statement as destination is issued.  (Note that a simple
+    Query message also destroys the unnamed statement.)  Named prepared
+    statements must be explicitly closed before they can be redefined by
+    a Parse message, but this is not required for the unnamed statement.
     Named prepared statements can also be created and accessed at the SQL
     command level, using <command>PREPARE</> and <command>EXECUTE</>.
    </para>
@@ -611,10 +615,13 @@
    </para>
 
    <para>
-    If successfully created, a named portal object lasts till
-    the end of the current transaction, unless explicitly destroyed.  An
-    unnamed portal is destroyed at the end of the transaction, or as soon
-    as the next Parse or Bind message is executed.
+    If successfully created, a named portal object lasts till the end of the
+    current transaction, unless explicitly destroyed.  An unnamed portal is
+    destroyed at the end of the transaction, or as soon as the next Bind
+    statement specifying the unnamed portal as destination is issued.  (Note
+    that a simple Query message also destroys the unnamed portal.)  Named
+    portals must be explicitly closed before they can be redefined by a Bind
+    message, but this is not required for the unnamed portal.
     Named portals can also be created and accessed at the SQL
     command level, using <command>DECLARE CURSOR</> and <command>FETCH</>.
    </para>
@@ -691,17 +698,19 @@
     The Describe message (statement variant) specifies the name of an existing
     prepared statement (or an empty string for the unnamed prepared
     statement).  The response is a ParameterDescription message describing the
-    parameters needed by the statement (if any), followed by a RowDescription
-    message describing the rows that will be returned when the statement is
-    eventually executed (or NoData if there is no SELECT-type query in the
-    prepared statement).  ErrorResponse is issued if there is no such prepared
-    statement.  This message may be useful if the client library is
-    uncertain about the parameters needed by a prepared statement.
+    parameters needed by the statement.  ErrorResponse is issued if there is
+    no such prepared statement.  This message may be useful if the client
+    library is uncertain about the parameters needed by a prepared statement.
    </para>
 
    <para>
     The Close message closes an existing prepared statement or portal
-    and releases resources.
+    and releases resources.  It is not an error to issue Close against
+    a nonexistent statement or portal name.  The response is normally
+    CloseComplete, but could be ErrorResponse if some difficulty is
+    encountered while releasing resources.  Note that closing a prepared
+    statement implicitly closes any open portals that were constructed
+    from that statement.
    </para>
 
    <para>
@@ -709,19 +718,20 @@
     but forces the backend to deliver any data pending in its output
     buffers.  A Flush must be sent after any extended-query command except
     Sync, if the frontend wishes to examine the results of that command before
-    issuing more commands.  Without Flush, returning data will be combined
-    into the minimum possible number of packets to minimize network overhead.
+    issuing more commands.  Without Flush, messages returned by the backend
+    will be combined into the minimum possible number of packets to minimize
+    network overhead.
    </para>
 
    <note>
     <para>
      The simple Query message is approximately equivalent to the series Parse,
-     Bind, portal Describe, Execute, Sync, using the unnamed prepared statement
-     and portal objects and no parameters.  One difference is that it
-     will accept multiple SQL statements in the query string, automatically
+     Bind, portal Describe, Execute, Close, Sync, using the unnamed prepared
+     statement and portal objects and no parameters.  One difference is that
+     it will accept multiple SQL statements in the query string, automatically
      performing the bind/describe/execute sequence for each one in succession.
-     Another is that it will not return ParseComplete, BindComplete, or
-     NoData messages.
+     Another difference is that it will not return ParseComplete, BindComplete,
+     CloseComplete, or NoData messages.
     </para>
    </note>
   </sect2>
@@ -1917,6 +1927,41 @@ Close (F)
 </VarListEntry>
 
 
+<VarListEntry>
+<Term>
+CloseComplete (B)
+</Term>
+<ListItem>
+<Para>
+
+<VariableList>
+<VarListEntry>
+<Term>
+        Byte1('3')
+</Term>
+<ListItem>
+<Para>
+                Identifies the message as a Close-complete indicator.
+</Para>
+</ListItem>
+</VarListEntry>
+<VarListEntry>
+<Term>
+        Int32(4)
+</Term>
+<ListItem>
+<Para>
+                Length of message contents in bytes, including self.
+</Para>
+</ListItem>
+</VarListEntry>
+</VariableList>
+
+</Para>
+</ListItem>
+</VarListEntry>
+
+
 <VarListEntry>
 <Term>
 CommandComplete (B)
@@ -3875,6 +3920,15 @@ The ReadyForQuery ('<literal>Z</>') message includes a transaction status
 indicator.
 </para>
 
+<para>
+There is a new <quote>extended query</> sub-protocol, which adds the frontend
+message types Parse, Bind, Execute, Describe, Close, Flush, and Sync, and the
+backend message types ParseComplete, BindComplete, PortalSuspended,
+ParameterDescription, NoData, and CloseComplete.  Existing clients do not
+have to concern themselves with this sub-protocol, but making use of it
+may allow improvements in performance or functionality.
+</para>
+
 <para>
 COPY data is now encapsulated into CopyData and CopyDone messages.  There
 is a well-defined way to recover from errors during COPY.  The special
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 160b703223f..584233c1873 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.67 2003/04/26 20:22:58 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/common/printtup.c,v 1.68 2003/05/05 00:44:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -48,6 +48,7 @@ typedef struct
 typedef struct
 {
 	DestReceiver pub;			/* publicly-known function pointers */
+	bool		sendDescrip;	/* send RowDescription at startup? */
 	TupleDesc	attrinfo;		/* The attr info we are set up for */
 	int			nattrs;
 	PrinttupAttrInfo *myinfo;	/* Cached info about each attr */
@@ -58,7 +59,7 @@ typedef struct
  * ----------------
  */
 DestReceiver *
-printtup_create_DR(bool isBinary)
+printtup_create_DR(bool isBinary, bool sendDescrip)
 {
 	DR_printtup *self = (DR_printtup *) palloc(sizeof(DR_printtup));
 
@@ -66,6 +67,8 @@ printtup_create_DR(bool isBinary)
 	self->pub.setup = printtup_setup;
 	self->pub.cleanup = printtup_cleanup;
 
+	self->sendDescrip = sendDescrip;
+
 	self->attrinfo = NULL;
 	self->nattrs = 0;
 	self->myinfo = NULL;
@@ -77,6 +80,8 @@ static void
 printtup_setup(DestReceiver *self, int operation,
 			   const char *portalName, TupleDesc typeinfo)
 {
+	DR_printtup *myState = (DR_printtup *) self;
+
 	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
 	{
 		/*
@@ -91,41 +96,11 @@ printtup_setup(DestReceiver *self, int operation,
 	}
 
 	/*
-	 * if this is a retrieve, then we send back the tuple descriptor of
-	 * the tuples.
+	 * If this is a retrieve, and we are supposed to emit row descriptions,
+	 * then we send back the tuple descriptor of the tuples.  
 	 */
-	if (operation == CMD_SELECT)
-	{
-		Form_pg_attribute *attrs = typeinfo->attrs;
-		int			natts = typeinfo->natts;
-		int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
-		int			i;
-		StringInfoData buf;
-
-		pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
-		pq_sendint(&buf, natts, 2);		/* # of attrs in tuples */
-
-		for (i = 0; i < natts; ++i)
-		{
-			pq_sendstring(&buf, NameStr(attrs[i]->attname));
-			/* column ID info appears in protocol 3.0 and up */
-			if (proto >= 3)
-			{
-				/* XXX not yet implemented, send zeroes */
-				pq_sendint(&buf, 0, 4);
-				pq_sendint(&buf, 0, 2);
-			}
-			pq_sendint(&buf, (int) attrs[i]->atttypid,
-					   sizeof(attrs[i]->atttypid));
-			pq_sendint(&buf, attrs[i]->attlen,
-					   sizeof(attrs[i]->attlen));
-			/* typmod appears in protocol 2.0 and up */
-			if (proto >= 2)
-				pq_sendint(&buf, attrs[i]->atttypmod,
-						   sizeof(attrs[i]->atttypmod));
-		}
-		pq_endmessage(&buf);
-	}
+	if (operation == CMD_SELECT && myState->sendDescrip)
+		SendRowDescriptionMessage(typeinfo);
 
 	/* ----------------
 	 * We could set up the derived attr info at this time, but we postpone it
@@ -139,6 +114,43 @@ printtup_setup(DestReceiver *self, int operation,
 	 */
 }
 
+/*
+ * SendRowDescriptionMessage --- send a RowDescription message to the frontend
+ */
+void
+SendRowDescriptionMessage(TupleDesc typeinfo)
+{
+	Form_pg_attribute *attrs = typeinfo->attrs;
+	int			natts = typeinfo->natts;
+	int			proto = PG_PROTOCOL_MAJOR(FrontendProtocol);
+	int			i;
+	StringInfoData buf;
+
+	pq_beginmessage(&buf, 'T');		/* tuple descriptor message type */
+	pq_sendint(&buf, natts, 2);		/* # of attrs in tuples */
+
+	for (i = 0; i < natts; ++i)
+	{
+		pq_sendstring(&buf, NameStr(attrs[i]->attname));
+		/* column ID info appears in protocol 3.0 and up */
+		if (proto >= 3)
+		{
+			/* XXX not yet implemented, send zeroes */
+			pq_sendint(&buf, 0, 4);
+			pq_sendint(&buf, 0, 2);
+		}
+		pq_sendint(&buf, (int) attrs[i]->atttypid,
+				   sizeof(attrs[i]->atttypid));
+		pq_sendint(&buf, attrs[i]->attlen,
+				   sizeof(attrs[i]->attlen));
+		/* typmod appears in protocol 2.0 and up */
+		if (proto >= 2)
+			pq_sendint(&buf, attrs[i]->atttypmod,
+					   sizeof(attrs[i]->atttypmod));
+	}
+	pq_endmessage(&buf);
+}
+
 static void
 printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 {
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 35ed8a270b5..82058ff5d10 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.13 2003/05/02 20:54:33 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.14 2003/05/05 00:44:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,7 +49,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
 	 * Disallow empty-string cursor name (conflicts with protocol-level
 	 * unnamed portal).
 	 */
-	if (strlen(stmt->portalname) == 0)
+	if (!stmt->portalname || stmt->portalname[0] == '\0')
 		elog(ERROR, "Invalid cursor name: must not be empty");
 
 	/*
@@ -148,6 +148,13 @@ PerformPortalFetch(FetchStmt *stmt,
 	Portal		portal;
 	long		nprocessed;
 
+	/*
+	 * Disallow empty-string cursor name (conflicts with protocol-level
+	 * unnamed portal).
+	 */
+	if (!stmt->portalname || stmt->portalname[0] == '\0')
+		elog(ERROR, "Invalid cursor name: must not be empty");
+
 	/* get the portal from the portal name */
 	portal = GetPortalByName(stmt->portalname);
 	if (!PortalIsValid(portal))
@@ -164,7 +171,9 @@ PerformPortalFetch(FetchStmt *stmt,
 	 * Adjust dest if needed.  MOVE wants dest = None.
 	 *
 	 * If fetching from a binary cursor and the requested destination is
-	 * Remote, change it to RemoteInternal.
+	 * Remote, change it to RemoteInternal.  Note we do NOT change if the
+	 * destination is RemoteExecute --- so the Execute message's format
+	 * specification wins out over the cursor's type.
 	 */
 	if (stmt->ismove)
 		dest = None;
@@ -189,10 +198,17 @@ PerformPortalFetch(FetchStmt *stmt,
  *		Close a cursor.
  */
 void
-PerformPortalClose(char *name)
+PerformPortalClose(const char *name)
 {
 	Portal		portal;
 
+	/*
+	 * Disallow empty-string cursor name (conflicts with protocol-level
+	 * unnamed portal).
+	 */
+	if (!name || name[0] == '\0')
+		elog(ERROR, "Invalid cursor name: must not be empty");
+
 	/*
 	 * get the portal from the portal name
 	 */
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 5a3e3f589d1..3f8beac53c1 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -3,10 +3,14 @@
  * prepare.c
  *	  Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE
  *
- * Copyright (c) 2002, PostgreSQL Global Development Group
+ * This module also implements storage of prepared statements that are
+ * accessed via the extended FE/BE query protocol.
+ *
+ *
+ * Copyright (c) 2002-2003, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.14 2003/05/02 20:54:33 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.15 2003/05/05 00:44:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,31 +29,15 @@
 #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.
+ * The keys for this hash table are the arguments to PREPARE and EXECUTE
+ * (statement names); the entries are PreparedStatement structs.
  */
 static HTAB *prepared_queries = NULL;
 
 static void InitQueryHashTable(void);
-static void StoreQuery(const char *stmt_name, List *query_list,
-					   List *plan_list, List *argtype_list);
-static QueryHashEntry *FetchQuery(const char *plan_name);
 static ParamListInfo EvaluateParams(EState *estate,
 									List *params, List *argtypes);
 
@@ -59,14 +47,36 @@ static ParamListInfo EvaluateParams(EState *estate,
 void
 PrepareQuery(PrepareStmt *stmt)
 {
+	const char *commandTag;
 	List	   *query_list,
 			   *plan_list;
 
-	if (!stmt->name)
-		elog(ERROR, "No statement name given");
+	/*
+	 * Disallow empty-string statement name (conflicts with protocol-level
+	 * unnamed statement).
+	 */
+	if (!stmt->name || stmt->name[0] == '\0')
+		elog(ERROR, "Invalid statement name: must not be empty");
 
-	if (stmt->query->commandType == CMD_UTILITY)
-		elog(ERROR, "Utility statements cannot be prepared");
+	switch (stmt->query->commandType)
+	{
+		case CMD_SELECT:
+			commandTag = "SELECT";
+			break;
+		case CMD_INSERT:
+			commandTag = "INSERT";
+			break;
+		case CMD_UPDATE:
+			commandTag = "UPDATE";
+			break;
+		case CMD_DELETE:
+			commandTag = "DELETE";
+			break;
+		default:
+			elog(ERROR, "Utility statements cannot be prepared");
+			commandTag = NULL;	/* keep compiler quiet */
+			break;
+	}
 
 	/*
 	 * Parse analysis is already done, but we must still rewrite and plan
@@ -80,7 +90,12 @@ PrepareQuery(PrepareStmt *stmt)
 	plan_list = pg_plan_queries(query_list, false);
 
 	/* Save the results. */
-	StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids);
+	StorePreparedStatement(stmt->name,
+						   NULL, /* text form not available */
+						   commandTag,
+						   query_list,
+						   plan_list,
+						   stmt->argtype_oids);
 }
 
 /*
@@ -89,7 +104,8 @@ PrepareQuery(PrepareStmt *stmt)
 void
 ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
 {
-	QueryHashEntry *entry;
+	PreparedStatement *entry;
+	char	   *query_string;
 	List	   *query_list,
 			   *plan_list;
 	MemoryContext qcontext;
@@ -98,8 +114,9 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
 	Portal		portal;
 
 	/* Look it up in the hash table */
-	entry = FetchQuery(stmt->name);
+	entry = FetchPreparedStatement(stmt->name, true);
 
+	query_string = entry->query_string;
 	query_list = entry->query_list;
 	plan_list = entry->plan_list;
 	qcontext = entry->context;
@@ -135,6 +152,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
 
 		oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 
+		if (query_string)
+			query_string = pstrdup(query_string);
 		query_list = copyObject(query_list);
 		plan_list = copyObject(plan_list);
 		qcontext = PortalGetHeapMemory(portal);
@@ -150,8 +169,8 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
 	}
 
 	PortalDefineQuery(portal,
-					  NULL,		/* XXX fixme: can we save query text? */
-					  NULL,		/* no command tag known either */
+					  query_string,
+					  entry->commandTag,
 					  query_list,
 					  plan_list,
 					  qcontext);
@@ -228,8 +247,8 @@ InitQueryHashTable(void)
 
 	MemSet(&hash_ctl, 0, sizeof(hash_ctl));
 
-	hash_ctl.keysize = HASH_KEY_LEN;
-	hash_ctl.entrysize = sizeof(QueryHashEntry);
+	hash_ctl.keysize = NAMEDATALEN;
+	hash_ctl.entrysize = sizeof(PreparedStatement);
 
 	prepared_queries = hash_create("Prepared Queries",
 								   32,
@@ -244,15 +263,24 @@ InitQueryHashTable(void)
  * Store all the data pertaining to a query in the hash table using
  * the specified key. A copy of the data is made in a memory context belonging
  * to the hash entry, so the caller can dispose of their copy.
+ *
+ * Exception: commandTag is presumed to be a pointer to a constant string,
+ * or possibly NULL, so it need not be copied.  Note that commandTag should
+ * be NULL only if the original query (before rewriting) was empty.
  */
-static void
-StoreQuery(const char *stmt_name, List *query_list,
-		   List *plan_list, List *argtype_list)
+void
+StorePreparedStatement(const char *stmt_name,
+					   const char *query_string,
+					   const char *commandTag,
+					   List *query_list,
+					   List *plan_list,
+					   List *argtype_list)
 {
-	QueryHashEntry *entry;
+	PreparedStatement *entry;
 	MemoryContext oldcxt,
 				entrycxt;
-	char		key[HASH_KEY_LEN];
+	char	   *qstring;
+	char		key[NAMEDATALEN];
 	bool		found;
 
 	/* Initialize the hash table, if necessary */
@@ -260,7 +288,7 @@ StoreQuery(const char *stmt_name, List *query_list,
 		InitQueryHashTable();
 
 	/* Check for pre-existing entry of same name */
-	/* See notes in FetchQuery */
+	/* See notes in FetchPreparedStatement */
 	MemSet(key, 0, sizeof(key));
 	strncpy(key, stmt_name, sizeof(key));
 
@@ -285,15 +313,16 @@ StoreQuery(const char *stmt_name, List *query_list,
 	 * out-of-memory failure only wastes memory and doesn't leave us with
 	 * an incomplete (ie corrupt) hashtable entry.
 	 */
+	qstring = query_string ? pstrdup(query_string) : (char *) NULL;
 	query_list = (List *) copyObject(query_list);
 	plan_list = (List *) copyObject(plan_list);
 	argtype_list = listCopy(argtype_list);
 
 	/* Now we can add entry to hash table */
-	entry = (QueryHashEntry *) hash_search(prepared_queries,
-										   key,
-										   HASH_ENTER,
-										   &found);
+	entry = (PreparedStatement *) hash_search(prepared_queries,
+											  key,
+											  HASH_ENTER,
+											  &found);
 
 	/* Shouldn't get a failure, nor a duplicate entry */
 	if (!entry || found)
@@ -301,6 +330,8 @@ StoreQuery(const char *stmt_name, List *query_list,
 			 stmt_name);
 
 	/* Fill in the hash table entry with copied data */
+	entry->query_string = qstring;
+	entry->commandTag = commandTag;
 	entry->query_list = query_list;
 	entry->plan_list = plan_list;
 	entry->argtype_list = argtype_list;
@@ -311,52 +342,53 @@ StoreQuery(const char *stmt_name, List *query_list,
 
 /*
  * Lookup an existing query in the hash table. If the query does not
- * actually exist, an elog(ERROR) is thrown.
+ * actually exist, throw elog(ERROR) or return NULL per second parameter.
  */
-static QueryHashEntry *
-FetchQuery(const char *plan_name)
+PreparedStatement *
+FetchPreparedStatement(const char *stmt_name, bool throwError)
 {
-	char		key[HASH_KEY_LEN];
-	QueryHashEntry *entry;
+	char		key[NAMEDATALEN];
+	PreparedStatement *entry;
 
 	/*
 	 * If the hash table hasn't been initialized, it can't be storing
 	 * anything, therefore it couldn't possibly store our plan.
 	 */
-	if (!prepared_queries)
-		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));
+	if (prepared_queries)
+	{
+		/*
+		 * We can't just use the statement name as supplied by the user: the
+		 * hash package is picky enough that it needs to be NULL-padded out to
+		 * the appropriate length to work correctly.
+		 */
+		MemSet(key, 0, sizeof(key));
+		strncpy(key, stmt_name, sizeof(key));
 
-	entry = (QueryHashEntry *) hash_search(prepared_queries,
-										   key,
-										   HASH_FIND,
-										   NULL);
+		entry = (PreparedStatement *) hash_search(prepared_queries,
+												  key,
+												  HASH_FIND,
+												  NULL);
+	}
+	else
+		entry = NULL;
 
-	if (!entry)
+	if (!entry && throwError)
 		elog(ERROR, "Prepared statement with name \"%s\" does not exist",
-			 plan_name);
+			 stmt_name);
 
 	return entry;
 }
 
 /*
- * Given a plan name, look up the stored plan (giving error if not found).
+ * Look up a prepared statement given the name (giving error if not found).
  * If found, return the list of argument type OIDs.
  */
 List *
-FetchQueryParams(const char *plan_name)
+FetchPreparedStatementParams(const char *stmt_name)
 {
-	QueryHashEntry *entry;
+	PreparedStatement *entry;
 
-	entry = FetchQuery(plan_name);
+	entry = FetchPreparedStatement(stmt_name, true);
 
 	return entry->argtype_list;
 }
@@ -368,20 +400,34 @@ FetchQueryParams(const char *plan_name)
 void
 DeallocateQuery(DeallocateStmt *stmt)
 {
-	QueryHashEntry *entry;
+	DropPreparedStatement(stmt->name, true);
+}
+
+/*
+ * Internal version of DEALLOCATE
+ *
+ * If showError is false, dropping a nonexistent statement is a no-op.
+ */
+void
+DropPreparedStatement(const char *stmt_name, bool showError)
+{
+	PreparedStatement *entry;
 
-	/* Find the query's hash table entry */
-	entry = FetchQuery(stmt->name);
+	/* Find the query's hash table entry; raise error if wanted */
+	entry = FetchPreparedStatement(stmt_name, showError);
 
-	/* Drop any open portals that depend on this prepared statement */
-	Assert(MemoryContextIsValid(entry->context));
-	DropDependentPortals(entry->context);
+	if (entry)
+	{
+		/* Drop any open portals that depend on this prepared statement */
+		Assert(MemoryContextIsValid(entry->context));
+		DropDependentPortals(entry->context);
 
-	/* Flush the context holding the subsidiary data */
-	MemoryContextDelete(entry->context);
+		/* Flush the context holding the subsidiary data */
+		MemoryContextDelete(entry->context);
 
-	/* Now we can remove the hash table entry */
-	hash_search(prepared_queries, entry->key, HASH_REMOVE, NULL);
+		/* Now we can remove the hash table entry */
+		hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
+	}
 }
 
 /*
@@ -391,7 +437,7 @@ void
 ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
 {
 	ExecuteStmt	   *execstmt = (ExecuteStmt *) stmt->query->utilityStmt;
-	QueryHashEntry *entry;
+	PreparedStatement *entry;
 	List	   *l,
 			   *query_list,
 			   *plan_list;
@@ -402,7 +448,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate)
 	Assert(execstmt && IsA(execstmt, ExecuteStmt));
 
 	/* Look it up in the hash table */
-	entry = FetchQuery(execstmt->name);
+	entry = FetchPreparedStatement(execstmt->name, true);
 
 	query_list = entry->query_list;
 	plan_list = entry->plan_list;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index a488d1d91e5..ad2d5ab5681 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.269 2003/05/02 20:54:34 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.270 2003/05/05 00:44:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -83,6 +83,12 @@ typedef struct
 	IndexStmt  *pkey;			/* PRIMARY KEY index, if any */
 } CreateStmtContext;
 
+typedef struct
+{
+	Oid		   *paramTypes;
+	int			numParams;
+} check_parameter_resolution_context;
+
 
 static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
 static Query *transformStmt(ParseState *pstate, Node *stmt,
@@ -124,6 +130,8 @@ static void transformColumnType(ParseState *pstate, ColumnDef *column);
 static bool relationHasPrimaryKey(Oid relationOid);
 static void release_pstate_resources(ParseState *pstate);
 static FromExpr *makeFromExpr(List *fromlist, Node *quals);
+static bool check_parameter_resolution_walker(Node *node,
+					   check_parameter_resolution_context *context);
 
 
 /*
@@ -179,6 +187,16 @@ parse_analyze_varparams(Node *parseTree, Oid **paramTypes, int *numParams)
 
 	pfree(pstate);
 
+	/* make sure all is well with parameter types */
+	if (*numParams > 0)
+	{
+		check_parameter_resolution_context context;
+
+		context.paramTypes = *paramTypes;
+		context.numParams = *numParams;
+		check_parameter_resolution_walker((Node *) result, &context);
+	}
+
 	return result;
 }
 
@@ -2465,7 +2483,7 @@ transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
 	result->commandType = CMD_UTILITY;
 	result->utilityStmt = (Node *) stmt;
 
-	paramtypes = FetchQueryParams(stmt->name);
+	paramtypes = FetchPreparedStatementParams(stmt->name);
 
 	if (stmt->params || paramtypes)
 	{
@@ -2879,3 +2897,44 @@ analyzeCreateSchemaStmt(CreateSchemaStmt *stmt)
 
 	return result;
 }
+
+/*
+ * Traverse a fully-analyzed tree to verify that parameter symbols
+ * match their types.  We need this because some Params might still
+ * be UNKNOWN, if there wasn't anything to force their coercion,
+ * and yet other instances seen later might have gotten coerced.
+ */
+static bool
+check_parameter_resolution_walker(Node *node,
+								  check_parameter_resolution_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Param))
+	{
+		Param	   *param = (Param *) node;
+
+		if (param->paramkind == PARAM_NUM)
+		{
+			int			paramno = param->paramid;
+
+			if (paramno <= 0 ||		/* shouldn't happen, but... */
+				paramno > context->numParams)
+				elog(ERROR, "Parameter '$%d' is out of range", paramno);
+
+			if (param->paramtype != context->paramTypes[paramno-1])
+				elog(ERROR, "Could not determine datatype of parameter $%d",
+					 paramno);
+		}
+		return false;
+	}
+	if (IsA(node, Query))
+	{
+		/* Recurse into RTE subquery or not-yet-planned sublink subquery */
+		return query_tree_walker((Query *) node,
+								 check_parameter_resolution_walker,
+								 (void *) context, 0);
+	}
+	return expression_tree_walker(node, check_parameter_resolution_walker,
+								  (void *) context);
+}
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 41906a348a4..a5905dedc7f 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.54 2003/04/26 20:22:59 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/dest.c,v 1.55 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -91,10 +91,20 @@ DestToFunction(CommandDest dest)
 	switch (dest)
 	{
 		case Remote:
-			return printtup_create_DR(false);
+			return printtup_create_DR(false, true);
 
 		case RemoteInternal:
-			return printtup_create_DR(true);
+			return printtup_create_DR(true, true);
+
+		case RemoteExecute:
+			/* like Remote, but suppress output of T message */
+			return printtup_create_DR(false, false);
+
+		case RemoteExecuteInternal:
+			return printtup_create_DR(true, false);
+
+		case None:
+			return &donothingDR;
 
 		case Debug:
 			return &debugtupDR;
@@ -104,9 +114,6 @@ DestToFunction(CommandDest dest)
 
 		case Tuplestore:
 			return tstoreReceiverCreateDR();
-
-		case None:
-			return &donothingDR;
 	}
 
 	/* should never get here */
@@ -124,13 +131,15 @@ EndCommand(const char *commandTag, CommandDest dest)
 	{
 		case Remote:
 		case RemoteInternal:
+		case RemoteExecute:
+		case RemoteExecuteInternal:
 			pq_puttextmessage('C', commandTag);
 			break;
 
 		case None:
 		case Debug:
-		case Tuplestore:
 		case SPI:
+		case Tuplestore:
 			break;
 	}
 }
@@ -152,8 +161,10 @@ NullCommand(CommandDest dest)
 {
 	switch (dest)
 	{
-		case RemoteInternal:
 		case Remote:
+		case RemoteInternal:
+		case RemoteExecute:
+		case RemoteExecuteInternal:
 
 			/*
 			 * tell the fe that we saw an empty query string.  In protocols
@@ -165,10 +176,10 @@ NullCommand(CommandDest dest)
 				pq_puttextmessage('I', "");
 			break;
 
+		case None:
 		case Debug:
+		case SPI:
 		case Tuplestore:
-		case None:
-		default:
 			break;
 	}
 }
@@ -189,8 +200,10 @@ ReadyForQuery(CommandDest dest)
 {
 	switch (dest)
 	{
-		case RemoteInternal:
 		case Remote:
+		case RemoteInternal:
+		case RemoteExecute:
+		case RemoteExecuteInternal:
 			if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
 			{
 				StringInfoData buf;
@@ -205,10 +218,10 @@ ReadyForQuery(CommandDest dest)
 			pq_flush();
 			break;
 
+		case None:
 		case Debug:
+		case SPI:
 		case Tuplestore:
-		case None:
-		default:
 			break;
 	}
 }
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 78fcfdb7e0e..65161c54ff3 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.60 2003/05/02 20:54:35 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.61 2003/05/05 00:44:56 tgl Exp $
  *
  * NOTES
  *	  This cruft is the server side of PQfn.
@@ -310,6 +310,14 @@ HandleFunctionRequest(StringInfo msgBuf)
 	if (aclresult != ACLCHECK_OK)
 		aclcheck_error(aclresult, get_func_name(fid));
 
+	/*
+	 * Set up a query snapshot in case function needs one.
+	 */
+	SetQuerySnapshot();
+
+	/*
+	 * Prepare function call info block.
+	 */
 	if (fip->flinfo.fn_nargs != nargs || nargs > FUNC_MAX_ARGS)
 		elog(ERROR, "HandleFunctionRequest: actual arguments (%d) != registered arguments (%d)",
 			 nargs, fip->flinfo.fn_nargs);
@@ -359,12 +367,8 @@ HandleFunctionRequest(StringInfo msgBuf)
 		}
 	}
 
-	/*
-	 * Set up a query snapshot in case function needs one.  (It is not safe
-	 * to do this if we are in transaction-abort state, so we have to postpone
-	 * it till now.  Ugh.)
-	 */
-	SetQuerySnapshot();
+	/* Verify we reached the end of the message where expected. */
+	pq_getmsgend(msgBuf);
 
 #ifdef NO_FASTPATH
 	/* force a NULL return */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b60898270a6..d57ccd973b2 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.331 2003/05/03 05:13:20 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.332 2003/05/05 00:44:56 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -33,8 +33,11 @@
 #include <getopt.h>
 #endif
 
+#include "access/printtup.h"
 #include "access/xlog.h"
+#include "catalog/pg_type.h"
 #include "commands/async.h"
+#include "commands/prepare.h"
 #include "commands/trigger.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -54,6 +57,7 @@
 #include "tcop/tcopprot.h"
 #include "tcop/utility.h"
 #include "utils/guc.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
 #include "mb/pg_wchar.h"
@@ -82,7 +86,15 @@ bool		InError = false;
 
 extern bool	autocommit;
 
-static bool EchoQuery = false;	/* default don't echo */
+/*
+ * Flags for expensive function optimization -- JMH 3/9/92
+ */
+int			XfuncMode = 0;
+
+/* ----------------
+ *		private variables
+ * ----------------
+ */
 
 /*
  * Flag to mark SIGHUP. Whenever the main loop comes around it
@@ -91,23 +103,41 @@ static bool EchoQuery = false;	/* default don't echo */
  */
 static volatile bool got_SIGHUP = false;
 
-/* ----------------
- *		people who want to use EOF should #define DONTUSENEWLINE in
- *		tcop/tcopdebug.h
- * ----------------
+/*
+ * Flag to keep track of whether we have started a transaction.
+ * For extended query protocol this has to be remembered across messages.
+ */
+static bool xact_started = false;
+
+/*
+ * Flags to implement skip-till-Sync-after-error behavior for messages of
+ * the extended query protocol.
+ */
+static bool doing_extended_query_message = false;
+static bool ignore_till_sync = false;
+
+/*
+ * If an unnamed prepared statement exists, it's stored here.
+ * We keep it separate from the hashtable kept by commands/prepare.c
+ * in order to reduce overhead for short-lived queries.
+ */
+static MemoryContext unnamed_stmt_context = NULL;
+static PreparedStatement *unnamed_stmt_pstmt = NULL;
+
+
+static bool EchoQuery = false;	/* default don't echo */
+
+/*
+ * people who want to use EOF should #define DONTUSENEWLINE in
+ * tcop/tcopdebug.h
  */
 #ifndef TCOP_DONTUSENEWLINE
-int			UseNewLine = 1;		/* Use newlines query delimiters (the
+static int	UseNewLine = 1;		/* Use newlines query delimiters (the
 								 * default) */
-
 #else
-int			UseNewLine = 0;		/* Use EOF as query delimiters */
+static int	UseNewLine = 0;		/* Use EOF as query delimiters */
 #endif   /* TCOP_DONTUSENEWLINE */
 
-/*
-** Flags for expensive function optimization -- JMH 3/9/92
-*/
-int			XfuncMode = 0;
 
 /* ----------------------------------------------------------------
  *		decls for routines only used in this file
@@ -254,10 +284,14 @@ SocketBackend(StringInfo inBuf)
 	 * Validate message type code before trying to read body; if we have
 	 * lost sync, better to say "command unknown" than to run out of memory
 	 * because we used garbage as a length word.
+	 *
+	 * This also gives us a place to set the doing_extended_query_message
+	 * flag as soon as possible.
 	 */
 	switch (qtype)
 	{
 		case 'Q':				/* simple query */
+			doing_extended_query_message = false;
 			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
 			{
 				/* old style without length word; convert */
@@ -270,15 +304,43 @@ SocketBackend(StringInfo inBuf)
 			break;
 
 		case 'F':				/* fastpath function call */
+			/* we let fastpath.c cope with old-style input of this */
+			doing_extended_query_message = false;
 			break;
 
 		case 'X':				/* terminate */
+			doing_extended_query_message = false;
+			break;
+
+		case 'B':				/* bind */
+		case 'C':				/* close */
+		case 'D':				/* describe */
+		case 'E':				/* execute */
+		case 'H':				/* flush */
+		case 'P':				/* parse */
+			doing_extended_query_message = true;
+			/* these are only legal in protocol 3 */
+			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+				elog(FATAL, "Socket command type %c unknown", qtype);
+			break;
+
+		case 'S':				/* sync */
+			/* stop any active skip-till-Sync */
+			ignore_till_sync = false;
+			/* mark not-extended, so that a new error doesn't begin skip */
+			doing_extended_query_message = false;
+			/* only legal in protocol 3 */
+			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+				elog(FATAL, "Socket command type %c unknown", qtype);
 			break;
 
 		case 'd':				/* copy data */
 		case 'c':				/* copy done */
 		case 'f':				/* copy fail */
-			/* Accept but ignore these messages, per protocol spec */
+			doing_extended_query_message = false;
+			/* these are only legal in protocol 3 */
+			if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+				elog(FATAL, "Socket command type %c unknown", qtype);
 			break;
 
 		default:
@@ -410,9 +472,6 @@ List *
 pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
 {
 	List	   *querytree_list;
-	List	   *list_item;
-	Query	   *querytree;
-	List	   *new_list;
 
 	/*
 	 * (1) Perform parse analysis.
@@ -423,21 +482,35 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
 	querytree_list = parse_analyze(parsetree, paramTypes, numParams);
 
 	if (log_parser_stats)
-	{
 		ShowUsage("PARSE ANALYSIS STATISTICS");
-		ResetUsage();
-	}
 
 	/*
 	 * (2) Rewrite the queries, as necessary
-	 *
+	 */
+	querytree_list = pg_rewrite_queries(querytree_list);
+
+	return querytree_list;
+}
+
+/*
+ * Perform rewriting of a list of queries produced by parse analysis.
+ */
+List *
+pg_rewrite_queries(List *querytree_list)
+{
+	List	   *new_list = NIL;
+	List	   *list_item;
+
+	if (log_parser_stats)
+		ResetUsage();
+
+	/*
 	 * rewritten queries are collected in new_list.  Note there may be more
 	 * or fewer than in the original list.
 	 */
-	new_list = NIL;
 	foreach(list_item, querytree_list)
 	{
-		querytree = (Query *) lfirst(list_item);
+		Query	   *querytree = (Query *) lfirst(list_item);
 
 		if (Debug_print_parse)
 			elog_node_display(LOG, "parse tree", querytree,
@@ -471,7 +544,7 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
 	new_list = (List *) copyObject(querytree_list);
 	/* This checks both copyObject() and the equal() routines... */
 	if (!equal(new_list, querytree_list))
-		elog(WARNING, "pg_analyze_and_rewrite: copyObject failed on parse tree");
+		elog(WARNING, "pg_rewrite_queries: copyObject failed on parse tree");
 	else
 		querytree_list = new_list;
 #endif
@@ -576,15 +649,13 @@ pg_plan_queries(List *querytrees, bool needSnapshot)
 
 
 /*
- * exec_simple_query()
+ * exec_simple_query
  *
  * Execute a "simple Query" protocol message.
  */
 static void
-exec_simple_query(const char *query_string,	/* string to execute */
-				  CommandDest dest)			/* where results should go */
+exec_simple_query(const char *query_string)
 {
-	bool		xact_started;
 	MemoryContext oldcontext;
 	List	   *parsetree_list,
 			   *parsetree_item;
@@ -619,13 +690,28 @@ exec_simple_query(const char *query_string,	/* string to execute */
 	 * that this will normally change current memory context.)
 	 */
 	start_xact_command();
-	xact_started = true;
+
+	/*
+	 * Zap any pre-existing unnamed statement.  (While not strictly
+	 * necessary, it seems best to define simple-Query mode as if it
+	 * used the unnamed statement and portal; this ensures we recover
+	 * any storage used by prior unnamed operations.)
+	 */
+	unnamed_stmt_pstmt = NULL;
+	if (unnamed_stmt_context)
+	{
+		DropDependentPortals(unnamed_stmt_context);
+		MemoryContextDelete(unnamed_stmt_context);
+	}
+	unnamed_stmt_context = NULL;
 
 	/*
 	 * Switch to appropriate context for constructing parsetrees.
 	 */
 	oldcontext = MemoryContextSwitchTo(MessageContext);
 
+	QueryContext = CurrentMemoryContext;
+
 	/*
 	 * Do basic parsing of the query or queries (this should be safe even
 	 * if we are in aborted transaction state!)
@@ -659,7 +745,7 @@ exec_simple_query(const char *query_string,	/* string to execute */
 
 		set_ps_display(commandTag);
 
-		BeginCommand(commandTag, dest);
+		BeginCommand(commandTag, whereToSendOutput);
 
 		/*
 		 * If we are in an aborted transaction, reject all commands except
@@ -688,11 +774,7 @@ exec_simple_query(const char *query_string,	/* string to execute */
 		}
 
 		/* Make sure we are in a transaction command */
-		if (!xact_started)
-		{
-			start_xact_command();
-			xact_started = true;
-		}
+		start_xact_command();
 
 		/* If we got a cancel signal in parsing or prior command, quit */
 		CHECK_FOR_INTERRUPTS();
@@ -735,37 +817,40 @@ exec_simple_query(const char *query_string,	/* string to execute */
 		 */
 		PortalStart(portal, NULL);
 
-		(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
+		(void) PortalRun(portal,
+						 FETCH_ALL,
+						 whereToSendOutput,
+						 whereToSendOutput,
+						 completionTag);
 
 		PortalDrop(portal, false);
 
-		/*
-		 * If this was a transaction control statement or a variable
-		 * set/show/reset statement, commit it and arrange to start a
-		 * new xact command for the next command (if any).
-		 */
+
 		if (IsA(parsetree, TransactionStmt) ||
 			IsA(parsetree, VariableSetStmt) ||
 			IsA(parsetree, VariableShowStmt) ||
 			IsA(parsetree, VariableResetStmt))
 		{
+			/*
+			 * If this was a transaction control statement or a variable
+			 * set/show/reset statement, commit it.  We will start a
+			 * new xact command for the next command (if any).
+			 */
 			finish_xact_command(true);
-			xact_started = false;
 		}
-		/*
-		 * If this is the last parsetree of the query string, close down
-		 * transaction statement before reporting command-complete.  This
-		 * is so that any end-of-transaction errors are reported before
-		 * the command-complete message is issued, to avoid confusing
-		 * clients who will expect either a command-complete message or an
-		 * error, not one and then the other.  But for compatibility with
-		 * historical Postgres behavior, we do not force a transaction
-		 * boundary between queries appearing in a single query string.
-		 */
 		else if (lnext(parsetree_item) == NIL || !autocommit)
 		{
+			/*
+			 * If this is the last parsetree of the query string, close down
+			 * transaction statement before reporting command-complete.  This
+			 * is so that any end-of-transaction errors are reported before
+			 * the command-complete message is issued, to avoid confusing
+			 * clients who will expect either a command-complete message or an
+			 * error, not one and then the other.  But for compatibility with
+			 * historical Postgres behavior, we do not force a transaction
+			 * boundary between queries appearing in a single query string.
+			 */
 			finish_xact_command(false);
-			xact_started = false;
 		}
 		else
 		{
@@ -783,20 +868,21 @@ exec_simple_query(const char *query_string,	/* string to execute */
 		 * (But a command aborted by error will not send an EndCommand
 		 * report at all.)
 		 */
-		EndCommand(completionTag, dest);
+		EndCommand(completionTag, whereToSendOutput);
 	}							/* end loop over parsetrees */
 
 	/*
 	 * If there were no parsetrees, return EmptyQueryResponse message.
 	 */
 	if (!parsetree_list)
-		NullCommand(dest);
+		NullCommand(whereToSendOutput);
+
+	QueryContext = NULL;
 
 	/*
 	 * Close down transaction statement, if one is open.
 	 */
-	if (xact_started)
-		finish_xact_command(false);
+	finish_xact_command(false);
 
 	/*
 	 * Finish up monitoring.
@@ -820,39 +906,609 @@ exec_simple_query(const char *query_string,	/* string to execute */
 	debug_query_string = NULL;
 }
 
+/*
+ * exec_parse_message
+ *
+ * Execute a "Parse" protocol message.
+ */
+static void
+exec_parse_message(const char *query_string,	/* string to execute */
+				   const char *stmt_name,		/* name for prepared stmt */
+				   Oid *paramTypes,				/* parameter types */
+				   int numParams)				/* number of parameters */
+{
+	MemoryContext oldcontext;
+	List	   *parsetree_list;
+	const char *commandTag;
+	List	   *querytree_list,
+			   *plantree_list,
+			   *param_list;
+	bool		is_named;
+	bool		save_log_statement_stats = log_statement_stats;
+
+	/*
+	 * Report query to various monitoring facilities.
+	 */
+	debug_query_string = query_string;
+
+	pgstat_report_activity(query_string);
+
+	set_ps_display("PARSE");
+
+	if (save_log_statement_stats)
+		ResetUsage();
+
+	/*
+	 * Start up a transaction command so we can run parse analysis etc.
+	 * (Note that this will normally change current memory context.)
+	 * Nothing happens if we are already in one.
+	 */
+	start_xact_command();
+
+	/*
+	 * Switch to appropriate context for constructing parsetrees.
+	 *
+	 * We have two strategies depending on whether the prepared statement
+	 * is named or not.  For a named prepared statement, we do parsing
+	 * in MessageContext and copy the finished trees into the prepared
+	 * statement's private context; then the reset of MessageContext releases
+	 * temporary space used by parsing and planning.  For an unnamed prepared
+	 * statement, we assume the statement isn't going to hang around long,
+	 * so getting rid of temp space quickly is probably not worth the costs
+	 * of copying parse/plan trees.  So in this case, we set up a special
+	 * context for the unnamed statement, and do all the parsing/planning
+	 * therein.
+	 */
+	is_named = (stmt_name[0] != '\0');
+	if (is_named)
+	{
+		/* Named prepared statement --- parse in MessageContext */
+		oldcontext = MemoryContextSwitchTo(MessageContext);
+	}
+	else
+	{
+		/* Unnamed prepared statement --- release any prior unnamed stmt */
+		unnamed_stmt_pstmt = NULL;
+		if (unnamed_stmt_context)
+		{
+			DropDependentPortals(unnamed_stmt_context);
+			MemoryContextDelete(unnamed_stmt_context);
+		}
+		unnamed_stmt_context = NULL;
+		/* create context for parsing/planning */
+		unnamed_stmt_context =
+			AllocSetContextCreate(TopMemoryContext,
+								  "unnamed prepared statement",
+								  ALLOCSET_DEFAULT_MINSIZE,
+								  ALLOCSET_DEFAULT_INITSIZE,
+								  ALLOCSET_DEFAULT_MAXSIZE);
+		oldcontext = MemoryContextSwitchTo(unnamed_stmt_context);
+	}
+
+	QueryContext = CurrentMemoryContext;
+
+	/*
+	 * Do basic parsing of the query or queries (this should be safe even
+	 * if we are in aborted transaction state!)
+	 */
+	parsetree_list = pg_parse_query(query_string);
+
+	/*
+	 * We only allow a single user statement in a prepared statement.
+	 * This is mainly to keep the protocol simple --- otherwise we'd need
+	 * to worry about multiple result tupdescs and things like that.
+	 */
+	if (length(parsetree_list) > 1)
+		elog(ERROR, "Cannot insert multiple commands into a prepared statement");
+
+	if (parsetree_list != NIL)
+	{
+		Node   *parsetree = (Node *) lfirst(parsetree_list);
+		int		i;
+
+		/*
+		 * Get the command name for possible use in status display.
+		 */
+		commandTag = CreateCommandTag(parsetree);
+
+		/*
+		 * If we are in an aborted transaction, reject all commands except
+		 * COMMIT/ROLLBACK.  It is important that this test occur before we
+		 * try to do parse analysis, rewrite, or planning, since all those
+		 * phases try to do database accesses, which may fail in abort
+		 * state. (It might be safe to allow some additional utility
+		 * commands in this state, but not many...)
+		 */
+		if (IsAbortedTransactionBlockState())
+		{
+			bool		allowit = false;
+
+			if (IsA(parsetree, TransactionStmt))
+			{
+				TransactionStmt *stmt = (TransactionStmt *) parsetree;
+
+				if (stmt->kind == TRANS_STMT_COMMIT ||
+					stmt->kind == TRANS_STMT_ROLLBACK)
+					allowit = true;
+			}
+
+			if (!allowit)
+				elog(ERROR, "current transaction is aborted, "
+					 "queries ignored until end of transaction block");
+		}
+
+		/*
+		 * OK to analyze, rewrite, and plan this query.  Note that the
+		 * originally specified parameter set is not required to be
+		 * complete, so we have to use parse_analyze_varparams().
+		 */
+		if (log_parser_stats)
+			ResetUsage();
+
+		querytree_list = parse_analyze_varparams(parsetree,
+												 &paramTypes,
+												 &numParams);
+
+		/*
+		 * Check all parameter types got determined, and convert array
+		 * representation to a list for storage.
+		 */
+		param_list = NIL;
+		for (i = 0; i < numParams; i++)
+		{
+			Oid		ptype = paramTypes[i];
+
+			if (ptype == InvalidOid || ptype == UNKNOWNOID)
+				elog(ERROR, "Could not determine datatype of parameter $%d",
+					 i + 1);
+			param_list = lappendo(param_list, ptype);
+		}
+
+		if (log_parser_stats)
+			ShowUsage("PARSE ANALYSIS STATISTICS");
+
+		querytree_list = pg_rewrite_queries(querytree_list);
+
+		plantree_list = pg_plan_queries(querytree_list, true);
+	}
+	else
+	{
+		/* Empty input string.  This is legal. */
+		commandTag = NULL;
+		querytree_list = NIL;
+		plantree_list = NIL;
+		param_list = NIL;
+	}
+
+	/* If we got a cancel signal in analysis or planning, quit */
+	CHECK_FOR_INTERRUPTS();
+
+	/*
+	 * Store the query as a prepared statement.  See above comments.
+	 */
+	if (is_named)
+	{
+		StorePreparedStatement(stmt_name,
+							   query_string,
+							   commandTag,
+							   querytree_list,
+							   plantree_list,
+							   param_list);
+	}
+	else
+	{
+		PreparedStatement *pstmt;
+
+		pstmt = (PreparedStatement *) palloc0(sizeof(PreparedStatement));
+		/* query_string needs to be copied into unnamed_stmt_context */
+		pstmt->query_string = pstrdup(query_string);
+		/* the rest is there already */
+		pstmt->commandTag = commandTag;
+		pstmt->query_list = querytree_list;
+		pstmt->plan_list = plantree_list;
+		pstmt->argtype_list = param_list;
+		pstmt->context = unnamed_stmt_context;
+		/* Now the unnamed statement is complete and valid */
+		unnamed_stmt_pstmt = pstmt;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	QueryContext = NULL;
+
+	/*
+	 * We do NOT close the open transaction command here; that only happens
+	 * when the client sends Sync.  Instead, do CommandCounterIncrement just
+	 * in case something happened during parse/plan.
+	 */
+	CommandCounterIncrement();
+
+	/*
+	 * Send ParseComplete.
+	 */
+	if (whereToSendOutput == Remote)
+		pq_putemptymessage('1');
+
+	if (save_log_statement_stats)
+		ShowUsage("PARSE MESSAGE STATISTICS");
+
+	debug_query_string = NULL;
+}
+
+/*
+ * exec_bind_message
+ *
+ * Process a "Bind" message to create a portal from a prepared statement
+ */
+static void
+exec_bind_message(StringInfo input_message)
+{
+	const char *portal_name;
+	const char *stmt_name;
+	int			is_binary;
+	int			numParams;
+	PreparedStatement *pstmt;
+	Portal		portal;
+	ParamListInfo params;
+
+	pgstat_report_activity("<BIND>");
+
+	set_ps_display("BIND");
+
+	/*
+	 * Start up a transaction command so we can call functions etc.
+	 * (Note that this will normally change current memory context.)
+	 * Nothing happens if we are already in one.
+	 */
+	start_xact_command();
+
+	/* Get the fixed part of the message */
+	portal_name = pq_getmsgstring(input_message);
+	stmt_name = pq_getmsgstring(input_message);
+	is_binary = pq_getmsgbyte(input_message);
+	numParams = pq_getmsgint(input_message, 4);
+
+	if (is_binary)
+		elog(ERROR, "Binary BIND not implemented yet");
+
+	/* Find prepared statement */
+	if (stmt_name[0] != '\0')
+		pstmt = FetchPreparedStatement(stmt_name, true);
+	else
+	{
+		/* special-case the unnamed statement */
+		pstmt = unnamed_stmt_pstmt;
+		if (!pstmt)
+			elog(ERROR, "Unnamed prepared statement does not exist");
+	}
+
+	if (numParams != length(pstmt->argtype_list))
+		elog(ERROR, "Bind message supplies %d parameters, but prepared statement \"%s\" requires %d",
+			 numParams, stmt_name, length(pstmt->argtype_list));
+
+	/*
+	 * Create the portal.  Allow silent replacement of an existing portal
+	 * only if the unnamed portal is specified.
+	 */
+	if (portal_name[0] == '\0')
+		portal = CreatePortal(portal_name, true, true);
+	else
+		portal = CreatePortal(portal_name, false, false);
+
+	PortalDefineQuery(portal,
+					  pstmt->query_string,
+					  pstmt->commandTag,
+					  pstmt->query_list,
+					  pstmt->plan_list,
+					  pstmt->context);
+
+	/*
+	 * Fetch parameters, if any, and store in the portal's memory context.
+	 *
+	 * In an aborted transaction, we can't risk calling user-defined functions,
+	 * so bind all parameters to null values.
+	 */
+	if (numParams > 0)
+	{
+		bool	isaborted = IsAbortedTransactionBlockState();
+		int		i = 0;
+		List   *l;
+		MemoryContext oldContext;
+
+		oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+
+		params = (ParamListInfo)
+			palloc0((numParams + 1) * sizeof(ParamListInfoData));
+
+		foreach(l, pstmt->argtype_list)
+		{
+			Oid			ptype = lfirsto(l);
+			bool		isNull;
+
+			isNull = (pq_getmsgbyte(input_message) != 0) ? false : true;
+			if (!isNull)
+			{
+				const char *ptext = pq_getmsgstring(input_message);
+
+				if (isaborted)
+					isNull = true;
+				else
+				{
+					Oid			typInput;
+					Oid			typElem;
+
+					getTypeInputInfo(ptype, &typInput, &typElem);
+					params[i].value =
+						OidFunctionCall3(typInput,
+										 CStringGetDatum(ptext),
+										 ObjectIdGetDatum(typElem),
+										 Int32GetDatum(-1));
+				}
+			}
+			params[i].kind = PARAM_NUM;
+			params[i].id = i + 1;
+			params[i].isnull = isNull;
+
+			i++;
+		}
+
+		params[i].kind = PARAM_INVALID;
+
+		MemoryContextSwitchTo(oldContext);
+	}
+	else
+		params = NULL;
+
+	pq_getmsgend(input_message);
+
+	/*
+	 * Start portal execution.
+	 */
+	PortalStart(portal, params);
+
+	/*
+	 * Send BindComplete.
+	 */
+	if (whereToSendOutput == Remote)
+		pq_putemptymessage('2');
+}
+
+/*
+ * exec_execute_message
+ *
+ * Process an "Execute" message for a portal
+ */
+static void
+exec_execute_message(const char *portal_name, int is_binary, long max_rows)
+{
+	CommandDest	dest;
+	Portal		portal;
+	bool		is_trans_stmt = false;
+	bool		is_trans_exit = false;
+	bool		completed;
+	char		completionTag[COMPLETION_TAG_BUFSIZE];
+
+	/* Adjust destination to tell printtup.c what to do */
+	dest = whereToSendOutput;
+	if (dest == Remote)
+		dest = is_binary ? RemoteExecuteInternal : RemoteExecute;
+
+	portal = GetPortalByName(portal_name);
+	if (!PortalIsValid(portal))
+		elog(ERROR, "Portal \"%s\" not found", portal_name);
+
+	/*
+	 * If the original query was a null string, just return EmptyQueryResponse.
+	 */
+	if (portal->commandTag == NULL)
+	{
+		Assert(portal->parseTrees == NIL);
+		NullCommand(dest);
+		return;
+	}
+
+	if (portal->sourceText)
+	{
+		debug_query_string = portal->sourceText;
+		pgstat_report_activity(portal->sourceText);
+	}
+	else
+	{
+		debug_query_string = "execute message";
+		pgstat_report_activity("<EXECUTE>");
+	}
+
+	set_ps_display(portal->commandTag);
+
+	BeginCommand(portal->commandTag, dest);
+
+	/* Check for transaction-control commands */
+	if (length(portal->parseTrees) == 1)
+	{
+		Query *query = (Query *) lfirst(portal->parseTrees);
+
+		if (query->commandType == CMD_UTILITY &&
+			query->utilityStmt != NULL &&
+			IsA(query->utilityStmt, TransactionStmt))
+		{
+			TransactionStmt *stmt = (TransactionStmt *) query->utilityStmt;
+
+			is_trans_stmt = true;
+			if (stmt->kind == TRANS_STMT_COMMIT ||
+				stmt->kind == TRANS_STMT_ROLLBACK)
+				is_trans_exit = true;
+		}
+	}
+
+	/*
+	 * Ensure we are in a transaction command (this should normally be
+	 * the case already due to prior BIND).
+	 */
+	start_xact_command();
+
+	/*
+	 * If we are in aborted transaction state, the only portals we can
+	 * actually run are those containing COMMIT or ROLLBACK commands.
+	 */
+	if (IsAbortedTransactionBlockState())
+	{
+		if (!is_trans_exit)
+			elog(ERROR, "current transaction is aborted, "
+				 "queries ignored until end of transaction block");
+	}
+
+	/* Check for cancel signal before we start execution */
+	CHECK_FOR_INTERRUPTS();
+
+	/*
+	 * Okay to run the portal.
+	 */
+	if (max_rows <= 0)
+		max_rows = FETCH_ALL;
+
+	completed = PortalRun(portal,
+						  max_rows,
+						  dest,
+						  dest,
+						  completionTag);
+
+	if (completed)
+	{
+		if (is_trans_stmt)
+		{
+			/*
+			 * If this was a transaction control statement, commit it.  We will
+			 * start a new xact command for the next command (if any).
+			 */
+			finish_xact_command(true);
+		}
+		else
+		{
+			/*
+			 * We need a CommandCounterIncrement after every query,
+			 * except those that start or end a transaction block.
+			 */
+			CommandCounterIncrement();
+		}
+
+		/* Send appropriate CommandComplete to client */
+		EndCommand(completionTag, dest);
+	}
+	else
+	{
+		/* Portal run not complete, so send PortalSuspended */
+		if (whereToSendOutput == Remote)
+			pq_putemptymessage('s');
+	}
+
+	debug_query_string = NULL;
+}
+
+/*
+ * exec_describe_statement_message
+ *
+ * Process a "Describe" message for a prepared statement
+ */
+static void
+exec_describe_statement_message(const char *stmt_name)
+{
+	PreparedStatement *pstmt;
+	List	   *l;
+	StringInfoData buf;
+
+	/* Find prepared statement */
+	if (stmt_name[0] != '\0')
+		pstmt = FetchPreparedStatement(stmt_name, true);
+	else
+	{
+		/* special-case the unnamed statement */
+		pstmt = unnamed_stmt_pstmt;
+		if (!pstmt)
+			elog(ERROR, "Unnamed prepared statement does not exist");
+	}
+
+	if (whereToSendOutput != Remote)
+		return;					/* can't actually do anything... */
+
+	pq_beginmessage(&buf, 't');		/* parameter description message type */
+	pq_sendint(&buf, length(pstmt->argtype_list), 4);
+
+	foreach(l, pstmt->argtype_list)
+	{
+		Oid			ptype = lfirsto(l);
+
+		pq_sendint(&buf, (int) ptype, 4);
+	}
+	pq_endmessage(&buf);
+}
+
+/*
+ * exec_describe_portal_message
+ *
+ * Process a "Describe" message for a portal
+ */
+static void
+exec_describe_portal_message(const char *portal_name)
+{
+	Portal		portal;
+
+	portal = GetPortalByName(portal_name);
+	if (!PortalIsValid(portal))
+		elog(ERROR, "Portal \"%s\" not found", portal_name);
+
+	if (whereToSendOutput != Remote)
+		return;					/* can't actually do anything... */
+
+	if (portal->tupDesc)
+		SendRowDescriptionMessage(portal->tupDesc);
+	else
+		pq_putemptymessage('n');	/* NoData */
+}
+
+
 /*
  * Convenience routines for starting/committing a single command.
  */
 static void
 start_xact_command(void)
 {
-	elog(DEBUG1, "StartTransactionCommand");
-	StartTransactionCommand(false);
+	if (!xact_started)
+	{
+		elog(DEBUG2, "StartTransactionCommand");
+		StartTransactionCommand(false);
+
+		/* Set statement timeout running, if any */
+		if (StatementTimeout > 0)
+			enable_sig_alarm(StatementTimeout, true);
 
-	/* Set statement timeout running, if any */
-	if (StatementTimeout > 0)
-		enable_sig_alarm(StatementTimeout, true);
+		xact_started = true;
+	}
 }
 
 static void
 finish_xact_command(bool forceCommit)
 {
-	/* Invoke IMMEDIATE constraint triggers */
-	DeferredTriggerEndQuery();
+	if (xact_started)
+	{
+		/* Invoke IMMEDIATE constraint triggers */
+		DeferredTriggerEndQuery();
 
-	/* Cancel any active statement timeout before committing */
-	disable_sig_alarm(true);
+		/* Cancel any active statement timeout before committing */
+		disable_sig_alarm(true);
 
-	/* Now commit the command */
-	elog(DEBUG1, "CommitTransactionCommand");
+		/* Now commit the command */
+		elog(DEBUG2, "CommitTransactionCommand");
 
-	CommitTransactionCommand(forceCommit);
+		CommitTransactionCommand(forceCommit);
 
 #ifdef SHOW_MEMORY_STATS
-	/* Print mem stats at each commit for leak tracking */
-	if (ShowStats)
-		MemoryContextStats(TopMemoryContext);
+		/* Print mem stats at each commit for leak tracking */
+		if (ShowStats)
+			MemoryContextStats(TopMemoryContext);
 #endif
+
+		xact_started = false;
+	}
 }
 
 
@@ -1679,7 +2335,7 @@ PostgresMain(int argc, char *argv[], const char *username)
 	if (!IsUnderPostmaster)
 	{
 		puts("\nPOSTGRES backend interactive interface ");
-		puts("$Revision: 1.331 $ $Date: 2003/05/03 05:13:20 $\n");
+		puts("$Revision: 1.332 $ $Date: 2003/05/05 00:44:56 $\n");
 	}
 
 	/*
@@ -1756,6 +2412,14 @@ PostgresMain(int argc, char *argv[], const char *username)
 		 * successfully.  (Flag was set in elog.c before longjmp().)
 		 */
 		InError = false;
+		xact_started = false;
+
+		/*
+		 * If we were handling an extended-query-protocol message,
+		 * initiate skip till next Sync.
+		 */
+		if (doing_extended_query_message)
+			ignore_till_sync = true;
 
 		/*
 		 * Exit interrupt holdoff section we implicitly established above.
@@ -1775,6 +2439,12 @@ PostgresMain(int argc, char *argv[], const char *username)
 
 	for (;;)
 	{
+		/*
+		 * At top of loop, reset extended-query-message flag, so that
+		 * any errors encountered in "idle" state don't provoke skip.
+		 */
+		doing_extended_query_message = false;
+
 		/*
 		 * Release storage left over from prior query cycle, and create a
 		 * new query input buffer in the cleared MessageContext.
@@ -1853,20 +2523,74 @@ PostgresMain(int argc, char *argv[], const char *username)
 		}
 
 		/*
-		 * (6) process the command.
+		 * (6) process the command.  But ignore it if we're skipping till Sync.
 		 */
+		if (ignore_till_sync)
+			continue;
+
 		switch (firstchar)
 		{
 			case 'Q':			/* simple query */
 				{
-					const char *query_string = pq_getmsgstring(input_message);
+					const char *query_string;
+
+					query_string = pq_getmsgstring(input_message);
+					pq_getmsgend(input_message);
 
-					exec_simple_query(query_string, whereToSendOutput);
+					exec_simple_query(query_string);
 
 					send_rfq = true;
 				}
 				break;
 
+			case 'P':			/* parse */
+				{
+					const char *stmt_name;
+					const char *query_string;
+					int			numParams;
+					Oid		   *paramTypes = NULL;
+
+					stmt_name = pq_getmsgstring(input_message);
+					query_string = pq_getmsgstring(input_message);
+					numParams = pq_getmsgint(input_message, 4);
+					if (numParams > 0)
+					{
+						int		i;
+
+						paramTypes = (Oid *) palloc(numParams * sizeof(Oid));
+						for (i = 0; i < numParams; i++)
+							paramTypes[i] = pq_getmsgint(input_message, 4);
+					}
+					pq_getmsgend(input_message);
+
+					exec_parse_message(query_string, stmt_name,
+									   paramTypes, numParams);
+				}
+				break;
+
+			case 'B':			/* bind */
+				/*
+				 * this message is complex enough that it seems best to put
+				 * the field extraction out-of-line
+				 */
+				exec_bind_message(input_message);
+				break;
+
+			case 'E':			/* execute */
+				{
+					const char *portal_name;
+					int		is_binary;
+					int		max_rows;
+
+					portal_name = pq_getmsgstring(input_message);
+					is_binary = pq_getmsgbyte(input_message);
+					max_rows = pq_getmsgint(input_message, 4);
+					pq_getmsgend(input_message);
+
+					exec_execute_message(portal_name, is_binary, max_rows);
+				}
+				break;
+
 			case 'F':			/* fastpath function call */
 				/* Tell the collector what we're doing */
 				pgstat_report_activity("<FASTPATH> function call");
@@ -1894,6 +2618,89 @@ PostgresMain(int argc, char *argv[], const char *username)
 				send_rfq = true;
 				break;
 
+			case 'C':				/* close */
+				{
+					int		close_type;
+					const char *close_target;
+
+					close_type = pq_getmsgbyte(input_message);
+					close_target = pq_getmsgstring(input_message);
+					pq_getmsgend(input_message);
+
+					switch (close_type)
+					{
+						case 'S':
+							if (close_target[0] != '\0')
+								DropPreparedStatement(close_target, false);
+							else
+							{
+								/* special-case the unnamed statement */
+								unnamed_stmt_pstmt = NULL;
+								if (unnamed_stmt_context)
+								{
+									DropDependentPortals(unnamed_stmt_context);
+									MemoryContextDelete(unnamed_stmt_context);
+								}
+								unnamed_stmt_context = NULL;
+							}
+							break;
+						case 'P':
+							{
+								Portal		portal;
+
+								portal = GetPortalByName(close_target);
+								if (PortalIsValid(portal))
+									PortalDrop(portal, false);
+							}
+							break;
+						default:
+							elog(ERROR, "Invalid Close message subtype %d",
+								 close_type);
+							break;
+					}
+
+					if (whereToSendOutput == Remote)
+						pq_putemptymessage('3'); /* CloseComplete */
+				}
+				break;
+
+			case 'D':			/* describe */
+				{
+					int		describe_type;
+					const char *describe_target;
+
+					describe_type = pq_getmsgbyte(input_message);
+					describe_target = pq_getmsgstring(input_message);
+					pq_getmsgend(input_message);
+
+					switch (describe_type)
+					{
+						case 'S':
+							exec_describe_statement_message(describe_target);
+							break;
+						case 'P':
+							exec_describe_portal_message(describe_target);
+							break;
+						default:
+							elog(ERROR, "Invalid Describe message subtype %d",
+								 describe_type);
+							break;
+					}
+				}
+				break;
+
+			case 'H':				/* flush */
+				pq_getmsgend(input_message);
+				if (whereToSendOutput == Remote)
+					pq_flush();
+				break;
+
+			case 'S':				/* sync */
+				pq_getmsgend(input_message);
+				finish_xact_command(false);
+				send_rfq = true;
+				break;
+
 				/*
 				 * 'X' means that the frontend is closing down the socket.
 				 * EOF means unexpected loss of frontend connection.
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 974e69a2f1a..2fb62f4abae 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.56 2003/05/02 20:54:35 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.57 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -222,11 +222,12 @@ CreateNewPortal(void)
  * PortalDefineQuery
  *		A simple subroutine to establish a portal's query.
  *
- * Notes: the passed commandTag must be a pointer to a constant string,
- * since it is not copied.  The caller is responsible for ensuring that
- * the passed sourceText (if any), parse and plan trees have adequate
- * lifetime.  Also, queryContext must accurately describe the location
- * of the parse and plan trees.
+ * Notes: commandTag shall be NULL if and only if the original query string
+ * (before rewriting) was an empty string.  Also, the passed commandTag must
+ * be a pointer to a constant string, since it is not copied.  The caller is
+ * responsible for ensuring that the passed sourceText (if any), parse and
+ * plan trees have adequate lifetime.  Also, queryContext must accurately
+ * describe the location of the parse and plan trees.
  */
 void
 PortalDefineQuery(Portal portal,
@@ -241,6 +242,8 @@ PortalDefineQuery(Portal portal,
 
 	Assert(length(parseTrees) == length(planTrees));
 
+	Assert(commandTag != NULL || parseTrees == NIL);
+
 	portal->sourceText = sourceText;
 	portal->commandTag = commandTag;
 	portal->parseTrees = parseTrees;
diff --git a/src/include/access/printtup.h b/src/include/access/printtup.h
index 728b0c89906..688a75cd2db 100644
--- a/src/include/access/printtup.h
+++ b/src/include/access/printtup.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: printtup.h,v 1.23 2003/01/21 22:06:12 tgl Exp $
+ * $Id: printtup.h,v 1.24 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,7 +16,9 @@
 
 #include "tcop/dest.h"
 
-extern DestReceiver *printtup_create_DR(bool isBinary);
+extern DestReceiver *printtup_create_DR(bool isBinary, bool sendDescrip);
+
+extern void SendRowDescriptionMessage(TupleDesc typeinfo);
 
 extern void debugSetup(DestReceiver *self, int operation,
 		   const char *portalName, TupleDesc typeinfo);
diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h
index efa60869fa6..f89ac36fa57 100644
--- a/src/include/commands/portalcmds.h
+++ b/src/include/commands/portalcmds.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: portalcmds.h,v 1.8 2003/05/02 20:54:35 tgl Exp $
+ * $Id: portalcmds.h,v 1.9 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,7 +22,7 @@ extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest);
 extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest,
 							   char *completionTag);
 
-extern void PerformPortalClose(char *name);
+extern void PerformPortalClose(const char *name);
 
 extern void PortalCleanup(Portal portal, bool isError);
 
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index aad64166757..5d24c579a40 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -1,12 +1,12 @@
 /*-------------------------------------------------------------------------
  *
  * prepare.h
- *	  PREPARE, EXECUTE and DEALLOCATE command prototypes
+ *	  PREPARE, EXECUTE and DEALLOCATE commands, and prepared-stmt storage
  *
  *
- * Copyright (c) 2002, PostgreSQL Global Development Group
+ * Copyright (c) 2002-2003, PostgreSQL Global Development Group
  *
- * $Id: prepare.h,v 1.3 2003/02/02 23:46:38 tgl Exp $
+ * $Id: prepare.h,v 1.4 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,10 +18,44 @@
 #include "tcop/dest.h"
 
 
+/*
+ * The data structure representing a prepared statement
+ *
+ * Note: all subsidiary storage lives in the context denoted by the context
+ * field.  However, the string referenced by commandTag is not subsidiary
+ * storage; it is assumed to be a compile-time-constant string.  As with
+ * portals, commandTag shall be NULL if and only if the original query string
+ * (before rewriting) was an empty string.
+ */
+typedef struct
+{
+	/* dynahash.c requires key to be first field */
+	char		stmt_name[NAMEDATALEN];
+	char	   *query_string;	/* text of query, or NULL */
+	const char *commandTag;		/* command tag (a constant!), or NULL */
+	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 */
+} PreparedStatement;
+
+
+/* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */
 extern void PrepareQuery(PrepareStmt *stmt);
 extern void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest);
 extern void DeallocateQuery(DeallocateStmt *stmt);
-extern List *FetchQueryParams(const char *plan_name);
 extern void ExplainExecuteQuery(ExplainStmt *stmt, TupOutputState *tstate);
 
+/* Low-level access to stored prepared statements */
+extern void StorePreparedStatement(const char *stmt_name,
+								   const char *query_string,
+								   const char *commandTag,
+								   List *query_list,
+								   List *plan_list,
+								   List *argtype_list);
+extern PreparedStatement *FetchPreparedStatement(const char *stmt_name,
+												 bool throwError);
+extern void DropPreparedStatement(const char *stmt_name, bool showError);
+extern List *FetchPreparedStatementParams(const char *stmt_name);
+
 #endif   /* PREPARE_H */
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index d5374a68fe6..b6961e824cd 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pqcomm.h,v 1.81 2003/04/26 20:22:59 tgl Exp $
+ * $Id: pqcomm.h,v 1.82 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,7 +106,7 @@ typedef union SockAddr
 /* The earliest and latest frontend/backend protocol version supported. */
 
 #define PG_PROTOCOL_EARLIEST	PG_PROTOCOL(1,0)
-#define PG_PROTOCOL_LATEST		PG_PROTOCOL(3,105) /* XXX temporary value */
+#define PG_PROTOCOL_LATEST		PG_PROTOCOL(3,106) /* XXX temporary value */
 
 typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
 
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 39063af6e16..5fbe9d33afe 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -44,7 +44,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: dest.h,v 1.34 2003/04/19 00:02:30 tgl Exp $
+ * $Id: dest.h,v 1.35 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -61,6 +61,10 @@
 /* ----------------
  *		CommandDest is a simplistic means of identifying the desired
  *		destination.  Someday this will probably need to be improved.
+ *
+ * Note: only the values None, Debug, Remote are legal for the global
+ * variable whereToSendOutput.  The other values may be selected
+ * as the destination for individual commands.
  * ----------------
  */
 typedef enum
@@ -71,7 +75,9 @@ typedef enum
 	RemoteInternal,				/* results sent to frontend process in
 								 * internal (binary) form */
 	SPI,						/* results sent to SPI manager */
-	Tuplestore					/* results sent to Tuplestore */
+	Tuplestore,					/* results sent to Tuplestore */
+	RemoteExecute,				/* sent to frontend, in Execute command */
+	RemoteExecuteInternal		/* same, but binary format */
 } CommandDest;
 
 /* ----------------
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index c1fa9c1a6d7..b5e171e1d36 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.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: tcopprot.h,v 1.56 2003/05/02 20:54:36 tgl Exp $
+ * $Id: tcopprot.h,v 1.57 2003/05/05 00:44:56 tgl Exp $
  *
  * OLD COMMENTS
  *	  This file was created so that other c files could get the two
@@ -35,11 +35,12 @@ extern DLLIMPORT const char *debug_query_string;
 
 #ifndef BOOTSTRAP_INCLUDE
 
+extern List *pg_parse_and_rewrite(const char *query_string,
+					 Oid *paramTypes, int numParams);
 extern List *pg_parse_query(const char *query_string);
 extern List *pg_analyze_and_rewrite(Node *parsetree,
 									Oid *paramTypes, int numParams);
-extern List *pg_parse_and_rewrite(const char *query_string,
-					 Oid *paramTypes, int numParams);
+extern List *pg_rewrite_queries(List *querytree_list);
 extern Plan *pg_plan_query(Query *querytree);
 extern List *pg_plan_queries(List *querytrees, bool needSnapshot);
 
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 3bf27da4278..75bcb43433b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.239 2003/04/28 04:52:13 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v 1.240 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -375,6 +375,17 @@ connectOptions1(PGconn *conn, const char *conninfo)
 static bool
 connectOptions2(PGconn *conn)
 {
+	/*
+	 * If database name was not given, default it to equal user name
+	 */
+	if ((conn->dbName == NULL || conn->dbName[0] == '\0')
+		&& conn->pguser != NULL)
+	{
+		if (conn->dbName)
+			free(conn->dbName);
+		conn->dbName = strdup(conn->pguser);
+	}
+
 	/*
 	 * Supply default password if none given
 	 */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index a5e6bceef42..3fcecd63e23 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: libpq-int.h,v 1.66 2003/04/26 20:23:00 tgl Exp $
+ * $Id: libpq-int.h,v 1.67 2003/05/05 00:44:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -56,7 +56,7 @@ typedef int ssize_t;			/* ssize_t doesn't exist in VC (atleast
  * pqcomm.h describe what the backend knows, not what libpq knows.
  */
 
-#define PG_PROTOCOL_LIBPQ	PG_PROTOCOL(3,105) /* XXX temporary value */
+#define PG_PROTOCOL_LIBPQ	PG_PROTOCOL(3,106) /* XXX temporary value */
 
 /*
  * POSTGRES backend dependent Constants.
-- 
GitLab