From 42599b322db75b1cfdf4ab518532ddffd15066ac Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 10 Feb 2005 20:36:28 +0000
Subject: [PATCH] Fix SPI cursor support to allow scanning the results of
 utility commands that return tuples (such as EXPLAIN).  Per gripe from
 Michael Fuhr. Side effect: fix an old bug that unintentionally disabled
 backward scans for all SPI-created cursors.

---
 src/backend/executor/spi.c | 40 +++++++++++++++++++++++++-------------
 src/backend/tcop/pquery.c  | 29 +++++++++++++++++++++++++--
 2 files changed, 54 insertions(+), 15 deletions(-)

diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index a0bfdf12bc..fd860fcb55 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.133 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.134 2005/02/10 20:36:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -832,7 +832,7 @@ SPI_cursor_open(const char *name, void *plan,
 	Portal		portal;
 	int			k;
 
-	/* Ensure that the plan contains only one regular SELECT query */
+	/* Ensure that the plan contains only one query */
 	if (list_length(ptlist) != 1 || list_length(qtlist) != 1)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
@@ -840,14 +840,27 @@ SPI_cursor_open(const char *name, void *plan,
 	queryTree = (Query *) linitial((List *) linitial(qtlist));
 	planTree = (Plan *) linitial(ptlist);
 
-	if (queryTree->commandType != CMD_SELECT)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-				 errmsg("cannot open non-SELECT query as cursor")));
-	if (queryTree->into != NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-				 errmsg("cannot open SELECT INTO query as cursor")));
+	/* Must be a query that returns tuples */
+	switch (queryTree->commandType)
+	{
+		case CMD_SELECT:
+			if (queryTree->into != NULL)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+						 errmsg("cannot open SELECT INTO query as cursor")));
+			break;
+		case CMD_UTILITY:
+			if (!UtilityReturnsTuples(queryTree->utilityStmt))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+						 errmsg("cannot open non-SELECT query as cursor")));
+			break;
+		default:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+					 errmsg("cannot open non-SELECT query as cursor")));
+			break;
+	}
 
 	/* Reset SPI result */
 	SPI_processed = 0;
@@ -911,7 +924,7 @@ SPI_cursor_open(const char *name, void *plan,
 	 */
 	PortalDefineQuery(portal,
 					  NULL,		/* unfortunately don't have sourceText */
-					  "SELECT", /* cursor's query is always a SELECT */
+					  "SELECT", /* nor the raw parse tree... */
 					  list_make1(queryTree),
 					  list_make1(planTree),
 					  PortalGetHeapMemory(portal));
@@ -922,7 +935,7 @@ SPI_cursor_open(const char *name, void *plan,
 	 * Set up options for portal.
 	 */
 	portal->cursorOptions &= ~(CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL);
-	if (ExecSupportsBackwardScan(plan))
+	if (planTree == NULL || ExecSupportsBackwardScan(planTree))
 		portal->cursorOptions |= CURSOR_OPT_SCROLL;
 	else
 		portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
@@ -944,7 +957,8 @@ SPI_cursor_open(const char *name, void *plan,
 	 */
 	PortalStart(portal, paramLI, snapshot);
 
-	Assert(portal->strategy == PORTAL_ONE_SELECT);
+	Assert(portal->strategy == PORTAL_ONE_SELECT ||
+		   portal->strategy == PORTAL_UTIL_SELECT);
 
 	/* Return the created portal */
 	return portal;
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 496cbb43f8..d4d5b94594 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.90 2005/02/01 23:28:40 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.91 2005/02/10 20:36:28 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1120,6 +1120,30 @@ PortalRunFetch(Portal portal,
 				result = DoPortalRunFetch(portal, fdirection, count, dest);
 				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)
+				{
+					DestReceiver *treceiver;
+
+					PortalCreateHoldStore(portal);
+					treceiver = CreateDestReceiver(Tuplestore, portal);
+					PortalRunUtility(portal, linitial(portal->parseTrees),
+									 treceiver, NULL);
+					(*treceiver->rDestroy) (treceiver);
+					portal->portalUtilReady = true;
+				}
+
+				/*
+				 * Now fetch desired portion of results.
+				 */
+				result = DoPortalRunFetch(portal, fdirection, count, dest);
+				break;
+
 			default:
 				elog(ERROR, "unsupported portal strategy");
 				result = 0;		/* keep compiler quiet */
@@ -1170,7 +1194,8 @@ DoPortalRunFetch(Portal portal,
 {
 	bool		forward;
 
-	Assert(portal->strategy == PORTAL_ONE_SELECT);
+	Assert(portal->strategy == PORTAL_ONE_SELECT ||
+		   portal->strategy == PORTAL_UTIL_SELECT);
 
 	switch (fdirection)
 	{
-- 
2.49.0