portalcmds.c 11.8 KB
Newer Older
1 2 3
/*-------------------------------------------------------------------------
 *
 * portalcmds.c
4 5 6
 *	  Utility commands affecting portals (that is, SQL cursor commands)
 *
 * Note: see also tcop/pquery.c, which implements portal operations for
Bruce Momjian's avatar
Bruce Momjian committed
7
 * the FE/BE protocol.	This module uses pquery.c for some operations.
8 9
 * And both modules depend on utils/mmgr/portalmem.c, which controls
 * storage management for portals (but doesn't run any queries in them).
Bruce Momjian's avatar
Bruce Momjian committed
10
 *
11
 *
Bruce Momjian's avatar
Bruce Momjian committed
12
 * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
13 14 15 16
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
17
 *	  $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.36 2004/09/16 16:58:28 tgl Exp $
18 19 20 21 22 23
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

24 25
#include <limits.h>

26 27
#include "commands/portalcmds.h"
#include "executor/executor.h"
28 29
#include "optimizer/planner.h"
#include "rewrite/rewriteHandler.h"
30
#include "tcop/pquery.h"
31
#include "utils/memutils.h"
32

33

34
/*
35 36
 * PerformCursorOpen
 *		Execute SQL DECLARE CURSOR command.
37 38
 */
void
39
PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
40
{
41 42 43 44 45
	List	   *rewritten;
	Query	   *query;
	Plan	   *plan;
	Portal		portal;
	MemoryContext oldContext;
46 47 48 49 50

	/*
	 * Disallow empty-string cursor name (conflicts with protocol-level
	 * unnamed portal).
	 */
51
	if (!stmt->portalname || stmt->portalname[0] == '\0')
52 53 54
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_NAME),
				 errmsg("invalid cursor name: must not be empty")));
55

56
	/*
57
	 * If this is a non-holdable cursor, we require that this statement
58 59 60 61 62
	 * has been executed inside a transaction block (or else, it would
	 * have no user-visible effect).
	 */
	if (!(stmt->options & CURSOR_OPT_HOLD))
		RequireTransactionChain((void *) stmt, "DECLARE CURSOR");
63

64
	/*
65 66
	 * The query has been through parse analysis, but not rewriting or
	 * planning as yet.  Note that the grammar ensured we have a SELECT
Bruce Momjian's avatar
Bruce Momjian committed
67 68
	 * query, so we are not expecting rule rewriting to do anything
	 * strange.
69
	 */
70
	rewritten = QueryRewrite((Query *) stmt->query);
71
	if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
72
		elog(ERROR, "unexpected rewrite result");
73
	query = (Query *) linitial(rewritten);
74
	if (query->commandType != CMD_SELECT)
75
		elog(ERROR, "unexpected rewrite result");
76 77

	if (query->into)
78 79 80
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("DECLARE CURSOR may not specify INTO")));
81
	if (query->rowMarks != NIL)
82 83 84 85
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("DECLARE CURSOR ... FOR UPDATE is not supported"),
				 errdetail("Cursors must be READ ONLY.")));
86

87
	plan = planner(query, true, stmt->options, NULL);
88

89
	/*
Bruce Momjian's avatar
Bruce Momjian committed
90
	 * Create a portal and copy the query and plan into its memory
91
	 * context.
92
	 */
93
	portal = CreatePortal(stmt->portalname, false, false);
94 95

	oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
96

97 98
	query = copyObject(query);
	plan = copyObject(plan);
99

100 101
	PortalDefineQuery(portal,
					  NULL,		/* unfortunately don't have sourceText */
Bruce Momjian's avatar
Bruce Momjian committed
102
					  "SELECT", /* cursor's query is always a SELECT */
103 104
					  list_make1(query),
					  list_make1(plan),
105 106
					  PortalGetHeapMemory(portal));

107 108
	/*
	 * Also copy the outer portal's parameter list into the inner portal's
Bruce Momjian's avatar
Bruce Momjian committed
109 110 111
	 * memory context.	We want to pass down the parameter values in case
	 * we had a command like DECLARE c CURSOR FOR SELECT ... WHERE foo =
	 * $1 This will have been parsed using the outer parameter set and the
112 113 114 115 116
	 * parameter value needs to be preserved for use when the cursor is
	 * executed.
	 */
	params = copyParamList(params);

117 118
	MemoryContextSwitchTo(oldContext);

119
	/*
120 121
	 * Set up options for portal.
	 *
Bruce Momjian's avatar
Bruce Momjian committed
122 123 124
	 * 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.
125
	 */
126 127 128 129 130 131 132 133
	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;
	}
134 135

	/*
136
	 * Start execution, inserting parameters if any.
137
	 */
138
	PortalStart(portal, params, ActiveSnapshot);
139

140
	Assert(portal->strategy == PORTAL_ONE_SELECT);
141 142

	/*
Bruce Momjian's avatar
Bruce Momjian committed
143 144
	 * We're done; the query won't actually be run until
	 * PerformPortalFetch is called.
145 146
	 */
}
147 148 149

/*
 * PerformPortalFetch
150
 *		Execute SQL FETCH or MOVE command.
151
 *
152
 *	stmt: parsetree node for command
153 154 155 156 157 158 159
 *	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.
 */
void
160
PerformPortalFetch(FetchStmt *stmt,
161
				   DestReceiver *dest,
162 163 164
				   char *completionTag)
{
	Portal		portal;
165
	long		nprocessed;
166

167 168 169 170 171
	/*
	 * Disallow empty-string cursor name (conflicts with protocol-level
	 * unnamed portal).
	 */
	if (!stmt->portalname || stmt->portalname[0] == '\0')
172 173 174
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_NAME),
				 errmsg("invalid cursor name: must not be empty")));
175

176
	/* get the portal from the portal name */
177
	portal = GetPortalByName(stmt->portalname);
178 179
	if (!PortalIsValid(portal))
	{
180
		ereport(ERROR,
181
				(errcode(ERRCODE_UNDEFINED_CURSOR),
Bruce Momjian's avatar
Bruce Momjian committed
182 183
			  errmsg("cursor \"%s\" does not exist", stmt->portalname)));
		return;					/* keep compiler happy */
184 185
	}

186
	/* Adjust dest if needed.  MOVE wants destination None */
187
	if (stmt->ismove)
188
		dest = None_Receiver;
189

190
	/* Do it */
191 192 193
	nprocessed = PortalRunFetch(portal,
								stmt->direction,
								stmt->howMany,
194
								dest);
195 196 197 198

	/* Return command status if wanted */
	if (completionTag)
		snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s %ld",
199
				 stmt->ismove ? "MOVE" : "FETCH",
200 201 202
				 nprocessed);
}

203 204
/*
 * PerformPortalClose
205
 *		Close a cursor.
206 207
 */
void
208
PerformPortalClose(const char *name)
209 210 211
{
	Portal		portal;

212 213 214 215 216
	/*
	 * Disallow empty-string cursor name (conflicts with protocol-level
	 * unnamed portal).
	 */
	if (!name || name[0] == '\0')
217 218 219
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_NAME),
				 errmsg("invalid cursor name: must not be empty")));
220

221 222 223 224 225 226
	/*
	 * get the portal from the portal name
	 */
	portal = GetPortalByName(name);
	if (!PortalIsValid(portal))
	{
227
		ereport(ERROR,
228
				(errcode(ERRCODE_UNDEFINED_CURSOR),
229
				 errmsg("cursor \"%s\" does not exist", name)));
Bruce Momjian's avatar
Bruce Momjian committed
230
		return;					/* keep compiler happy */
231 232 233 234 235
	}

	/*
	 * Note: PortalCleanup is called as a side-effect
	 */
236
	PortalDrop(portal, false);
237
}
238 239 240 241

/*
 * PortalCleanup
 *
242 243
 * Clean up a portal when it's dropped.  This is the standard cleanup hook
 * for portals.
244 245
 */
void
246
PortalCleanup(Portal portal)
247
{
248 249
	QueryDesc  *queryDesc;

250 251 252 253 254 255
	/*
	 * sanity checks
	 */
	AssertArg(PortalIsValid(portal));
	AssertArg(portal->cleanup == PortalCleanup);

256 257 258
	/*
	 * Shut down executor, if still running.  We skip this during error
	 * abort, since other mechanisms will take care of releasing executor
Bruce Momjian's avatar
Bruce Momjian committed
259 260
	 * resources, and we can't be sure that ExecutorEnd itself wouldn't
	 * fail.
261
	 */
262 263
	queryDesc = PortalGetQueryDesc(portal);
	if (queryDesc)
264
	{
265
		portal->queryDesc = NULL;
266 267 268 269 270 271
		if (portal->status != PORTAL_FAILED)
		{
			ResourceOwner saveResourceOwner;

			/* We must make the portal's resource owner current */
			saveResourceOwner = CurrentResourceOwner;
272 273 274 275
			PG_TRY();
			{
				CurrentResourceOwner = portal->resowner;
				ExecutorEnd(queryDesc);
276
				/* we do not need AfterTriggerEndQuery() here */
277 278 279 280 281 282 283 284
			}
			PG_CATCH();
			{
				/* Ensure CurrentResourceOwner is restored on error */
				CurrentResourceOwner = saveResourceOwner;
				PG_RE_THROW();
			}
			PG_END_TRY();
285 286
			CurrentResourceOwner = saveResourceOwner;
		}
287
	}
288 289 290 291 292 293 294 295 296 297 298 299 300
}

/*
 * PersistHoldablePortal
 *
 * Prepare the specified Portal for access outside of the current
 * transaction. When this function returns, all future accesses to the
 * portal must be done via the Tuplestore (not by invoking the
 * executor).
 */
void
PersistHoldablePortal(Portal portal)
{
Bruce Momjian's avatar
Bruce Momjian committed
301
	QueryDesc  *queryDesc = PortalGetQueryDesc(portal);
302
	Portal		saveActivePortal;
303
	ResourceOwner saveResourceOwner;
304 305
	MemoryContext savePortalContext;
	MemoryContext saveQueryContext;
306
	MemoryContext oldcxt;
307 308

	/*
Bruce Momjian's avatar
Bruce Momjian committed
309 310
	 * If we're preserving a holdable portal, we had better be inside the
	 * transaction that originally created it.
311
	 */
312
	Assert(portal->createSubid != InvalidSubTransactionId);
313
	Assert(queryDesc != NULL);
314 315

	/*
316
	 * Caller must have created the tuplestore already.
317
	 */
318
	Assert(portal->holdContext != NULL);
319
	Assert(portal->holdStore != NULL);
320

321
	/*
322 323
	 * Before closing down the executor, we must copy the tupdesc into
	 * long-term memory, since it was created in executor memory.
324
	 */
325 326
	oldcxt = MemoryContextSwitchTo(portal->holdContext);

327 328 329 330 331 332 333
	portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);

	MemoryContextSwitchTo(oldcxt);

	/*
	 * Check for improper portal use, and mark portal active.
	 */
334
	if (portal->status != PORTAL_READY)
335
		ereport(ERROR,
336 337 338
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("portal \"%s\" cannot be run", portal->name)));
	portal->status = PORTAL_ACTIVE;
339 340

	/*
341
	 * Set up global portal context pointers.
342
	 */
343
	saveActivePortal = ActivePortal;
344
	saveResourceOwner = CurrentResourceOwner;
345 346
	savePortalContext = PortalContext;
	saveQueryContext = QueryContext;
347 348 349 350 351 352 353 354 355 356
	PG_TRY();
	{
		ActivePortal = portal;
		CurrentResourceOwner = portal->resowner;
		PortalContext = PortalGetHeapMemory(portal);
		QueryContext = portal->queryContext;

		MemoryContextSwitchTo(PortalContext);

		/*
Bruce Momjian's avatar
Bruce Momjian committed
357 358 359
		 * Rewind the executor: we need to store the entire result set in
		 * the tuplestore, so that subsequent backward FETCHs can be
		 * processed.
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
		 */
		ExecutorRewind(queryDesc);

		/* Change the destination to output to the tuplestore */
		queryDesc->dest = CreateDestReceiver(Tuplestore, portal);

		/* Fetch the result set into the tuplestore */
		ExecutorRun(queryDesc, ForwardScanDirection, 0L);

		(*queryDesc->dest->rDestroy) (queryDesc->dest);
		queryDesc->dest = NULL;

		/*
		 * Now shut down the inner executor.
		 */
Bruce Momjian's avatar
Bruce Momjian committed
375
		portal->queryDesc = NULL;		/* prevent double shutdown */
376
		ExecutorEnd(queryDesc);
377
		/* we do not need AfterTriggerEndQuery() here */
378 379 380

		/*
		 * Reset the position in the result set: ideally, this could be
Bruce Momjian's avatar
Bruce Momjian committed
381 382 383 384
		 * 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.  FIXME someday?
385 386 387 388 389 390
		 */
		MemoryContextSwitchTo(portal->holdContext);

		if (!portal->atEnd)
		{
			long		store_pos;
391

392 393
			if (portal->posOverflow)	/* oops, cannot trust portalPos */
				ereport(ERROR,
Bruce Momjian's avatar
Bruce Momjian committed
394 395
					  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					   errmsg("could not reposition held cursor")));
396

397
			tuplestore_rescan(portal->holdStore);
398

399 400 401 402
			for (store_pos = 0; store_pos < portal->portalPos; store_pos++)
			{
				HeapTuple	tup;
				bool		should_free;
403

404 405
				tup = tuplestore_gettuple(portal->holdStore, true,
										  &should_free);
406

407 408
				if (tup == NULL)
					elog(ERROR, "unexpected end of tuple stream");
409

410 411 412 413 414 415
				if (should_free)
					pfree(tup);
			}
		}
	}
	PG_CATCH();
416
	{
417 418
		/* Uncaught error while executing portal: mark it dead */
		portal->status = PORTAL_FAILED;
419

420 421 422 423 424
		/* Restore global vars and propagate error */
		ActivePortal = saveActivePortal;
		CurrentResourceOwner = saveResourceOwner;
		PortalContext = savePortalContext;
		QueryContext = saveQueryContext;
425

426
		PG_RE_THROW();
427
	}
428
	PG_END_TRY();
429

430
	MemoryContextSwitchTo(oldcxt);
431

432 433 434 435 436 437 438
	/* Mark portal not active */
	portal->status = PORTAL_READY;

	ActivePortal = saveActivePortal;
	CurrentResourceOwner = saveResourceOwner;
	PortalContext = savePortalContext;
	QueryContext = saveQueryContext;
439 440 441 442 443 444 445 446

	/*
	 * 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));
447
}