portalcmds.c 12.3 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
 *
12
 * Portions Copyright (c) 1996-2005, 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.42 2005/06/03 23:05: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 67 68 69 70 71 72
	/*
	 * Because the planner is not cool about not scribbling on its input,
	 * we make a preliminary copy of the source querytree.  This prevents
	 * problems in the case that the DECLARE CURSOR is in a portal and is
	 * executed repeatedly.  XXX the planner really shouldn't modify its
	 * input ... FIXME someday.
	 */
	query = copyObject(stmt->query);

73
	/*
74 75
	 * 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
76 77
	 * query, so we are not expecting rule rewriting to do anything
	 * strange.
78
	 */
79
	AcquireRewriteLocks(query);
80
	rewritten = QueryRewrite(query);
81
	if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
82
		elog(ERROR, "unexpected rewrite result");
83
	query = (Query *) linitial(rewritten);
84
	if (query->commandType != CMD_SELECT)
85
		elog(ERROR, "unexpected rewrite result");
86 87

	if (query->into)
88 89 90
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("DECLARE CURSOR may not specify INTO")));
91
	if (query->rowMarks != NIL)
92 93
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
94
				 errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"),
95
				 errdetail("Cursors must be READ ONLY.")));
96

97
	plan = planner(query, true, stmt->options, NULL);
98

99
	/*
Bruce Momjian's avatar
Bruce Momjian committed
100
	 * Create a portal and copy the query and plan into its memory
101
	 * context.
102
	 */
103
	portal = CreatePortal(stmt->portalname, false, false);
104 105

	oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
106

107 108
	query = copyObject(query);
	plan = copyObject(plan);
109

110 111
	PortalDefineQuery(portal,
					  NULL,		/* unfortunately don't have sourceText */
Bruce Momjian's avatar
Bruce Momjian committed
112
					  "SELECT", /* cursor's query is always a SELECT */
113 114
					  list_make1(query),
					  list_make1(plan),
115 116
					  PortalGetHeapMemory(portal));

117 118
	/*
	 * Also copy the outer portal's parameter list into the inner portal's
Bruce Momjian's avatar
Bruce Momjian committed
119 120 121
	 * 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
122 123 124 125 126
	 * parameter value needs to be preserved for use when the cursor is
	 * executed.
	 */
	params = copyParamList(params);

127 128
	MemoryContextSwitchTo(oldContext);

129
	/*
130 131
	 * Set up options for portal.
	 *
Bruce Momjian's avatar
Bruce Momjian committed
132 133 134
	 * 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.
135
	 */
136 137 138 139 140 141 142 143
	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;
	}
144 145

	/*
146
	 * Start execution, inserting parameters if any.
147
	 */
148
	PortalStart(portal, params, ActiveSnapshot);
149

150
	Assert(portal->strategy == PORTAL_ONE_SELECT);
151 152

	/*
Bruce Momjian's avatar
Bruce Momjian committed
153 154
	 * We're done; the query won't actually be run until
	 * PerformPortalFetch is called.
155 156
	 */
}
157 158 159

/*
 * PerformPortalFetch
160
 *		Execute SQL FETCH or MOVE command.
161
 *
162
 *	stmt: parsetree node for command
163 164 165 166 167 168 169
 *	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
170
PerformPortalFetch(FetchStmt *stmt,
171
				   DestReceiver *dest,
172 173 174
				   char *completionTag)
{
	Portal		portal;
175
	long		nprocessed;
176

177 178 179 180 181
	/*
	 * Disallow empty-string cursor name (conflicts with protocol-level
	 * unnamed portal).
	 */
	if (!stmt->portalname || stmt->portalname[0] == '\0')
182 183 184
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_NAME),
				 errmsg("invalid cursor name: must not be empty")));
185

186
	/* get the portal from the portal name */
187
	portal = GetPortalByName(stmt->portalname);
188 189
	if (!PortalIsValid(portal))
	{
190
		ereport(ERROR,
191
				(errcode(ERRCODE_UNDEFINED_CURSOR),
Bruce Momjian's avatar
Bruce Momjian committed
192 193
			  errmsg("cursor \"%s\" does not exist", stmt->portalname)));
		return;					/* keep compiler happy */
194 195
	}

196
	/* Adjust dest if needed.  MOVE wants destination None */
197
	if (stmt->ismove)
198
		dest = None_Receiver;
199

200
	/* Do it */
201 202 203
	nprocessed = PortalRunFetch(portal,
								stmt->direction,
								stmt->howMany,
204
								dest);
205 206 207 208

	/* Return command status if wanted */
	if (completionTag)
		snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s %ld",
209
				 stmt->ismove ? "MOVE" : "FETCH",
210 211 212
				 nprocessed);
}

213 214
/*
 * PerformPortalClose
215
 *		Close a cursor.
216 217
 */
void
218
PerformPortalClose(const char *name)
219 220 221
{
	Portal		portal;

222 223 224 225 226
	/*
	 * Disallow empty-string cursor name (conflicts with protocol-level
	 * unnamed portal).
	 */
	if (!name || name[0] == '\0')
227 228 229
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_CURSOR_NAME),
				 errmsg("invalid cursor name: must not be empty")));
230

231 232 233 234 235 236
	/*
	 * get the portal from the portal name
	 */
	portal = GetPortalByName(name);
	if (!PortalIsValid(portal))
	{
237
		ereport(ERROR,
238
				(errcode(ERRCODE_UNDEFINED_CURSOR),
239
				 errmsg("cursor \"%s\" does not exist", name)));
Bruce Momjian's avatar
Bruce Momjian committed
240
		return;					/* keep compiler happy */
241 242 243 244 245
	}

	/*
	 * Note: PortalCleanup is called as a side-effect
	 */
246
	PortalDrop(portal, false);
247
}
248 249 250 251

/*
 * PortalCleanup
 *
252 253
 * Clean up a portal when it's dropped.  This is the standard cleanup hook
 * for portals.
254 255
 */
void
256
PortalCleanup(Portal portal)
257
{
258 259
	QueryDesc  *queryDesc;

260 261 262 263 264 265
	/*
	 * sanity checks
	 */
	AssertArg(PortalIsValid(portal));
	AssertArg(portal->cleanup == PortalCleanup);

266 267 268
	/*
	 * 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
269 270
	 * resources, and we can't be sure that ExecutorEnd itself wouldn't
	 * fail.
271
	 */
272 273
	queryDesc = PortalGetQueryDesc(portal);
	if (queryDesc)
274
	{
275
		portal->queryDesc = NULL;
276 277 278 279 280 281
		if (portal->status != PORTAL_FAILED)
		{
			ResourceOwner saveResourceOwner;

			/* We must make the portal's resource owner current */
			saveResourceOwner = CurrentResourceOwner;
282 283 284
			PG_TRY();
			{
				CurrentResourceOwner = portal->resowner;
285
				/* we do not need AfterTriggerEndQuery() here */
286
				ExecutorEnd(queryDesc);
287 288 289 290 291 292 293 294
			}
			PG_CATCH();
			{
				/* Ensure CurrentResourceOwner is restored on error */
				CurrentResourceOwner = saveResourceOwner;
				PG_RE_THROW();
			}
			PG_END_TRY();
295 296
			CurrentResourceOwner = saveResourceOwner;
		}
297
	}
298 299 300 301 302 303 304 305 306 307 308 309 310
}

/*
 * 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
311
	QueryDesc  *queryDesc = PortalGetQueryDesc(portal);
312
	Portal		saveActivePortal;
313
	Snapshot	saveActiveSnapshot;
314
	ResourceOwner saveResourceOwner;
315 316
	MemoryContext savePortalContext;
	MemoryContext saveQueryContext;
317
	MemoryContext oldcxt;
318 319

	/*
Bruce Momjian's avatar
Bruce Momjian committed
320 321
	 * If we're preserving a holdable portal, we had better be inside the
	 * transaction that originally created it.
322
	 */
323
	Assert(portal->createSubid != InvalidSubTransactionId);
324
	Assert(queryDesc != NULL);
325 326

	/*
327
	 * Caller must have created the tuplestore already.
328
	 */
329
	Assert(portal->holdContext != NULL);
330
	Assert(portal->holdStore != NULL);
331

332
	/*
333 334
	 * Before closing down the executor, we must copy the tupdesc into
	 * long-term memory, since it was created in executor memory.
335
	 */
336 337
	oldcxt = MemoryContextSwitchTo(portal->holdContext);

338 339 340 341 342 343 344
	portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);

	MemoryContextSwitchTo(oldcxt);

	/*
	 * Check for improper portal use, and mark portal active.
	 */
345
	if (portal->status != PORTAL_READY)
346
		ereport(ERROR,
347 348 349
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("portal \"%s\" cannot be run", portal->name)));
	portal->status = PORTAL_ACTIVE;
350 351

	/*
352
	 * Set up global portal context pointers.
353
	 */
354
	saveActivePortal = ActivePortal;
355
	saveActiveSnapshot = ActiveSnapshot;
356
	saveResourceOwner = CurrentResourceOwner;
357 358
	savePortalContext = PortalContext;
	saveQueryContext = QueryContext;
359 360 361
	PG_TRY();
	{
		ActivePortal = portal;
362
		ActiveSnapshot = queryDesc->snapshot;
363 364 365 366 367 368 369
		CurrentResourceOwner = portal->resowner;
		PortalContext = PortalGetHeapMemory(portal);
		QueryContext = portal->queryContext;

		MemoryContextSwitchTo(PortalContext);

		/*
Bruce Momjian's avatar
Bruce Momjian committed
370 371 372
		 * Rewind the executor: we need to store the entire result set in
		 * the tuplestore, so that subsequent backward FETCHs can be
		 * processed.
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
		 */
		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
388
		portal->queryDesc = NULL;		/* prevent double shutdown */
389
		/* we do not need AfterTriggerEndQuery() here */
390
		ExecutorEnd(queryDesc);
391 392 393

		/*
		 * Reset the position in the result set: ideally, this could be
Bruce Momjian's avatar
Bruce Momjian committed
394 395 396 397
		 * 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?
398 399 400 401 402 403
		 */
		MemoryContextSwitchTo(portal->holdContext);

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

405 406
			if (portal->posOverflow)	/* oops, cannot trust portalPos */
				ereport(ERROR,
Bruce Momjian's avatar
Bruce Momjian committed
407 408
					  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
					   errmsg("could not reposition held cursor")));
409

410
			tuplestore_rescan(portal->holdStore);
411

412 413 414 415
			for (store_pos = 0; store_pos < portal->portalPos; store_pos++)
			{
				HeapTuple	tup;
				bool		should_free;
416

417 418
				tup = tuplestore_gettuple(portal->holdStore, true,
										  &should_free);
419

420 421
				if (tup == NULL)
					elog(ERROR, "unexpected end of tuple stream");
422

423 424 425 426 427 428
				if (should_free)
					pfree(tup);
			}
		}
	}
	PG_CATCH();
429
	{
430 431
		/* Uncaught error while executing portal: mark it dead */
		portal->status = PORTAL_FAILED;
432

433 434
		/* Restore global vars and propagate error */
		ActivePortal = saveActivePortal;
435
		ActiveSnapshot = saveActiveSnapshot;
436 437 438
		CurrentResourceOwner = saveResourceOwner;
		PortalContext = savePortalContext;
		QueryContext = saveQueryContext;
439

440
		PG_RE_THROW();
441
	}
442
	PG_END_TRY();
443

444
	MemoryContextSwitchTo(oldcxt);
445

446 447 448 449
	/* Mark portal not active */
	portal->status = PORTAL_READY;

	ActivePortal = saveActivePortal;
450
	ActiveSnapshot = saveActiveSnapshot;
451 452 453
	CurrentResourceOwner = saveResourceOwner;
	PortalContext = savePortalContext;
	QueryContext = saveQueryContext;
454 455 456 457 458 459 460 461

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