From d27f363e3fceb7612997ae89a8fc84de6754a213 Mon Sep 17 00:00:00 2001
From: Jan Wieck <JanWieck@Yahoo.com>
Date: Mon, 21 May 2001 14:22:19 +0000
Subject: [PATCH] Enhancement of SPI to get access to portals

- New functions to create a portal using a prepared/saved
  SPI plan or lookup an existing portal by name.
- Functions to fetch/move from/in portals. Results are placed
  in the usual SPI_processed and SPI_tuptable, so the entire
  set of utility functions can be used to gain attribute access.
- Prepared/saved SPI plans now use their own memory context
  and SPI_freeplan(plan) can remove them.
- Tuple result sets (SPI_tuptable) now uses it's own memory
  context and can be free'd by SPI_freetuptable(tuptab).

Enhancement of PL/pgSQL

- Uses generic named portals internally in FOR ... SELECT
  loops to avoid running out of memory on huge result sets.
- Support for CURSOR and REFCURSOR syntax using the new SPI
  functionality. Cursors used internally only need no explicit
  transaction block. Refcursor variables can be used inside
  of explicit transaction block to pass cursors between main
  application and functions.


Jan
---
 src/backend/commands/command.c   |   8 +-
 src/backend/executor/spi.c       | 321 +++++++++++++-
 src/include/catalog/catversion.h |   4 +-
 src/include/catalog/pg_type.h    |   7 +-
 src/include/executor/spi.h       |  10 +
 src/include/executor/spi_priv.h  |   3 +-
 src/include/utils/portal.h       |   4 +-
 src/pl/plpgsql/src/gram.y        | 408 +++++++++++++++++-
 src/pl/plpgsql/src/pl_comp.c     |  15 +-
 src/pl/plpgsql/src/pl_exec.c     | 704 +++++++++++++++++++++++++++----
 src/pl/plpgsql/src/pl_funcs.c    |  93 +++-
 src/pl/plpgsql/src/plpgsql.h     |  44 +-
 src/pl/plpgsql/src/scan.l        |   7 +-
 13 files changed, 1514 insertions(+), 114 deletions(-)

diff --git a/src/backend/commands/command.c b/src/backend/commands/command.c
index cd7e1d29524..bc5153b8005 100644
--- a/src/backend/commands/command.c
+++ b/src/backend/commands/command.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.127 2001/05/09 21:10:38 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.128 2001/05/21 14:22:11 wieck Exp $
  *
  * NOTES
  *	  The PerformAddAttribute() code, like most of the relation
@@ -108,6 +108,7 @@ PerformPortalFetch(char *name,
 	QueryDesc  *queryDesc;
 	EState	   *estate;
 	MemoryContext oldcontext;
+	bool		faked_desc = false;
 
 	/*
 	 * sanity checks
@@ -143,13 +144,14 @@ PerformPortalFetch(char *name,
 	queryDesc = PortalGetQueryDesc(portal);
 	estate = PortalGetState(portal);
 
-	if (dest == None)			/* MOVE */
+	if (dest != queryDesc->dest)			/* MOVE */
 	{
 		QueryDesc  *qdesc = (QueryDesc *) palloc(sizeof(QueryDesc));
 
 		memcpy(qdesc, queryDesc, sizeof(QueryDesc));
 		qdesc->dest = dest;
 		queryDesc = qdesc;
+		faked_desc = true;
 	}
 
 	BeginCommand(name,
@@ -197,7 +199,7 @@ PerformPortalFetch(char *name,
 	/*
 	 * Clean up and switch back to old context.
 	 */
-	if (dest == None)			/* MOVE */
+	if (faked_desc)			/* MOVE */
 		pfree(queryDesc);
 
 	MemoryContextSwitchTo(oldcontext);
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 4aa8c475c30..d3698277427 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -3,12 +3,13 @@
  * spi.c
  *				Server Programming Interface
  *
- * $Id: spi.c,v 1.53 2001/03/22 03:59:29 momjian Exp $
+ * $Id: spi.c,v 1.54 2001/05/21 14:22:17 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "executor/spi_priv.h"
 #include "access/printtup.h"
+#include "commands/command.h"
 
 uint32		SPI_processed = 0;
 Oid			SPI_lastoid = InvalidOid;
@@ -26,6 +27,9 @@ static int	_SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount);
 static int _SPI_execute_plan(_SPI_plan *plan,
 				  Datum *Values, char *Nulls, int tcount);
 
+static void _SPI_cursor_operation(Portal portal, bool forward, int count,
+					CommandDest dest);
+
 static _SPI_plan *_SPI_copy_plan(_SPI_plan *plan, int location);
 
 static int	_SPI_begin_call(bool execmem);
@@ -272,6 +276,18 @@ SPI_saveplan(void *plan)
 
 }
 
+int
+SPI_freeplan(void *plan)
+{
+	_SPI_plan  *spiplan = (_SPI_plan *)plan;
+
+	if (plan == NULL)
+		return SPI_ERROR_ARGUMENT;
+
+	MemoryContextDelete(spiplan->plancxt);
+	return 0;
+}
+
 HeapTuple
 SPI_copytuple(HeapTuple tuple)
 {
@@ -555,6 +571,181 @@ SPI_freetuple(HeapTuple tuple)
 	heap_freetuple(tuple);
 }
 
+void
+SPI_freetuptable(SPITupleTable *tuptable)
+{
+	if (tuptable != NULL)
+		MemoryContextDelete(tuptable->tuptabcxt);
+}
+
+
+
+/*
+ * SPI_cursor_open()
+ *
+ *	Open a prepared SPI plan as a portal
+ */
+Portal
+SPI_cursor_open(char *name, void *plan, Datum *Values, 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;
+	QueryDesc		   *queryDesc;
+	EState			   *eState;
+	TupleDesc			attinfo;
+	MemoryContext		oldcontext;
+	Portal				portal;
+	char				portalname[64];
+	int					k;
+
+	/* Ensure that the plan contains only one regular SELECT query */
+	if (length(ptlist) != 1)
+		elog(ERROR, "cannot open multi-query plan as cursor");
+	queryTree = (Query *)lfirst(qtlist);
+	planTree  = (Plan *)lfirst(ptlist);
+
+	if (queryTree->commandType != CMD_SELECT)
+		elog(ERROR, "plan in SPI_cursor_open() is not a SELECT");
+	if (queryTree->isPortal)
+		elog(ERROR, "plan in SPI_cursor_open() must NOT be a DECLARE already");
+	else if (queryTree->into != NULL)
+		elog(ERROR, "plan in SPI_cursor_open() must NOT be a SELECT INTO");
+
+	/* Reset SPI result */
+	SPI_processed = 0;
+	SPI_tuptable = NULL;
+	_SPI_current->processed = 0;
+	_SPI_current->tuptable = NULL;
+
+	/* Make up a portal name if none given */
+	if (name == NULL)
+	{
+		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;
+	}
+
+	/* Ensure the portal doesn't exist already */
+	portal = GetPortalByName(name);
+	if (portal != NULL)
+		elog(ERROR, "cursor \"%s\" already in use", name);
+
+	/* Create the portal */
+	portal = CreatePortal(name);
+	if (portal == NULL)
+		elog(ERROR, "failed to create portal \"%s\"", name);
+
+	/* Switch to portals memory and copy the parsetree and plan to there */
+	oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+	queryTree  = copyObject(queryTree);
+	planTree   = copyObject(planTree);
+
+	/* Modify the parsetree to be a cursor */
+	queryTree->isPortal = true;
+	queryTree->into     = pstrdup(name);
+	queryTree->isBinary = false;
+	
+	/* Create the QueryDesc object and the executor state */
+	queryDesc = CreateQueryDesc(queryTree, planTree, SPI);
+	eState    = CreateExecutorState();
+
+	/* If the plan has parameters, put them into the executor state */
+	if (spiplan->nargs > 0)
+	{
+		ParamListInfo	paramLI = (ParamListInfo) palloc((spiplan->nargs + 1) *
+									sizeof(ParamListInfoData));
+		eState->es_param_list_info = paramLI;
+		for (k = 0; k < spiplan->nargs; paramLI++, k++)
+		{
+			paramLI->kind	= PARAM_NUM;
+			paramLI->id		= k + 1;
+			paramLI->isnull	= (Nulls && Nulls[k] == 'n');
+			paramLI->value	= Values[k];
+		}
+		paramLI->kind = PARAM_INVALID;
+	}
+	else
+		eState->es_param_list_info = NULL;
+
+	/* Start the executor */
+	attinfo = ExecutorStart(queryDesc, eState);
+
+	/* Put all the objects into the portal */
+	PortalSetQuery(portal, queryDesc, attinfo, eState, PortalCleanup);
+
+	/* Switch back to the callers memory context */
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Return the created portal */
+	return portal;
+}
+
+
+/*
+ * SPI_cursor_find()
+ *
+ *	Find the portal of an existing open cursor
+ */
+Portal
+SPI_cursor_find(char *name)
+{
+	return GetPortalByName(name);
+}
+
+
+/*
+ * SPI_cursor_fetch()
+ *
+ *	Fetch rows in a cursor
+ */
+void
+SPI_cursor_fetch(Portal portal, bool forward, int count)
+{
+	_SPI_cursor_operation(portal, forward, count, SPI);
+}
+
+
+/*
+ * SPI_cursor_move()
+ *
+ *	Move in a cursor
+ */
+void
+SPI_cursor_move(Portal portal, bool forward, int count)
+{
+	_SPI_cursor_operation(portal, forward, count, None);
+}
+
+
+/*
+ * SPI_cursor_close()
+ *
+ *	Close a cursor
+ */
+void
+SPI_cursor_close(Portal portal)
+{
+	Portal	my_portal = portal;
+
+	if (!PortalIsValid(my_portal))
+		elog(ERROR, "invalid portal in SPI cursor operation");
+
+	PortalDrop(&my_portal);
+}
+
 /* =================== private functions =================== */
 
 /*
@@ -568,6 +759,7 @@ spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
 {
 	SPITupleTable *tuptable;
 	MemoryContext oldcxt;
+	MemoryContext tuptabcxt;
 
 	/*
 	 * When called by Executor _SPI_curid expected to be equal to
@@ -583,18 +775,31 @@ spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
 	tuptable = _SPI_current->tuptable;
 	if (tuptable == NULL)
 	{
+		tuptabcxt = AllocSetContextCreate(CurrentMemoryContext,
+												  "SPI TupTable",
+												  ALLOCSET_DEFAULT_MINSIZE,
+												  ALLOCSET_DEFAULT_INITSIZE,
+												  ALLOCSET_DEFAULT_MAXSIZE);
+		MemoryContextSwitchTo(tuptabcxt);
+
 		_SPI_current->tuptable = tuptable = (SPITupleTable *)
 			palloc(sizeof(SPITupleTable));
+		tuptable->tuptabcxt = tuptabcxt;
 		tuptable->alloced = tuptable->free = 128;
 		tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple));
 		tuptable->tupdesc = CreateTupleDescCopy(tupdesc);
 	}
-	else if (tuptable->free == 0)
+	else 
 	{
-		tuptable->free = 256;
-		tuptable->alloced += tuptable->free;
-		tuptable->vals = (HeapTuple *) repalloc(tuptable->vals,
-								  tuptable->alloced * sizeof(HeapTuple));
+		MemoryContextSwitchTo(tuptable->tuptabcxt);
+
+		if (tuptable->free == 0)
+		{
+			tuptable->free = 256;
+			tuptable->alloced += tuptable->free;
+			tuptable->vals = (HeapTuple *) repalloc(tuptable->vals,
+									  tuptable->alloced * sizeof(HeapTuple));
+		}
 	}
 
 	tuptable->vals[tuptable->alloced - tuptable->free] = heap_copytuple(tuple);
@@ -876,6 +1081,86 @@ _SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount)
 
 }
 
+/*
+ * _SPI_cursor_operation()
+ *
+ *	Do a FETCH or MOVE in a cursor
+ */
+static void
+_SPI_cursor_operation(Portal portal, bool forward, int count,
+					CommandDest dest)
+{
+    QueryDesc	   *querydesc;
+	EState		   *estate;
+	MemoryContext	oldcontext;
+	CommandDest		olddest;
+
+	/* Check that the portal is valid */
+	if (!PortalIsValid(portal))
+		elog(ERROR, "invalid portal in SPI cursor operation");
+
+	/* Push the SPI stack */
+	_SPI_begin_call(true);
+
+	/* Reset the SPI result */
+	SPI_processed = 0;
+	SPI_tuptable = NULL;
+	_SPI_current->processed = 0;
+	_SPI_current->tuptable = NULL;
+
+	/* Switch to the portals memory context */
+	oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+	querydesc  = PortalGetQueryDesc(portal);
+	estate     = PortalGetState(portal);
+
+	/* Save the queries command destination and set it to SPI (for fetch) */
+	/* or None (for move) */
+	olddest = querydesc->dest;
+	querydesc->dest = dest;
+
+	/* Run the executor like PerformPortalFetch and remember states */
+	if (forward)
+	{
+		if (!portal->atEnd)
+		{
+			ExecutorRun(querydesc, estate, EXEC_FOR, (long)count);
+			_SPI_current->processed = estate->es_processed;
+			if (estate->es_processed > 0)
+				portal->atStart = false;
+			if (count <= 0 || (int) estate->es_processed < count)
+				portal->atEnd = true;
+		}
+	}
+	else
+	{
+		if (!portal->atStart)
+		{
+			ExecutorRun(querydesc, estate, EXEC_BACK, (long) count);
+			_SPI_current->processed = estate->es_processed;
+			if (estate->es_processed > 0)
+				portal->atEnd = false;
+			if (count <= 0 || estate->es_processed < count)
+				portal->atStart = true;
+		}
+	}
+
+	/* Restore the old command destination and switch back to callers */
+	/* memory context */
+	querydesc->dest = olddest;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (dest == SPI && _SPI_checktuples())
+		elog(FATAL, "SPI_fetch: # of processed tuples check failed");
+
+	/* Put the result into place for access by caller */
+	SPI_processed = _SPI_current->processed;
+	SPI_tuptable  = _SPI_current->tuptable;
+
+	/* Pop the SPI stack */
+	_SPI_end_call(true);
+}
+
+
 static MemoryContext
 _SPI_execmem()
 {
@@ -956,14 +1241,33 @@ static _SPI_plan *
 _SPI_copy_plan(_SPI_plan *plan, int location)
 {
 	_SPI_plan  *newplan;
-	MemoryContext oldcxt = NULL;
+	MemoryContext oldcxt;
+	MemoryContext plancxt;
+	MemoryContext parentcxt = CurrentMemoryContext;
 
+	/* Determine correct parent for the plans memory context */
 	if (location == _SPI_CPLAN_PROCXT)
+		parentcxt = _SPI_current->procCxt;
+		/*
 		oldcxt = MemoryContextSwitchTo(_SPI_current->procCxt);
+		*/
 	else if (location == _SPI_CPLAN_TOPCXT)
+		parentcxt = TopMemoryContext;
+		/*
 		oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+		*/
 
+	/* Create a memory context for the plan */
+	plancxt = AllocSetContextCreate(parentcxt,
+									  "SPI Plan",
+									  ALLOCSET_DEFAULT_MINSIZE,
+									  ALLOCSET_DEFAULT_INITSIZE,
+									  ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(plancxt);
+
+	/* Copy the SPI plan into it's own context */
 	newplan = (_SPI_plan *) palloc(sizeof(_SPI_plan));
+	newplan->plancxt = plancxt;
 	newplan->qtlist = (List *) copyObject(plan->qtlist);
 	newplan->ptlist = (List *) copyObject(plan->ptlist);
 	newplan->nargs = plan->nargs;
@@ -975,8 +1279,7 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
 	else
 		newplan->argtypes = NULL;
 
-	if (oldcxt != NULL)
-		MemoryContextSwitchTo(oldcxt);
+	MemoryContextSwitchTo(oldcxt);
 
 	return newplan;
 }
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index e04f97e0ec8..2616b4af7d4 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.79 2001/05/20 20:28:19 tgl Exp $
+ * $Id: catversion.h,v 1.80 2001/05/21 14:22:17 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	200105191
+#define CATALOG_VERSION_NO	200105211
 
 #endif
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 1cb9d2a5702..5b330d9ea30 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_type.h,v 1.105 2001/05/14 20:30:21 momjian Exp $
+ * $Id: pg_type.h,v 1.106 2001/05/21 14:22:18 wieck Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -413,6 +413,11 @@ DATA(insert OID = 1700 ( numeric	   PGUID -1  -1 f b t \054 0  0 numeric_in nume
 DESCR("numeric(precision, decimal), arbitrary precision number");
 #define NUMERICOID		1700
 
+/* OID 1790 */
+DATA(insert OID = 1790 ( refcursor	   PGUID -1  -1 f b t \054 0  0 textin textout textin textout i x _null_ ));
+DESCR("reference cursor (portal name)");
+#define REFCURSOROID	1790
+
 
 /*
  * prototypes for functions in pg_type.c
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index b95eaae4e9e..3a7c7807942 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -41,6 +41,7 @@
 
 typedef struct
 {
+	MemoryContext tuptabcxt;	/* memory context of result table */
 	uint32		alloced;		/* # of alloced vals */
 	uint32		free;			/* # of free vals */
 	TupleDesc	tupdesc;		/* tuple descriptor */
@@ -83,6 +84,7 @@ extern int	SPI_exec(char *src, int tcount);
 extern int	SPI_execp(void *plan, Datum *values, char *Nulls, int tcount);
 extern void *SPI_prepare(char *src, int nargs, Oid *argtypes);
 extern void *SPI_saveplan(void *plan);
+extern int  SPI_freeplan(void *plan);
 
 extern HeapTuple SPI_copytuple(HeapTuple tuple);
 extern HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
@@ -98,6 +100,14 @@ extern void *SPI_palloc(Size size);
 extern void *SPI_repalloc(void *pointer, Size size);
 extern void SPI_pfree(void *pointer);
 extern void SPI_freetuple(HeapTuple pointer);
+extern void SPI_freetuptable(SPITupleTable *tuptable);
+
+extern Portal SPI_cursor_open(char *name, void *plan, 
+				Datum *Values, char *Nulls);
+extern Portal SPI_cursor_find(char *name);
+extern void   SPI_cursor_fetch(Portal portal, bool forward, int count);
+extern void   SPI_cursor_move(Portal portal, bool forward, int count);
+extern void   SPI_cursor_close(Portal portal);
 
 extern void AtEOXact_SPI(void);
 
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index 00b28a58037..e5626634001 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -3,7 +3,7 @@
  * spi.c
  *				Server Programming Interface private declarations
  *
- * $Header: /cvsroot/pgsql/src/include/executor/spi_priv.h,v 1.7 2000/06/28 03:33:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/include/executor/spi_priv.h,v 1.8 2001/05/21 14:22:18 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,6 +25,7 @@ typedef struct
 
 typedef struct
 {
+	MemoryContext plancxt;
 	List	   *qtlist;
 	List	   *ptlist;
 	int			nargs;
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index 1cdd770dfdc..6a17ec86704 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: portal.h,v 1.27 2001/03/22 04:01:14 momjian Exp $
+ * $Id: portal.h,v 1.28 2001/05/21 14:22:18 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -55,7 +55,7 @@ typedef struct PortalData
  * estimate of the maximum number of open portals a user would have,
  * used in initially sizing the PortalHashTable in EnablePortalManager()
  */
-#define PORTALS_PER_USER	   10
+#define PORTALS_PER_USER	   64
 
 
 extern void EnablePortalManager(void);
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index b1373674540..9dee73acc97 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -4,7 +4,7 @@
  *						  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.18 2001/05/18 21:16:59 wieck Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/gram.y,v 1.19 2001/05/21 14:22:18 wieck Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -47,6 +47,7 @@
 
 static	PLpgSQL_expr	*read_sqlstmt(int until, char *s, char *sqlstart);
 static	PLpgSQL_stmt	*make_select_stmt(void);
+static	PLpgSQL_stmt	*make_fetch_stmt(void);
 static	PLpgSQL_expr	*make_tupret_expr(PLpgSQL_row *row);
 
 %}
@@ -99,17 +100,17 @@ static	PLpgSQL_expr	*make_tupret_expr(PLpgSQL_row *row);
 %type <varname> decl_varname
 %type <str>		decl_renname
 %type <ival>	decl_const, decl_notnull, decl_atttypmod, decl_atttypmodval
-%type <expr>	decl_defval
+%type <expr>	decl_defval, decl_cursor_query
 %type <dtype>	decl_datatype, decl_dtypename
-%type <row>		decl_rowtype
+%type <row>		decl_rowtype, decl_cursor_args, decl_cursor_arglist
 %type <nsitem>	decl_aliasitem
 %type <str>		decl_stmts, decl_stmt
 
 %type <expr>	expr_until_semi, expr_until_then, expr_until_loop
 %type <expr>	opt_exitcond
 
-%type <ival>	assign_var
-%type <var>		fori_var
+%type <ival>	assign_var, cursor_variable
+%type <var>		fori_var, cursor_varptr, decl_cursor_arg
 %type <varname> fori_varname
 %type <forilow> fori_lower
 %type <rec>		fors_target
@@ -124,6 +125,7 @@ static	PLpgSQL_expr	*make_tupret_expr(PLpgSQL_row *row);
 %type <stmt>	stmt_return, stmt_raise, stmt_execsql, stmt_fori
 %type <stmt>	stmt_fors, stmt_select, stmt_perform
 %type <stmt>	stmt_dynexecute, stmt_dynfors, stmt_getdiag
+%type <stmt>	stmt_open, stmt_fetch, stmt_close
 
 %type <intlist>	raise_params
 %type <ival>	raise_level, raise_param
@@ -140,7 +142,9 @@ static	PLpgSQL_expr	*make_tupret_expr(PLpgSQL_row *row);
 %token	K_ALIAS
 %token	K_ASSIGN
 %token	K_BEGIN
+%token	K_CLOSE
 %token	K_CONSTANT
+%token	K_CURSOR
 %token	K_DEBUG
 %token	K_DECLARE
 %token	K_DEFAULT
@@ -153,15 +157,18 @@ static	PLpgSQL_expr	*make_tupret_expr(PLpgSQL_row *row);
 %token	K_EXECUTE
 %token	K_EXIT
 %token	K_FOR
+%token	K_FETCH
 %token	K_FROM
 %token	K_GET
 %token	K_IF
 %token	K_IN
 %token	K_INTO
+%token	K_IS
 %token	K_LOOP
 %token	K_NOT
 %token	K_NOTICE
 %token	K_NULL
+%token	K_OPEN
 %token	K_PERFORM
 %token	K_ROW_COUNT
 %token	K_RAISE
@@ -300,6 +307,7 @@ decl_statement	: decl_varname decl_const decl_datatype decl_notnull decl_defval
 						PLpgSQL_var		*new;
 
 						new = malloc(sizeof(PLpgSQL_var));
+						memset(new, 0, sizeof(PLpgSQL_var));
 
 						new->dtype		= PLPGSQL_DTYPE_VAR;
 						new->refname	= $1.name;
@@ -347,8 +355,152 @@ decl_statement	: decl_varname decl_const decl_datatype decl_notnull decl_defval
 					{
 						plpgsql_ns_rename($2, $4);
 					}
+				| decl_varname K_CURSOR decl_cursor_args K_IS K_SELECT decl_cursor_query
+					{
+						PLpgSQL_var *new;
+						PLpgSQL_expr *curname_def;
+						char		buf[1024];
+						char		*cp1;
+						char		*cp2;
+
+						plpgsql_ns_pop();
+
+						new = malloc(sizeof(PLpgSQL_var));
+						memset(new, 0, sizeof(PLpgSQL_var));
+
+						curname_def = malloc(sizeof(PLpgSQL_var));
+						memset(curname_def, 0, sizeof(PLpgSQL_var));
+
+						new->dtype		= PLPGSQL_DTYPE_VAR;
+						new->refname	= $1.name;
+						new->lineno		= $1.lineno;
+
+						curname_def->dtype = PLPGSQL_DTYPE_EXPR;
+						strcpy(buf, "SELECT '");
+						cp1 = new->refname;
+						cp2 = buf + strlen(buf);
+						while (*cp1 != '\0')
+						{
+							if (*cp1 == '\\' || *cp1 == '\'')
+								*cp2++ = '\\';
+							*cp2++ = *cp1++;
+						}
+						strcat(buf, "'");
+						curname_def->query = strdup(buf);
+						new->default_val = curname_def;
+
+						plpgsql_parse_word("refcursor");
+						new->datatype	= yylval.dtype;
+
+						new->cursor_explicit_expr = $6;
+						if ($3 == NULL)
+							new->cursor_explicit_argrow = -1;
+						else
+							new->cursor_explicit_argrow = $3->rowno;
+
+						plpgsql_adddatum((PLpgSQL_datum *)new);
+						plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, new->varno,
+												$1.name);
+					}
+				;
+
+decl_cursor_query :
+					{
+						PLpgSQL_expr *query;
+
+						plpgsql_ns_setlocal(false);
+						query = plpgsql_read_expression(';', ";");
+						plpgsql_ns_setlocal(true);
+						
+						$$ = query;
+					}
 				;
 
+decl_cursor_args :
+					{
+						$$ = NULL;
+					}
+				| decl_cursor_openparen decl_cursor_arglist ')'
+					{
+						char **ftmp;
+						int *vtmp;
+
+						ftmp = malloc($2->nfields * sizeof(char *));
+						vtmp = malloc($2->nfields * sizeof(int));
+						memcpy(ftmp, $2->fieldnames, $2->nfields * sizeof(char *));
+						memcpy(vtmp, $2->varnos, $2->nfields * sizeof(int));
+
+						pfree((char *)($2->fieldnames));
+						pfree((char *)($2->varnos));
+
+						$2->fieldnames = ftmp;
+						$2->varnos = vtmp;
+
+						plpgsql_adddatum((PLpgSQL_datum *)$2);
+
+						$$ = $2;
+					}
+				;
+
+decl_cursor_arglist : decl_cursor_arg
+					{
+						PLpgSQL_row *new;
+
+						new = malloc(sizeof(PLpgSQL_row));
+						memset(new, 0, sizeof(PLpgSQL_row));
+
+						new->dtype = PLPGSQL_DTYPE_ROW;
+						new->refname = strdup("*internal*");
+						new->lineno = yylineno;
+						new->rowtypeclass = InvalidOid;
+						new->fieldnames = palloc(1024 * sizeof(char *));
+						new->varnos = palloc(1024 * sizeof(int));
+						new->nfields = 1;
+
+						new->fieldnames[0] = $1->refname;
+						new->varnos[0] = $1->varno;
+
+						$$ = new;
+					}
+				| decl_cursor_arglist ',' decl_cursor_arg
+					{
+						int i = $1->nfields++;
+
+						$1->fieldnames[i] = $3->refname;
+						$1->varnos[i] = $3->varno;
+					}
+				;
+
+decl_cursor_arg : decl_varname decl_datatype
+					{
+						PLpgSQL_var *new;
+
+						new = malloc(sizeof(PLpgSQL_var));
+						memset(new, 0, sizeof(PLpgSQL_var));
+
+						new->dtype		= PLPGSQL_DTYPE_VAR;
+						new->refname	= $1.name;
+						new->lineno		= $1.lineno;
+
+						new->datatype	= $2;
+						new->isconst	= false;
+						new->notnull	= false;
+
+						plpgsql_adddatum((PLpgSQL_datum *)new);
+						plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR, new->varno,
+												$1.name);
+						
+						$$ = new;
+					}
+				;
+
+decl_cursor_openparen : '('
+					{
+						plpgsql_ns_push(NULL);
+					}
+				;
+				
+
 decl_aliasitem	: T_WORD
 					{
 						PLpgSQL_nsitem *nsi;
@@ -581,6 +733,12 @@ proc_stmt		: pl_block
 						{ $$ = $1; }
 				| stmt_getdiag
 						{ $$ = $1; }
+				| stmt_open
+						{ $$ = $1; }
+				| stmt_fetch
+						{ $$ = $1; }
+				| stmt_close
+						{ $$ = $1; }
 				;
 
 stmt_perform	: K_PERFORM lno expr_until_semi
@@ -836,6 +994,7 @@ fori_var		: fori_varname
 						PLpgSQL_var		*new;
 
 						new = malloc(sizeof(PLpgSQL_var));
+						memset(new, 0, sizeof(PLpgSQL_var));
 
 						new->dtype		= PLPGSQL_DTYPE_VAR;
 						new->refname	= $1.name;
@@ -1189,15 +1348,137 @@ stmt_execsql	: execsql_start lno
 
 stmt_dynexecute : K_EXECUTE lno expr_until_semi
 						{
-								PLpgSQL_stmt_dynexecute *new;
+							PLpgSQL_stmt_dynexecute *new;
 
-						new = malloc(sizeof(PLpgSQL_stmt_dynexecute));
-						new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
-						new->lineno   = $2;
-						new->query	  = $3;
+							new = malloc(sizeof(PLpgSQL_stmt_dynexecute));
+							new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
+							new->lineno   = $2;
+							new->query	  = $3;
+
+							$$ = (PLpgSQL_stmt *)new;
+						}
+				;
+
+stmt_open		: K_OPEN lno cursor_varptr
+					{
+						PLpgSQL_stmt_open *new;
+						int				  tok;
+
+						new = malloc(sizeof(PLpgSQL_stmt_open));
+						memset(new, 0, sizeof(PLpgSQL_stmt_open));
+
+						new->cmd_type = PLPGSQL_STMT_OPEN;
+						new->lineno = $2;
+						new->curvar = $3->varno;
+
+						if ($3->cursor_explicit_expr == NULL)
+						{
+						    tok = yylex();
+
+							if (tok != K_FOR)
+							{
+								plpgsql_comperrinfo();
+								elog(ERROR, "syntax error at \"%s\" - expected FOR to open a reference cursor", yytext);
+							}
+
+							tok = yylex();
+							switch (tok)
+							{
+								case K_SELECT:
+									new->query = plpgsql_read_expression(';', ";");
+									break;
+
+								case K_EXECUTE:
+									new->dynquery = plpgsql_read_expression(';', ";");
+									break;
+
+								default:
+									plpgsql_comperrinfo();
+									elog(ERROR, "syntax error at \"%s\"", yytext);
+							}
+
+						}
+						else
+						{
+							if ($3->cursor_explicit_argrow >= 0)
+							{
+								tok = yylex();
+
+								if (tok != '(')
+								{
+									plpgsql_comperrinfo();
+									elog(ERROR, "cursor %s has arguments", $3->refname);
+								}
+
+								new->argquery = read_sqlstmt(';', ";", "SELECT (");
+							}
+							else
+							{
+								tok = yylex();
+
+								if (tok == '(')
+								{
+									plpgsql_comperrinfo();
+									elog(ERROR, "cursor %s has no arguments", $3->refname);
+								}
+								
+								if (tok != ';')
+								{
+									plpgsql_comperrinfo();
+									elog(ERROR, "syntax error at \"%s\"", yytext);
+								}
+							}
+						}
+
+						$$ = (PLpgSQL_stmt *)new;
+					}
+				;
+
+stmt_fetch		: K_FETCH lno cursor_variable K_INTO
+					{
+						PLpgSQL_stmt_fetch *new;
+
+						new = (PLpgSQL_stmt_fetch *)make_fetch_stmt();
+						new->curvar = $3;
 
 						$$ = (PLpgSQL_stmt *)new;
+						$$->lineno = $2;
+					}
+				;
+
+stmt_close		: K_CLOSE lno cursor_variable ';'
+					{
+						PLpgSQL_stmt_close *new;
+
+						new = malloc(sizeof(PLpgSQL_stmt_close));
+						new->cmd_type = PLPGSQL_STMT_CLOSE;
+						new->lineno = $2;
+						new->curvar = $3;
+
+						$$ = (PLpgSQL_stmt *)new;
+					}
+				;
+
+cursor_varptr	: T_VARIABLE
+					{
+						if (yylval.var->datatype->typoid != REFCURSOROID)
+						{
+							plpgsql_comperrinfo();
+							elog(ERROR, "%s must be of type cursor or refcursor", yylval.var->refname);
 						}
+						$$ = yylval.var;
+					}
+				;
+
+cursor_variable	: T_VARIABLE
+					{
+						if (yylval.var->datatype->typoid != REFCURSOROID)
+						{
+							plpgsql_comperrinfo();
+							elog(ERROR, "%s must be of type refcursor", yylval.var->refname);
+						}
+						$$ = yylval.var->varno;
+					}
 				;
 
 execsql_start	: T_WORD
@@ -1615,6 +1896,113 @@ make_select_stmt()
 }
 
 
+static PLpgSQL_stmt *
+make_fetch_stmt()
+{
+	int					tok;
+	PLpgSQL_row		   *row = NULL;
+	PLpgSQL_rec		   *rec = NULL;
+	PLpgSQL_stmt_fetch *fetch;
+	int					have_nexttok = 0;
+
+	tok = yylex();
+	switch (tok)
+	{
+		case T_ROW:
+			row = yylval.row;
+			break;
+
+		case T_RECORD:
+			rec = yylval.rec;
+			break;
+
+		case T_VARIABLE:
+		case T_RECFIELD:
+			{
+				PLpgSQL_var		*var;
+				PLpgSQL_recfield *recfield;
+				int				nfields = 1;
+				char			*fieldnames[1024];
+				int				varnos[1024];
+
+				switch (tok)
+				{	
+					case T_VARIABLE:
+						var = yylval.var;
+						fieldnames[0] = strdup(yytext);
+						varnos[0]	  = var->varno;
+						break;
+
+					case T_RECFIELD:
+						recfield = yylval.recfield;
+						fieldnames[0] = strdup(yytext);
+						varnos[0]	  = recfield->rfno;
+						break;
+				}
+
+				while ((tok = yylex()) == ',')
+				{
+					tok = yylex();
+					switch(tok)
+					{
+						case T_VARIABLE:
+							var = yylval.var;
+							fieldnames[nfields] = strdup(yytext);
+							varnos[nfields++]	= var->varno;
+							break;
+
+						case T_RECFIELD:
+							recfield = yylval.recfield;
+							fieldnames[0] = strdup(yytext);
+							varnos[0]	  = recfield->rfno;
+							break;
+
+						default:
+							elog(ERROR, "plpgsql: %s is not a variable or record field", yytext);
+					}
+				}
+				row = malloc(sizeof(PLpgSQL_row));
+				row->dtype = PLPGSQL_DTYPE_ROW;
+				row->refname = strdup("*internal*");
+				row->lineno = yylineno;
+				row->rowtypeclass = InvalidOid;
+				row->nfields = nfields;
+				row->fieldnames = malloc(sizeof(char *) * nfields);
+				row->varnos = malloc(sizeof(int) * nfields);
+				while (--nfields >= 0)
+				{
+					row->fieldnames[nfields] = fieldnames[nfields];
+					row->varnos[nfields] = varnos[nfields];
+				}
+
+				plpgsql_adddatum((PLpgSQL_datum *)row);
+
+				have_nexttok = 1;
+			}
+			break;
+
+		default:
+			{
+				elog(ERROR, "syntax error at '%s'", yytext);
+			}
+	}
+
+	if (!have_nexttok)
+		tok = yylex();
+
+	if (tok != ';')
+		elog(ERROR, "syntax error at '%s'", yytext);
+
+	fetch = malloc(sizeof(PLpgSQL_stmt_select));
+	memset(fetch, 0, sizeof(PLpgSQL_stmt_fetch));
+	fetch->cmd_type = PLPGSQL_STMT_FETCH;
+	fetch->rec		 = rec;
+	fetch->row		 = row;
+
+	return (PLpgSQL_stmt *)fetch;
+}
+
+
 static PLpgSQL_expr *
 make_tupret_expr(PLpgSQL_row *row)
 {
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 9cfa7482413..5d939850286 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.30 2001/04/18 20:42:56 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.31 2001/05/21 14:22:18 wieck Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -282,6 +282,7 @@ plpgsql_compile(Oid fn_oid, int functype)
 					perm_fmgr_info(typeStruct->typinput, &(var->datatype->typinput));
 					var->datatype->typelem = typeStruct->typelem;
 					var->datatype->typbyval = typeStruct->typbyval;
+					var->datatype->typlen = typeStruct->typlen;
 					var->datatype->atttypmod = -1;
 					var->isconst = true;
 					var->notnull = false;
@@ -313,6 +314,9 @@ plpgsql_compile(Oid fn_oid, int functype)
 			memset(rec, 0, sizeof(PLpgSQL_rec));
 			rec->dtype = PLPGSQL_DTYPE_REC;
 			rec->refname = strdup("new");
+			rec->tup = NULL;
+			rec->tupdesc = NULL;
+			rec->freetup = false;
 			plpgsql_adddatum((PLpgSQL_datum *) rec);
 			plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
 			function->new_varno = rec->recno;
@@ -324,6 +328,9 @@ plpgsql_compile(Oid fn_oid, int functype)
 			memset(rec, 0, sizeof(PLpgSQL_rec));
 			rec->dtype = PLPGSQL_DTYPE_REC;
 			rec->refname = strdup("old");
+			rec->tup = NULL;
+			rec->tupdesc = NULL;
+			rec->freetup = false;
 			plpgsql_adddatum((PLpgSQL_datum *) rec);
 			plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->recno, rec->refname);
 			function->old_varno = rec->recno;
@@ -632,6 +639,7 @@ plpgsql_parse_word(char *word)
 		perm_fmgr_info(typeStruct->typinput, &(typ->typinput));
 		typ->typelem = typeStruct->typelem;
 		typ->typbyval = typeStruct->typbyval;
+		typ->typlen = typeStruct->typlen;
 		typ->atttypmod = -1;
 
 		plpgsql_yylval.dtype = typ;
@@ -948,6 +956,7 @@ plpgsql_parse_wordtype(char *word)
 		perm_fmgr_info(typeStruct->typinput, &(typ->typinput));
 		typ->typelem = typeStruct->typelem;
 		typ->typbyval = typeStruct->typbyval;
+		typ->typlen = typeStruct->typlen;
 		typ->atttypmod = -1;
 
 		plpgsql_yylval.dtype = typ;
@@ -1091,6 +1100,7 @@ plpgsql_parse_dblwordtype(char *string)
 	perm_fmgr_info(typeStruct->typinput, &(typ->typinput));
 	typ->typelem = typeStruct->typelem;
 	typ->typbyval = typeStruct->typbyval;
+	typ->typlen = typeStruct->typlen;
 	typ->atttypmod = attrStruct->atttypmod;
 
 	plpgsql_yylval.dtype = typ;
@@ -1230,13 +1240,14 @@ plpgsql_parse_wordrowtype(char *string)
 		perm_fmgr_info(typeStruct->typinput, &(var->datatype->typinput));
 		var->datatype->typelem = typeStruct->typelem;
 		var->datatype->typbyval = typeStruct->typbyval;
+		var->datatype->typlen = typeStruct->typlen;
 		var->datatype->atttypmod = attrStruct->atttypmod;
 		var->isconst = false;
 		var->notnull = false;
 		var->default_val = NULL;
 		var->value = (Datum) 0;
 		var->isnull = true;
-		var->shouldfree = false;
+		var->freeval = false;
 
 		ReleaseSysCache(typetup);
 		ReleaseSysCache(attrtup);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index ba3b1b495fb..1ad04739a33 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.42 2001/05/08 01:00:53 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.43 2001/05/21 14:22:19 wieck Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -95,6 +95,12 @@ static int exec_stmt_fors(PLpgSQL_execstate * estate,
 			   PLpgSQL_stmt_fors * stmt);
 static int exec_stmt_select(PLpgSQL_execstate * estate,
 				 PLpgSQL_stmt_select * stmt);
+static int exec_stmt_open(PLpgSQL_execstate * estate,
+				 PLpgSQL_stmt_open * stmt);
+static int exec_stmt_fetch(PLpgSQL_execstate * estate,
+				 PLpgSQL_stmt_fetch * stmt);
+static int exec_stmt_close(PLpgSQL_execstate * estate,
+				 PLpgSQL_stmt_close * stmt);
 static int exec_stmt_exit(PLpgSQL_execstate * estate,
 			   PLpgSQL_stmt_exit * stmt);
 static int exec_stmt_return(PLpgSQL_execstate * estate,
@@ -128,7 +134,7 @@ static Datum exec_eval_expr(PLpgSQL_execstate * estate,
 			   bool *isNull,
 			   Oid *rettype);
 static int exec_run_select(PLpgSQL_execstate * estate,
-				PLpgSQL_expr * expr, int maxtuples);
+				PLpgSQL_expr * expr, int maxtuples, Portal *portalP);
 static void exec_move_row(PLpgSQL_execstate * estate,
 			  PLpgSQL_rec * rec,
 			  PLpgSQL_row * row,
@@ -232,6 +238,12 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
 					case PLPGSQL_STMT_DYNFORS:
 						stmttype = "for over execute statement";
 						break;
+					case PLPGSQL_STMT_FETCH:
+						stmttype = "fetch";
+						break;
+					case PLPGSQL_STMT_CLOSE:
+						stmttype = "close";
+						break;
 					default:
 						stmttype = "unknown";
 						break;
@@ -314,7 +326,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
 
 					var->value = fcinfo->arg[i];
 					var->isnull = fcinfo->argnull[i];
-					var->shouldfree = false;
+					var->freeval = false;
 				}
 				break;
 
@@ -353,7 +365,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
 
 					var->value = 0;
 					var->isnull = true;
-					var->shouldfree = false;
+					var->freeval = false;
 				}
 				break;
 
@@ -607,6 +619,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
 	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
 	var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
 	var->isnull = false;
+	var->freeval = false;
 
 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
 	{
@@ -644,11 +657,13 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
 	 */
 	var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
 	var->isnull = false;
+	var->freeval = false;
 	var->value = DirectFunctionCall1(namein,
 						  CStringGetDatum(trigdata->tg_trigger->tgname));
 
 	var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]);
 	var->isnull = false;
+	var->freeval = false;
 	if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
 		var->value = DirectFunctionCall1(textin, CStringGetDatum("BEFORE"));
 	else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
@@ -658,6 +673,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
 
 	var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
 	var->isnull = false;
+	var->freeval = false;
 	if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
 		var->value = DirectFunctionCall1(textin, CStringGetDatum("ROW"));
 	else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
@@ -667,6 +683,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
 
 	var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
 	var->isnull = false;
+	var->freeval = false;
 	var->value = (Datum) (trigdata->tg_relation->rd_id);
 
 	var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]);
@@ -676,6 +693,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
 
 	var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]);
 	var->isnull = false;
+	var->freeval = false;
 	var->value = (Datum) (trigdata->tg_trigger->tgnargs);
 
 	/*
@@ -709,7 +727,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
 
 					var->value = 0;
 					var->isnull = true;
-					var->shouldfree = false;
+					var->freeval = false;
 				}
 				break;
 
@@ -794,6 +812,7 @@ copy_var(PLpgSQL_var * var)
 	PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));
 
 	memcpy(new, var, sizeof(PLpgSQL_var));
+	new->freeval = false;
 
 	return new;
 }
@@ -805,6 +824,10 @@ copy_rec(PLpgSQL_rec * rec)
 	PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));
 
 	memcpy(new, rec, sizeof(PLpgSQL_rec));
+	new->tup = NULL;
+	new->tupdesc = NULL;
+	new->freetup = false;
+	new->freetupdesc = false;
 
 	return new;
 }
@@ -834,6 +857,12 @@ exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block)
 				{
 					PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);
 
+					if (var->freeval)
+					{
+						pfree((void *)(var->value));
+						var->freeval = false;
+					}
+
 					if (!var->isconst || var->isnull)
 					{
 						if (var->default_val == NULL)
@@ -856,6 +885,13 @@ exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block)
 				{
 					PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[n]);
 
+					if (rec->freetup)
+					{
+						heap_freetuple(rec->tup);
+						FreeTupleDesc(rec->tupdesc);
+						rec->freetup = false;
+					}
+
 					rec->tup = NULL;
 					rec->tupdesc = NULL;
 				}
@@ -1002,6 +1038,18 @@ exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt)
 			rc = exec_stmt_dynfors(estate, (PLpgSQL_stmt_dynfors *) stmt);
 			break;
 
+		case PLPGSQL_STMT_OPEN:
+			rc = exec_stmt_open(estate, (PLpgSQL_stmt_open *) stmt);
+			break;
+
+		case PLPGSQL_STMT_FETCH:
+			rc = exec_stmt_fetch(estate, (PLpgSQL_stmt_fetch *) stmt);
+			break;
+
+		case PLPGSQL_STMT_CLOSE:
+			rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt);
+			break;
+
 		default:
 			error_info_stmt = save_estmt;
 			elog(ERROR, "unknown cmdtype %d in exec_stmt",
@@ -1092,6 +1140,7 @@ exec_stmt_if(PLpgSQL_execstate * estate, PLpgSQL_stmt_if * stmt)
 	bool		isnull = false;
 
 	value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);
+	SPI_freetuptable(SPI_tuptable);
 
 	if (value)
 	{
@@ -1166,6 +1215,7 @@ exec_stmt_while(PLpgSQL_execstate * estate, PLpgSQL_stmt_while * stmt)
 	for (;;)
 	{
 		value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);
+		SPI_freetuptable(SPI_tuptable);
 		if (!value)
 			break;
 
@@ -1227,6 +1277,7 @@ exec_stmt_fori(PLpgSQL_execstate * estate, PLpgSQL_stmt_fori * stmt)
 		elog(ERROR, "lower bound of FOR loop cannot be NULL");
 	var->value = value;
 	var->isnull = false;
+	SPI_freetuptable(SPI_tuptable);
 
 	/*
 	 * Get the value of the upper bound
@@ -1238,6 +1289,7 @@ exec_stmt_fori(PLpgSQL_execstate * estate, PLpgSQL_stmt_fori * stmt)
 							var->datatype->atttypmod, &isnull);
 	if (isnull)
 		elog(ERROR, "upper bound of FOR loop cannot be NULL");
+	SPI_freetuptable(SPI_tuptable);
 
 	/*
 	 * Now do the loop
@@ -1317,6 +1369,7 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
 	PLpgSQL_rec *rec = NULL;
 	PLpgSQL_row *row = NULL;
 	SPITupleTable *tuptab;
+	Portal		portal;
 	int			rc;
 	int			i;
 	int			n;
@@ -1340,12 +1393,12 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
 	}
 
 	/*
-	 * Run the query
+	 * Open the implicit cursor for the statement and fetch
+	 * the initial 10 rows.
 	 */
-	exec_run_select(estate, stmt->query, 0);
+	exec_run_select(estate, stmt->query, 0, &portal);
+	SPI_cursor_fetch(portal, true, 10);
 	n = SPI_processed;
-	tuptab = SPI_tuptable;
-	SPI_tuptable = NULL;
 
 	/*
 	 * If the query didn't return any row, set the target to NULL and
@@ -1354,6 +1407,7 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
 	if (n == 0)
 	{
 		exec_move_row(estate, rec, row, NULL, NULL);
+		SPI_cursor_close(portal);
 		return PLPGSQL_RC_OK;
 	}
 
@@ -1365,45 +1419,71 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt)
 	/*
 	 * Now do the loop
 	 */
-	for (i = 0; i < n; i++)
+	for (;;)
 	{
+		tuptab = SPI_tuptable;
+		SPI_tuptable = NULL;
 
-		/*
-		 * Assign the tuple to the target
-		 */
-		exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
+		for (i = 0; i < n; i++)
+		{
 
-		/*
-		 * Execute the statements
-		 */
-		rc = exec_stmts(estate, stmt->body);
+			/*
+			 * Assign the tuple to the target
+			 */
+			exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
 
-		/*
-		 * Check returncode
-		 */
-		switch (rc)
-		{
-			case PLPGSQL_RC_OK:
-				break;
+			/*
+			 * Execute the statements
+			 */
+			rc = exec_stmts(estate, stmt->body);
 
-			case PLPGSQL_RC_EXIT:
-				if (estate->exitlabel == NULL)
+			/*
+			 * Check returncode
+			 */
+			switch (rc)
+			{
+				case PLPGSQL_RC_OK:
+					break;
+
+				case PLPGSQL_RC_EXIT:
+					SPI_freetuptable(tuptab);
+					SPI_cursor_close(portal);
+
+					if (estate->exitlabel == NULL)
+						return PLPGSQL_RC_OK;
+					if (stmt->label == NULL)
+						return PLPGSQL_RC_EXIT;
+					if (strcmp(stmt->label, estate->exitlabel))
+						return PLPGSQL_RC_EXIT;
+					estate->exitlabel = NULL;
 					return PLPGSQL_RC_OK;
-				if (stmt->label == NULL)
-					return PLPGSQL_RC_EXIT;
-				if (strcmp(stmt->label, estate->exitlabel))
-					return PLPGSQL_RC_EXIT;
-				estate->exitlabel = NULL;
-				return PLPGSQL_RC_OK;
 
-			case PLPGSQL_RC_RETURN:
-				return PLPGSQL_RC_RETURN;
+				case PLPGSQL_RC_RETURN:
+					SPI_freetuptable(tuptab);
+					SPI_cursor_close(portal);
 
-			default:
-				elog(ERROR, "unknown rc %d from exec_stmts()", rc);
+					return PLPGSQL_RC_RETURN;
+
+				default:
+					elog(ERROR, "unknown rc %d from exec_stmts()", rc);
+			}
 		}
+
+		SPI_freetuptable(tuptab);
+
+		/*
+		 * Fetch the next 50 tuples
+		 */
+		SPI_cursor_fetch(portal, true, 50);
+		if ((n = SPI_processed) == 0)
+			break;
 	}
 
+	/*
+	 * Close the implicit cursor
+	 */
+	SPI_cursor_close(portal);
+
 	return PLPGSQL_RC_OK;
 }
 
@@ -1442,7 +1522,7 @@ exec_stmt_select(PLpgSQL_execstate * estate, PLpgSQL_stmt_select * stmt)
 	/*
 	 * Run the query
 	 */
-	exec_run_select(estate, stmt->query, 1);
+	exec_run_select(estate, stmt->query, 1, NULL);
 	n = SPI_processed;
 	tuptab = SPI_tuptable;
 	SPI_tuptable = NULL;
@@ -1461,8 +1541,8 @@ exec_stmt_select(PLpgSQL_execstate * estate, PLpgSQL_stmt_select * stmt)
 	 * Put the result into the target and set found to true
 	 */
 	exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
-
 	exec_set_found(estate, true);
+	SPI_freetuptable(tuptab);
 
 	return PLPGSQL_RC_OK;
 }
@@ -1485,6 +1565,7 @@ exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt)
 	if (stmt->cond != NULL)
 	{
 		value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype);
+		SPI_freetuptable(SPI_tuptable);
 		if (!value)
 			return PLPGSQL_RC_OK;
 	}
@@ -1523,7 +1604,7 @@ exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt)
 		}
 		else
 		{
-			exec_run_select(estate, stmt->expr, 1);
+			exec_run_select(estate, stmt->expr, 1, NULL);
 			estate->retval = (Datum) SPI_copytuple(SPI_tuptable->vals[0]);
 			estate->rettupdesc = SPI_tuptable->tupdesc;
 			estate->retisnull = false;
@@ -1643,6 +1724,7 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt)
 							(estate->datums[stmt->params[pidx]]);
 						value = (int) exec_eval_expr(estate, trigarg->argnum,
 												   &valisnull, &valtype);
+						SPI_freetuptable(SPI_tuptable);
 						if (valisnull)
 							extval = "<INDEX_IS_NULL>";
 						else
@@ -1835,6 +1917,7 @@ exec_stmt_execsql(PLpgSQL_execstate * estate,
 				trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]);
 				tgargno = (int) exec_eval_expr(estate, trigarg->argnum,
 											   &isnull, &tgargoid);
+				SPI_freetuptable(SPI_tuptable);
 				if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
 				{
 					values[i] = 0;
@@ -1922,8 +2005,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate * estate,
 								   ObjectIdGetDatum(typeStruct->typelem),
 											 Int32GetDatum(-1)));
 
-	if (!typeStruct->typbyval)
-		pfree((void *) query);
+	SPI_freetuptable(SPI_tuptable);
 
 	ReleaseSysCache(typetup);
 
@@ -1995,6 +2077,8 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt)
 	HeapTuple	typetup;
 	Form_pg_type typeStruct;
 	FmgrInfo	finfo_output;
+	void		*plan;
+	Portal		portal;
 
 	/*
 	 * Initialize the global found variable to false
@@ -2038,21 +2122,28 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt)
 								   ObjectIdGetDatum(typeStruct->typelem),
 											 Int32GetDatum(-1)));
 
-	if (!typeStruct->typbyval)
-		pfree((void *) query);
+	SPI_freetuptable(SPI_tuptable);
 
 	ReleaseSysCache(typetup);
 
 	/*
-	 * Run the query
+	 * Prepare a plan and open an implicit cursor for the query
 	 */
-	if (SPI_exec(querystr, 0) != SPI_OK_SELECT)
-		elog(ERROR, "FOR ... EXECUTE query '%s' was not SELECT", querystr);
+	plan = SPI_prepare(querystr, 0, NULL);
+	if (plan == NULL)
+		elog(ERROR, "SPI_prepare() failed for dynamic query \"%s\"", querystr);
+	portal = SPI_cursor_open(NULL, plan, NULL, NULL);
+	if (portal == NULL)
+		elog(ERROR, "failed to open implicit cursor for dynamic query \"%s\"",
+					querystr);
 	pfree(querystr);
+	SPI_freeplan(plan);
 
+	/*
+	 * Fetch the initial 10 tuples
+	 */
+	SPI_cursor_fetch(portal, true, 10);
 	n = SPI_processed;
-	tuptab = SPI_tuptable;
-	SPI_tuptable = NULL;
 
 	/*
 	 * If the query didn't return any row, set the target to NULL and
@@ -2061,6 +2152,7 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt)
 	if (n == 0)
 	{
 		exec_move_row(estate, rec, row, NULL, NULL);
+		SPI_cursor_close(portal);
 		return PLPGSQL_RC_OK;
 	}
 
@@ -2072,44 +2164,431 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt)
 	/*
 	 * Now do the loop
 	 */
-	for (i = 0; i < n; i++)
+	for (;;)
 	{
+		tuptab = SPI_tuptable;
+		SPI_tuptable = NULL;
+
+		for (i = 0; i < n; i++)
+		{
+
+			/*
+			 * Assign the tuple to the target
+			 */
+			exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
+
+			/*
+			 * Execute the statements
+			 */
+			rc = exec_stmts(estate, stmt->body);
+
+			/*
+			 * Check returncode
+			 */
+			switch (rc)
+			{
+				case PLPGSQL_RC_OK:
+					break;
+
+				case PLPGSQL_RC_EXIT:
+					SPI_freetuptable(tuptab);
+					SPI_cursor_close(portal);
+
+					if (estate->exitlabel == NULL)
+						return PLPGSQL_RC_OK;
+					if (stmt->label == NULL)
+						return PLPGSQL_RC_EXIT;
+					if (strcmp(stmt->label, estate->exitlabel))
+						return PLPGSQL_RC_EXIT;
+					estate->exitlabel = NULL;
+					return PLPGSQL_RC_OK;
+
+				case PLPGSQL_RC_RETURN:
+					SPI_freetuptable(tuptab);
+					SPI_cursor_close(portal);
+
+					return PLPGSQL_RC_RETURN;
+
+				default:
+					elog(ERROR, "unknown rc %d from exec_stmts()", rc);
+			}
+		}
+
+		SPI_freetuptable(tuptab);
 
 		/*
-		 * Assign the tuple to the target
+		 * Fetch the next 50 tuples
 		 */
-		exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc);
+		SPI_cursor_fetch(portal, true, 50);
+		if ((n = SPI_processed) == 0)
+			break;
+	}
 
-		/*
-		 * Execute the statements
+	/*
+	 * Close the cursor
+	 */
+	SPI_cursor_close(portal);
+
+	return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_open			Execute an OPEN cursor statement
+ * ----------
+ */
+static int
+exec_stmt_open(PLpgSQL_execstate * estate, PLpgSQL_stmt_open * stmt)
+{
+	PLpgSQL_var *curvar = NULL;
+	char		*curname = NULL;
+	PLpgSQL_expr *query = NULL;
+	Portal		portal;
+
+	PLpgSQL_var *var;
+	PLpgSQL_rec *rec;
+	PLpgSQL_recfield *recfield;
+	PLpgSQL_trigarg *trigarg;
+	int			tgargno;
+	Oid			tgargoid;
+	int			i;
+	Datum	   *values;
+	char	   *nulls;
+	int			fno;
+	bool		isnull;
+
+
+	/* ----------
+	 * Get the cursor variable and if it has an assigned name, check
+	 * that it's not in use currently.
+	 * ----------
+	 */
+	curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+	if (!curvar->isnull)
+	{
+		curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
+		if (SPI_cursor_find(curname) != NULL)
+			elog(ERROR, "cursor \"%s\" already in use", curname);
+	}
+
+	/* ----------
+	 * Process the OPEN according to it's type.
+	 * ----------
+	 */
+	if (stmt->query != NULL)
+	{
+		/* ----------
+		 * This is an OPEN refcursor FOR SELECT ...
+		 *
+		 * We just make sure the query is planned. The real work is
+		 * done downstairs.
+		 * ----------
 		 */
-		rc = exec_stmts(estate, stmt->body);
+		query = stmt->query; 
+		if (query->plan == NULL)
+			exec_prepare_plan(estate, query);
+	}
+	else if (stmt->dynquery != NULL)
+	{
+		/* ----------
+		 * This is an OPEN refcursor FOR EXECUTE ...
+		 * ----------
+		 */
+		Datum		queryD;
+		Oid			restype;
+		char	   *querystr;
+		HeapTuple	typetup;
+		Form_pg_type typeStruct;
+		FmgrInfo	finfo_output;
+		void		*curplan = NULL;
+
+		/* ----------
+		 * We evaluate the string expression after the
+		 * EXECUTE keyword. It's result is the querystring we have
+		 * to execute.
+		 * ----------
+		 */
+		queryD = exec_eval_expr(estate, stmt->dynquery, &isnull, &restype);
+		if (isnull)
+			elog(ERROR, "cannot EXECUTE NULL query");
 
-		/*
-		 * Check returncode
+		/* ----------
+		 * Get the C-String representation.
+		 * ----------
 		 */
-		switch (rc)
+		typetup = SearchSysCache(TYPEOID,
+								 ObjectIdGetDatum(restype),
+								 0, 0, 0);
+		if (!HeapTupleIsValid(typetup))
+			elog(ERROR, "cache lookup for type %u failed (1)", restype);
+		typeStruct = (Form_pg_type) GETSTRUCT(typetup);
+
+		fmgr_info(typeStruct->typoutput, &finfo_output);
+		querystr = DatumGetCString(FunctionCall3(&finfo_output,
+												 queryD,
+												 ObjectIdGetDatum(typeStruct->typelem),
+												 Int32GetDatum(-1)));
+
+		SPI_freetuptable(SPI_tuptable);
+		ReleaseSysCache(typetup);
+
+		/* ----------
+		 * Now we prepare a query plan for it and open a cursor
+		 * ----------
+		 */
+		curplan = SPI_prepare(querystr, 0, NULL);
+		portal = SPI_cursor_open(curname, curplan, NULL, NULL);
+		if (portal == NULL)
+			elog(ERROR, "Failed to open cursor");
+		pfree(querystr);
+
+		/* ----------
+		 * Store the eventually assigned cursor name in the cursor variable
+		 * ----------
+		 */
+		if (curvar->freeval)
+			pfree((void *)(curvar->value));
+
+		curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
+		curvar->isnull = false;
+		curvar->freeval = true;
+
+		return PLPGSQL_RC_OK;
+	}
+	else
+	{
+		/* ----------
+		 * This is an OPEN cursor
+		 * ----------
+		 */
+		if (stmt->argquery != NULL)
 		{
-			case PLPGSQL_RC_OK:
+			/* ----------
+			 * Er - OPEN CURSOR (args). We fake a SELECT ... INTO ...
+			 * statement to evaluate the args and put 'em into the
+			 * internal row.
+			 * ----------
+			 */
+			PLpgSQL_stmt_select	set_args;
+
+			memset(&set_args, 0, sizeof(set_args));
+			set_args.cmd_type	= PLPGSQL_STMT_SELECT;
+			set_args.lineno		= stmt->lineno;
+			set_args.row		= (PLpgSQL_row *) 
+								  (estate->datums[curvar->cursor_explicit_argrow]);
+			set_args.query		= stmt->argquery;
+
+			if (exec_stmt_select(estate, &set_args) != PLPGSQL_RC_OK)
+				elog(ERROR, "open cursor failed during argument processing");
+		}
+
+		query = curvar->cursor_explicit_expr; 
+		if (query->plan == NULL)
+			exec_prepare_plan(estate, query);
+	}
+
+	/* ----------
+	 * Here we go if we have a saved plan where we have to put
+	 * values into, either from an explicit cursor or from a
+	 * refcursor opened with OPEN ... FOR SELECT ...;
+	 * ----------
+	 */
+	values = palloc(sizeof(Datum) * (query->nparams + 1));
+	nulls = palloc(query->nparams + 1);
+
+	for (i = 0; i < query->nparams; i++)
+	{
+		switch (estate->datums[query->params[i]]->dtype)
+		{
+			case PLPGSQL_DTYPE_VAR:
+				var = (PLpgSQL_var *) (estate->datums[query->params[i]]);
+				values[i] = var->value;
+				if (var->isnull)
+					nulls[i] = 'n';
+				else
+					nulls[i] = ' ';
 				break;
 
-			case PLPGSQL_RC_EXIT:
-				if (estate->exitlabel == NULL)
-					return PLPGSQL_RC_OK;
-				if (stmt->label == NULL)
-					return PLPGSQL_RC_EXIT;
-				if (strcmp(stmt->label, estate->exitlabel))
-					return PLPGSQL_RC_EXIT;
-				estate->exitlabel = NULL;
-				return PLPGSQL_RC_OK;
+			case PLPGSQL_DTYPE_RECFIELD:
+				recfield = (PLpgSQL_recfield *) (estate->datums[query->params[i]]);
+				rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);
 
-			case PLPGSQL_RC_RETURN:
-				return PLPGSQL_RC_RETURN;
+				if (!HeapTupleIsValid(rec->tup))
+					elog(ERROR, "record %s is unassigned yet", rec->refname);
+				fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+				if (fno == SPI_ERROR_NOATTRIBUTE)
+					elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+
+				if (query->plan_argtypes[i] != SPI_gettypeid(rec->tupdesc, fno))
+					elog(ERROR, "type of %s.%s doesn't match that when preparing the plan", rec->refname, recfield->fieldname);
+
+				values[i] = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull);
+				if (isnull)
+					nulls[i] = 'n';
+				else
+					nulls[i] = ' ';
+				break;
+
+			case PLPGSQL_DTYPE_TRIGARG:
+				trigarg = (PLpgSQL_trigarg *) (estate->datums[query->params[i]]);
+				tgargno = (int) exec_eval_expr(estate, trigarg->argnum,
+											   &isnull, &tgargoid);
+				SPI_freetuptable(SPI_tuptable);
+				if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
+				{
+					values[i] = 0;
+					nulls[i] = 'n';
+				}
+				else
+				{
+					values[i] = estate->trig_argv[tgargno];
+					nulls[i] = ' ';
+				}
+				break;
 
 			default:
-				elog(ERROR, "unknown rc %d from exec_stmts()", rc);
+				elog(ERROR, "unknown parameter dtype %d in exec_stmt_open()",
+					 estate->datums[query->params[i]]->dtype);
 		}
 	}
+	nulls[i] = '\0';
+
+	/* ----------
+	 * Open the cursor
+	 * ----------
+	 */
+	portal = SPI_cursor_open(curname, query->plan, values, nulls);
+	if (portal == NULL)
+		elog(ERROR, "Failed to open cursor");
+
+	pfree(values);
+	pfree(nulls);
+
+	/* ----------
+	 * Store the eventually assigned portal name in the cursor variable
+	 * ----------
+	 */
+	if (curvar->freeval)
+		pfree((void *)(curvar->value));
+
+	curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
+	curvar->isnull = false;
+	curvar->freeval = true;
+
+	return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_fetch			Fetch from a cursor into a target
+ * ----------
+ */
+static int
+exec_stmt_fetch(PLpgSQL_execstate * estate, PLpgSQL_stmt_fetch * stmt)
+{
+	PLpgSQL_var *curvar = NULL;
+	PLpgSQL_rec *rec = NULL;
+	PLpgSQL_row *row = NULL;
+	Portal		portal;
+	char		*curname;
+	int			n;
+
+	/* ----------
+	 * Get the portal of the cursor by name
+	 * ----------
+	 */
+	curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+	if (curvar->isnull)
+		elog(ERROR, "cursor variable \"%s\" is NULL", curvar->refname);
+	curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
+
+	portal = SPI_cursor_find(curname);
+	if (portal == NULL)
+		elog(ERROR, "cursor \"%s\" is invalid", curname);
+	pfree(curname);
+
+	/* ----------
+	 * Initialize the global found variable to false
+	 * ----------
+	 */
+	exec_set_found(estate, false);
+
+	/* ----------
+	 * Determine if we fetch into a record or a row
+	 * ----------
+	 */
+	if (stmt->rec != NULL)
+		rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+	else
+	{
+		if (stmt->row != NULL)
+			row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+		else
+			elog(ERROR, "unsupported target in exec_stmt_select()");
+	}
+
+	/* ----------
+	 * Fetch 1 tuple from the cursor
+	 * ----------
+	 */
+	SPI_cursor_fetch(portal, true, 1);
+	n = SPI_processed;
+
+	/* ----------
+	 * If the FETCH didn't return a row, set the target
+	 * to NULL and return with FOUND = false.
+	 * ----------
+	 */
+	if (n == 0)
+	{
+		exec_move_row(estate, rec, row, NULL, NULL);
+		return PLPGSQL_RC_OK;
+	}
+
+	/* ----------
+	 * Put the result into the target and set found to true
+	 * ----------
+	 */
+	exec_move_row(estate, rec, row, SPI_tuptable->vals[0], 
+				SPI_tuptable->tupdesc);
+	exec_set_found(estate, true);
+
+	SPI_freetuptable(SPI_tuptable);
+
+	return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_close			Close a cursor
+ * ----------
+ */
+static int
+exec_stmt_close(PLpgSQL_execstate * estate, PLpgSQL_stmt_close * stmt)
+{
+	PLpgSQL_var *curvar = NULL;
+	Portal		portal;
+	char		*curname;
+
+	/* ----------
+	 * Get the portal of the cursor by name
+	 * ----------
+	 */
+	curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+	if (curvar->isnull)
+		elog(ERROR, "cursor variable \"%s\" is NULL", curvar->refname);
+	curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value));
+
+	portal = SPI_cursor_find(curname);
+	if (portal == NULL)
+		elog(ERROR, "cursor \"%s\" is invalid", curname);
+	pfree(curname);
+
+	/* ----------
+	 * And close it.
+	 * ----------
+	 */
+	SPI_cursor_close(portal);
 
 	return PLPGSQL_RC_OK;
 }
@@ -2131,6 +2610,8 @@ exec_assign_expr(PLpgSQL_execstate * estate, PLpgSQL_datum * target,
 	value = exec_eval_expr(estate, expr, &isnull, &valtype);
 	if (target != NULL)
 		exec_assign_value(estate, target, value, valtype, &isnull);
+
+	SPI_freetuptable(SPI_tuptable);
 }
 
 
@@ -2156,6 +2637,7 @@ exec_assign_value(PLpgSQL_execstate * estate,
 	Oid			atttype;
 	int32		atttypmod;
 	HeapTuple	typetup;
+	HeapTuple	newtup;
 	Form_pg_type typeStruct;
 	FmgrInfo	finfo_input;
 
@@ -2164,19 +2646,39 @@ exec_assign_value(PLpgSQL_execstate * estate,
 		case PLPGSQL_DTYPE_VAR:
 
 			/*
-			 * Target field is a variable - that's easy
+			 * Target field is a variable
 			 */
 			var = (PLpgSQL_var *) target;
+
+			if (var->freeval)
+			{
+				pfree((void *)(var->value));
+				var->freeval = false;
+			}
+
 			newvalue = exec_cast_value(value, valtype, var->datatype->typoid,
 									   &(var->datatype->typinput),
 									   var->datatype->typelem,
 									   var->datatype->atttypmod,
 									   isNull);
 
+			if (!var->datatype->typbyval && newvalue == value && !*isNull)
+			{
+				int		len;
+				if (var->datatype->typlen < 0)
+					len = VARSIZE(newvalue);
+				else
+					len = var->datatype->typlen;
+				var->value = (Datum)palloc(len);
+				memcpy((void *)(var->value), (void *)newvalue, len);
+				var->freeval = true;
+			}
+			else
+				var->value = newvalue;
+
 			if (*isNull && var->notnull)
 				elog(ERROR, "NULL assignment to variable '%s' declared NOT NULL", var->refname);
 
-			var->value = newvalue;
 			var->isnull = *isNull;
 			break;
 
@@ -2263,7 +2765,14 @@ exec_assign_value(PLpgSQL_execstate * estate,
 			 * replaces the old one in the record.
 			 */
 			nulls[i] = '\0';
-			rec->tup = heap_formtuple(rec->tupdesc, values, nulls);
+			newtup = heap_formtuple(rec->tupdesc, values, nulls);
+
+			if (rec->freetup)
+				heap_freetuple(rec->tup);
+
+			rec->tup = newtup;
+			rec->freetup = true;
+
 			pfree(values);
 			pfree(nulls);
 
@@ -2289,6 +2798,9 @@ exec_eval_expr(PLpgSQL_execstate * estate,
 {
 	int			rc;
 
+	SPI_tuptable = NULL;
+	SPI_processed = 0;
+
 	/*
 	 * If not already done create a plan for this expression
 	 */
@@ -2302,7 +2814,7 @@ exec_eval_expr(PLpgSQL_execstate * estate,
 	if (expr->plan_simple_expr != NULL)
 		return exec_eval_simple_expr(estate, expr, isNull, rettype);
 
-	rc = exec_run_select(estate, expr, 2);
+	rc = exec_run_select(estate, expr, 2, NULL);
 	if (rc != SPI_OK_SELECT)
 		elog(ERROR, "query \"%s\" didn't return data", expr->query);
 
@@ -2321,7 +2833,8 @@ exec_eval_expr(PLpgSQL_execstate * estate,
 	if (SPI_processed > 1)
 		elog(ERROR, "query \"%s\" returned more than one row", expr->query);
 	if (SPI_tuptable->tupdesc->natts != 1)
-		elog(ERROR, "query \"%s\" returned more than one column", expr->query);
+		elog(ERROR, "query \"%s\" returned %d columns", expr->query,
+				SPI_tuptable->tupdesc->natts);
 
 	/*
 	 * Return the result and its type
@@ -2337,7 +2850,7 @@ exec_eval_expr(PLpgSQL_execstate * estate,
  */
 static int
 exec_run_select(PLpgSQL_execstate * estate,
-				PLpgSQL_expr * expr, int maxtuples)
+				PLpgSQL_expr * expr, int maxtuples, Portal *portalP)
 {
 	PLpgSQL_var *var;
 	PLpgSQL_rec *rec;
@@ -2401,6 +2914,7 @@ exec_run_select(PLpgSQL_execstate * estate,
 				trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]);
 				tgargno = (int) exec_eval_expr(estate, trigarg->argnum,
 											   &isnull, &tgargoid);
+				SPI_freetuptable(SPI_tuptable);
 				if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
 				{
 					values[i] = 0;
@@ -2423,6 +2937,16 @@ exec_run_select(PLpgSQL_execstate * estate,
 	/*
 	 * Execute the query
 	 */
+	if (portalP != NULL)
+	{
+		*portalP = SPI_cursor_open(NULL, expr->plan, values, nulls);
+		if (*portalP == NULL)
+			elog(ERROR, "failed to open implicit cursor for \"%s\"",
+						expr->query);
+		pfree(values);
+		pfree(nulls);
+		return SPI_OK_CURSOR;
+	}
 	rc = SPI_execp(expr->plan, values, nulls, maxtuples);
 	if (rc != SPI_OK_SELECT)
 		elog(ERROR, "query \"%s\" isn't a SELECT", expr->query);
@@ -2510,6 +3034,7 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate,
 				trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]);
 				tgargno = (int) exec_eval_expr(estate, trigarg->argnum,
 											   &isnull, &tgargoid);
+				SPI_freetuptable(SPI_tuptable);
 				if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
 				{
 					paramLI->value = 0;
@@ -2581,10 +3106,23 @@ exec_move_row(PLpgSQL_execstate * estate,
 	 */
 	if (rec != NULL)
 	{
+		if (rec->freetup)
+		{
+			heap_freetuple(rec->tup);
+			rec->freetup = false;
+		}
+		if (rec->freetupdesc)
+		{
+			FreeTupleDesc(rec->tupdesc);
+			rec->freetupdesc = false;
+		}
+
 		if (HeapTupleIsValid(tup))
 		{
-			rec->tup = tup;
-			rec->tupdesc = tupdesc;
+			rec->tup = heap_copytuple(tup);
+			rec->tupdesc = CreateTupleDescCopy(tupdesc);
+			rec->freetup = true;
+			rec->freetupdesc = true;
 		}
 		else
 		{
@@ -2766,6 +3304,12 @@ exec_simple_check_plan(PLpgSQL_expr * expr)
 	TargetEntry *tle;
 
 	expr->plan_simple_expr = NULL;
+	/*
+	 * Disabled for now until we can execute simple expressions
+	 * without collecting all the memory allocations until procedure
+	 * returns. 05/17/2001 Jan
+	 */
+    return;
 
 	/*
 	 * 1. We can only evaluate queries that resulted in one single
@@ -2833,3 +3377,5 @@ exec_set_found(PLpgSQL_execstate * estate, bool state)
 	var->value = (Datum) state;
 	var->isnull = false;
 }
+
+
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 13fd9673767..a657512fda1 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.12 2001/03/22 06:16:21 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.13 2001/05/21 14:22:19 wieck Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -387,6 +387,9 @@ static void dump_execsql(PLpgSQL_stmt_execsql * stmt);
 static void dump_dynexecute(PLpgSQL_stmt_dynexecute * stmt);
 static void dump_dynfors(PLpgSQL_stmt_dynfors * stmt);
 static void dump_getdiag(PLpgSQL_stmt_getdiag * stmt);
+static void dump_open(PLpgSQL_stmt_open * stmt);
+static void dump_fetch(PLpgSQL_stmt_fetch * stmt);
+static void dump_close(PLpgSQL_stmt_close * stmt);
 static void dump_expr(PLpgSQL_expr * expr);
 
 
@@ -450,6 +453,15 @@ dump_stmt(PLpgSQL_stmt * stmt)
 		case PLPGSQL_STMT_GETDIAG:
 			dump_getdiag((PLpgSQL_stmt_getdiag *) stmt);
 			break;
+		case PLPGSQL_STMT_OPEN:
+			dump_open((PLpgSQL_stmt_open *) stmt);
+			break;
+		case PLPGSQL_STMT_FETCH:
+			dump_fetch((PLpgSQL_stmt_fetch *) stmt);
+			break;
+		case PLPGSQL_STMT_CLOSE:
+			dump_close((PLpgSQL_stmt_close *) stmt);
+			break;
 		default:
 			elog(ERROR, "plpgsql_dump: unknown cmd_type %d\n", stmt->cmd_type);
 			break;
@@ -619,6 +631,66 @@ dump_select(PLpgSQL_stmt_select * stmt)
 
 }
 
+static void
+dump_open(PLpgSQL_stmt_open * stmt)
+{
+	dump_ind();
+	printf("OPEN curvar=%d\n", stmt->curvar);
+
+	dump_indent += 2;
+	if (stmt->argquery != NULL)
+	{
+		dump_ind();
+		printf("  arguments = '");
+		dump_expr(stmt->argquery);
+		printf("'\n");
+	}
+	if (stmt->query != NULL)
+	{
+		dump_ind();
+		printf("  query = '");
+		dump_expr(stmt->query);
+		printf("'\n");
+	}
+	if (stmt->dynquery != NULL)
+	{
+		dump_ind();
+		printf("  execute = '");
+		dump_expr(stmt->dynquery);
+		printf("'\n");
+	}
+	dump_indent -= 2;
+
+}
+
+static void
+dump_fetch(PLpgSQL_stmt_fetch * stmt)
+{
+	dump_ind();
+	printf("FETCH curvar=%d\n", stmt->curvar);
+
+	dump_indent += 2;
+	if (stmt->rec != NULL)
+	{
+		dump_ind();
+		printf("    target = %d %s\n", stmt->rec->recno, stmt->rec->refname);
+	}
+	if (stmt->row != NULL)
+	{
+		dump_ind();
+		printf("    target = %d %s\n", stmt->row->rowno, stmt->row->refname);
+	}
+	dump_indent -= 2;
+
+}
+
+static void
+dump_close(PLpgSQL_stmt_close * stmt)
+{
+	dump_ind();
+	printf("CLOSE curvar=%d\n", stmt->curvar);
+}
+
 static void
 dump_exit(PLpgSQL_stmt_exit * stmt)
 {
@@ -777,6 +849,25 @@ plpgsql_dumptree(PLpgSQL_function * func)
 						   var->refname, var->datatype->typname,
 						   var->datatype->typoid,
 						   var->datatype->atttypmod);
+					if (var->isconst)
+						printf("                                  CONSTANT\n");
+					if (var->notnull)
+						printf("                                  NOT NULL\n");
+					if (var->default_val != NULL)
+					{
+						printf("                                  DEFAULT ");
+						dump_expr(var->default_val);
+						printf("\n");
+					}
+					if (var->cursor_explicit_expr != NULL)
+					{
+						if (var->cursor_explicit_argrow >= 0)
+							printf("                                  CURSOR argument row %d\n", var->cursor_explicit_argrow);
+
+						printf("                                  CURSOR IS ");
+						dump_expr(var->cursor_explicit_expr);
+						printf("\n");
+					}
 				}
 				break;
 			case PLPGSQL_DTYPE_ROW:
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index f364b39e12c..7089144988b 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.13 2001/03/22 04:01:42 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.14 2001/05/21 14:22:19 wieck Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -94,7 +94,10 @@ enum
 	PLPGSQL_STMT_EXECSQL,
 	PLPGSQL_STMT_DYNEXECUTE,
 	PLPGSQL_STMT_DYNFORS,
-	PLPGSQL_STMT_GETDIAG
+	PLPGSQL_STMT_GETDIAG,
+	PLPGSQL_STMT_OPEN,
+	PLPGSQL_STMT_FETCH,
+	PLPGSQL_STMT_CLOSE
 };
 
 
@@ -139,6 +142,7 @@ typedef struct
 	Oid			typoid;
 	FmgrInfo	typinput;
 	Oid			typelem;
+	int16		typlen;
 	bool		typbyval;
 	int32		atttypmod;
 }			PLpgSQL_type;
@@ -176,10 +180,12 @@ typedef struct
 	int			isconst;
 	int			notnull;
 	PLpgSQL_expr *default_val;
+	PLpgSQL_expr *cursor_explicit_expr;
+	int			cursor_explicit_argrow;
 
 	Datum		value;
 	bool		isnull;
-	int			shouldfree;
+	bool		freeval;
 }			PLpgSQL_var;
 
 
@@ -206,6 +212,8 @@ typedef struct
 
 	HeapTuple	tup;
 	TupleDesc	tupdesc;
+	bool		freetup;
+	bool		freetupdesc;
 }			PLpgSQL_rec;
 
 
@@ -369,6 +377,36 @@ typedef struct
 }			PLpgSQL_stmt_select;
 
 
+typedef struct
+{								/* OPEN a curvar					*/
+	int			cmd_type;
+	int			lineno;
+	int			curvar;
+	PLpgSQL_row	*returntype;
+	PLpgSQL_expr *argquery;
+	PLpgSQL_expr *query;
+	PLpgSQL_expr *dynquery;
+}			PLpgSQL_stmt_open;
+
+
+typedef struct
+{								/* FETCH curvar INTO statement		*/
+	int			cmd_type;
+	int			lineno;
+	PLpgSQL_rec *rec;
+	PLpgSQL_row *row;
+	int			curvar;
+}			PLpgSQL_stmt_fetch;
+
+
+typedef struct
+{								/* CLOSE curvar						*/
+	int			cmd_type;
+	int			lineno;
+	int			curvar;
+}			PLpgSQL_stmt_close;
+
+
 typedef struct
 {								/* EXIT statement			*/
 	int			cmd_type;
diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l
index 65c5c75faaa..08f9fb9d06f 100644
--- a/src/pl/plpgsql/src/scan.l
+++ b/src/pl/plpgsql/src/scan.l
@@ -4,7 +4,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.11 2001/05/18 21:16:59 wieck Exp $
+ *    $Header: /cvsroot/pgsql/src/pl/plpgsql/src/Attic/scan.l,v 1.12 2001/05/21 14:22:19 wieck Exp $
  *
  *    This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -93,7 +93,9 @@ alias			{ return K_ALIAS;			}
 begin			{ return K_BEGIN;			}
 bpchar			{ return T_BPCHAR;			}
 char			{ return T_CHAR;			}
+close			{ return K_CLOSE;			}
 constant		{ return K_CONSTANT;		}
+cursor			{ return K_CURSOR;			}
 debug			{ return K_DEBUG;			}
 declare			{ return K_DECLARE;			}
 default			{ return K_DEFAULT;			}
@@ -104,16 +106,19 @@ end				{ return K_END;				}
 exception		{ return K_EXCEPTION;		}
 execute			{ return K_EXECUTE;			}
 exit			{ return K_EXIT;			}
+fetch			{ return K_FETCH;			}
 for				{ return K_FOR;				}
 from			{ return K_FROM;			}
 get				{ return K_GET;				}
 if				{ return K_IF;				}
 in				{ return K_IN;				}
 into			{ return K_INTO;			}
+is				{ return K_IS;				}
 loop			{ return K_LOOP;			}
 not				{ return K_NOT;				}
 notice			{ return K_NOTICE;			}
 null			{ return K_NULL;			}
+open			{ return K_OPEN;			}
 perform			{ return K_PERFORM;			}
 raise			{ return K_RAISE;			}
 record			{ return K_RECORD;			}
-- 
GitLab