diff --git a/contrib/intagg/int_aggregate.c b/contrib/intagg/int_aggregate.c
index 1c14e7c2db7b82aeccb8fa0b2baa7cbf8bf7decd..9f4b5d09182c809aeaa38f1b9819ab822009832f 100644
--- a/contrib/intagg/int_aggregate.c
+++ b/contrib/intagg/int_aggregate.c
@@ -77,7 +77,9 @@ PG_FUNCTION_INFO_V1(int_enum);
 
 /*
  * Manage the aggregation state of the array
- * You need to specify the correct memory context, or it will vanish!
+ *
+ * Need to specify a suitably long-lived memory context, or it will vanish!
+ * PortalContext isn't really right, but it's close enough.
  */
 static PGARRAY *
 GetPGArray(int4 state, int fAdd)
@@ -89,14 +91,7 @@ GetPGArray(int4 state, int fAdd)
 		/* New array */
 		int			cb = PGARRAY_SIZE(START_NUM);
 
-		p = (PGARRAY *) MemoryContextAlloc(TopTransactionContext, cb);
-
-		if (!p)
-		{
-			elog(ERROR, "Integer aggregator, cant allocate TopTransactionContext memory");
-			return 0;
-		}
-
+		p = (PGARRAY *) MemoryContextAlloc(PortalContext, cb);
 		p->a.size = cb;
 		p->a.ndim = 0;
 		p->a.flags = 0;
@@ -115,18 +110,6 @@ GetPGArray(int4 state, int fAdd)
 			int			cbNew = PGARRAY_SIZE(n);
 
 			pn = (PGARRAY *) repalloc(p, cbNew);
-
-			if (!pn)
-			{					/* Realloc failed! Reallocate new block. */
-				pn = (PGARRAY *) MemoryContextAlloc(TopTransactionContext, cbNew);
-				if (!pn)
-				{
-					elog(ERROR, "Integer aggregator, REALLY REALLY can't alloc memory");
-					return (PGARRAY *) NULL;
-				}
-				memcpy(pn, p, p->a.size);
-				pfree(p);
-			}
 			pn->a.size = cbNew;
 			pn->lower = n;
 			return pn;
@@ -149,24 +132,19 @@ ShrinkPGArray(PGARRAY * p)
 
 		/* use current transaction context */
 		pnew = palloc(cb);
-
-		if (pnew)
-		{
-			/*
-			 * Fix up the fields in the new structure, so Postgres
-			 * understands
-			 */
-			memcpy(pnew, p, cb);
-			pnew->a.size = cb;
-			pnew->a.ndim = 1;
-			pnew->a.flags = 0;
+		/*
+		 * Fix up the fields in the new structure, so Postgres
+		 * understands
+		 */
+		memcpy(pnew, p, cb);
+		pnew->a.size = cb;
+		pnew->a.ndim = 1;
+		pnew->a.flags = 0;
 #ifndef PG_7_2
-			pnew->a.elemtype = INT4OID;
+		pnew->a.elemtype = INT4OID;
 #endif
-			pnew->lower = 0;
-		}
-		else
-			elog(ERROR, "Integer aggregator, can't allocate memory");
+		pnew->lower = 0;
+
 		pfree(p);
 	}
 	return pnew;
diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml
index 6532cc1c8942c59cba93930ffecf635c964445ed..e09c523b1ab2b236b3ef30a62040a72b40ed803c 100644
--- a/doc/src/sgml/ref/postgres-ref.sgml
+++ b/doc/src/sgml/ref/postgres-ref.sgml
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/postgres-ref.sgml,v 1.32 2003/03/25 16:15:43 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/postgres-ref.sgml,v 1.33 2003/05/02 20:54:33 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -28,7 +28,6 @@ PostgreSQL documentation
    <arg>-E</arg>
    <arg>-f<group choice="plain"><arg>s</arg><arg>i</arg><arg>t</arg><arg>n</arg><arg>m</arg><arg>h</arg></group></arg>
    <arg>-F</arg>
-   <arg>-i</arg>
    <arg>-N</arg>
    <arg>-o <replaceable>filename</replaceable></arg>
    <arg>-O</arg>
@@ -52,7 +51,6 @@ PostgreSQL documentation
    <arg>-e</arg>
    <arg>-f<group choice="plain"><arg>s</arg><arg>i</arg><arg>t</arg><arg>n</arg><arg>m</arg><arg>h</arg></group></arg>
    <arg>-F</arg>
-   <arg>-i</arg>
    <arg>-o <replaceable>filename</replaceable></arg>
    <arg>-O</arg>
    <arg>-p <replaceable>database</replaceable></arg>
@@ -281,15 +279,6 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
-     <varlistentry>
-      <term><option>-i</option></term>
-      <listitem>
-       <para>
-	Prevents query execution, but shows the plan tree.
-       </para>
-      </listitem>
-     </varlistentry>
-
      <varlistentry>
       <term><option>-O</option></term>
       <listitem>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index a15985d3bd787f808142ef00283c5b39f4ccf511..59b43656d5f049db763f24f68417c70cac962562 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.146 2003/04/26 20:22:59 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.147 2003/05/02 20:54:33 tgl Exp $
  *
  * NOTES
  *		Transaction aborts can now occur two ways:
@@ -49,7 +49,7 @@
  *		* CleanupTransaction() executes when we finally see a user COMMIT
  *		  or ROLLBACK command; it cleans things up and gets us out of
  *		  the transaction internally.  In particular, we mustn't destroy
- *		  TransactionCommandContext until this point.
+ *		  TopTransactionContext until this point.
  *
  *	 NOTES
  *		The essential aspects of the transaction system are:
@@ -456,13 +456,12 @@ static void
 AtStart_Memory(void)
 {
 	/*
-	 * We shouldn't have any transaction contexts already.
+	 * We shouldn't have a transaction context already.
 	 */
 	Assert(TopTransactionContext == NULL);
-	Assert(TransactionCommandContext == NULL);
 
 	/*
-	 * Create a toplevel context for the transaction.
+	 * Create a toplevel context for the transaction, and make it active.
 	 */
 	TopTransactionContext =
 		AllocSetContextCreate(TopMemoryContext,
@@ -471,16 +470,7 @@ AtStart_Memory(void)
 							  ALLOCSET_DEFAULT_INITSIZE,
 							  ALLOCSET_DEFAULT_MAXSIZE);
 
-	/*
-	 * Create a statement-level context and make it active.
-	 */
-	TransactionCommandContext =
-		AllocSetContextCreate(TopTransactionContext,
-							  "TransactionCommandContext",
-							  ALLOCSET_DEFAULT_MINSIZE,
-							  ALLOCSET_DEFAULT_INITSIZE,
-							  ALLOCSET_DEFAULT_MAXSIZE);
-	MemoryContextSwitchTo(TransactionCommandContext);
+	MemoryContextSwitchTo(TopTransactionContext);
 }
 
 
@@ -659,7 +649,6 @@ AtCommit_Memory(void)
 	Assert(TopTransactionContext != NULL);
 	MemoryContextDelete(TopTransactionContext);
 	TopTransactionContext = NULL;
-	TransactionCommandContext = NULL;
 }
 
 /* ----------------------------------------------------------------
@@ -769,19 +758,18 @@ AtAbort_Memory(void)
 {
 	/*
 	 * Make sure we are in a valid context (not a child of
-	 * TransactionCommandContext...).  Note that it is possible for this
+	 * TopTransactionContext...).  Note that it is possible for this
 	 * code to be called when we aren't in a transaction at all; go
 	 * directly to TopMemoryContext in that case.
 	 */
-	if (TransactionCommandContext != NULL)
+	if (TopTransactionContext != NULL)
 	{
-		MemoryContextSwitchTo(TransactionCommandContext);
+		MemoryContextSwitchTo(TopTransactionContext);
 
 		/*
-		 * We do not want to destroy transaction contexts yet, but it
-		 * should be OK to delete any command-local memory.
+		 * We do not want to destroy the transaction's global state yet,
+		 * so we can't free any memory here.
 		 */
-		MemoryContextResetAndDeleteChildren(TransactionCommandContext);
 	}
 	else
 		MemoryContextSwitchTo(TopMemoryContext);
@@ -812,7 +800,6 @@ AtCleanup_Memory(void)
 	if (TopTransactionContext != NULL)
 		MemoryContextDelete(TopTransactionContext);
 	TopTransactionContext = NULL;
-	TransactionCommandContext = NULL;
 }
 
 
@@ -926,7 +913,7 @@ CommitTransaction(void)
 	 * access, and in fact could still cause an error...)
 	 */
 
-	AtEOXact_portals(true);
+	AtCommit_Portals();
 
 	/* handle commit for large objects [ PA, 7/17/98 ] */
 	/* XXX probably this does not belong here */
@@ -1057,7 +1044,7 @@ AbortTransaction(void)
 	 * do abort processing
 	 */
 	DeferredTriggerAbortXact();
-	AtEOXact_portals(false);
+	AtAbort_Portals();
 	lo_commit(false);			/* 'false' means it's abort */
 	AtAbort_Notify();
 	AtEOXact_UpdatePasswordFile(false);
@@ -1126,7 +1113,8 @@ CleanupTransaction(void)
 	/*
 	 * do abort cleanup processing
 	 */
-	AtCleanup_Memory();
+	AtCleanup_Portals();		/* now safe to release portal memory */
+	AtCleanup_Memory();			/* and transaction memory */
 
 	/*
 	 * done with abort processing, set current transaction state back to
@@ -1220,11 +1208,11 @@ StartTransactionCommand(bool preventChain)
 	}
 
 	/*
-	 * We must switch to TransactionCommandContext before returning. This
+	 * We must switch to TopTransactionContext before returning. This
 	 * is already done if we called StartTransaction, otherwise not.
 	 */
-	Assert(TransactionCommandContext != NULL);
-	MemoryContextSwitchTo(TransactionCommandContext);
+	Assert(TopTransactionContext != NULL);
+	MemoryContextSwitchTo(TopTransactionContext);
 }
 
 /*
@@ -1266,7 +1254,6 @@ CommitTransactionCommand(bool forceCommit)
 				Assert(s->blockState == TBLOCK_INPROGRESS);
 				/* This code must match the TBLOCK_INPROGRESS case below: */
 				CommandCounterIncrement();
-				MemoryContextResetAndDeleteChildren(TransactionCommandContext);
 			}
 			break;
 
@@ -1283,15 +1270,10 @@ CommitTransactionCommand(bool forceCommit)
 			/*
 			 * This is the case when we have finished executing a command
 			 * someplace within a transaction block.  We increment the
-			 * command counter and return.	Someday we may free resources
-			 * local to the command.
-			 *
-			 * That someday is today, at least for memory allocated in
-			 * TransactionCommandContext. - vadim 03/25/97
+			 * command counter and return.
 			 */
 		case TBLOCK_INPROGRESS:
 			CommandCounterIncrement();
-			MemoryContextResetAndDeleteChildren(TransactionCommandContext);
 			break;
 
 			/*
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 0c1a7d3c70c2d588ffa6c6208c95d40c4126ec3e..6c01094c5a9f74a7700e6dcd777e54de36f88619 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.107 2003/03/20 03:34:55 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.108 2003/05/02 20:54:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -189,10 +189,10 @@ cluster(ClusterStmt *stmt)
 		/*
 		 * Create special memory context for cross-transaction storage.
 		 *
-		 * Since it is a child of QueryContext, it will go away even in case
+		 * Since it is a child of PortalContext, it will go away even in case
 		 * of error.
 		 */
-		cluster_context = AllocSetContextCreate(QueryContext,
+		cluster_context = AllocSetContextCreate(PortalContext,
 												"Cluster",
 												ALLOCSET_DEFAULT_MINSIZE,
 												ALLOCSET_DEFAULT_INITSIZE,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 10d240d6d148ba0d9e5593796dd24d0f29b3471c..5232faadc5c952c4cf721c0053650444e984d139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.97 2003/01/23 15:18:40 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.98 2003/05/02 20:54:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -720,11 +720,11 @@ ReindexDatabase(const char *dbname, bool force, bool all)
 
 	/*
 	 * Create a memory context that will survive forced transaction
-	 * commits we do below.  Since it is a child of QueryContext, it will
+	 * commits we do below.  Since it is a child of PortalContext, it will
 	 * go away eventually even if we suffer an error; there's no need for
 	 * special abort cleanup logic.
 	 */
-	private_context = AllocSetContextCreate(QueryContext,
+	private_context = AllocSetContextCreate(PortalContext,
 											"ReindexDatabase",
 											ALLOCSET_DEFAULT_MINSIZE,
 											ALLOCSET_DEFAULT_INITSIZE,
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 9dc7583f06c70bbaa1eab90e4895cbcacefcdef4..35ed8a270b5a475f090a418824c7c8aba800408d 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -1,14 +1,20 @@
 /*-------------------------------------------------------------------------
  *
  * portalcmds.c
- *	  portal support code
+ *	  Utility commands affecting portals (that is, SQL cursor commands)
+ *
+ * Note: see also tcop/pquery.c, which implements portal operations for
+ * the FE/BE protocol.  This module uses pquery.c for some operations.
+ * And both modules depend on utils/mmgr/portalmem.c, which controls
+ * storage management for portals (but doesn't run any queries in them).
+ * 
  *
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.12 2003/04/29 03:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.13 2003/05/02 20:54:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,18 +28,10 @@
 #include "executor/executor.h"
 #include "optimizer/planner.h"
 #include "rewrite/rewriteHandler.h"
+#include "tcop/pquery.h"
 #include "utils/memutils.h"
 
 
-static long DoRelativeFetch(Portal portal,
-							bool forward,
-							long count,
-							CommandDest dest);
-static uint32 RunFromStore(Portal portal, ScanDirection direction, long count);
-static void DoPortalRewind(Portal portal);
-static Portal PreparePortal(DeclareCursorStmt *stmt);
-
-
 /*
  * PerformCursorOpen
  *		Execute SQL DECLARE CURSOR command.
@@ -46,8 +44,13 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
 	Plan	   *plan;
 	Portal		portal;
 	MemoryContext oldContext;
-	char	   *cursorName;
-	QueryDesc  *queryDesc;
+
+	/*
+	 * Disallow empty-string cursor name (conflicts with protocol-level
+	 * unnamed portal).
+	 */
+	if (strlen(stmt->portalname) == 0)
+		elog(ERROR, "Invalid cursor name: must not be empty");
 
 	/*
 	 * If this is a non-holdable cursor, we require that this statement
@@ -77,38 +80,53 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
 
 	plan = planner(query, true, stmt->options);
 
-	/* If binary cursor, switch to alternate output format */
-	if ((stmt->options & CURSOR_OPT_BINARY) && dest == Remote)
-		dest = RemoteInternal;
-
 	/*
 	 * Create a portal and copy the query and plan into its memory context.
+	 * (If a duplicate cursor name already exists, warn and drop it.)
 	 */
-	portal = PreparePortal(stmt);
+	portal = CreatePortal(stmt->portalname, true, false);
 
 	oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+
 	query = copyObject(query);
 	plan = copyObject(plan);
 
+	PortalDefineQuery(portal,
+					  NULL,		/* unfortunately don't have sourceText */
+					  "SELECT",	/* cursor's query is always a SELECT */
+					  makeList1(query),
+					  makeList1(plan),
+					  PortalGetHeapMemory(portal));
+
+	MemoryContextSwitchTo(oldContext);
+
 	/*
-	 * Create the QueryDesc object in the portal context, too.
+	 * Set up options for portal.
+	 *
+	 * If the user didn't specify a SCROLL type, allow or disallow
+	 * scrolling based on whether it would require any additional
+	 * runtime overhead to do so.
 	 */
-	cursorName = pstrdup(stmt->portalname);
-	queryDesc = CreateQueryDesc(query, plan, dest, cursorName, NULL, false);
+	portal->cursorOptions = stmt->options;
+	if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
+	{
+		if (ExecSupportsBackwardScan(plan))
+			portal->cursorOptions |= CURSOR_OPT_SCROLL;
+		else
+			portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
+	}
 
 	/*
-	 * call ExecStart to prepare the plan for execution
+	 * Start execution --- never any params for a cursor.
 	 */
-	ExecutorStart(queryDesc);
+	PortalStart(portal, NULL);
 
-	/* Arrange to shut down the executor if portal is dropped */
-	PortalSetQuery(portal, queryDesc);
+	Assert(portal->strategy == PORTAL_ONE_SELECT);
 
 	/*
 	 * We're done; the query won't actually be run until PerformPortalFetch
 	 * is called.
 	 */
-	MemoryContextSwitchTo(oldContext);
 }
 
 /*
@@ -130,10 +148,6 @@ PerformPortalFetch(FetchStmt *stmt,
 	Portal		portal;
 	long		nprocessed;
 
-	/* initialize completion status in case of early exit */
-	if (completionTag)
-		strcpy(completionTag, stmt->ismove ? "MOVE 0" : "FETCH 0");
-
 	/* get the portal from the portal name */
 	portal = GetPortalByName(stmt->portalname);
 	if (!PortalIsValid(portal))
@@ -141,14 +155,27 @@ PerformPortalFetch(FetchStmt *stmt,
 		/* FIXME: shouldn't this be an ERROR? */
 		elog(WARNING, "PerformPortalFetch: portal \"%s\" not found",
 			 stmt->portalname);
+		if (completionTag)
+			strcpy(completionTag, stmt->ismove ? "MOVE 0" : "FETCH 0");
 		return;
 	}
 
+	/*
+	 * Adjust dest if needed.  MOVE wants dest = None.
+	 *
+	 * If fetching from a binary cursor and the requested destination is
+	 * Remote, change it to RemoteInternal.
+	 */
+	if (stmt->ismove)
+		dest = None;
+	else if (dest == Remote && (portal->cursorOptions & CURSOR_OPT_BINARY))
+		dest = RemoteInternal;
+
 	/* Do it */
-	nprocessed = DoPortalFetch(portal,
-							   stmt->direction,
-							   stmt->howMany,
-							   stmt->ismove ? None : dest);
+	nprocessed = PortalRunFetch(portal,
+								stmt->direction,
+								stmt->howMany,
+								dest);
 
 	/* Return command status if wanted */
 	if (completionTag)
@@ -157,416 +184,6 @@ PerformPortalFetch(FetchStmt *stmt,
 				 nprocessed);
 }
 
-/*
- * DoPortalFetch
- *		Guts of PerformPortalFetch --- shared with SPI cursor operations.
- *		Caller must already have validated the Portal.
- *
- * Returns number of rows processed (suitable for use in result tag)
- */
-long
-DoPortalFetch(Portal portal,
-			  FetchDirection fdirection,
-			  long count,
-			  CommandDest dest)
-{
-	bool		forward;
-
-	switch (fdirection)
-	{
-		case FETCH_FORWARD:
-			if (count < 0)
-			{
-				fdirection = FETCH_BACKWARD;
-				count = -count;
-			}
-			/* fall out of switch to share code with FETCH_BACKWARD */
-			break;
-		case FETCH_BACKWARD:
-			if (count < 0)
-			{
-				fdirection = FETCH_FORWARD;
-				count = -count;
-			}
-			/* fall out of switch to share code with FETCH_FORWARD */
-			break;
-		case FETCH_ABSOLUTE:
-			if (count > 0)
-			{
-				/*
-				 * Definition: Rewind to start, advance count-1 rows, return
-				 * next row (if any).  In practice, if the goal is less than
-				 * halfway back to the start, it's better to scan from where
-				 * we are.  In any case, we arrange to fetch the target row
-				 * going forwards.
-				 */
-				if (portal->posOverflow || portal->portalPos == LONG_MAX ||
-					count-1 <= portal->portalPos / 2)
-				{
-					DoPortalRewind(portal);
-					if (count > 1)
-						DoRelativeFetch(portal, true, count-1, None);
-				}
-				else
-				{
-					long		pos = portal->portalPos;
-
-					if (portal->atEnd)
-						pos++;	/* need one extra fetch if off end */
-					if (count <= pos)
-						DoRelativeFetch(portal, false, pos-count+1, None);
-					else if (count > pos+1)
-						DoRelativeFetch(portal, true, count-pos-1, None);
-				}
-				return DoRelativeFetch(portal, true, 1L, dest);
-			}
-			else if (count < 0)
-			{
-				/*
-				 * Definition: Advance to end, back up abs(count)-1 rows,
-				 * return prior row (if any).  We could optimize this if we
-				 * knew in advance where the end was, but typically we won't.
-				 * (Is it worth considering case where count > half of size
-				 * of query?  We could rewind once we know the size ...)
-				 */
-				DoRelativeFetch(portal, true, FETCH_ALL, None);
-				if (count < -1)
-					DoRelativeFetch(portal, false, -count-1, None);
-				return DoRelativeFetch(portal, false, 1L, dest);
-			}
-			else /* count == 0 */
-			{
-				/* Rewind to start, return zero rows */
-				DoPortalRewind(portal);
-				return DoRelativeFetch(portal, true, 0L, dest);
-			}
-			break;
-		case FETCH_RELATIVE:
-			if (count > 0)
-			{
-				/*
-				 * Definition: advance count-1 rows, return next row (if any).
-				 */
-				if (count > 1)
-					DoRelativeFetch(portal, true, count-1, None);
-				return DoRelativeFetch(portal, true, 1L, dest);
-			}
-			else if (count < 0)
-			{
-				/*
-				 * Definition: back up abs(count)-1 rows, return prior row
-				 * (if any).
-				 */
-				if (count < -1)
-					DoRelativeFetch(portal, false, -count-1, None);
-				return DoRelativeFetch(portal, false, 1L, dest);
-			}
-			else /* count == 0 */
-			{
-				/* Same as FETCH FORWARD 0, so fall out of switch */
-				fdirection = FETCH_FORWARD;
-			}
-			break;
-		default:
-			elog(ERROR, "DoPortalFetch: bogus direction");
-			break;
-	}
-
-	/*
-	 * Get here with fdirection == FETCH_FORWARD or FETCH_BACKWARD,
-	 * and count >= 0.
-	 */
-	forward = (fdirection == FETCH_FORWARD);
-
-	/*
-	 * Zero count means to re-fetch the current row, if any (per SQL92)
-	 */
-	if (count == 0)
-	{
-		bool	on_row;
-
-		/* Are we sitting on a row? */
-		on_row = (!portal->atStart && !portal->atEnd);
-
-		if (dest == None)
-		{
-			/* MOVE 0 returns 0/1 based on if FETCH 0 would return a row */
-			return on_row ? 1L : 0L;
-		}
-		else
-		{
-			/*
-			 * If we are sitting on a row, back up one so we can re-fetch it.
-			 * If we are not sitting on a row, we still have to start up and
-			 * shut down the executor so that the destination is initialized
-			 * and shut down correctly; so keep going.  To DoRelativeFetch,
-			 * count == 0 means we will retrieve no row.
-			 */
-			if (on_row)
-			{
-				DoRelativeFetch(portal, false, 1L, None);
-				/* Set up to fetch one row forward */
-				count = 1;
-				forward = true;
-			}
-		}
-	}
-
-	/*
-	 * Optimize MOVE BACKWARD ALL into a Rewind.
-	 */
-	if (!forward && count == FETCH_ALL && dest == None)
-	{
-		long	result = portal->portalPos;
-
-		if (result > 0 && !portal->atEnd)
-			result--;
-		DoPortalRewind(portal);
-		/* result is bogus if pos had overflowed, but it's best we can do */
-		return result;
-	}
-
-	return DoRelativeFetch(portal, forward, count, dest);
-}
-
-/*
- * DoRelativeFetch
- *		Do fetch for a simple N-rows-forward-or-backward case.
- *
- * count <= 0 is interpreted as a no-op: the destination gets started up
- * and shut down, but nothing else happens.  Also, count == FETCH_ALL is
- * interpreted as "all rows".
- *
- * Caller must already have validated the Portal.
- *
- * Returns number of rows processed (suitable for use in result tag)
- */
-static long
-DoRelativeFetch(Portal portal,
-				bool forward,
-				long count,
-				CommandDest dest)
-{
-	QueryDesc  *queryDesc;
-	QueryDesc	temp_queryDesc;
-	ScanDirection direction;
-	uint32		nprocessed;
-
-	queryDesc = PortalGetQueryDesc(portal);
-
-	/*
-	 * If the requested destination is not the same as the query's
-	 * original destination, make a temporary QueryDesc with the proper
-	 * destination.  This supports MOVE, for example, which will pass in
-	 * dest = None.
-	 *
-	 * EXCEPTION: if the query's original dest is RemoteInternal (ie, it's a
-	 * binary cursor) and the request is Remote, we do NOT override the
-	 * original dest.  This is necessary since a FETCH command will pass
-	 * dest = Remote, not knowing whether the cursor is binary or not.
-	 */
-	if (dest != queryDesc->dest &&
-		!(queryDesc->dest == RemoteInternal && dest == Remote))
-	{
-		memcpy(&temp_queryDesc, queryDesc, sizeof(QueryDesc));
-		temp_queryDesc.dest = dest;
-		queryDesc = &temp_queryDesc;
-	}
-
-	/*
-	 * Determine which direction to go in, and check to see if we're
-	 * already at the end of the available tuples in that direction.  If
-	 * so, set the direction to NoMovement to avoid trying to fetch any
-	 * tuples.	(This check exists because not all plan node types are
-	 * robust about being called again if they've already returned NULL
-	 * once.)  Then call the executor (we must not skip this, because the
-	 * destination needs to see a setup and shutdown even if no tuples are
-	 * available).	Finally, update the portal position state depending on
-	 * the number of tuples that were retrieved.
-	 */
-	if (forward)
-	{
-		if (portal->atEnd || count <= 0)
-			direction = NoMovementScanDirection;
-		else
-			direction = ForwardScanDirection;
-
-		/* In the executor, zero count processes all rows */
-		if (count == FETCH_ALL)
-			count = 0;
-
-		if (portal->holdStore)
-			nprocessed = RunFromStore(portal, direction, count);
-		else
-		{
-			Assert(portal->executorRunning);
-			ExecutorRun(queryDesc, direction, count);
-			nprocessed = queryDesc->estate->es_processed;
-		}
-
-		if (direction != NoMovementScanDirection)
-		{
-			long	oldPos;
-
-			if (nprocessed > 0)
-				portal->atStart = false;	/* OK to go backward now */
-			if (count == 0 ||
-				(unsigned long) nprocessed < (unsigned long) count)
-				portal->atEnd = true;		/* we retrieved 'em all */
-			oldPos = portal->portalPos;
-			portal->portalPos += nprocessed;
-			/* portalPos doesn't advance when we fall off the end */
-			if (portal->portalPos < oldPos)
-				portal->posOverflow = true;
-		}
-	}
-	else
-	{
-		if (portal->scrollType == DISABLE_SCROLL)
-			elog(ERROR, "Cursor can only scan forward"
-				 "\n\tDeclare it with SCROLL option to enable backward scan");
-
-		if (portal->atStart || count <= 0)
-			direction = NoMovementScanDirection;
-		else
-			direction = BackwardScanDirection;
-
-		/* In the executor, zero count processes all rows */
-		if (count == FETCH_ALL)
-			count = 0;
-
-		if (portal->holdStore)
-			nprocessed = RunFromStore(portal, direction, count);
-		else
-		{
-			Assert(portal->executorRunning);
-			ExecutorRun(queryDesc, direction, count);
-			nprocessed = queryDesc->estate->es_processed;
-		}
-
-		if (direction != NoMovementScanDirection)
-		{
-			if (nprocessed > 0 && portal->atEnd)
-			{
-				portal->atEnd = false;		/* OK to go forward now */
-				portal->portalPos++;		/* adjust for endpoint case */
-			}
-			if (count == 0 ||
-				(unsigned long) nprocessed < (unsigned long) count)
-			{
-				portal->atStart = true;		/* we retrieved 'em all */
-				portal->portalPos = 0;
-				portal->posOverflow = false;
-			}
-			else
-			{
-				long	oldPos;
-
-				oldPos = portal->portalPos;
-				portal->portalPos -= nprocessed;
-				if (portal->portalPos > oldPos ||
-					portal->portalPos <= 0)
-					portal->posOverflow = true;
-			}
-		}
-	}
-
-	return nprocessed;
-}
-
-/*
- * RunFromStore
- *		Fetch tuples from the portal's tuple store.
- *
- * Calling conventions are similar to ExecutorRun, except that we
- * do not depend on having an estate, and therefore return the number
- * of tuples processed as the result, not in estate->es_processed.
- *
- * One difference from ExecutorRun is that the destination receiver functions
- * are run in the caller's memory context (since we have no estate).  Watch
- * out for memory leaks.
- */
-static uint32
-RunFromStore(Portal portal, ScanDirection direction, long count)
-{
-	QueryDesc *queryDesc = PortalGetQueryDesc(portal);
-	DestReceiver *destfunc;
-	long		current_tuple_count = 0;
-
-	destfunc = DestToFunction(queryDesc->dest);
-	(*destfunc->setup) (destfunc, queryDesc->operation,
-						queryDesc->portalName, queryDesc->tupDesc);
-
-	if (direction == NoMovementScanDirection)
-	{
-		/* do nothing except start/stop the destination */
-	}
-	else
-	{
-		bool	forward = (direction == ForwardScanDirection);
-
-		for (;;)
-		{
-			MemoryContext oldcontext;
-			HeapTuple tup;
-			bool should_free;
-
-			oldcontext = MemoryContextSwitchTo(portal->holdContext);
-
-			tup = tuplestore_getheaptuple(portal->holdStore, forward,
-										  &should_free);
-
-			MemoryContextSwitchTo(oldcontext);
-
-			if (tup == NULL)
-				break;
-
-			(*destfunc->receiveTuple) (tup, queryDesc->tupDesc, destfunc);
-
-			if (should_free)
-				pfree(tup);
-
-			/*
-			 * check our tuple count.. if we've processed the proper number
-			 * then quit, else loop again and process more tuples.  Zero
-			 * count means no limit.
-			 */
-			current_tuple_count++;
-			if (count && count == current_tuple_count)
-				break;
-		}
-	}
-
-	(*destfunc->cleanup) (destfunc);
-
-	return (uint32) current_tuple_count;
-}
-
-/*
- * DoPortalRewind - rewind a Portal to starting point
- */
-static void
-DoPortalRewind(Portal portal)
-{
-	if (portal->holdStore)
-	{
-		MemoryContext oldcontext;
-
-		oldcontext = MemoryContextSwitchTo(portal->holdContext);
-		tuplestore_rescan(portal->holdStore);
-		MemoryContextSwitchTo(oldcontext);
-	}
-	if (portal->executorRunning)
-	{
-		ExecutorRewind(PortalGetQueryDesc(portal));
-	}
-
-	portal->atStart = true;
-	portal->atEnd = false;
-	portal->portalPos = 0;
-	portal->posOverflow = false;
-}
-
 /*
  * PerformPortalClose
  *		Close a cursor.
@@ -593,53 +210,6 @@ PerformPortalClose(char *name)
 	PortalDrop(portal, false);
 }
 
-/*
- * PreparePortal
- *		Given a DECLARE CURSOR statement, returns the Portal data
- *		structure based on that statement that is used to manage the
- *		Portal internally. If a portal with specified name already
- *		exists, it is replaced.
- */
-static Portal
-PreparePortal(DeclareCursorStmt *stmt)
-{
-	Portal		portal;
-
-	/*
-	 * Check for already-in-use portal name.
-	 */
-	portal = GetPortalByName(stmt->portalname);
-	if (PortalIsValid(portal))
-	{
-		/*
-		 * XXX Should we raise an error rather than closing the old
-		 * portal?
-		 */
-		elog(WARNING, "Closing pre-existing portal \"%s\"",
-			 stmt->portalname);
-		PortalDrop(portal, false);
-	}
-
-	/*
-	 * Create the new portal.
-	 */
-	portal = CreatePortal(stmt->portalname);
-
-	/*
-	 * Modify the newly created portal based on the options specified in
-	 * the DECLARE CURSOR statement.
-	 */
-	if (stmt->options & CURSOR_OPT_SCROLL)
-		portal->scrollType = ENABLE_SCROLL;
-	else if (stmt->options & CURSOR_OPT_NO_SCROLL)
-		portal->scrollType = DISABLE_SCROLL;
-
-	if (stmt->options & CURSOR_OPT_HOLD)
-		portal->holdOpen = true;
-
-	return portal;
-}
-
 /*
  * PortalCleanup
  *
@@ -649,6 +219,8 @@ PreparePortal(DeclareCursorStmt *stmt)
 void
 PortalCleanup(Portal portal, bool isError)
 {
+	QueryDesc  *queryDesc;
+
 	/*
 	 * sanity checks
 	 */
@@ -658,7 +230,8 @@ PortalCleanup(Portal portal, bool isError)
 	/*
 	 * Delete tuplestore if present.  (Note: portalmem.c is responsible
 	 * for removing holdContext.)  We should do this even under error
-	 * conditions.
+	 * conditions; since the tuplestore would have been using cross-
+	 * transaction storage, its temp files need to be explicitly deleted.
 	 */
 	if (portal->holdStore)
 	{
@@ -674,11 +247,12 @@ PortalCleanup(Portal portal, bool isError)
 	 * abort, since other mechanisms will take care of releasing executor
 	 * resources, and we can't be sure that ExecutorEnd itself wouldn't fail.
 	 */
-	if (portal->executorRunning)
+	queryDesc = PortalGetQueryDesc(portal);
+	if (queryDesc)
 	{
-		portal->executorRunning = false;
+		portal->queryDesc = NULL;
 		if (!isError)
-			ExecutorEnd(PortalGetQueryDesc(portal));
+			ExecutorEnd(queryDesc);
 	}
 }
 
@@ -694,9 +268,10 @@ void
 PersistHoldablePortal(Portal portal)
 {
 	QueryDesc *queryDesc = PortalGetQueryDesc(portal);
+	Portal		saveCurrentPortal;
+	MemoryContext savePortalContext;
+	MemoryContext saveQueryContext;
 	MemoryContext oldcxt;
-	CommandDest olddest;
-	TupleDesc	tupdesc;
 
 	/*
 	 * If we're preserving a holdable portal, we had better be
@@ -704,6 +279,9 @@ PersistHoldablePortal(Portal portal)
 	 */
 	Assert(portal->createXact == GetCurrentTransactionId());
 	Assert(portal->holdStore == NULL);
+	Assert(queryDesc != NULL);
+	Assert(portal->portalReady);
+	Assert(!portal->portalDone);
 
 	/*
 	 * This context is used to store the tuple set.
@@ -715,6 +293,34 @@ PersistHoldablePortal(Portal portal)
 	/* XXX: Should SortMem be used for this? */
 	portal->holdStore = tuplestore_begin_heap(true, true, SortMem);
 
+	/*
+	 * Before closing down the executor, we must copy the tupdesc, since
+	 * it was created in executor memory.  Note we are copying it into
+	 * the holdContext.
+	 */
+	portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
+
+	MemoryContextSwitchTo(oldcxt);
+
+	/*
+	 * Check for improper portal use, and mark portal active.
+	 */
+	if (portal->portalActive)
+		elog(ERROR, "Portal \"%s\" already active", portal->name);
+	portal->portalActive = true;
+
+	/*
+	 * Set global portal and context pointers.
+	 */
+	saveCurrentPortal = CurrentPortal;
+	CurrentPortal = portal;
+	savePortalContext = PortalContext;
+	PortalContext = PortalGetHeapMemory(portal);
+	saveQueryContext = QueryContext;
+	QueryContext = portal->queryContext;
+
+	MemoryContextSwitchTo(PortalContext);
+
 	/*
 	 * Rewind the executor: we need to store the entire result set in
 	 * the tuplestore, so that subsequent backward FETCHs can be
@@ -723,40 +329,40 @@ PersistHoldablePortal(Portal portal)
 	ExecutorRewind(queryDesc);
 
 	/* Set the destination to output to the tuplestore */
-	olddest = queryDesc->dest;
 	queryDesc->dest = Tuplestore;
 
 	/* Fetch the result set into the tuplestore */
 	ExecutorRun(queryDesc, ForwardScanDirection, 0L);
 
-	queryDesc->dest = olddest;
-
-	/*
-	 * Before closing down the executor, we must copy the tupdesc, since
-	 * it was created in executor memory.
-	 */
-	tupdesc = CreateTupleDescCopy(queryDesc->tupDesc);
-
 	/*
 	 * Now shut down the inner executor.
 	 */
-	portal->executorRunning = false;
+	portal->queryDesc = NULL;	/* prevent double shutdown */
 	ExecutorEnd(queryDesc);
 
-	/* ExecutorEnd clears this, so must wait to save copied pointer */
-	queryDesc->tupDesc = tupdesc;
+	/* Mark portal not active */
+	portal->portalActive = false;
+
+	CurrentPortal = saveCurrentPortal;
+	PortalContext = savePortalContext;
+	QueryContext = saveQueryContext;
 
 	/*
 	 * Reset the position in the result set: ideally, this could be
 	 * implemented by just skipping straight to the tuple # that we need
 	 * to be at, but the tuplestore API doesn't support that. So we
 	 * start at the beginning of the tuplestore and iterate through it
-	 * until we reach where we need to be.
+	 * until we reach where we need to be.  FIXME someday?
 	 */
+	MemoryContextSwitchTo(portal->holdContext);
+
 	if (!portal->atEnd)
 	{
 		long	store_pos;
 
+		if (portal->posOverflow)		/* oops, cannot trust portalPos */
+			elog(ERROR, "Unable to reposition held cursor");
+
 		tuplestore_rescan(portal->holdStore);
 
 		for (store_pos = 0; store_pos < portal->portalPos; store_pos++)
@@ -777,4 +383,12 @@ PersistHoldablePortal(Portal portal)
 	}
 
 	MemoryContextSwitchTo(oldcxt);
+
+	/*
+	 * We can now release any subsidiary memory of the portal's heap
+	 * context; we'll never use it again.  The executor already dropped
+	 * its context, but this will clean up anything that glommed onto
+	 * the portal's heap via PortalContext.
+	 */
+	MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
 }
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index f6cd5d0a802f0fef4e5b48e5b2594630a6cc9506..5a3e3f589d19ffc971cd239e75f9e7534ab5a16e 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -6,7 +6,7 @@
  * Copyright (c) 2002, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.13 2003/02/02 23:46:38 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.14 2003/05/02 20:54:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -59,9 +59,8 @@ static ParamListInfo EvaluateParams(EState *estate,
 void
 PrepareQuery(PrepareStmt *stmt)
 {
-	List	   *plan_list = NIL;
 	List	   *query_list,
-			   *query_list_item;
+			   *plan_list;
 
 	if (!stmt->name)
 		elog(ERROR, "No statement name given");
@@ -69,19 +68,18 @@ PrepareQuery(PrepareStmt *stmt)
 	if (stmt->query->commandType == CMD_UTILITY)
 		elog(ERROR, "Utility statements cannot be prepared");
 
+	/*
+	 * Parse analysis is already done, but we must still rewrite and plan
+	 * the query.
+	 */
+
 	/* Rewrite the query. The result could be 0, 1, or many queries. */
 	query_list = QueryRewrite(stmt->query);
 
-	foreach(query_list_item, query_list)
-	{
-		Query	   *query = (Query *) lfirst(query_list_item);
-		Plan	   *plan;
-
-		plan = pg_plan_query(query);
-
-		plan_list = lappend(plan_list, plan);
-	}
+	/* Generate plans for queries.  Snapshot is already set. */
+	plan_list = pg_plan_queries(query_list, false);
 
+	/* Save the results. */
 	StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids);
 }
 
@@ -92,17 +90,19 @@ void
 ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
 {
 	QueryHashEntry *entry;
-	List	   *l,
-			   *query_list,
+	List	   *query_list,
 			   *plan_list;
+	MemoryContext qcontext;
 	ParamListInfo paramLI = NULL;
 	EState	   *estate = NULL;
+	Portal		portal;
 
 	/* Look it up in the hash table */
 	entry = FetchQuery(stmt->name);
 
 	query_list = entry->query_list;
 	plan_list = entry->plan_list;
+	qcontext = entry->context;
 
 	Assert(length(query_list) == length(plan_list));
 
@@ -117,57 +117,53 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
 		paramLI = EvaluateParams(estate, stmt->params, entry->argtype_list);
 	}
 
-	/* Execute each query */
-	foreach(l, query_list)
-	{
-		Query	  *query = (Query *) lfirst(l);
-		Plan	  *plan = (Plan *) lfirst(plan_list);
-		bool		is_last_query;
-
-		plan_list = lnext(plan_list);
-		is_last_query = (plan_list == NIL);
-
-		if (query->commandType == CMD_UTILITY)
-			ProcessUtility(query->utilityStmt, outputDest, NULL);
-		else
-		{
-			QueryDesc  *qdesc;
-
-			if (log_executor_stats)
-				ResetUsage();
+	/*
+	 * Create a new portal to run the query in
+	 */
+	portal = CreateNewPortal();
 
-			qdesc = CreateQueryDesc(query, plan, outputDest, NULL,
-									paramLI, false);
+	/*
+	 * For EXECUTE INTO, make a copy of the stored query so that we can
+	 * modify its destination (yech, but INTO has always been ugly).
+	 * For regular EXECUTE we can just use the stored query where it sits,
+	 * since the executor is read-only.
+	 */
+	if (stmt->into)
+	{
+		MemoryContext oldContext;
+		Query  *query;
 
-			if (stmt->into)
-			{
-				if (qdesc->operation != CMD_SELECT)
-					elog(ERROR, "INTO clause specified for non-SELECT query");
+		oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 
-				query->into = stmt->into;
-				qdesc->dest = None;
-			}
+		query_list = copyObject(query_list);
+		plan_list = copyObject(plan_list);
+		qcontext = PortalGetHeapMemory(portal);
 
-			ExecutorStart(qdesc);
+		if (length(query_list) != 1)
+			elog(ERROR, "INTO clause specified for non-SELECT query");
+		query = (Query *) lfirst(query_list);
+		if (query->commandType != CMD_SELECT)
+			elog(ERROR, "INTO clause specified for non-SELECT query");
+		query->into = copyObject(stmt->into);
 
-			ExecutorRun(qdesc, ForwardScanDirection, 0L);
+		MemoryContextSwitchTo(oldContext);
+	}
 
-			ExecutorEnd(qdesc);
+	PortalDefineQuery(portal,
+					  NULL,		/* XXX fixme: can we save query text? */
+					  NULL,		/* no command tag known either */
+					  query_list,
+					  plan_list,
+					  qcontext);
 
-			FreeQueryDesc(qdesc);
+	/*
+	 * Run the portal to completion.
+	 */
+	PortalStart(portal, paramLI);
 
-			if (log_executor_stats)
-				ShowUsage("EXECUTOR STATISTICS");
-		}
+	(void) PortalRun(portal, FETCH_ALL, outputDest, outputDest, NULL);
 
-		/*
-		 * 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();
-	}
+	PortalDrop(portal, false);
 
 	if (estate)
 		FreeExecutorState(estate);
@@ -377,8 +373,11 @@ DeallocateQuery(DeallocateStmt *stmt)
 	/* Find the query's hash table entry */
 	entry = FetchQuery(stmt->name);
 
-	/* Flush the context holding the subsidiary data */
+	/* Drop any open portals that depend on this prepared statement */
 	Assert(MemoryContextIsValid(entry->context));
+	DropDependentPortals(entry->context);
+
+	/* Flush the context holding the subsidiary data */
 	MemoryContextDelete(entry->context);
 
 	/* Now we can remove the hash table entry */
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 03bd3de32853a63ec69ea4240ac8f016e0604335..9667eabb8bf1f71b346633a62ac67a896165eec0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.251 2003/03/04 21:51:20 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.252 2003/05/02 20:54:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -189,11 +189,11 @@ vacuum(VacuumStmt *vacstmt)
 	/*
 	 * Create special memory context for cross-transaction storage.
 	 *
-	 * Since it is a child of QueryContext, it will go away eventually even
+	 * Since it is a child of PortalContext, it will go away eventually even
 	 * if we suffer an error; there's no need for special abort cleanup
 	 * logic.
 	 */
-	vac_context = AllocSetContextCreate(QueryContext,
+	vac_context = AllocSetContextCreate(PortalContext,
 										"Vacuum",
 										ALLOCSET_DEFAULT_MINSIZE,
 										ALLOCSET_DEFAULT_INITSIZE,
@@ -205,7 +205,7 @@ vacuum(VacuumStmt *vacstmt)
 	 * lifetime.
 	 */
 	if (vacstmt->analyze && !vacstmt->vacuum)
-		anl_context = AllocSetContextCreate(QueryContext,
+		anl_context = AllocSetContextCreate(PortalContext,
 											"Analyze",
 											ALLOCSET_DEFAULT_MINSIZE,
 											ALLOCSET_DEFAULT_INITSIZE,
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 8c6a7c04b6603bc6794278ea3b4ab06b9b128ba3..a2d0a8346f1d1e776372d5373310dec6236edf69 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.128 2003/04/08 23:20:00 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.129 2003/05/02 20:54:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -377,7 +377,7 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull)
 	 * XXX this is a horrid crock: since the pointer to the slot might live
 	 * longer than the current evaluation context, we are forced to copy
 	 * the tuple and slot into a long-lived context --- we use
-	 * TransactionCommandContext which should be safe enough.  This
+	 * the econtext's per-query memory which should be safe enough.  This
 	 * represents a serious memory leak if many such tuples are processed
 	 * in one command, however.  We ought to redesign the representation
 	 * of whole-tuple datums so that this is not necessary.
@@ -391,7 +391,7 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull)
 		TupleTableSlot *tempSlot;
 		HeapTuple	tup;
 
-		oldContext = MemoryContextSwitchTo(TransactionCommandContext);
+		oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 		tempSlot = MakeTupleTableSlot();
 		tup = heap_copytuple(heapTuple);
 		ExecStoreTuple(tup, tempSlot, InvalidBuffer, true);
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index aca27d0aebf3597900e057dbe046e6be9c53777c..3b1e6c4bb3f99472ebd7e7b624e910c41891c6c1 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.93 2003/04/29 22:13:09 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.94 2003/05/02 20:54:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,7 +16,6 @@
 
 #include "access/printtup.h"
 #include "catalog/heap.h"
-#include "commands/portalcmds.h"
 #include "executor/spi_priv.h"
 #include "tcop/tcopprot.h"
 #include "utils/lsyscache.h"
@@ -91,7 +90,13 @@ SPI_connect(void)
 	_SPI_current->processed = 0;
 	_SPI_current->tuptable = NULL;
 
-	/* Create memory contexts for this procedure */
+	/*
+	 * Create memory contexts for this procedure
+	 *
+	 * XXX it would be better to use PortalContext as the parent context,
+	 * but we may not be inside a portal (consider deferred-trigger
+	 * execution).
+	 */
 	_SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext,
 												  "SPI Proc",
 												ALLOCSET_DEFAULT_MINSIZE,
@@ -703,18 +708,14 @@ SPI_freetuptable(SPITupleTable *tuptable)
 Portal
 SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
 {
-	static int	unnamed_portal_count = 0;
-
 	_SPI_plan  *spiplan = (_SPI_plan *) plan;
 	List	   *qtlist = spiplan->qtlist;
 	List	   *ptlist = spiplan->ptlist;
 	Query	   *queryTree;
 	Plan	   *planTree;
 	ParamListInfo paramLI;
-	QueryDesc  *queryDesc;
 	MemoryContext oldcontext;
 	Portal		portal;
-	char		portalname[64];
 	int			k;
 
 	/* Ensure that the plan contains only one regular SELECT query */
@@ -737,32 +738,18 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
 	_SPI_current->processed = 0;
 	_SPI_current->tuptable = NULL;
 
-	if (name == NULL)
+	/* Create the portal */
+	if (name == NULL || name[0] == '\0')
 	{
-		/* Make up a portal name if none given */
-		for (;;)
-		{
-			unnamed_portal_count++;
-			if (unnamed_portal_count < 0)
-				unnamed_portal_count = 0;
-			sprintf(portalname, "<unnamed cursor %d>", unnamed_portal_count);
-			if (GetPortalByName(portalname) == NULL)
-				break;
-		}
-
-		name = portalname;
+		/* Use a random nonconflicting name */
+		portal = CreateNewPortal();
 	}
 	else
 	{
-		/* Ensure the portal doesn't exist already */
-		portal = GetPortalByName(name);
-		if (portal != NULL)
-			elog(ERROR, "cursor \"%s\" already in use", name);
+		/* In this path, error if portal of same name already exists */
+		portal = CreatePortal(name, false, false);
 	}
 
-	/* Create the portal */
-	portal = CreatePortal(name);
-
 	/* Switch to portals memory and copy the parsetree and plan to there */
 	oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 	queryTree = copyObject(queryTree);
@@ -801,18 +788,33 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
 	else
 		paramLI = NULL;
 
-	/* Create the QueryDesc object */
-	queryDesc = CreateQueryDesc(queryTree, planTree, SPI, pstrdup(name),
-								paramLI, false);
+	/*
+	 * Set up the portal.
+	 */
+	PortalDefineQuery(portal,
+					  NULL,		/* unfortunately don't have sourceText */
+					  "SELECT",	/* cursor's query is always a SELECT */
+					  makeList1(queryTree),
+					  makeList1(planTree),
+					  PortalGetHeapMemory(portal));
 
-	/* Start the executor */
-	ExecutorStart(queryDesc);
+	MemoryContextSwitchTo(oldcontext);
+
+	/*
+	 * Set up options for portal.
+	 */
+	portal->cursorOptions &= ~(CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL);
+	if (ExecSupportsBackwardScan(plan))
+		portal->cursorOptions |= CURSOR_OPT_SCROLL;
+	else
+		portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
 
-	/* Arrange to shut down the executor if portal is dropped */
-	PortalSetQuery(portal, queryDesc);
+	/*
+	 * Start portal execution.
+	 */
+	PortalStart(portal, paramLI);
 
-	/* Switch back to the callers memory context */
-	MemoryContextSwitchTo(oldcontext);
+	Assert(portal->strategy == PORTAL_ONE_SELECT);
 
 	/* Return the created portal */
 	return portal;
@@ -1008,39 +1010,15 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
 	foreach(list_item, raw_parsetree_list)
 	{
 		Node	   *parsetree = (Node *) lfirst(list_item);
-		CmdType		origCmdType;
-		bool		foundOriginalQuery = false;
 		List	   *query_list;
 		List	   *query_list_item;
 
-		switch (nodeTag(parsetree))
-		{
-			case T_InsertStmt:
-				origCmdType = CMD_INSERT;
-				break;
-			case T_DeleteStmt:
-				origCmdType = CMD_DELETE;
-				break;
-			case T_UpdateStmt:
-				origCmdType = CMD_UPDATE;
-				break;
-			case T_SelectStmt:
-				origCmdType = CMD_SELECT;
-				break;
-			default:
-				/* Otherwise, never match commandType */
-				origCmdType = CMD_UNKNOWN;
-				break;
-		}
-
-		if (plan)
-			plan->origCmdType = origCmdType;
-
 		query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs);
 
 		query_list_list = lappend(query_list_list, query_list);
 
 		/* Reset state for each original parsetree */
+		/* (at most one of its querytrees will be marked canSetTag) */
 		SPI_processed = 0;
 		SPI_lastoid = InvalidOid;
 		SPI_tuptable = NULL;
@@ -1050,39 +1028,11 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
 		{
 			Query	   *queryTree = (Query *) lfirst(query_list_item);
 			Plan	   *planTree;
-			bool		canSetResult;
 			QueryDesc  *qdesc;
 
 			planTree = pg_plan_query(queryTree);
 			plan_list = lappend(plan_list, planTree);
 
-			/*
-			 * This query can set the SPI result if it is the original
-			 * query, or if it is an INSTEAD query of the same kind as the
-			 * original and we haven't yet seen the original query.
-			 */
-			if (queryTree->querySource == QSRC_ORIGINAL)
-			{
-				canSetResult = true;
-				foundOriginalQuery = true;
-			}
-			else if (!foundOriginalQuery &&
-					 queryTree->commandType == origCmdType &&
-					 (queryTree->querySource == QSRC_INSTEAD_RULE ||
-					  queryTree->querySource == QSRC_QUAL_INSTEAD_RULE))
-				canSetResult = true;
-			else
-				canSetResult = false;
-
-			/* Reset state if can set result */
-			if (canSetResult)
-			{
-				SPI_processed = 0;
-				SPI_lastoid = InvalidOid;
-				SPI_tuptable = NULL;
-				_SPI_current->tuptable = NULL;
-			}
-
 			if (queryTree->commandType == CMD_UTILITY)
 			{
 				if (IsA(queryTree->utilityStmt, CopyStmt))
@@ -1108,9 +1058,10 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
 			else if (plan == NULL)
 			{
 				qdesc = CreateQueryDesc(queryTree, planTree,
-										canSetResult ? SPI : None,
+										queryTree->canSetTag ? SPI : None,
 										NULL, NULL, false);
-				res = _SPI_pquery(qdesc, true, canSetResult ? tcount : 0);
+				res = _SPI_pquery(qdesc, true,
+								  queryTree->canSetTag ? tcount : 0);
 				if (res < 0)
 					return res;
 				CommandCounterIncrement();
@@ -1118,7 +1069,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
 			else
 			{
 				qdesc = CreateQueryDesc(queryTree, planTree,
-										canSetResult ? SPI : None,
+										queryTree->canSetTag ? SPI : None,
 										NULL, NULL, false);
 				res = _SPI_pquery(qdesc, false, 0);
 				if (res < 0)
@@ -1145,10 +1096,31 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 	List	   *query_list_list_item;
 	int			nargs = plan->nargs;
 	int			res = 0;
+	ParamListInfo paramLI;
 
 	/* Increment CommandCounter to see changes made by now */
 	CommandCounterIncrement();
 
+	/* Convert parameters to form wanted by executor */
+	if (nargs > 0)
+	{
+		int			k;
+
+		paramLI = (ParamListInfo)
+			palloc0((nargs + 1) * sizeof(ParamListInfoData));
+
+		for (k = 0; k < nargs; k++)
+		{
+			paramLI[k].kind = PARAM_NUM;
+			paramLI[k].id = k + 1;
+			paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
+			paramLI[k].value = Values[k];
+		}
+		paramLI[k].kind = PARAM_INVALID;
+	}
+	else
+		paramLI = NULL;
+
 	/* Reset state (only needed in case string is empty) */
 	SPI_processed = 0;
 	SPI_lastoid = InvalidOid;
@@ -1159,9 +1131,9 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 	{
 		List   *query_list = lfirst(query_list_list_item);
 		List   *query_list_item;
-		bool	foundOriginalQuery = false;
 
 		/* Reset state for each original parsetree */
+		/* (at most one of its querytrees will be marked canSetTag) */
 		SPI_processed = 0;
 		SPI_lastoid = InvalidOid;
 		SPI_tuptable = NULL;
@@ -1171,72 +1143,24 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
 		{
 			Query  *queryTree = (Query *) lfirst(query_list_item);
 			Plan	   *planTree;
-			bool		canSetResult;
 			QueryDesc  *qdesc;
 
 			planTree = lfirst(plan_list);
 			plan_list = lnext(plan_list);
 
-			/*
-			 * This query can set the SPI result if it is the original
-			 * query, or if it is an INSTEAD query of the same kind as the
-			 * original and we haven't yet seen the original query.
-			 */
-			if (queryTree->querySource == QSRC_ORIGINAL)
-			{
-				canSetResult = true;
-				foundOriginalQuery = true;
-			}
-			else if (!foundOriginalQuery &&
-					 queryTree->commandType == plan->origCmdType &&
-					 (queryTree->querySource == QSRC_INSTEAD_RULE ||
-					  queryTree->querySource == QSRC_QUAL_INSTEAD_RULE))
-				canSetResult = true;
-			else
-				canSetResult = false;
-
-			/* Reset state if can set result */
-			if (canSetResult)
-			{
-				SPI_processed = 0;
-				SPI_lastoid = InvalidOid;
-				SPI_tuptable = NULL;
-				_SPI_current->tuptable = NULL;
-			}
-
 			if (queryTree->commandType == CMD_UTILITY)
 			{
-				res = SPI_OK_UTILITY;
 				ProcessUtility(queryTree->utilityStmt, None, NULL);
+				res = SPI_OK_UTILITY;
 				CommandCounterIncrement();
 			}
 			else
 			{
-				ParamListInfo paramLI;
-
-				if (nargs > 0)
-				{
-					int			k;
-
-					paramLI = (ParamListInfo)
-						palloc0((nargs + 1) * sizeof(ParamListInfoData));
-
-					for (k = 0; k < plan->nargs; k++)
-					{
-						paramLI[k].kind = PARAM_NUM;
-						paramLI[k].id = k + 1;
-						paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
-						paramLI[k].value = Values[k];
-					}
-					paramLI[k].kind = PARAM_INVALID;
-				}
-				else
-					paramLI = NULL;
-
 				qdesc = CreateQueryDesc(queryTree, planTree,
-										canSetResult ? SPI : None,
+										queryTree->canSetTag ? SPI : None,
 										NULL, paramLI, false);
-				res = _SPI_pquery(qdesc, true, canSetResult ? tcount : 0);
+				res = _SPI_pquery(qdesc, true,
+								  queryTree->canSetTag ? tcount : 0);
 				if (res < 0)
 					return res;
 				CommandCounterIncrement();
@@ -1346,10 +1270,10 @@ _SPI_cursor_operation(Portal portal, bool forward, int count,
 
 	/* Run the cursor */
 	_SPI_current->processed =
-		DoPortalFetch(portal,
-					  forward ? FETCH_FORWARD : FETCH_BACKWARD,
-					  (long) count,
-					  dest);
+		PortalRunFetch(portal,
+					   forward ? FETCH_FORWARD : FETCH_BACKWARD,
+					   (long) count,
+					   dest);
 
 	if (dest == SPI && _SPI_checktuples())
 		elog(FATAL, "SPI_fetch: # of processed tuples check failed");
@@ -1467,7 +1391,6 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
 	}
 	else
 		newplan->argtypes = NULL;
-	newplan->origCmdType = plan->origCmdType;
 
 	MemoryContextSwitchTo(oldcxt);
 
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index 01fffed8168168d6b31f9509ba38c411169b132c..c4d16ef5e9846081e799b42f1bef7313501fb6ca 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/tstoreReceiver.c,v 1.2 2003/04/29 03:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/tstoreReceiver.c,v 1.3 2003/05/02 20:54:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,18 +43,18 @@ tstoreSetupReceiver(DestReceiver *self, int operation,
 					const char *portalname, TupleDesc typeinfo)
 {
 	TStoreState *myState = (TStoreState *) self;
-	Portal portal;
 
-	if (operation != CMD_SELECT)
-		elog(ERROR, "Unexpected operation type: %d", operation);
+	/* Should only be called within a suitably-prepped portal */
+	if (CurrentPortal == NULL ||
+		CurrentPortal->holdStore == NULL)
+		elog(ERROR, "Tuplestore destination used in wrong context");
 
-	portal = GetPortalByName(portalname);
+	/* Debug check: make sure portal's result tuple desc is correct */
+	Assert(CurrentPortal->tupDesc != NULL);
+	Assert(equalTupleDescs(CurrentPortal->tupDesc, typeinfo));
 
-	if (portal == NULL)
-		elog(ERROR, "Specified portal does not exist: %s", portalname);
-
-	myState->tstore = portal->holdStore;
-	myState->cxt = portal->holdContext;
+	myState->tstore = CurrentPortal->holdStore;
+	myState->cxt = CurrentPortal->holdContext;
 }
 
 /*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index cbf61970d4748aa71811cf9031fc2dbaee5dd126..4c773657fe49bfed6e3b159bcfd147255df465e9 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.248 2003/04/08 23:20:01 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.249 2003/05/02 20:54:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1476,6 +1476,7 @@ _copyQuery(Query *from)
 
 	COPY_SCALAR_FIELD(commandType);
 	COPY_SCALAR_FIELD(querySource);
+	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
 	COPY_NODE_FIELD(into);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 31e5319711617f3e4ef126a54b2113a0e2b98160..e4c10ed968665f4759c3e8eaf5ed413bddf3eff2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.191 2003/04/08 23:20:01 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.192 2003/05/02 20:54:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -566,6 +566,7 @@ _equalQuery(Query *a, Query *b)
 {
 	COMPARE_SCALAR_FIELD(commandType);
 	COMPARE_SCALAR_FIELD(querySource);
+	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
 	COMPARE_NODE_FIELD(into);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 45c7a2a301b67464e44dece5d3f6fdb9e096c401..654905b09628138f92dc6dc7ae8578b51e8bc1bb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.203 2003/04/24 21:16:43 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.204 2003/05/02 20:54:34 tgl Exp $
  *
  * NOTES
  *	  Every node type that can appear in stored rules' parsetrees *must*
@@ -1177,6 +1177,7 @@ _outQuery(StringInfo str, Query *node)
 
 	WRITE_ENUM_FIELD(commandType, CmdType);
 	WRITE_ENUM_FIELD(querySource, QuerySource);
+	WRITE_BOOL_FIELD(canSetTag);
 
 	/*
 	 * Hack to work around missing outfuncs routines for a lot of the
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 4f75942169ba65d7dcf8b843d504fb5f53a83167..3c8e7501f434c6626e8b369a9918f44c33f31118 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.151 2003/04/08 23:20:01 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.152 2003/05/02 20:54:34 tgl Exp $
  *
  * NOTES
  *	  Path and Plan nodes do not have any readfuncs support, because we
@@ -195,6 +195,7 @@ _readQuery(void)
 
 	READ_ENUM_FIELD(commandType, CmdType);
 	READ_ENUM_FIELD(querySource, QuerySource);
+	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
 	READ_NODE_FIELD(into);
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
index d53a160a4ebd2d3d40b3cf74bdf3f8ced10fea9b..1be69e93f352b201f06ba27ca43e7f5d139d0946 100644
--- a/src/backend/optimizer/geqo/geqo_eval.c
+++ b/src/backend/optimizer/geqo/geqo_eval.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/optimizer/geqo/geqo_eval.c,v 1.61 2003/01/20 18:54:49 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.62 2003/05/02 20:54:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -65,10 +65,10 @@ geqo_eval(Query *root, List *initial_rels, Gene *tour, int num_gene)
 	 *
 	 * Since geqo_eval() will be called many times, we can't afford to let
 	 * all that memory go unreclaimed until end of statement.  Note we
-	 * make the temp context a child of TransactionCommandContext, so that
+	 * make the temp context a child of the planner's normal context, so that
 	 * it will be freed even if we abort via elog(ERROR).
 	 */
-	mycontext = AllocSetContextCreate(TransactionCommandContext,
+	mycontext = AllocSetContextCreate(CurrentMemoryContext,
 									  "GEQO",
 									  ALLOCSET_DEFAULT_MINSIZE,
 									  ALLOCSET_DEFAULT_INITSIZE,
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c2159a70e0e78f1e550030cc239f2f7c215e1cde..a488d1d91e5cfd4d2626fef40cf77a557784faa0 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.268 2003/04/29 22:13:09 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.269 2003/05/02 20:54:34 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -237,13 +237,23 @@ do_parse_analyze(Node *parseTree, ParseState *pstate)
 	/*
 	 * Make sure that only the original query is marked original. We have
 	 * to do this explicitly since recursive calls of do_parse_analyze will
-	 * have marked some of the added-on queries as "original".
+	 * have marked some of the added-on queries as "original".  Also mark
+	 * only the original query as allowed to set the command-result tag.
 	 */
 	foreach(listscan, result)
 	{
 		Query	   *q = lfirst(listscan);
 
-		q->querySource = (q == query ? QSRC_ORIGINAL : QSRC_PARSER);
+		if (q == query)
+		{
+			q->querySource = QSRC_ORIGINAL;
+			q->canSetTag = true;
+		}
+		else
+		{
+			q->querySource = QSRC_PARSER;
+			q->canSetTag = false;
+		}
 	}
 
 	return result;
@@ -399,6 +409,11 @@ transformStmt(ParseState *pstate, Node *parseTree,
 			result->utilityStmt = (Node *) parseTree;
 			break;
 	}
+
+	/* Mark as original query until we learn differently */
+	result->querySource = QSRC_ORIGINAL;
+	result->canSetTag = true;
+
 	return result;
 }
 
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 8ca7fb954f098817f29d1f3a35bcfd2b355a9018..5d481a3f0a5adb226a7cf022eff199bfce16b880 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.119 2003/04/29 22:13:10 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.120 2003/05/02 20:54:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1016,6 +1016,7 @@ fireRules(Query *parsetree,
 											event_qual, rt_index, event);
 
 			rule_action->querySource = qsrc;
+			rule_action->canSetTag = false;		/* might change later */
 
 			results = lappend(results, rule_action);
 		}
@@ -1181,6 +1182,9 @@ QueryRewrite(Query *parsetree)
 	List	   *querylist;
 	List	   *results = NIL;
 	List	   *l;
+	CmdType		origCmdType;
+	bool		foundOriginalQuery;
+	Query	   *lastInstead;
 
 	/*
 	 * Step 1
@@ -1235,5 +1239,51 @@ QueryRewrite(Query *parsetree)
 		results = lappend(results, query);
 	}
 
+	/*
+	 * Step 3
+	 *
+	 * Determine which, if any, of the resulting queries is supposed to set
+	 * the command-result tag; and update the canSetTag fields accordingly.
+	 *
+	 * If the original query is still in the list, it sets the command tag.
+	 * Otherwise, the last INSTEAD query of the same kind as the original
+	 * is allowed to set the tag.  (Note these rules can leave us with no
+	 * query setting the tag.  The tcop code has to cope with this by
+	 * setting up a default tag based on the original un-rewritten query.)
+	 *
+	 * The Asserts verify that at most one query in the result list is marked
+	 * canSetTag.  If we aren't checking asserts, we can fall out of the loop
+	 * as soon as we find the original query.
+	 */
+	origCmdType = parsetree->commandType;
+	foundOriginalQuery = false;
+	lastInstead = NULL;
+
+	foreach(l, results)
+	{
+		Query	   *query = (Query *) lfirst(l);
+
+		if (query->querySource == QSRC_ORIGINAL)
+		{
+			Assert(query->canSetTag);
+			Assert(!foundOriginalQuery);
+			foundOriginalQuery = true;
+#ifndef USE_ASSERT_CHECKING
+			break;
+#endif
+		}
+		else
+		{
+			Assert(!query->canSetTag);
+			if (query->commandType == origCmdType &&
+				(query->querySource == QSRC_INSTEAD_RULE ||
+				 query->querySource == QSRC_QUAL_INSTEAD_RULE))
+				lastInstead = query;
+		}
+	}
+
+	if (!foundOriginalQuery && lastInstead != NULL)
+		lastInstead->canSetTag = true;
+
 	return results;
 }
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index b87509573497abb51715cac3a9e0cf3cd3bcd4fe..78fcfdb7e0ef845230fb53c19fe40099973389cd 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.59 2003/04/22 00:08:07 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.60 2003/05/02 20:54:35 tgl Exp $
  *
  * NOTES
  *	  This cruft is the server side of PQfn.
@@ -255,7 +255,7 @@ fetch_fp_info(Oid func_id, struct fp_info * fip)
  *
  * Note: palloc()s done here and in the called function do not need to be
  * cleaned up explicitly.  We are called from PostgresMain() in the
- * QueryContext memory context, which will be automatically reset when
+ * MessageContext memory context, which will be automatically reset when
  * control returns to PostgresMain.
  */
 int
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index bc98d1d91ecd17f0a93d91d6b7ae883846799daa..6c2e456ccc17d0c94a2d2a8766665975a1da5593 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.326 2003/04/29 22:13:11 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.327 2003/05/02 20:54:35 tgl Exp $
  *
  * NOTES
  *	  this is the "main" module of the postgres backend and
@@ -74,8 +74,6 @@ const char *debug_query_string; /* for pgmonitor and
 /* Note: whereToSendOutput is initialized for the bootstrap/standalone case */
 CommandDest whereToSendOutput = Debug;
 
-static bool dontExecute = false;
-
 /* note: these declarations had better match tcopprot.h */
 sigjmp_buf	Warn_restart;
 
@@ -122,7 +120,6 @@ static void start_xact_command(void);
 static void finish_xact_command(bool forceCommit);
 static void SigHupHandler(SIGNAL_ARGS);
 static void FloatExceptionHandler(SIGNAL_ARGS);
-static const char *CreateCommandTag(Node *parsetree);
 
 
 /* ----------------------------------------------------------------
@@ -310,9 +307,9 @@ SocketBackend(StringInfo inBuf)
 
 /* ----------------
  *		ReadCommand reads a command from either the frontend or
- *		standard input, places it in inBuf, and returns a char
- *		representing whether the string is a 'Q'uery or a 'F'astpath
- *		call.  EOF is returned if end of file.
+ *		standard input, places it in inBuf, and returns the
+ *		message type code (first byte of the message).
+ *		EOF is returned if end of file.
  * ----------------
  */
 static int
@@ -487,7 +484,7 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
 }
 
 
-/* Generate a plan for a single query. */
+/* Generate a plan for a single already-rewritten query. */
 Plan *
 pg_plan_query(Query *querytree)
 {
@@ -534,41 +531,58 @@ pg_plan_query(Query *querytree)
 	return plan;
 }
 
-
-/* ----------------------------------------------------------------
- *		pg_exec_query_string()
- *
- *		Takes a querystring, runs the parser/utilities or
- *		parser/planner/executor over it as necessary.
- *
- * Assumptions:
- *
- * At call, we are not inside a transaction command.
- *
- * The CurrentMemoryContext after starting a transaction command must be
- * appropriate for execution of individual queries (typically this will be
- * TransactionCommandContext).	Note that this routine resets that context
- * after each individual query, so don't store anything there that
- * must outlive the call!
- *
- * parse_context references a context suitable for holding the
- * parse/rewrite trees (typically this will be QueryContext).
- * This context *must* be longer-lived than the transaction context!
- * In fact, if the query string might contain BEGIN/COMMIT commands,
- * parse_context had better outlive TopTransactionContext!
+/*
+ * Generate plans for a list of already-rewritten queries.
  *
- * We could have hard-wired knowledge about QueryContext and
- * TransactionCommandContext into this routine, but it seems better
- * not to, in case callers from outside this module need to use some
- * other contexts.
+ * If needSnapshot is TRUE, we haven't yet set a snapshot for the current
+ * query.  A snapshot must be set before invoking the planner, since it
+ * might try to evaluate user-defined functions.  But we must not set a
+ * snapshot if the list contains only utility statements, because some
+ * utility statements depend on not having frozen the snapshot yet.
+ * (We assume that such statements cannot appear together with plannable
+ * statements in the rewriter's output.)
+ */
+List *
+pg_plan_queries(List *querytrees, bool needSnapshot)
+{
+	List	   *plan_list = NIL;
+	List	   *query_list;
+
+	foreach(query_list, querytrees)
+	{
+		Query	   *query = (Query *) lfirst(query_list);
+		Plan	   *plan;
+
+		if (query->commandType == CMD_UTILITY)
+		{
+			/* Utility commands have no plans. */
+			plan = NULL;
+		}
+		else
+		{
+			if (needSnapshot)
+			{
+				SetQuerySnapshot();
+				needSnapshot = false;
+			}
+			plan = pg_plan_query(query);
+		}
+
+		plan_list = lappend(plan_list, plan);
+	}
+
+	return plan_list;
+}
+
+
+/*
+ * exec_simple_query()
  *
- * ----------------------------------------------------------------
+ * Execute a "simple Query" protocol message.
  */
 static void
-pg_exec_query_string(const char *query_string,	/* string to execute */
-					 CommandDest dest,	/* where results should go */
-					 MemoryContext parse_context)		/* context for
-														 * parsetrees */
+exec_simple_query(const char *query_string,	/* string to execute */
+				  CommandDest dest)			/* where results should go */
 {
 	bool		xact_started;
 	MemoryContext oldcontext;
@@ -577,39 +591,40 @@ pg_exec_query_string(const char *query_string,	/* string to execute */
 	struct timeval start_t,
 				stop_t;
 	bool		save_log_duration = log_duration;
+	bool		save_log_statement_stats = log_statement_stats;
 
+	/*
+	 * Report query to various monitoring facilities.
+	 */
 	debug_query_string = query_string;
 
+	pgstat_report_activity(query_string);
+
 	/*
 	 * We use save_log_duration so "SET log_duration = true" doesn't
 	 * report incorrect time because gettimeofday() wasn't called.
+	 * Similarly, log_statement_stats has to be captured once.
 	 */
 	if (save_log_duration)
 		gettimeofday(&start_t, NULL);
 
+	if (save_log_statement_stats)
+		ResetUsage();
+
 	/*
 	 * Start up a transaction command.	All queries generated by the
 	 * query_string will be in this same command block, *unless* we find a
 	 * BEGIN/COMMIT/ABORT statement; we have to force a new xact command
 	 * after one of those, else bad things will happen in xact.c. (Note
-	 * that this will possibly change current memory context.)
+	 * that this will normally change current memory context.)
 	 */
 	start_xact_command();
 	xact_started = true;
 
-	/*
-	 * parse_context *must* be different from the execution memory
-	 * context, else the context reset at the bottom of the loop will
-	 * destroy the parsetree list.	(We really ought to check that
-	 * parse_context isn't a child of CurrentMemoryContext either, but
-	 * that would take more cycles than it's likely to be worth.)
-	 */
-	Assert(parse_context != CurrentMemoryContext);
-
 	/*
 	 * Switch to appropriate context for constructing parsetrees.
 	 */
-	oldcontext = MemoryContextSwitchTo(parse_context);
+	oldcontext = MemoryContextSwitchTo(MessageContext);
 
 	/*
 	 * Do basic parsing of the query or queries (this should be safe even
@@ -618,57 +633,36 @@ pg_exec_query_string(const char *query_string,	/* string to execute */
 	parsetree_list = pg_parse_query(query_string);
 
 	/*
-	 * Switch back to execution context to enter the loop.
+	 * Switch back to transaction context to enter the loop.
 	 */
 	MemoryContextSwitchTo(oldcontext);
 
 	/*
-	 * Run through the parsetree(s) and process each one.
+	 * Run through the raw parsetree(s) and process each one.
 	 */
 	foreach(parsetree_item, parsetree_list)
 	{
 		Node	   *parsetree = (Node *) lfirst(parsetree_item);
 		const char *commandTag;
 		char		completionTag[COMPLETION_TAG_BUFSIZE];
-		CmdType		origCmdType;
-		bool		foundOriginalQuery = false;
 		List	   *querytree_list,
-				   *querytree_item;
+				   *plantree_list;
+		Portal		portal;
 
 		/*
-		 * First we set the command-completion tag to the main query (as
-		 * opposed to each of the others that may be generated by analyze
-		 * and rewrite).  Also set ps_status and do any special
-		 * start-of-SQL-command processing needed by the destination.
+		 * Get the command name for use in status display (it also becomes the
+		 * default completion tag, down inside PortalRun).  Set ps_status and
+		 * do any special start-of-SQL-command processing needed by the
+		 * destination.
 		 */
 		commandTag = CreateCommandTag(parsetree);
 
-		switch (nodeTag(parsetree))
-		{
-			case T_InsertStmt:
-				origCmdType = CMD_INSERT;
-				break;
-			case T_DeleteStmt:
-				origCmdType = CMD_DELETE;
-				break;
-			case T_UpdateStmt:
-				origCmdType = CMD_UPDATE;
-				break;
-			case T_SelectStmt:
-				origCmdType = CMD_SELECT;
-				break;
-			default:
-				/* Otherwise, never match commandType */
-				origCmdType = CMD_UNKNOWN;
-				break;
-		}
-
 		set_ps_display(commandTag);
 
 		BeginCommand(commandTag, dest);
 
 		/*
-		 * If we are in an aborted transaction, ignore all commands except
+		 * If we are in an aborted transaction, reject all commands except
 		 * COMMIT/ABORT.  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
@@ -704,202 +698,60 @@ pg_exec_query_string(const char *query_string,	/* string to execute */
 		CHECK_FOR_INTERRUPTS();
 
 		/*
-		 * OK to analyze and rewrite this query.
+		 * OK to analyze, rewrite, and plan this query.
 		 *
 		 * Switch to appropriate context for constructing querytrees (again,
 		 * these must outlive the execution context).
 		 */
-		oldcontext = MemoryContextSwitchTo(parse_context);
+		oldcontext = MemoryContextSwitchTo(MessageContext);
 
 		querytree_list = pg_analyze_and_rewrite(parsetree, NULL, 0);
 
+		plantree_list = pg_plan_queries(querytree_list, true);
+
+		/* If we got a cancel signal in analysis or planning, quit */
+		CHECK_FOR_INTERRUPTS();
+
 		/*
-		 * Switch back to execution context for planning and execution.
+		 * Switch back to transaction context for execution.
 		 */
 		MemoryContextSwitchTo(oldcontext);
 
 		/*
-		 * Inner loop handles the individual queries generated from a
-		 * single parsetree by analysis and rewrite.
+		 * Create unnamed portal to run the query or queries in.
+		 * If there already is one, silently drop it.
 		 */
-		foreach(querytree_item, querytree_list)
-		{
-			Query	   *querytree = (Query *) lfirst(querytree_item);
-			bool		endTransactionBlock = false;
-			bool		canSetTag;
-
-			/* Make sure we are in a transaction command */
-			if (!xact_started)
-			{
-				start_xact_command();
-				xact_started = true;
-			}
-
-			/*
-			 * If we got a cancel signal in analysis or prior command,
-			 * quit
-			 */
-			CHECK_FOR_INTERRUPTS();
-
-			/*
-			 * This query can set the completion tag if it is the original
-			 * query, or if it is an INSTEAD query of the same kind as the
-			 * original and we haven't yet seen the original query.
-			 */
-			if (querytree->querySource == QSRC_ORIGINAL)
-			{
-				canSetTag = true;
-				foundOriginalQuery = true;
-			}
-			else if (!foundOriginalQuery &&
-					 querytree->commandType == origCmdType &&
-					 (querytree->querySource == QSRC_INSTEAD_RULE ||
-					  querytree->querySource == QSRC_QUAL_INSTEAD_RULE))
-				canSetTag = true;
-			else
-				canSetTag = false;
-
-			if (querytree->commandType == CMD_UTILITY)
-			{
-				/*
-				 * process utility functions (create, destroy, etc..)
-				 */
-				Node   *utilityStmt = querytree->utilityStmt;
-
-				elog(DEBUG2, "ProcessUtility");
-
-				/*
-				 * Set snapshot if utility stmt needs one.  Most reliable
-				 * way to do this seems to be to enumerate those that do not
-				 * need one; this is a short list.  Transaction control,
-				 * LOCK, and SET must *not* set a snapshot since they need
-				 * to be executable at the start of a serializable transaction
-				 * without freezing a snapshot.  By extension we allow SHOW
-				 * not to set a snapshot.  The other stmts listed are just
-				 * efficiency hacks.  Beware of listing anything that can
-				 * modify the database --- if, say, it has to update a
-				 * functional index, then it had better have a snapshot.
-				 */
-				if (! (IsA(utilityStmt, TransactionStmt) ||
-					   IsA(utilityStmt, LockStmt) ||
-					   IsA(utilityStmt, VariableSetStmt) ||
-					   IsA(utilityStmt, VariableShowStmt) ||
-					   IsA(utilityStmt, VariableResetStmt) ||
-					   IsA(utilityStmt, ConstraintsSetStmt) ||
-					   /* efficiency hacks from here down */
-					   IsA(utilityStmt, FetchStmt) ||
-					   IsA(utilityStmt, ListenStmt) ||
-					   IsA(utilityStmt, NotifyStmt) ||
-					   IsA(utilityStmt, UnlistenStmt) ||
-					   IsA(utilityStmt, CheckPointStmt)))
-					SetQuerySnapshot();
-
-				/* end transaction block if transaction or variable stmt */
-				if (IsA(utilityStmt, TransactionStmt) ||
-					IsA(utilityStmt, VariableSetStmt) ||
-					IsA(utilityStmt, VariableShowStmt) ||
-					IsA(utilityStmt, VariableResetStmt))
-					endTransactionBlock = true;
-
-				if (canSetTag)
-				{
-					/* utility statement can override default tag string */
-					ProcessUtility(utilityStmt, dest, completionTag);
-					if (completionTag[0])
-						commandTag = completionTag;
-				}
-				else
-				{
-					/* utility added by rewrite cannot override tag */
-					ProcessUtility(utilityStmt, dest, NULL);
-				}
-			}
-			else
-			{
-				/*
-				 * process a plannable query.
-				 */
-				Plan	   *plan;
-
-				/*
-				 * Initialize snapshot state for query.  This has to
-				 * be done before running the planner, because it might
-				 * try to evaluate immutable or stable functions, which
-				 * in turn might run queries.
-				 */
-				SetQuerySnapshot();
-
-				/* Make the plan */
-				plan = pg_plan_query(querytree);
+		portal = CreatePortal("", true, true);
 
-				/* if we got a cancel signal whilst planning, quit */
-				CHECK_FOR_INTERRUPTS();
-
-				/*
-				 * execute the plan
-				 */
-				if (log_executor_stats)
-					ResetUsage();
-
-				if (dontExecute)
-				{
-					/* don't execute it, just show the query plan */
-					print_plan(plan, querytree);
-				}
-				else
-				{
-					elog(DEBUG2, "ProcessQuery");
-
-					if (canSetTag)
-					{
-						/* statement can override default tag string */
-						ProcessQuery(querytree, plan, dest, completionTag);
-						commandTag = completionTag;
-					}
-					else
-					{
-						/* stmt added by rewrite cannot override tag */
-						ProcessQuery(querytree, plan, dest, NULL);
-					}
-				}
+		PortalDefineQuery(portal,
+						  query_string,
+						  commandTag,
+						  querytree_list,
+						  plantree_list,
+						  MessageContext);
 
-				if (log_executor_stats)
-					ShowUsage("EXECUTOR STATISTICS");
-			}
-
-			/*
-			 * In a query block, we want to increment the command counter
-			 * between queries so that the effects of early queries are
-			 * visible to subsequent ones.	In particular we'd better do
-			 * so before checking constraints.
-			 */
-			if (!endTransactionBlock)
-				CommandCounterIncrement();
-
-			/*
-			 * Clear the execution context to recover temporary memory
-			 * used by the query.  NOTE: if query string contains
-			 * BEGIN/COMMIT transaction commands, execution context may
-			 * now be different from what we were originally passed; so be
-			 * careful to clear current context not "oldcontext".
-			 */
-			Assert(parse_context != CurrentMemoryContext);
+		/*
+		 * Run the portal to completion, and then drop it.
+		 */
+		PortalStart(portal, NULL);
 
-			MemoryContextResetAndDeleteChildren(CurrentMemoryContext);
+		(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
 
-			/*
-			 * 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 (endTransactionBlock)
-			{
-				finish_xact_command(true);
-				xact_started = false;
-			}
-		}						/* end loop over queries generated from a
-								 * parsetree */
+		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))
+		{
+			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
@@ -910,28 +762,18 @@ pg_exec_query_string(const char *query_string,	/* string to execute */
 		 * historical Postgres behavior, we do not force a transaction
 		 * boundary between queries appearing in a single query string.
 		 */
-		if ((lnext(parsetree_item) == NIL || !autocommit) && xact_started)
+		else if (lnext(parsetree_item) == NIL || !autocommit)
 		{
 			finish_xact_command(false);
 			xact_started = false;
 		}
-
-		/*
-		 * It is possible that the original query was removed due to a DO
-		 * INSTEAD rewrite rule.  If so, and if we found no INSTEAD query
-		 * matching the command type, we will still have the default
-		 * completion tag.  This is fine for most purposes, but it
-		 * may confuse clients if it's INSERT/UPDATE/DELETE. Clients
-		 * expect those tags to have counts after them (cf. ProcessQuery).
-		 */
-		if (!foundOriginalQuery)
+		else
 		{
-			if (strcmp(commandTag, "INSERT") == 0)
-				commandTag = "INSERT 0 0";
-			else if (strcmp(commandTag, "UPDATE") == 0)
-				commandTag = "UPDATE 0";
-			else if (strcmp(commandTag, "DELETE") == 0)
-				commandTag = "DELETE 0";
+			/*
+			 * We need a CommandCounterIncrement after every query,
+			 * except those that start or end a transaction block.
+			 */
+			CommandCounterIncrement();
 		}
 
 		/*
@@ -941,20 +783,24 @@ pg_exec_query_string(const char *query_string,	/* string to execute */
 		 * (But a command aborted by error will not send an EndCommand
 		 * report at all.)
 		 */
-		EndCommand(commandTag, dest);
+		EndCommand(completionTag, dest);
 	}							/* end loop over parsetrees */
 
-	/* No parsetree - return empty result */
+	/*
+	 * If there were no parsetrees, return EmptyQueryResponse message.
+	 */
 	if (!parsetree_list)
 		NullCommand(dest);
 
 	/*
-	 * Close down transaction statement, if one is open. (Note that this
-	 * will only happen if the querystring was empty.)
+	 * Close down transaction statement, if one is open.
 	 */
 	if (xact_started)
 		finish_xact_command(false);
 
+	/*
+	 * Finish up monitoring.
+	 */
 	if (save_log_duration)
 	{
 		gettimeofday(&stop_t, NULL);
@@ -968,6 +814,9 @@ pg_exec_query_string(const char *query_string,	/* string to execute */
 			 (long) (stop_t.tv_usec - start_t.tv_usec));
 	}
 
+	if (save_log_statement_stats)
+		ShowUsage("QUERY STATISTICS");
+
 	debug_query_string = NULL;
 }
 
@@ -1431,10 +1280,6 @@ PostgresMain(int argc, char *argv[], const char *username)
 					SetConfigOption(tmp, "false", ctx, gucsource);
 				break;
 
-			case 'i':
-				dontExecute = true;
-				break;
-
 			case 'N':
 
 				/*
@@ -1827,22 +1672,20 @@ PostgresMain(int argc, char *argv[], const char *username)
 	if (!IsUnderPostmaster)
 	{
 		puts("\nPOSTGRES backend interactive interface ");
-		puts("$Revision: 1.326 $ $Date: 2003/04/29 22:13:11 $\n");
+		puts("$Revision: 1.327 $ $Date: 2003/05/02 20:54:35 $\n");
 	}
 
 	/*
 	 * Create the memory context we will use in the main loop.
 	 *
-	 * QueryContext is reset once per iteration of the main loop, ie, upon
-	 * completion of processing of each supplied query string. It can
-	 * therefore be used for any data that should live just as long as the
-	 * query string --- parse trees, for example.
+	 * MessageContext is reset once per iteration of the main loop, ie, upon
+	 * completion of processing of each command message from the client.
 	 */
-	QueryContext = AllocSetContextCreate(TopMemoryContext,
-										 "QueryContext",
-										 ALLOCSET_DEFAULT_MINSIZE,
-										 ALLOCSET_DEFAULT_INITSIZE,
-										 ALLOCSET_DEFAULT_MAXSIZE);
+	MessageContext = AllocSetContextCreate(TopMemoryContext,
+										   "MessageContext",
+										   ALLOCSET_DEFAULT_MINSIZE,
+										   ALLOCSET_DEFAULT_INITSIZE,
+										   ALLOCSET_DEFAULT_MAXSIZE);
 
 	/* ----------
 	 * Tell the statistics collector that we're alive and
@@ -1897,6 +1740,9 @@ PostgresMain(int argc, char *argv[], const char *username)
 		 */
 		MemoryContextSwitchTo(TopMemoryContext);
 		MemoryContextResetAndDeleteChildren(ErrorContext);
+		CurrentPortal = NULL;
+		PortalContext = NULL;
+		QueryContext = NULL;
 
 		/*
 		 * Clear flag to indicate that we got out of error recovery mode
@@ -1924,10 +1770,10 @@ PostgresMain(int argc, char *argv[], const char *username)
 	{
 		/*
 		 * Release storage left over from prior query cycle, and create a
-		 * new query input buffer in the cleared QueryContext.
+		 * new query input buffer in the cleared MessageContext.
 		 */
-		MemoryContextSwitchTo(QueryContext);
-		MemoryContextResetAndDeleteChildren(QueryContext);
+		MemoryContextSwitchTo(MessageContext);
+		MemoryContextResetAndDeleteChildren(MessageContext);
 
 		input_message = makeStringInfo();
 
@@ -2006,25 +1852,9 @@ PostgresMain(int argc, char *argv[], const char *username)
 		{
 			case 'Q':			/* simple query */
 				{
-					/*
-					 * Process the query string.
-					 *
-					 * Note: transaction command start/end is now done within
-					 * pg_exec_query_string(), not here.
-					 */
 					const char *query_string = pq_getmsgstring(input_message);
 
-					if (log_statement_stats)
-						ResetUsage();
-
-					pgstat_report_activity(query_string);
-
-					pg_exec_query_string(query_string,
-										 whereToSendOutput,
-										 QueryContext);
-
-					if (log_statement_stats)
-						ShowUsage("QUERY STATISTICS");
+					exec_simple_query(query_string, whereToSendOutput);
 
 					send_rfq = true;
 				}
@@ -2225,381 +2055,3 @@ ShowUsage(const char *title)
 
 	pfree(str.data);
 }
-
-/* ----------------------------------------------------------------
- *		CreateCommandTag
- *
- *		utility to get a string representation of the
- *		command operation.
- * ----------------------------------------------------------------
- */
-static const char *
-CreateCommandTag(Node *parsetree)
-{
-	const char *tag;
-
-	switch (nodeTag(parsetree))
-	{
-		case T_InsertStmt:
-			tag = "INSERT";
-			break;
-
-		case T_DeleteStmt:
-			tag = "DELETE";
-			break;
-
-		case T_UpdateStmt:
-			tag = "UPDATE";
-			break;
-
-		case T_SelectStmt:
-			tag = "SELECT";
-			break;
-
-		case T_TransactionStmt:
-			{
-				TransactionStmt *stmt = (TransactionStmt *) parsetree;
-
-				switch (stmt->kind)
-				{
-					case TRANS_STMT_BEGIN:
-						tag = "BEGIN";
-						break;
-
-					case TRANS_STMT_START:
-						tag = "START TRANSACTION";
-						break;
-
-					case TRANS_STMT_COMMIT:
-						tag = "COMMIT";
-						break;
-
-					case TRANS_STMT_ROLLBACK:
-						tag = "ROLLBACK";
-						break;
-
-					default:
-						tag = "???";
-						break;
-				}
-			}
-			break;
-
-		case T_DeclareCursorStmt:
-			tag = "DECLARE CURSOR";
-			break;
-
-		case T_ClosePortalStmt:
-			tag = "CLOSE CURSOR";
-			break;
-
-		case T_FetchStmt:
-			{
-				FetchStmt  *stmt = (FetchStmt *) parsetree;
-
-				tag = (stmt->ismove) ? "MOVE" : "FETCH";
-			}
-			break;
-
-		case T_CreateDomainStmt:
-			tag = "CREATE DOMAIN";
-			break;
-
-		case T_CreateSchemaStmt:
-			tag = "CREATE SCHEMA";
-			break;
-
-		case T_CreateStmt:
-			tag = "CREATE TABLE";
-			break;
-
-		case T_DropStmt:
-			switch (((DropStmt *) parsetree)->removeType)
-			{
-				case DROP_TABLE:
-					tag = "DROP TABLE";
-					break;
-				case DROP_SEQUENCE:
-					tag = "DROP SEQUENCE";
-					break;
-				case DROP_VIEW:
-					tag = "DROP VIEW";
-					break;
-				case DROP_INDEX:
-					tag = "DROP INDEX";
-					break;
-				case DROP_TYPE:
-					tag = "DROP TYPE";
-					break;
-				case DROP_DOMAIN:
-					tag = "DROP DOMAIN";
-					break;
-				case DROP_CONVERSION:
-					tag = "DROP CONVERSION";
-					break;
-				case DROP_SCHEMA:
-					tag = "DROP SCHEMA";
-					break;
-				default:
-					tag = "???";
-			}
-			break;
-
-		case T_TruncateStmt:
-			tag = "TRUNCATE TABLE";
-			break;
-
-		case T_CommentStmt:
-			tag = "COMMENT";
-			break;
-
-		case T_CopyStmt:
-			tag = "COPY";
-			break;
-
-		case T_RenameStmt:
-			if (((RenameStmt *) parsetree)->renameType == RENAME_TRIGGER)
-				tag = "ALTER TRIGGER";
-			else
-				tag = "ALTER TABLE";
-			break;
-
-		case T_AlterTableStmt:
-			tag = "ALTER TABLE";
-			break;
-
-		case T_AlterDomainStmt:
-			tag = "ALTER DOMAIN";
-			break;
-
-		case T_GrantStmt:
-			{
-				GrantStmt  *stmt = (GrantStmt *) parsetree;
-
-				tag = (stmt->is_grant) ? "GRANT" : "REVOKE";
-			}
-			break;
-
-		case T_DefineStmt:
-			switch (((DefineStmt *) parsetree)->kind)
-			{
-				case DEFINE_STMT_AGGREGATE:
-					tag = "CREATE AGGREGATE";
-					break;
-				case DEFINE_STMT_OPERATOR:
-					tag = "CREATE OPERATOR";
-					break;
-				case DEFINE_STMT_TYPE:
-					tag = "CREATE TYPE";
-					break;
-				default:
-					tag = "???";
-			}
-			break;
-
-		case T_CompositeTypeStmt:
-			tag = "CREATE TYPE";
-			break;
-
-		case T_ViewStmt:
-			tag = "CREATE VIEW";
-			break;
-
-		case T_CreateFunctionStmt:
-			tag = "CREATE FUNCTION";
-			break;
-
-		case T_IndexStmt:
-			tag = "CREATE INDEX";
-			break;
-
-		case T_RuleStmt:
-			tag = "CREATE RULE";
-			break;
-
-		case T_CreateSeqStmt:
-			tag = "CREATE SEQUENCE";
-			break;
-
-		case T_AlterSeqStmt:
-			tag = "ALTER SEQUENCE";
-			break;
-
-		case T_RemoveAggrStmt:
-			tag = "DROP AGGREGATE";
-			break;
-
-		case T_RemoveFuncStmt:
-			tag = "DROP FUNCTION";
-			break;
-
-		case T_RemoveOperStmt:
-			tag = "DROP OPERATOR";
-			break;
-
-		case T_CreatedbStmt:
-			tag = "CREATE DATABASE";
-			break;
-
-		case T_AlterDatabaseSetStmt:
-			tag = "ALTER DATABASE";
-			break;
-
-		case T_DropdbStmt:
-			tag = "DROP DATABASE";
-			break;
-
-		case T_NotifyStmt:
-			tag = "NOTIFY";
-			break;
-
-		case T_ListenStmt:
-			tag = "LISTEN";
-			break;
-
-		case T_UnlistenStmt:
-			tag = "UNLISTEN";
-			break;
-
-		case T_LoadStmt:
-			tag = "LOAD";
-			break;
-
-		case T_ClusterStmt:
-			tag = "CLUSTER";
-			break;
-
-		case T_VacuumStmt:
-			if (((VacuumStmt *) parsetree)->vacuum)
-				tag = "VACUUM";
-			else
-				tag = "ANALYZE";
-			break;
-
-		case T_ExplainStmt:
-			tag = "EXPLAIN";
-			break;
-
-		case T_VariableSetStmt:
-			tag = "SET";
-			break;
-
-		case T_VariableShowStmt:
-			tag = "SHOW";
-			break;
-
-		case T_VariableResetStmt:
-			tag = "RESET";
-			break;
-
-		case T_CreateTrigStmt:
-			tag = "CREATE TRIGGER";
-			break;
-
-		case T_DropPropertyStmt:
-			switch (((DropPropertyStmt *) parsetree)->removeType)
-			{
-				case DROP_TRIGGER:
-					tag = "DROP TRIGGER";
-					break;
-				case DROP_RULE:
-					tag = "DROP RULE";
-					break;
-				default:
-					tag = "???";
-			}
-			break;
-
-		case T_CreatePLangStmt:
-			tag = "CREATE LANGUAGE";
-			break;
-
-		case T_DropPLangStmt:
-			tag = "DROP LANGUAGE";
-			break;
-
-		case T_CreateUserStmt:
-			tag = "CREATE USER";
-			break;
-
-		case T_AlterUserStmt:
-			tag = "ALTER USER";
-			break;
-
-		case T_AlterUserSetStmt:
-			tag = "ALTER USER";
-			break;
-
-		case T_DropUserStmt:
-			tag = "DROP USER";
-			break;
-
-		case T_LockStmt:
-			tag = "LOCK TABLE";
-			break;
-
-		case T_ConstraintsSetStmt:
-			tag = "SET CONSTRAINTS";
-			break;
-
-		case T_CreateGroupStmt:
-			tag = "CREATE GROUP";
-			break;
-
-		case T_AlterGroupStmt:
-			tag = "ALTER GROUP";
-			break;
-
-		case T_DropGroupStmt:
-			tag = "DROP GROUP";
-			break;
-
-		case T_CheckPointStmt:
-			tag = "CHECKPOINT";
-			break;
-
-		case T_ReindexStmt:
-			tag = "REINDEX";
-			break;
-
-		case T_CreateConversionStmt:
-			tag = "CREATE CONVERSION";
-			break;
-
-		case T_CreateCastStmt:
-			tag = "CREATE CAST";
-			break;
-
-		case T_DropCastStmt:
-			tag = "DROP CAST";
-			break;
-
-		case T_CreateOpClassStmt:
-			tag = "CREATE OPERATOR CLASS";
-			break;
-
-		case T_RemoveOpClassStmt:
-			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));
-			tag = "???";
-			break;
-	}
-
-	return tag;
-}
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 29d5018440dee68ec5f58644aefa0c75f321de73..f70b91322445713f14921f0930037dd5b2b11acf 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,15 +8,35 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.59 2003/03/10 03:53:51 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.60 2003/05/02 20:54:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
-
 #include "postgres.h"
 
 #include "executor/executor.h"
+#include "miscadmin.h"
+#include "tcop/tcopprot.h"
 #include "tcop/pquery.h"
+#include "tcop/utility.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+
+static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
+						   CommandDest dest);
+static long PortalRunSelect(Portal portal, bool forward, long count,
+							CommandDest dest);
+static void PortalRunUtility(Portal portal, Query *query,
+							 CommandDest dest, char *completionTag);
+static void PortalRunMulti(Portal portal,
+						   CommandDest dest, CommandDest altdest,
+						   char *completionTag);
+static long DoPortalRunFetch(Portal portal,
+							 FetchDirection fdirection,
+							 long count,
+							 CommandDest dest);
+static void DoPortalRewind(Portal portal);
 
 
 /*
@@ -63,19 +83,26 @@ FreeQueryDesc(QueryDesc *qdesc)
 
 /*
  * ProcessQuery
- *		Execute a query
+ *		Execute a single query
  *
  *	parsetree: the query tree
  *	plan: the plan tree for the query
+ *	params: any parameters needed
+ *	portalName: name of portal being used
  *	dest: where to send results
  *	completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
  *		in which to store a command completion status string.
  *
  * completionTag may be NULL if caller doesn't want a status string.
+ *
+ * Must be called in a memory context that will be reset or deleted on
+ * error; otherwise the executor's memory usage will be leaked.
  */
 void
 ProcessQuery(Query *parsetree,
 			 Plan *plan,
+			 ParamListInfo params,
+			 const char *portalName,
 			 CommandDest dest,
 			 char *completionTag)
 {
@@ -103,7 +130,8 @@ ProcessQuery(Query *parsetree,
 	/*
 	 * Create the QueryDesc object
 	 */
-	queryDesc = CreateQueryDesc(parsetree, plan, dest, NULL, NULL, false);
+	queryDesc = CreateQueryDesc(parsetree, plan, dest, portalName, params,
+								false);
 
 	/*
 	 * Call ExecStart to prepare the plan for execution
@@ -111,7 +139,7 @@ ProcessQuery(Query *parsetree,
 	ExecutorStart(queryDesc);
 
 	/*
-	 * And run the plan.
+	 * Run the plan to completion.
 	 */
 	ExecutorRun(queryDesc, ForwardScanDirection, 0L);
 
@@ -156,3 +184,871 @@ ProcessQuery(Query *parsetree,
 
 	FreeQueryDesc(queryDesc);
 }
+
+
+/*
+ * PortalStart
+ *		Prepare a portal for execution.
+ *
+ * Caller must already have created the portal, done PortalDefineQuery(),
+ * and adjusted portal options if needed.  If parameters are needed by
+ * the query, they must be passed in here (caller is responsible for
+ * giving them appropriate lifetime).
+ *
+ * On return, portal is ready to accept PortalRun() calls, and the result
+ * tupdesc (if any) is known.
+ */
+void
+PortalStart(Portal portal, ParamListInfo params)
+{
+	MemoryContext oldContext;
+	Query	   *query = NULL;
+	QueryDesc  *queryDesc;
+
+	AssertArg(PortalIsValid(portal));
+	AssertState(portal->queryContext != NULL); /* query defined? */
+	AssertState(!portal->portalReady); /* else extra PortalStart */
+
+	oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+
+	/* Must remember portal param list, if any */
+	portal->portalParams = params;
+
+	/*
+	 * Determine the portal execution strategy (see comments in portal.h)
+	 */
+	portal->strategy = PORTAL_MULTI_QUERY;		/* default assumption */
+	if (length(portal->parseTrees) == 1)
+	{
+		query = (Query *) lfirst(portal->parseTrees);
+		if (query->commandType == CMD_SELECT &&
+			query->canSetTag &&
+			query->into == NULL)
+			portal->strategy = PORTAL_ONE_SELECT;
+		else if (query->commandType == CMD_UTILITY &&
+				 query->canSetTag &&
+				 query->utilityStmt != NULL)
+		{
+			/* XXX check for things that can be PORTAL_UTIL_SELECT */
+		}
+	}
+
+	/*
+	 * Fire her up according to the strategy
+	 */
+	switch (portal->strategy)
+	{
+		case PORTAL_ONE_SELECT:
+			/*
+			 * Must set query snapshot before starting executor.
+			 */
+			SetQuerySnapshot();
+			/*
+			 * Create QueryDesc in portal's context; for the moment, set
+			 * the destination to None.
+			 */
+			queryDesc = CreateQueryDesc(query,
+										(Plan *) lfirst(portal->planTrees),
+										None,
+										portal->name,
+										params,
+										false);
+			/*
+			 * Call ExecStart to prepare the plan for execution
+			 */
+			ExecutorStart(queryDesc);
+			/*
+			 * This tells PortalCleanup to shut down the executor
+			 */
+			portal->queryDesc = queryDesc;
+			portal->tupDesc = queryDesc->tupDesc;
+			/*
+			 * Reset cursor position data to "start of query"
+			 */
+			portal->atStart = true;
+			portal->atEnd = false;		/* allow fetches */
+			portal->portalPos = 0;
+			portal->posOverflow = false;
+			break;
+
+		case PORTAL_UTIL_SELECT:
+			/* XXX implement later */
+			/* XXX query snapshot here? no, RunUtility will do it */
+			/* xxx what about Params? */
+			portal->tupDesc = NULL;
+			break;
+
+		case PORTAL_MULTI_QUERY:
+			/* Need do nothing now */
+			portal->tupDesc = NULL;
+			break;
+	}
+
+	MemoryContextSwitchTo(oldContext);
+
+	portal->portalReady = true;
+}
+
+/*
+ * PortalRun
+ *		Run a portal's query or queries.
+ *
+ * count <= 0 is interpreted as a no-op: the destination gets started up
+ * and shut down, but nothing else happens.  Also, count == FETCH_ALL is
+ * interpreted as "all rows".  Note that count is ignored in multi-query
+ * situations, where we always run the portal to completion.
+ *
+ * dest: where to send output of primary (canSetTag) query
+ *
+ * altdest: where to send output of non-primary queries
+ *
+ * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
+ *		in which to store a command completion status string.
+ *		May be NULL if caller doesn't want a status string.
+ *
+ * Returns TRUE if the portal's execution is complete, FALSE if it was
+ * suspended due to exhaustion of the count parameter.
+ */
+bool
+PortalRun(Portal portal, long count, CommandDest dest, CommandDest altdest,
+		  char *completionTag)
+{
+	bool		result;
+	Portal		saveCurrentPortal;
+	MemoryContext savePortalContext;
+	MemoryContext saveQueryContext;
+	MemoryContext oldContext;
+
+	AssertArg(PortalIsValid(portal));
+	AssertState(portal->portalReady); /* else no PortalStart */
+
+	/* Initialize completion tag to empty string */
+	if (completionTag)
+		completionTag[0] = '\0';
+
+	/*
+	 * Check for improper portal use, and mark portal active.
+	 */
+	if (portal->portalDone)
+		elog(ERROR, "Portal \"%s\" cannot be run anymore", portal->name);
+	if (portal->portalActive)
+		elog(ERROR, "Portal \"%s\" already active", portal->name);
+	portal->portalActive = true;
+
+	/*
+	 * Set global portal and context pointers.
+	 */
+	saveCurrentPortal = CurrentPortal;
+	CurrentPortal = portal;
+	savePortalContext = PortalContext;
+	PortalContext = PortalGetHeapMemory(portal);
+	saveQueryContext = QueryContext;
+	QueryContext = portal->queryContext;
+
+	oldContext = MemoryContextSwitchTo(PortalContext);
+
+	switch (portal->strategy)
+	{
+		case PORTAL_ONE_SELECT:
+			(void) PortalRunSelect(portal, true, count, dest);
+			/* we know the query is supposed to set the tag */
+			if (completionTag && portal->commandTag)
+				strcpy(completionTag, portal->commandTag);
+			/*
+			 * Since it's a forward fetch, say DONE iff atEnd is now true.
+			 */
+			result = portal->atEnd;
+			break;
+
+		case PORTAL_UTIL_SELECT:
+			/*
+			 * If we have not yet run the utility statement, do so,
+			 * storing its results in the portal's tuplestore.
+			 */
+			if (!portal->portalUtilReady)
+			{
+				PortalRunUtility(portal, lfirst(portal->parseTrees),
+								 Tuplestore, NULL);
+				portal->portalUtilReady = true;
+			}
+			/*
+			 * Now fetch desired portion of results.
+			 */
+			(void) PortalRunSelect(portal, true, count, dest);
+			/*
+			 * We know the query is supposed to set the tag; we assume
+			 * only the default tag is needed.
+			 */
+			if (completionTag && portal->commandTag)
+				strcpy(completionTag, portal->commandTag);
+			/*
+			 * Since it's a forward fetch, say DONE iff atEnd is now true.
+			 */
+			result = portal->atEnd;
+			break;
+
+		case PORTAL_MULTI_QUERY:
+			PortalRunMulti(portal, dest, altdest, completionTag);
+			/* Always complete at end of RunMulti */
+			result = true;
+			break;
+
+		default:
+			elog(ERROR, "PortalRun: bogus portal strategy");
+			result = false;		/* keep compiler quiet */
+			break;
+	}
+
+	MemoryContextSwitchTo(oldContext);
+
+	/* Mark portal not active */
+	portal->portalActive = false;
+
+	CurrentPortal = saveCurrentPortal;
+	PortalContext = savePortalContext;
+	QueryContext = saveQueryContext;
+
+	return result;
+}
+
+/*
+ * PortalRunSelect
+ *		Execute a portal's query in SELECT cases (also UTIL_SELECT).
+ *
+ * This handles simple N-rows-forward-or-backward cases.  For more complex
+ * nonsequential access to a portal, see PortalRunFetch.
+ *
+ * count <= 0 is interpreted as a no-op: the destination gets started up
+ * and shut down, but nothing else happens.  Also, count == FETCH_ALL is
+ * interpreted as "all rows".
+ *
+ * Caller must already have validated the Portal and done appropriate
+ * setup (cf. PortalRun).
+ *
+ * Returns number of rows processed (suitable for use in result tag)
+ */
+long
+PortalRunSelect(Portal portal,
+				bool forward,
+				long count,
+				CommandDest dest)
+{
+	QueryDesc  *queryDesc;
+	ScanDirection direction;
+	uint32		nprocessed;
+
+	/*
+	 * NB: queryDesc will be NULL if we are fetching from a held cursor
+	 * or a completed utility query; can't use it in that path.
+	 */
+	queryDesc = PortalGetQueryDesc(portal);
+
+	/* Caller messed up if we have neither a ready query nor held data. */
+	Assert(queryDesc || portal->holdStore);
+
+	/*
+	 * Force the queryDesc destination to the right thing.  This supports
+	 * MOVE, for example, which will pass in dest = None.  This is okay to
+	 * change as long as we do it on every fetch.  (The Executor must not
+	 * assume that dest never changes.)
+	 */
+	if (queryDesc)
+		queryDesc->dest = dest;
+
+	/*
+	 * Determine which direction to go in, and check to see if we're
+	 * already at the end of the available tuples in that direction.  If
+	 * so, set the direction to NoMovement to avoid trying to fetch any
+	 * tuples.	(This check exists because not all plan node types are
+	 * robust about being called again if they've already returned NULL
+	 * once.)  Then call the executor (we must not skip this, because the
+	 * destination needs to see a setup and shutdown even if no tuples are
+	 * available).	Finally, update the portal position state depending on
+	 * the number of tuples that were retrieved.
+	 */
+	if (forward)
+	{
+		if (portal->atEnd || count <= 0)
+			direction = NoMovementScanDirection;
+		else
+			direction = ForwardScanDirection;
+
+		/* In the executor, zero count processes all rows */
+		if (count == FETCH_ALL)
+			count = 0;
+
+		if (portal->holdStore)
+			nprocessed = RunFromStore(portal, direction, count, dest);
+		else
+		{
+			ExecutorRun(queryDesc, direction, count);
+			nprocessed = queryDesc->estate->es_processed;
+		}
+
+		if (direction != NoMovementScanDirection)
+		{
+			long	oldPos;
+
+			if (nprocessed > 0)
+				portal->atStart = false;	/* OK to go backward now */
+			if (count == 0 ||
+				(unsigned long) nprocessed < (unsigned long) count)
+				portal->atEnd = true;		/* we retrieved 'em all */
+			oldPos = portal->portalPos;
+			portal->portalPos += nprocessed;
+			/* portalPos doesn't advance when we fall off the end */
+			if (portal->portalPos < oldPos)
+				portal->posOverflow = true;
+		}
+	}
+	else
+	{
+		if (portal->cursorOptions & CURSOR_OPT_NO_SCROLL)
+			elog(ERROR, "Cursor can only scan forward"
+				 "\n\tDeclare it with SCROLL option to enable backward scan");
+
+		if (portal->atStart || count <= 0)
+			direction = NoMovementScanDirection;
+		else
+			direction = BackwardScanDirection;
+
+		/* In the executor, zero count processes all rows */
+		if (count == FETCH_ALL)
+			count = 0;
+
+		if (portal->holdStore)
+			nprocessed = RunFromStore(portal, direction, count, dest);
+		else
+		{
+			ExecutorRun(queryDesc, direction, count);
+			nprocessed = queryDesc->estate->es_processed;
+		}
+
+		if (direction != NoMovementScanDirection)
+		{
+			if (nprocessed > 0 && portal->atEnd)
+			{
+				portal->atEnd = false;		/* OK to go forward now */
+				portal->portalPos++;		/* adjust for endpoint case */
+			}
+			if (count == 0 ||
+				(unsigned long) nprocessed < (unsigned long) count)
+			{
+				portal->atStart = true;		/* we retrieved 'em all */
+				portal->portalPos = 0;
+				portal->posOverflow = false;
+			}
+			else
+			{
+				long	oldPos;
+
+				oldPos = portal->portalPos;
+				portal->portalPos -= nprocessed;
+				if (portal->portalPos > oldPos ||
+					portal->portalPos <= 0)
+					portal->posOverflow = true;
+			}
+		}
+	}
+
+	return nprocessed;
+}
+
+/*
+ * RunFromStore
+ *		Fetch tuples from the portal's tuple store.
+ *
+ * Calling conventions are similar to ExecutorRun, except that we
+ * do not depend on having a queryDesc or estate.  Therefore we return the
+ * number of tuples processed as the result, not in estate->es_processed.
+ *
+ * One difference from ExecutorRun is that the destination receiver functions
+ * are run in the caller's memory context (since we have no estate).  Watch
+ * out for memory leaks.
+ */
+static uint32
+RunFromStore(Portal portal, ScanDirection direction, long count,
+			 CommandDest dest)
+{
+	DestReceiver *destfunc;
+	long		current_tuple_count = 0;
+
+	destfunc = DestToFunction(dest);
+	(*destfunc->setup) (destfunc, CMD_SELECT, portal->name, portal->tupDesc);
+
+	if (direction == NoMovementScanDirection)
+	{
+		/* do nothing except start/stop the destination */
+	}
+	else
+	{
+		bool	forward = (direction == ForwardScanDirection);
+
+		for (;;)
+		{
+			MemoryContext oldcontext;
+			HeapTuple tup;
+			bool should_free;
+
+			oldcontext = MemoryContextSwitchTo(portal->holdContext);
+
+			tup = tuplestore_getheaptuple(portal->holdStore, forward,
+										  &should_free);
+
+			MemoryContextSwitchTo(oldcontext);
+
+			if (tup == NULL)
+				break;
+
+			(*destfunc->receiveTuple) (tup, portal->tupDesc, destfunc);
+
+			if (should_free)
+				pfree(tup);
+
+			/*
+			 * check our tuple count.. if we've processed the proper number
+			 * then quit, else loop again and process more tuples.  Zero
+			 * count means no limit.
+			 */
+			current_tuple_count++;
+			if (count && count == current_tuple_count)
+				break;
+		}
+	}
+
+	(*destfunc->cleanup) (destfunc);
+
+	return (uint32) current_tuple_count;
+}
+
+/*
+ * PortalRunUtility
+ *		Execute a utility statement inside a portal.
+ */
+static void
+PortalRunUtility(Portal portal, Query *query,
+				 CommandDest dest, char *completionTag)
+{
+	Node   *utilityStmt = query->utilityStmt;
+
+	elog(DEBUG2, "ProcessUtility");
+
+	/*
+	 * Set snapshot if utility stmt needs one.  Most reliable
+	 * way to do this seems to be to enumerate those that do not
+	 * need one; this is a short list.  Transaction control,
+	 * LOCK, and SET must *not* set a snapshot since they need
+	 * to be executable at the start of a serializable transaction
+	 * without freezing a snapshot.  By extension we allow SHOW
+	 * not to set a snapshot.  The other stmts listed are just
+	 * efficiency hacks.  Beware of listing anything that can
+	 * modify the database --- if, say, it has to update a
+	 * functional index, then it had better have a snapshot.
+	 */
+	if (! (IsA(utilityStmt, TransactionStmt) ||
+		   IsA(utilityStmt, LockStmt) ||
+		   IsA(utilityStmt, VariableSetStmt) ||
+		   IsA(utilityStmt, VariableShowStmt) ||
+		   IsA(utilityStmt, VariableResetStmt) ||
+		   IsA(utilityStmt, ConstraintsSetStmt) ||
+		   /* efficiency hacks from here down */
+		   IsA(utilityStmt, FetchStmt) ||
+		   IsA(utilityStmt, ListenStmt) ||
+		   IsA(utilityStmt, NotifyStmt) ||
+		   IsA(utilityStmt, UnlistenStmt) ||
+		   IsA(utilityStmt, CheckPointStmt)))
+		SetQuerySnapshot();
+
+	if (query->canSetTag)
+	{
+		/* utility statement can override default tag string */
+		ProcessUtility(utilityStmt, dest, completionTag);
+		if (completionTag && completionTag[0] == '\0' && portal->commandTag)
+			strcpy(completionTag, portal->commandTag); /* use the default */
+	}
+	else
+	{
+		/* utility added by rewrite cannot set tag */
+		ProcessUtility(utilityStmt, dest, NULL);
+	}
+
+	/* Some utility statements may change context on us */
+	MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+}
+
+/*
+ * PortalRunMulti
+ *		Execute a portal's queries in the general case (multi queries).
+ */
+static void
+PortalRunMulti(Portal portal,
+			   CommandDest dest, CommandDest altdest,
+			   char *completionTag)
+{
+	List	   *plantree_list = portal->planTrees;
+	List	   *querylist_item;
+
+	/*
+	 * Loop to handle the individual queries generated from a
+	 * single parsetree by analysis and rewrite.
+	 */
+	foreach(querylist_item, portal->parseTrees)
+	{
+		Query	   *query = (Query *) lfirst(querylist_item);
+		Plan	   *plan = (Plan *) lfirst(plantree_list);
+
+		plantree_list = lnext(plantree_list);
+
+		/*
+		 * If we got a cancel signal in prior command, quit
+		 */
+		CHECK_FOR_INTERRUPTS();
+
+		if (query->commandType == CMD_UTILITY)
+		{
+			/*
+			 * process utility functions (create, destroy, etc..)
+			 */
+			Assert(plan == NULL);
+
+			PortalRunUtility(portal, query,
+							 query->canSetTag ? dest : altdest,
+							 completionTag);
+		}
+		else
+		{
+			/*
+			 * process a plannable query.
+			 */
+			elog(DEBUG2, "ProcessQuery");
+
+			/* Must always set snapshot for plannable queries */
+			SetQuerySnapshot();
+
+			/*
+			 * execute the plan
+			 */
+			if (log_executor_stats)
+				ResetUsage();
+
+			if (query->canSetTag)
+			{
+				/* statement can set tag string */
+				ProcessQuery(query, plan,
+							 portal->portalParams, portal->name,
+							 dest, completionTag);
+			}
+			else
+			{
+				/* stmt added by rewrite cannot set tag */
+				ProcessQuery(query, plan,
+							 portal->portalParams, portal->name,
+							 altdest, NULL);
+			}
+
+			if (log_executor_stats)
+				ShowUsage("EXECUTOR STATISTICS");
+		}
+
+		/*
+		 * Increment command counter between queries, but not after the
+		 * last one.
+		 */
+		if (plantree_list != NIL)
+			CommandCounterIncrement();
+
+		/*
+		 * Clear subsidiary contexts to recover temporary memory.
+		 */
+		Assert(PortalGetHeapMemory(portal) == CurrentMemoryContext);
+
+		MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
+	}
+
+	/*
+	 * If a command completion tag was supplied, use it.  Otherwise
+	 * use the portal's commandTag as the default completion tag.
+	 *
+	 * Exception: clients will expect INSERT/UPDATE/DELETE tags to
+	 * have counts, so fake something up if necessary.  (This could
+	 * happen if the original query was replaced by a DO INSTEAD rule.)
+	 */
+	if (completionTag && completionTag[0] == '\0')
+	{
+		if (portal->commandTag)
+			strcpy(completionTag, portal->commandTag);
+		if (strcmp(completionTag, "INSERT") == 0)
+			strcpy(completionTag, "INSERT 0 0");
+		else if (strcmp(completionTag, "UPDATE") == 0)
+			strcpy(completionTag, "UPDATE 0");
+		else if (strcmp(completionTag, "DELETE") == 0)
+			strcpy(completionTag, "DELETE 0");
+	}
+
+	/* Prevent portal's commands from being re-executed */
+	portal->portalDone = true;
+}
+
+/*
+ * PortalRunFetch
+ *		Variant form of PortalRun that supports SQL FETCH directions.
+ *
+ * Returns number of rows processed (suitable for use in result tag)
+ */
+long
+PortalRunFetch(Portal portal,
+			   FetchDirection fdirection,
+			   long count,
+			   CommandDest dest)
+{
+	long		result;
+	Portal		saveCurrentPortal;
+	MemoryContext savePortalContext;
+	MemoryContext saveQueryContext;
+	MemoryContext oldContext;
+
+	AssertArg(PortalIsValid(portal));
+	AssertState(portal->portalReady); /* else no PortalStart */
+
+	/*
+	 * Check for improper portal use, and mark portal active.
+	 */
+	if (portal->portalDone)
+		elog(ERROR, "Portal \"%s\" cannot be run anymore", portal->name);
+	if (portal->portalActive)
+		elog(ERROR, "Portal \"%s\" already active", portal->name);
+	portal->portalActive = true;
+
+	/*
+	 * Set global portal and context pointers.
+	 */
+	saveCurrentPortal = CurrentPortal;
+	CurrentPortal = portal;
+	savePortalContext = PortalContext;
+	PortalContext = PortalGetHeapMemory(portal);
+	saveQueryContext = QueryContext;
+	QueryContext = portal->queryContext;
+
+	oldContext = MemoryContextSwitchTo(PortalContext);
+
+	switch (portal->strategy)
+	{
+		case PORTAL_ONE_SELECT:
+			result = DoPortalRunFetch(portal, fdirection, count, dest);
+			break;
+
+		default:
+			elog(ERROR, "PortalRunFetch: unsupported portal strategy");
+			result = 0;			/* keep compiler quiet */
+			break;
+	}
+
+	MemoryContextSwitchTo(oldContext);
+
+	/* Mark portal not active */
+	portal->portalActive = false;
+
+	CurrentPortal = saveCurrentPortal;
+	PortalContext = savePortalContext;
+	QueryContext = saveQueryContext;
+
+	return result;
+}
+
+/*
+ * DoPortalRunFetch
+ *		Guts of PortalRunFetch --- the portal context is already set up
+ *
+ * Returns number of rows processed (suitable for use in result tag)
+ */
+static long
+DoPortalRunFetch(Portal portal,
+				 FetchDirection fdirection,
+				 long count,
+				 CommandDest dest)
+{
+	bool		forward;
+
+	Assert(portal->strategy == PORTAL_ONE_SELECT);
+
+	switch (fdirection)
+	{
+		case FETCH_FORWARD:
+			if (count < 0)
+			{
+				fdirection = FETCH_BACKWARD;
+				count = -count;
+			}
+			/* fall out of switch to share code with FETCH_BACKWARD */
+			break;
+		case FETCH_BACKWARD:
+			if (count < 0)
+			{
+				fdirection = FETCH_FORWARD;
+				count = -count;
+			}
+			/* fall out of switch to share code with FETCH_FORWARD */
+			break;
+		case FETCH_ABSOLUTE:
+			if (count > 0)
+			{
+				/*
+				 * Definition: Rewind to start, advance count-1 rows, return
+				 * next row (if any).  In practice, if the goal is less than
+				 * halfway back to the start, it's better to scan from where
+				 * we are.  In any case, we arrange to fetch the target row
+				 * going forwards.
+				 */
+				if (portal->posOverflow || portal->portalPos == LONG_MAX ||
+					count-1 <= portal->portalPos / 2)
+				{
+					DoPortalRewind(portal);
+					if (count > 1)
+						PortalRunSelect(portal, true, count-1, None);
+				}
+				else
+				{
+					long		pos = portal->portalPos;
+
+					if (portal->atEnd)
+						pos++;	/* need one extra fetch if off end */
+					if (count <= pos)
+						PortalRunSelect(portal, false, pos-count+1, None);
+					else if (count > pos+1)
+						PortalRunSelect(portal, true, count-pos-1, None);
+				}
+				return PortalRunSelect(portal, true, 1L, dest);
+			}
+			else if (count < 0)
+			{
+				/*
+				 * Definition: Advance to end, back up abs(count)-1 rows,
+				 * return prior row (if any).  We could optimize this if we
+				 * knew in advance where the end was, but typically we won't.
+				 * (Is it worth considering case where count > half of size
+				 * of query?  We could rewind once we know the size ...)
+				 */
+				PortalRunSelect(portal, true, FETCH_ALL, None);
+				if (count < -1)
+					PortalRunSelect(portal, false, -count-1, None);
+				return PortalRunSelect(portal, false, 1L, dest);
+			}
+			else /* count == 0 */
+			{
+				/* Rewind to start, return zero rows */
+				DoPortalRewind(portal);
+				return PortalRunSelect(portal, true, 0L, dest);
+			}
+			break;
+		case FETCH_RELATIVE:
+			if (count > 0)
+			{
+				/*
+				 * Definition: advance count-1 rows, return next row (if any).
+				 */
+				if (count > 1)
+					PortalRunSelect(portal, true, count-1, None);
+				return PortalRunSelect(portal, true, 1L, dest);
+			}
+			else if (count < 0)
+			{
+				/*
+				 * Definition: back up abs(count)-1 rows, return prior row
+				 * (if any).
+				 */
+				if (count < -1)
+					PortalRunSelect(portal, false, -count-1, None);
+				return PortalRunSelect(portal, false, 1L, dest);
+			}
+			else /* count == 0 */
+			{
+				/* Same as FETCH FORWARD 0, so fall out of switch */
+				fdirection = FETCH_FORWARD;
+			}
+			break;
+		default:
+			elog(ERROR, "PortalRunFetch: bogus direction");
+			break;
+	}
+
+	/*
+	 * Get here with fdirection == FETCH_FORWARD or FETCH_BACKWARD,
+	 * and count >= 0.
+	 */
+	forward = (fdirection == FETCH_FORWARD);
+
+	/*
+	 * Zero count means to re-fetch the current row, if any (per SQL92)
+	 */
+	if (count == 0)
+	{
+		bool	on_row;
+
+		/* Are we sitting on a row? */
+		on_row = (!portal->atStart && !portal->atEnd);
+
+		if (dest == None)
+		{
+			/* MOVE 0 returns 0/1 based on if FETCH 0 would return a row */
+			return on_row ? 1L : 0L;
+		}
+		else
+		{
+			/*
+			 * If we are sitting on a row, back up one so we can re-fetch it.
+			 * If we are not sitting on a row, we still have to start up and
+			 * shut down the executor so that the destination is initialized
+			 * and shut down correctly; so keep going.  To PortalRunSelect,
+			 * count == 0 means we will retrieve no row.
+			 */
+			if (on_row)
+			{
+				PortalRunSelect(portal, false, 1L, None);
+				/* Set up to fetch one row forward */
+				count = 1;
+				forward = true;
+			}
+		}
+	}
+
+	/*
+	 * Optimize MOVE BACKWARD ALL into a Rewind.
+	 */
+	if (!forward && count == FETCH_ALL && dest == None)
+	{
+		long	result = portal->portalPos;
+
+		if (result > 0 && !portal->atEnd)
+			result--;
+		DoPortalRewind(portal);
+		/* result is bogus if pos had overflowed, but it's best we can do */
+		return result;
+	}
+
+	return PortalRunSelect(portal, forward, count, dest);
+}
+
+/*
+ * DoPortalRewind - rewind a Portal to starting point
+ */
+static void
+DoPortalRewind(Portal portal)
+{
+	if (portal->holdStore)
+	{
+		MemoryContext oldcontext;
+
+		oldcontext = MemoryContextSwitchTo(portal->holdContext);
+		tuplestore_rescan(portal->holdStore);
+		MemoryContextSwitchTo(oldcontext);
+	}
+	if (PortalGetQueryDesc(portal))
+	{
+		ExecutorRewind(PortalGetQueryDesc(portal));
+	}
+
+	portal->atStart = true;
+	portal->atEnd = false;
+	portal->portalPos = 0;
+	portal->posOverflow = false;
+}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 390adeb9cb9982146a2065ca04ca23a3c78b41d7..a0431f350c8f55d144b17ca980cfae4b4d845fbd 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.197 2003/03/20 18:52:48 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.198 2003/05/02 20:54:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1027,3 +1027,386 @@ ProcessUtility(Node *parsetree,
 			break;
 	}
 }
+
+
+/*
+ * CreateCommandTag
+ *		utility to get a string representation of the
+ *		command operation, given a raw (un-analyzed) parsetree.
+ *
+ * This must handle all raw command types, but since the vast majority
+ * of 'em are utility commands, it seems sensible to keep it here.
+ *
+ * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
+ * Also, the result must point at a true constant (permanent storage).
+ */
+const char *
+CreateCommandTag(Node *parsetree)
+{
+	const char *tag;
+
+	switch (nodeTag(parsetree))
+	{
+		case T_InsertStmt:
+			tag = "INSERT";
+			break;
+
+		case T_DeleteStmt:
+			tag = "DELETE";
+			break;
+
+		case T_UpdateStmt:
+			tag = "UPDATE";
+			break;
+
+		case T_SelectStmt:
+			tag = "SELECT";
+			break;
+
+		case T_TransactionStmt:
+			{
+				TransactionStmt *stmt = (TransactionStmt *) parsetree;
+
+				switch (stmt->kind)
+				{
+					case TRANS_STMT_BEGIN:
+						tag = "BEGIN";
+						break;
+
+					case TRANS_STMT_START:
+						tag = "START TRANSACTION";
+						break;
+
+					case TRANS_STMT_COMMIT:
+						tag = "COMMIT";
+						break;
+
+					case TRANS_STMT_ROLLBACK:
+						tag = "ROLLBACK";
+						break;
+
+					default:
+						tag = "???";
+						break;
+				}
+			}
+			break;
+
+		case T_DeclareCursorStmt:
+			tag = "DECLARE CURSOR";
+			break;
+
+		case T_ClosePortalStmt:
+			tag = "CLOSE CURSOR";
+			break;
+
+		case T_FetchStmt:
+			{
+				FetchStmt  *stmt = (FetchStmt *) parsetree;
+
+				tag = (stmt->ismove) ? "MOVE" : "FETCH";
+			}
+			break;
+
+		case T_CreateDomainStmt:
+			tag = "CREATE DOMAIN";
+			break;
+
+		case T_CreateSchemaStmt:
+			tag = "CREATE SCHEMA";
+			break;
+
+		case T_CreateStmt:
+			tag = "CREATE TABLE";
+			break;
+
+		case T_DropStmt:
+			switch (((DropStmt *) parsetree)->removeType)
+			{
+				case DROP_TABLE:
+					tag = "DROP TABLE";
+					break;
+				case DROP_SEQUENCE:
+					tag = "DROP SEQUENCE";
+					break;
+				case DROP_VIEW:
+					tag = "DROP VIEW";
+					break;
+				case DROP_INDEX:
+					tag = "DROP INDEX";
+					break;
+				case DROP_TYPE:
+					tag = "DROP TYPE";
+					break;
+				case DROP_DOMAIN:
+					tag = "DROP DOMAIN";
+					break;
+				case DROP_CONVERSION:
+					tag = "DROP CONVERSION";
+					break;
+				case DROP_SCHEMA:
+					tag = "DROP SCHEMA";
+					break;
+				default:
+					tag = "???";
+			}
+			break;
+
+		case T_TruncateStmt:
+			tag = "TRUNCATE TABLE";
+			break;
+
+		case T_CommentStmt:
+			tag = "COMMENT";
+			break;
+
+		case T_CopyStmt:
+			tag = "COPY";
+			break;
+
+		case T_RenameStmt:
+			if (((RenameStmt *) parsetree)->renameType == RENAME_TRIGGER)
+				tag = "ALTER TRIGGER";
+			else
+				tag = "ALTER TABLE";
+			break;
+
+		case T_AlterTableStmt:
+			tag = "ALTER TABLE";
+			break;
+
+		case T_AlterDomainStmt:
+			tag = "ALTER DOMAIN";
+			break;
+
+		case T_GrantStmt:
+			{
+				GrantStmt  *stmt = (GrantStmt *) parsetree;
+
+				tag = (stmt->is_grant) ? "GRANT" : "REVOKE";
+			}
+			break;
+
+		case T_DefineStmt:
+			switch (((DefineStmt *) parsetree)->kind)
+			{
+				case DEFINE_STMT_AGGREGATE:
+					tag = "CREATE AGGREGATE";
+					break;
+				case DEFINE_STMT_OPERATOR:
+					tag = "CREATE OPERATOR";
+					break;
+				case DEFINE_STMT_TYPE:
+					tag = "CREATE TYPE";
+					break;
+				default:
+					tag = "???";
+			}
+			break;
+
+		case T_CompositeTypeStmt:
+			tag = "CREATE TYPE";
+			break;
+
+		case T_ViewStmt:
+			tag = "CREATE VIEW";
+			break;
+
+		case T_CreateFunctionStmt:
+			tag = "CREATE FUNCTION";
+			break;
+
+		case T_IndexStmt:
+			tag = "CREATE INDEX";
+			break;
+
+		case T_RuleStmt:
+			tag = "CREATE RULE";
+			break;
+
+		case T_CreateSeqStmt:
+			tag = "CREATE SEQUENCE";
+			break;
+
+		case T_AlterSeqStmt:
+			tag = "ALTER SEQUENCE";
+			break;
+
+		case T_RemoveAggrStmt:
+			tag = "DROP AGGREGATE";
+			break;
+
+		case T_RemoveFuncStmt:
+			tag = "DROP FUNCTION";
+			break;
+
+		case T_RemoveOperStmt:
+			tag = "DROP OPERATOR";
+			break;
+
+		case T_CreatedbStmt:
+			tag = "CREATE DATABASE";
+			break;
+
+		case T_AlterDatabaseSetStmt:
+			tag = "ALTER DATABASE";
+			break;
+
+		case T_DropdbStmt:
+			tag = "DROP DATABASE";
+			break;
+
+		case T_NotifyStmt:
+			tag = "NOTIFY";
+			break;
+
+		case T_ListenStmt:
+			tag = "LISTEN";
+			break;
+
+		case T_UnlistenStmt:
+			tag = "UNLISTEN";
+			break;
+
+		case T_LoadStmt:
+			tag = "LOAD";
+			break;
+
+		case T_ClusterStmt:
+			tag = "CLUSTER";
+			break;
+
+		case T_VacuumStmt:
+			if (((VacuumStmt *) parsetree)->vacuum)
+				tag = "VACUUM";
+			else
+				tag = "ANALYZE";
+			break;
+
+		case T_ExplainStmt:
+			tag = "EXPLAIN";
+			break;
+
+		case T_VariableSetStmt:
+			tag = "SET";
+			break;
+
+		case T_VariableShowStmt:
+			tag = "SHOW";
+			break;
+
+		case T_VariableResetStmt:
+			tag = "RESET";
+			break;
+
+		case T_CreateTrigStmt:
+			tag = "CREATE TRIGGER";
+			break;
+
+		case T_DropPropertyStmt:
+			switch (((DropPropertyStmt *) parsetree)->removeType)
+			{
+				case DROP_TRIGGER:
+					tag = "DROP TRIGGER";
+					break;
+				case DROP_RULE:
+					tag = "DROP RULE";
+					break;
+				default:
+					tag = "???";
+			}
+			break;
+
+		case T_CreatePLangStmt:
+			tag = "CREATE LANGUAGE";
+			break;
+
+		case T_DropPLangStmt:
+			tag = "DROP LANGUAGE";
+			break;
+
+		case T_CreateUserStmt:
+			tag = "CREATE USER";
+			break;
+
+		case T_AlterUserStmt:
+			tag = "ALTER USER";
+			break;
+
+		case T_AlterUserSetStmt:
+			tag = "ALTER USER";
+			break;
+
+		case T_DropUserStmt:
+			tag = "DROP USER";
+			break;
+
+		case T_LockStmt:
+			tag = "LOCK TABLE";
+			break;
+
+		case T_ConstraintsSetStmt:
+			tag = "SET CONSTRAINTS";
+			break;
+
+		case T_CreateGroupStmt:
+			tag = "CREATE GROUP";
+			break;
+
+		case T_AlterGroupStmt:
+			tag = "ALTER GROUP";
+			break;
+
+		case T_DropGroupStmt:
+			tag = "DROP GROUP";
+			break;
+
+		case T_CheckPointStmt:
+			tag = "CHECKPOINT";
+			break;
+
+		case T_ReindexStmt:
+			tag = "REINDEX";
+			break;
+
+		case T_CreateConversionStmt:
+			tag = "CREATE CONVERSION";
+			break;
+
+		case T_CreateCastStmt:
+			tag = "CREATE CAST";
+			break;
+
+		case T_DropCastStmt:
+			tag = "DROP CAST";
+			break;
+
+		case T_CreateOpClassStmt:
+			tag = "CREATE OPERATOR CLASS";
+			break;
+
+		case T_RemoveOpClassStmt:
+			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));
+			tag = "???";
+			break;
+	}
+
+	return tag;
+}
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 729137a1b9290c0a3dd6a4e865e16948e5be13fe..ba4c48f71ba019b5e3b986c5e30fb352a0e77e7b 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/mmgr/mcxt.c,v 1.39 2003/03/27 16:51:29 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/mmgr/mcxt.c,v 1.40 2003/05/02 20:54:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,9 +43,11 @@ MemoryContext TopMemoryContext = NULL;
 MemoryContext ErrorContext = NULL;
 MemoryContext PostmasterContext = NULL;
 MemoryContext CacheMemoryContext = NULL;
-MemoryContext QueryContext = NULL;
+MemoryContext MessageContext = NULL;
 MemoryContext TopTransactionContext = NULL;
-MemoryContext TransactionCommandContext = NULL;
+/* These two are transient links to contexts owned by other objects: */
+MemoryContext QueryContext = NULL;
+MemoryContext PortalContext = NULL;
 
 
 /*****************************************************************************
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 0b093b2613c492213f64d7d8137cbdb566145deb..974e69a2f1aee9189c15f1ba12acf1ef69f0473b 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -1,36 +1,21 @@
 /*-------------------------------------------------------------------------
  *
  * portalmem.c
- *	  backend portal memory context management stuff
+ *	  backend portal memory management
+ *
+ * Portals are objects representing the execution state of a query.
+ * This module provides memory management services for portals, but it
+ * doesn't actually run the executor for them.
+ *
  *
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.55 2003/04/29 03:21:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.56 2003/05/02 20:54:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
-/*
- * NOTES
- *		A "Portal" is a structure used to keep track of cursor queries.
- *
- *		When the backend sees a "declare cursor" query, it allocates a
- *		"PortalData" structure, plans the query and then stores the query
- *		in the portal without executing it.  Later, when the backend
- *		sees a
- *				fetch 1 from foo
- *		the system looks up the portal named "foo" in the portal table,
- *		gets the planned query and then calls the executor with a count
- *		of 1.  The executor then runs the query and returns a single
- *		tuple.	The problem is that we have to hold onto the state of the
- *		portal query until we see a "close".  This means we have to be
- *		careful about memory management.
- *
- *		I hope this makes things clearer to whoever reads this -cim 2/22/91
- */
-
 #include "postgres.h"
 
 #include "commands/portalcmds.h"
@@ -51,6 +36,9 @@
  * ----------------
  */
 
+Portal	CurrentPortal = NULL;	/* globally visible pointer */
+
+
 #define MAX_PORTALNAME_LEN		NAMEDATALEN
 
 typedef struct portalhashent
@@ -66,7 +54,7 @@ do { \
 	PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
 	\
 	MemSet(key, 0, MAX_PORTALNAME_LEN); \
-	snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", NAME); \
+	StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
 	hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
 										 key, HASH_FIND, NULL); \
 	if (hentry) \
@@ -75,12 +63,12 @@ do { \
 		PORTAL = NULL; \
 } while(0)
 
-#define PortalHashTableInsert(PORTAL) \
+#define PortalHashTableInsert(PORTAL, NAME) \
 do { \
 	PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
 	\
 	MemSet(key, 0, MAX_PORTALNAME_LEN); \
-	snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
+	StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
 	hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
 										 key, HASH_ENTER, &found); \
 	if (hentry == NULL) \
@@ -88,6 +76,8 @@ do { \
 	if (found) \
 		elog(WARNING, "trying to insert a portal name that exists."); \
 	hentry->portal = PORTAL; \
+	/* To avoid duplicate storage, make PORTAL->name point to htab entry */ \
+	PORTAL->name = hentry->portalname; \
 } while(0)
 
 #define PortalHashTableDelete(PORTAL) \
@@ -95,7 +85,7 @@ do { \
 	PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
 	\
 	MemSet(key, 0, MAX_PORTALNAME_LEN); \
-	snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
+	StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \
 	hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
 										 key, HASH_REMOVE, NULL); \
 	if (hentry == NULL) \
@@ -155,50 +145,17 @@ GetPortalByName(const char *name)
 	return portal;
 }
 
-/*
- * PortalSetQuery
- *		Attaches a QueryDesc to the specified portal.  This should be
- *		called only after successfully doing ExecutorStart for the query.
- *
- * Note that in the case of DECLARE CURSOR, some Portal options have
- * already been set in portalcmds.c's PreparePortal().  This is grotty.
- */
-void
-PortalSetQuery(Portal portal, QueryDesc *queryDesc)
-{
-	AssertArg(PortalIsValid(portal));
-
-	/*
-	 * If the user didn't specify a SCROLL type, allow or disallow
-	 * scrolling based on whether it would require any additional
-	 * runtime overhead to do so.
-	 */
-	if (portal->scrollType == DEFAULT_SCROLL)
-	{
-		if (ExecSupportsBackwardScan(queryDesc->plantree))
-			portal->scrollType = ENABLE_SCROLL;
-		else
-			portal->scrollType = DISABLE_SCROLL;
-	}
-
-	portal->queryDesc = queryDesc;
-	portal->executorRunning = true;	/* now need to shut down executor */
-
-	portal->atStart = true;
-	portal->atEnd = false;		/* allow fetches */
-	portal->portalPos = 0;
-	portal->posOverflow = false;
-}
-
 /*
  * CreatePortal
  *		Returns a new portal given a name.
  *
- *		An elog(WARNING) is emitted if portal name is in use (existing
- *		portal is returned!)
+ * allowDup: if true, automatically drop any pre-existing portal of the
+ * same name (if false, an error is raised).
+ *
+ * dupSilent: if true, don't even emit a WARNING.
  */
 Portal
-CreatePortal(const char *name)
+CreatePortal(const char *name, bool allowDup, bool dupSilent)
 {
 	Portal		portal;
 
@@ -207,43 +164,90 @@ CreatePortal(const char *name)
 	portal = GetPortalByName(name);
 	if (PortalIsValid(portal))
 	{
-		elog(WARNING, "CreatePortal: portal \"%s\" already exists", name);
-		return portal;
+		if (!allowDup)
+			elog(ERROR, "Portal \"%s\" already exists", name);
+		if (!dupSilent)
+			elog(WARNING, "Closing pre-existing portal \"%s\"", name);
+		PortalDrop(portal, false);
 	}
 
 	/* make new portal structure */
-	portal = (Portal) MemoryContextAlloc(PortalMemory, sizeof *portal);
+	portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal);
 
-	/* initialize portal name */
-	portal->name = MemoryContextStrdup(PortalMemory, name);
-
-	/* initialize portal heap context */
+	/* initialize portal heap context; typically it won't store much */
 	portal->heap = AllocSetContextCreate(PortalMemory,
 										 "PortalHeapMemory",
-										 ALLOCSET_DEFAULT_MINSIZE,
-										 ALLOCSET_DEFAULT_INITSIZE,
-										 ALLOCSET_DEFAULT_MAXSIZE);
+										 ALLOCSET_SMALL_MINSIZE,
+										 ALLOCSET_SMALL_INITSIZE,
+										 ALLOCSET_SMALL_MAXSIZE);
 
-	/* initialize portal query */
-	portal->queryDesc = NULL;
+	/* initialize portal fields that don't start off zero */
 	portal->cleanup = PortalCleanup;
-	portal->scrollType = DEFAULT_SCROLL;
-	portal->executorRunning = false;
-	portal->holdOpen = false;
 	portal->createXact = GetCurrentTransactionId();
-	portal->holdStore = NULL;
-	portal->holdContext = NULL;
+	portal->strategy = PORTAL_MULTI_QUERY;
+	portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
 	portal->atStart = true;
 	portal->atEnd = true;		/* disallow fetches until query is set */
-	portal->portalPos = 0;
-	portal->posOverflow = false;
 
-	/* put portal in table */
-	PortalHashTableInsert(portal);
+	/* put portal in table (sets portal->name) */
+	PortalHashTableInsert(portal, name);
 
 	return portal;
 }
 
+/*
+ * CreateNewPortal
+ *		Create a new portal, assigning it a random nonconflicting name.
+ */
+Portal
+CreateNewPortal(void)
+{
+	static unsigned int unnamed_portal_count = 0;
+
+	char		portalname[MAX_PORTALNAME_LEN];
+
+	/* Select a nonconflicting name */
+	for (;;)
+	{
+		unnamed_portal_count++;
+		sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count);
+		if (GetPortalByName(portalname) == NULL)
+			break;
+	}
+
+	return CreatePortal(portalname, false, false);
+}
+
+/*
+ * 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.
+ */
+void
+PortalDefineQuery(Portal portal,
+				  const char *sourceText,
+				  const char *commandTag,
+				  List *parseTrees,
+				  List *planTrees,
+				  MemoryContext queryContext)
+{
+	AssertArg(PortalIsValid(portal));
+	AssertState(portal->queryContext == NULL); /* else defined already */
+
+	Assert(length(parseTrees) == length(planTrees));
+
+	portal->sourceText = sourceText;
+	portal->commandTag = commandTag;
+	portal->parseTrees = parseTrees;
+	portal->planTrees = planTrees;
+	portal->queryContext = queryContext;
+}
+
 /*
  * PortalDrop
  *		Destroy the portal.
@@ -256,6 +260,10 @@ PortalDrop(Portal portal, bool isError)
 {
 	AssertArg(PortalIsValid(portal));
 
+	/* Not sure if this case can validly happen or not... */
+	if (portal->portalActive)
+		elog(ERROR, "PortalDrop: can't drop active portal");
+
 	/*
 	 * Remove portal from hash table.  Because we do this first, we will
 	 * not come back to try to remove the portal again if there's any error
@@ -273,21 +281,43 @@ PortalDrop(Portal portal, bool isError)
 		MemoryContextDelete(portal->holdContext);
 
 	/* release subsidiary storage */
-	if (PortalGetHeapMemory(portal))
-		MemoryContextDelete(PortalGetHeapMemory(portal));
+	MemoryContextDelete(PortalGetHeapMemory(portal));
 
-	/* release name and portal data (both are in PortalMemory) */
-	pfree(portal->name);
+	/* release portal struct (it's in PortalMemory) */
 	pfree(portal);
 }
 
 /*
- * Cleanup the portals created in the current transaction. If the
- * transaction was aborted, all the portals created in this transaction
- * should be removed. If the transaction was successfully committed, any
- * holdable cursors created in this transaction need to be kept
- * open. In any case, portals remaining from prior transactions should
- * be left untouched.
+ * DropDependentPortals
+ *		Drop any portals using the specified context as queryContext.
+ *
+ * This is normally used to make sure we can safely drop a prepared statement.
+ */
+void
+DropDependentPortals(MemoryContext queryContext)
+{
+	HASH_SEQ_STATUS status;
+	PortalHashEnt *hentry;
+
+	hash_seq_init(&status, PortalHashTable);
+
+	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	{
+		Portal portal = hentry->portal;
+
+		if (portal->queryContext == queryContext)
+			PortalDrop(portal, false);
+	}
+}
+
+
+/*
+ * Pre-commit processing for portals.
+ *
+ * Any holdable cursors created in this transaction need to be converted to
+ * materialized form, since we are going to close down the executor and
+ * release locks.  Remove all other portals created in this transaction.
+ * Portals remaining from prior transactions should be left untouched.
  *
  * XXX This assumes that portals can be deleted in a random order, ie,
  * no portal has a reference to any other (at least not one that will be
@@ -296,7 +326,7 @@ PortalDrop(Portal portal, bool isError)
  * references...
  */
 void
-AtEOXact_portals(bool isCommit)
+AtCommit_Portals(void)
 {
 	HASH_SEQ_STATUS status;
 	PortalHashEnt *hentry;
@@ -308,11 +338,21 @@ AtEOXact_portals(bool isCommit)
 	{
 		Portal portal = hentry->portal;
 
-		if (portal->createXact != xact)
+		/*
+		 * Do not touch active portals --- this can only happen in the case of
+		 * a multi-transaction utility command, such as VACUUM.
+		 */
+		if (portal->portalActive)
 			continue;
 
-		if (portal->holdOpen && isCommit)
+		if (portal->cursorOptions & CURSOR_OPT_HOLD)
 		{
+			/*
+			 * Do nothing to cursors held over from a previous transaction.
+			 */
+			if (portal->createXact != xact)
+				continue;
+
 			/*
 			 * We are exiting the transaction that created a holdable
 			 * cursor.  Instead of dropping the portal, prepare it for
@@ -321,7 +361,8 @@ AtEOXact_portals(bool isCommit)
 
 			/*
 			 * Create the memory context that is used for storage of
-			 * the held cursor's tuple set.
+			 * the held cursor's tuple set.  Note this is NOT a child
+			 * of the portal's heap memory.
 			 */
 			portal->holdContext =
 				AllocSetContextCreate(PortalMemory,
@@ -341,7 +382,91 @@ AtEOXact_portals(bool isCommit)
 		}
 		else
 		{
-			PortalDrop(portal, !isCommit);
+			/* Zap all non-holdable portals */
+			PortalDrop(portal, false);
+		}
+	}
+}
+
+/*
+ * Abort processing for portals.
+ *
+ * At this point we reset the "active" flags and run the cleanup hook if
+ * present, but we can't release memory until the cleanup call.
+ *
+ * The reason we need to reset active is so that we can replace the unnamed
+ * portal, else we'll fail to execute ROLLBACK when it arrives.  Also, we
+ * want to run the cleanup hook now to be certain it knows that we had an
+ * error abort and not successful conclusion.
+ */
+void
+AtAbort_Portals(void)
+{
+	HASH_SEQ_STATUS status;
+	PortalHashEnt *hentry;
+	TransactionId xact = GetCurrentTransactionId();
+
+	hash_seq_init(&status, PortalHashTable);
+
+	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	{
+		Portal portal = hentry->portal;
+
+		portal->portalActive = false;
+
+		/*
+		 * Do nothing else to cursors held over from a previous transaction.
+		 * (This test must include checking CURSOR_OPT_HOLD, else we will
+		 * fail to clean up a VACUUM portal if it fails after its first
+		 * sub-transaction.)
+		 */
+		if (portal->createXact != xact &&
+			(portal->cursorOptions & CURSOR_OPT_HOLD))
+			continue;
+
+		/* let portalcmds.c clean up the state it knows about */
+		if (PointerIsValid(portal->cleanup))
+		{
+			(*portal->cleanup) (portal, true);
+			portal->cleanup = NULL;
 		}
 	}
 }
+
+/*
+ * Post-abort cleanup for portals.
+ *
+ * Delete all portals not held over from prior transactions.
+ */
+void
+AtCleanup_Portals(void)
+{
+	HASH_SEQ_STATUS status;
+	PortalHashEnt *hentry;
+	TransactionId xact = GetCurrentTransactionId();
+
+	hash_seq_init(&status, PortalHashTable);
+
+	while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+	{
+		Portal portal = hentry->portal;
+
+		/*
+		 * Let's just make sure no one's active...
+		 */
+		portal->portalActive = false;
+
+		/*
+		 * Do nothing else to cursors held over from a previous transaction.
+		 * (This test must include checking CURSOR_OPT_HOLD, else we will
+		 * fail to clean up a VACUUM portal if it fails after its first
+		 * sub-transaction.)
+		 */
+		if (portal->createXact != xact &&
+			(portal->cursorOptions & CURSOR_OPT_HOLD))
+			continue;
+
+		/* Else zap it with prejudice. */
+		PortalDrop(portal, true);
+	}
+}
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index c1aaadaac3df8a9cff2c67140819a348dc1d5ab2..0c14811682d4e053e8b5044031d168452043eb62 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.184 2003/04/08 23:20:02 tgl Exp $
+ * $Id: catversion.h,v 1.185 2003/05/02 20:54:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200304071
+#define CATALOG_VERSION_NO	200305011
 
 #endif
diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h
index 66bfca2b8b6b81c549ae8ddcfd1b22716d03f8cf..efa60869fa68e27c8479d3f761945ec1ef98ae6e 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.7 2003/04/29 03:21:30 tgl Exp $
+ * $Id: portalcmds.h,v 1.8 2003/05/02 20:54:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -22,13 +22,10 @@ extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest);
 extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest,
 							   char *completionTag);
 
-extern long DoPortalFetch(Portal portal,
-						  FetchDirection fdirection,
-						  long count,
-						  CommandDest dest);
-
 extern void PerformPortalClose(char *name);
 
 extern void PortalCleanup(Portal portal, bool isError);
 
+extern void PersistHoldablePortal(Portal portal);
+
 #endif   /* PORTALCMDS_H */
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index 6a2acd7b21c680299eccdc816f2bad838e7beb56..5dd0ff2a6e32d3e4c34b1d0e6d423cdcb0bd0dac 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: spi_priv.h,v 1.13 2002/10/14 23:49:20 tgl Exp $
+ * $Id: spi_priv.h,v 1.14 2003/05/02 20:54:35 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -36,8 +36,6 @@ typedef struct
 	/* Argument types, if a prepared plan */
 	int			nargs;
 	Oid		   *argtypes;
-	/* Command type of last original parsetree */
-	CmdType		origCmdType;
 } _SPI_plan;
 
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3d4b235e5629e4b999b8a7236d5d1a33204ca610..4ad35b683a765496f6de09615c5bb3b137a3c2f0 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.236 2003/03/27 16:51:29 momjian Exp $
+ * $Id: parsenodes.h,v 1.237 2003/05/02 20:54:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -47,6 +47,8 @@ typedef struct Query
 
 	QuerySource	querySource;	/* where did I come from? */
 
+	bool		canSetTag;		/* do I set the command result tag? */
+
 	Node	   *utilityStmt;	/* non-null if this is a non-optimizable
 								 * statement */
 
diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h
index c992306a9af377c9b5eb79edb04724c9ff87b3d1..bf3344d2bd7c5e71131a762e666684824da42ba6 100644
--- a/src/include/tcop/pquery.h
+++ b/src/include/tcop/pquery.h
@@ -7,17 +7,32 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pquery.h,v 1.24 2003/03/10 03:53:52 tgl Exp $
+ * $Id: pquery.h,v 1.25 2003/05/02 20:54:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef PQUERY_H
 #define PQUERY_H
 
-#include "executor/execdesc.h"
+#include "utils/portal.h"
 
 
-extern void ProcessQuery(Query *parsetree, Plan *plan, CommandDest dest,
-			 char *completionTag);
+extern void ProcessQuery(Query *parsetree,
+						 Plan *plan,
+						 ParamListInfo params,
+						 const char *portalName,
+						 CommandDest dest,
+						 char *completionTag);
+
+extern void PortalStart(Portal portal, ParamListInfo params);
+
+extern bool PortalRun(Portal portal, long count,
+					  CommandDest dest, CommandDest altdest,
+					  char *completionTag);
+
+extern long PortalRunFetch(Portal portal,
+						   FetchDirection fdirection,
+						   long count,
+						   CommandDest dest);
 
 #endif   /* PQUERY_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index b81df05320cd106a6f423a908b4ced14a4876033..c1fa9c1a6d737a27a7ff0d9006c3f89e5334a164 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.55 2003/04/29 22:13:11 tgl Exp $
+ * $Id: tcopprot.h,v 1.56 2003/05/02 20:54:36 tgl Exp $
  *
  * OLD COMMENTS
  *	  This file was created so that other c files could get the two
@@ -41,6 +41,7 @@ extern List *pg_analyze_and_rewrite(Node *parsetree,
 extern List *pg_parse_and_rewrite(const char *query_string,
 					 Oid *paramTypes, int numParams);
 extern Plan *pg_plan_query(Query *querytree);
+extern List *pg_plan_queries(List *querytrees, bool needSnapshot);
 
 #endif   /* BOOTSTRAP_INCLUDE */
 
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 9cca85415f565cb49dbc26ed96f2de7db2efe6ce..96c0dc43c5c602b8d43077180f31577b3bdf80f1 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.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: utility.h,v 1.16 2002/09/04 20:31:45 momjian Exp $
+ * $Id: utility.h,v 1.17 2003/05/02 20:54:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,4 +19,6 @@
 extern void ProcessUtility(Node *parsetree, CommandDest dest,
 			   char *completionTag);
 
+extern const char *CreateCommandTag(Node *parsetree);
+
 #endif   /* UTILITY_H */
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 09198d1c6e60bdc8cc2a85967928d300aa4ad955..6934d109623909e78bbd7649c00054247fe91214 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: memutils.h,v 1.50 2002/12/16 16:22:46 tgl Exp $
+ * $Id: memutils.h,v 1.51 2003/05/02 20:54:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -68,9 +68,11 @@ extern DLLIMPORT MemoryContext TopMemoryContext;
 extern DLLIMPORT MemoryContext ErrorContext;
 extern DLLIMPORT MemoryContext PostmasterContext;
 extern DLLIMPORT MemoryContext CacheMemoryContext;
-extern DLLIMPORT MemoryContext QueryContext;
+extern DLLIMPORT MemoryContext MessageContext;
 extern DLLIMPORT MemoryContext TopTransactionContext;
-extern DLLIMPORT MemoryContext TransactionCommandContext;
+/* These two are transient links to contexts owned by other objects: */
+extern DLLIMPORT MemoryContext QueryContext;
+extern DLLIMPORT MemoryContext PortalContext;
 
 
 /*
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index 2615db0490d56d623be7359483c9c82dfdc53c53..2353ba01d4cc23f1c27b6447f9b2d5b492051d51 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -4,12 +4,42 @@
  *	  POSTGRES portal definitions.
  *
  * A portal is an abstraction which represents the execution state of
- * a running query (specifically, a CURSOR).
+ * a running or runnable query.  Portals support both SQL-level CURSORs
+ * and protocol-level portals.
+ *
+ * Scrolling (nonsequential access) and suspension of execution are allowed
+ * only for portals that contain a single SELECT-type query.  We do not want
+ * to let the client suspend an update-type query partway through!  Because
+ * the query rewriter does not allow arbitrary ON SELECT rewrite rules,
+ * only queries that were originally update-type could produce multiple
+ * parse/plan trees; so the restriction to a single query is not a problem
+ * in practice.
+ *
+ * For SQL cursors, we support three kinds of scroll behavior:
+ *
+ * (1) Neither NO SCROLL nor SCROLL was specified: to remain backward
+ *     compatible, we allow backward fetches here, unless it would
+ *     impose additional runtime overhead to do so.
+ *
+ * (2) NO SCROLL was specified: don't allow any backward fetches.
+ *
+ * (3) SCROLL was specified: allow all kinds of backward fetches, even
+ *     if we need to take a performance hit to do so.  (The planner sticks
+ *	   a Materialize node atop the query plan if needed.)
+ *
+ * Case #1 is converted to #2 or #3 by looking at the query itself and
+ * determining if scrollability can be supported without additional
+ * overhead.
+ *
+ * Protocol-level portals have no nonsequential-fetch API and so the
+ * distinction doesn't matter for them.  They are always initialized
+ * to look like NO SCROLL cursors.
+ *
  *
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: portal.h,v 1.41 2003/04/29 03:21:30 tgl Exp $
+ * $Id: portal.h,v 1.42 2003/05/02 20:54:36 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,41 +50,81 @@
 #include "nodes/memnodes.h"
 #include "utils/tuplestore.h"
 
+
 /*
- * We support three kinds of scroll behavior:
+ * We have several execution strategies for Portals, depending on what
+ * query or queries are to be executed.  (Note: in all cases, a Portal
+ * executes just a single source-SQL query, and thus produces just a
+ * single result from the user's viewpoint.  However, the rule rewriter
+ * may expand the single source query to zero or many actual queries.)
  *
- * (1) Neither NO SCROLL nor SCROLL was specified: to remain backward
- *     compatible, we allow backward fetches here, unless it would
- *     impose additional runtime overhead to do so.
+ * PORTAL_ONE_SELECT: the portal contains one single SELECT query.  We run
+ * the Executor incrementally as results are demanded.  This strategy also
+ * supports holdable cursors (the Executor results can be dumped into a
+ * tuplestore for access after transaction completion).
  *
- * (2) NO SCROLL was specified: don't allow any backward fetches.
- *
- * (3) SCROLL was specified: allow all kinds of backward fetches, even
- *     if we need to take a slight performance hit to do so.
+ * PORTAL_UTIL_SELECT: the portal contains a utility statement that returns
+ * a SELECT-like result (for example, EXPLAIN or SHOW).  On first execution,
+ * we run the statement and dump its results into the portal tuplestore;
+ * the results are then returned to the client as demanded.
  *
- * Case #1 is converted to #2 or #3 by looking at the query itself and
- * determining if scrollability can be supported without additional
- * overhead.
+ * PORTAL_MULTI_QUERY: all other cases.  Here, we do not support partial
+ * execution: the portal's queries will be run to completion on first call.
  */
-typedef enum
+
+typedef enum PortalStrategy
 {
-	DEFAULT_SCROLL,
-	DISABLE_SCROLL,
-	ENABLE_SCROLL
-} ScrollType;
+	PORTAL_ONE_SELECT,
+	PORTAL_UTIL_SELECT,
+	PORTAL_MULTI_QUERY
+} PortalStrategy;
 
 typedef struct PortalData *Portal;
 
 typedef struct PortalData
 {
-	char	   *name;			/* Portal's name */
-	MemoryContext heap;			/* subsidiary memory */
-	QueryDesc  *queryDesc;		/* Info about query associated with portal */
-	void		(*cleanup) (Portal portal, bool isError);	/* Cleanup hook */
-	ScrollType	scrollType;		/* Allow backward fetches? */
-	bool		executorRunning;	/* T if we need to call ExecutorEnd */
-	bool		holdOpen;		/* hold open after xact ends? */
+	/* Bookkeeping data */
+	const char *name;			/* portal's name */
+	MemoryContext heap;			/* subsidiary memory for portal */
+	void		(*cleanup) (Portal portal, bool isError);	/* cleanup hook */
 	TransactionId createXact;	/* the xid of the creating xact */
+
+	/* The query or queries the portal will execute */
+	const char *sourceText;		/* text of query, if known (may be NULL) */
+	const char *commandTag;		/* command tag for original query */
+	List	   *parseTrees;		/* parse tree(s) */
+	List	   *planTrees;		/* plan tree(s) */
+	MemoryContext queryContext;	/* where the above trees live */
+	/*
+	 * Note: queryContext effectively identifies which prepared statement
+	 * the portal depends on, if any.  The queryContext is *not* owned by
+	 * the portal and is not to be deleted by portal destruction.  (But for
+	 * a cursor it is the same as "heap", and that context is deleted by
+	 * portal destruction.)
+	 */
+	ParamListInfo portalParams;	/* params to pass to query */
+
+	/* Features/options */
+	PortalStrategy strategy;	/* see above */
+	int			cursorOptions;	/* DECLARE CURSOR option bits */
+
+	/* Status data */
+	bool		portalReady;	/* PortalStart complete? */
+	bool		portalUtilReady; /* PortalRunUtility complete? */
+	bool		portalActive;	/* portal is running (can't delete it) */
+	bool		portalDone;		/* portal is finished (don't re-run it) */
+
+	/* If not NULL, Executor is active; call ExecutorEnd eventually: */
+	QueryDesc  *queryDesc;		/* info needed for executor invocation */
+
+	/* If portal returns tuples, this is their tupdesc: */
+	TupleDesc	tupDesc;		/* descriptor for result tuples */
+
+	/*
+	 * Where we store tuples for a held cursor or a PORTAL_UTIL_SELECT query.
+	 * (A cursor held past the end of its transaction no longer has any
+	 * active executor state.)
+	 */
 	Tuplestorestate *holdStore;	/* store for holdable cursors */
 	MemoryContext holdContext;  /* memory containing holdStore */
 
@@ -86,12 +156,25 @@ typedef struct PortalData
 #define PortalGetHeapMemory(portal) ((portal)->heap)
 
 
+/* Currently executing Portal, if any */
+extern DLLIMPORT Portal CurrentPortal;
+
+
+/* Prototypes for functions in utils/mmgr/portalmem.c */
 extern void EnablePortalManager(void);
-extern void AtEOXact_portals(bool isCommit);
-extern Portal CreatePortal(const char *name);
+extern void AtCommit_Portals(void);
+extern void AtAbort_Portals(void);
+extern void AtCleanup_Portals(void);
+extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent);
+extern Portal CreateNewPortal(void);
 extern void PortalDrop(Portal portal, bool isError);
+extern void DropDependentPortals(MemoryContext queryContext);
 extern Portal GetPortalByName(const char *name);
-extern void PortalSetQuery(Portal portal, QueryDesc *queryDesc);
-extern void PersistHoldablePortal(Portal portal);
+extern void PortalDefineQuery(Portal portal,
+							  const char *sourceText,
+							  const char *commandTag,
+							  List *parseTrees,
+							  List *planTrees,
+							  MemoryContext queryContext);
 
 #endif   /* PORTAL_H */