Portal and memory management infrastructure for extended query protocol.

Both plannable queries and utility commands are now always executed
within Portals, which have been revamped so that they can handle the
load (they used to be good only for single SELECT queries).  Restructure
code to push command-completion-tag selection logic out of postgres.c,
so that it won't have to be duplicated between simple and extended queries.
initdb forced due to addition of a field to Query nodes.
This commit is contained in:
Tom Lane 2003-05-02 20:54:36 +00:00
parent 1940434f1e
commit de28dc9a04
33 changed files with 2210 additions and 1698 deletions

View File

@ -77,7 +77,9 @@ PG_FUNCTION_INFO_V1(int_enum);
/*
* Manage the aggregation state of the array
* You need to specify the correct memory context, or it will vanish!
*
* Need to specify a suitably long-lived memory context, or it will vanish!
* PortalContext isn't really right, but it's close enough.
*/
static PGARRAY *
GetPGArray(int4 state, int fAdd)
@ -89,14 +91,7 @@ GetPGArray(int4 state, int fAdd)
/* New array */
int cb = PGARRAY_SIZE(START_NUM);
p = (PGARRAY *) MemoryContextAlloc(TopTransactionContext, cb);
if (!p)
{
elog(ERROR, "Integer aggregator, cant allocate TopTransactionContext memory");
return 0;
}
p = (PGARRAY *) MemoryContextAlloc(PortalContext, cb);
p->a.size = cb;
p->a.ndim = 0;
p->a.flags = 0;
@ -115,18 +110,6 @@ GetPGArray(int4 state, int fAdd)
int cbNew = PGARRAY_SIZE(n);
pn = (PGARRAY *) repalloc(p, cbNew);
if (!pn)
{ /* Realloc failed! Reallocate new block. */
pn = (PGARRAY *) MemoryContextAlloc(TopTransactionContext, cbNew);
if (!pn)
{
elog(ERROR, "Integer aggregator, REALLY REALLY can't alloc memory");
return (PGARRAY *) NULL;
}
memcpy(pn, p, p->a.size);
pfree(p);
}
pn->a.size = cbNew;
pn->lower = n;
return pn;
@ -149,24 +132,19 @@ ShrinkPGArray(PGARRAY * p)
/* use current transaction context */
pnew = palloc(cb);
if (pnew)
{
/*
* Fix up the fields in the new structure, so Postgres
* understands
*/
memcpy(pnew, p, cb);
pnew->a.size = cb;
pnew->a.ndim = 1;
pnew->a.flags = 0;
/*
* Fix up the fields in the new structure, so Postgres
* understands
*/
memcpy(pnew, p, cb);
pnew->a.size = cb;
pnew->a.ndim = 1;
pnew->a.flags = 0;
#ifndef PG_7_2
pnew->a.elemtype = INT4OID;
pnew->a.elemtype = INT4OID;
#endif
pnew->lower = 0;
}
else
elog(ERROR, "Integer aggregator, can't allocate memory");
pnew->lower = 0;
pfree(p);
}
return pnew;

View File

@ -1,5 +1,5 @@
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/ref/postgres-ref.sgml,v 1.32 2003/03/25 16:15:43 petere Exp $
$Header: /cvsroot/pgsql/doc/src/sgml/ref/postgres-ref.sgml,v 1.33 2003/05/02 20:54:33 tgl Exp $
PostgreSQL documentation
-->
@ -28,7 +28,6 @@ PostgreSQL documentation
<arg>-E</arg>
<arg>-f<group choice="plain"><arg>s</arg><arg>i</arg><arg>t</arg><arg>n</arg><arg>m</arg><arg>h</arg></group></arg>
<arg>-F</arg>
<arg>-i</arg>
<arg>-N</arg>
<arg>-o <replaceable>filename</replaceable></arg>
<arg>-O</arg>
@ -52,7 +51,6 @@ PostgreSQL documentation
<arg>-e</arg>
<arg>-f<group choice="plain"><arg>s</arg><arg>i</arg><arg>t</arg><arg>n</arg><arg>m</arg><arg>h</arg></group></arg>
<arg>-F</arg>
<arg>-i</arg>
<arg>-o <replaceable>filename</replaceable></arg>
<arg>-O</arg>
<arg>-p <replaceable>database</replaceable></arg>
@ -281,15 +279,6 @@ PostgreSQL documentation
</listitem>
</varlistentry>
<varlistentry>
<term><option>-i</option></term>
<listitem>
<para>
Prevents query execution, but shows the plan tree.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-O</option></term>
<listitem>

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.146 2003/04/26 20:22:59 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.147 2003/05/02 20:54:33 tgl Exp $
*
* NOTES
* Transaction aborts can now occur two ways:
@ -49,7 +49,7 @@
* * CleanupTransaction() executes when we finally see a user COMMIT
* or ROLLBACK command; it cleans things up and gets us out of
* the transaction internally. In particular, we mustn't destroy
* TransactionCommandContext until this point.
* TopTransactionContext until this point.
*
* NOTES
* The essential aspects of the transaction system are:
@ -456,13 +456,12 @@ static void
AtStart_Memory(void)
{
/*
* We shouldn't have any transaction contexts already.
* We shouldn't have a transaction context already.
*/
Assert(TopTransactionContext == NULL);
Assert(TransactionCommandContext == NULL);
/*
* Create a toplevel context for the transaction.
* Create a toplevel context for the transaction, and make it active.
*/
TopTransactionContext =
AllocSetContextCreate(TopMemoryContext,
@ -471,16 +470,7 @@ AtStart_Memory(void)
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
* Create a statement-level context and make it active.
*/
TransactionCommandContext =
AllocSetContextCreate(TopTransactionContext,
"TransactionCommandContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
MemoryContextSwitchTo(TransactionCommandContext);
MemoryContextSwitchTo(TopTransactionContext);
}
@ -659,7 +649,6 @@ AtCommit_Memory(void)
Assert(TopTransactionContext != NULL);
MemoryContextDelete(TopTransactionContext);
TopTransactionContext = NULL;
TransactionCommandContext = NULL;
}
/* ----------------------------------------------------------------
@ -769,19 +758,18 @@ AtAbort_Memory(void)
{
/*
* Make sure we are in a valid context (not a child of
* TransactionCommandContext...). Note that it is possible for this
* TopTransactionContext...). Note that it is possible for this
* code to be called when we aren't in a transaction at all; go
* directly to TopMemoryContext in that case.
*/
if (TransactionCommandContext != NULL)
if (TopTransactionContext != NULL)
{
MemoryContextSwitchTo(TransactionCommandContext);
MemoryContextSwitchTo(TopTransactionContext);
/*
* We do not want to destroy transaction contexts yet, but it
* should be OK to delete any command-local memory.
* We do not want to destroy the transaction's global state yet,
* so we can't free any memory here.
*/
MemoryContextResetAndDeleteChildren(TransactionCommandContext);
}
else
MemoryContextSwitchTo(TopMemoryContext);
@ -812,7 +800,6 @@ AtCleanup_Memory(void)
if (TopTransactionContext != NULL)
MemoryContextDelete(TopTransactionContext);
TopTransactionContext = NULL;
TransactionCommandContext = NULL;
}
@ -926,7 +913,7 @@ CommitTransaction(void)
* access, and in fact could still cause an error...)
*/
AtEOXact_portals(true);
AtCommit_Portals();
/* handle commit for large objects [ PA, 7/17/98 ] */
/* XXX probably this does not belong here */
@ -1057,7 +1044,7 @@ AbortTransaction(void)
* do abort processing
*/
DeferredTriggerAbortXact();
AtEOXact_portals(false);
AtAbort_Portals();
lo_commit(false); /* 'false' means it's abort */
AtAbort_Notify();
AtEOXact_UpdatePasswordFile(false);
@ -1126,7 +1113,8 @@ CleanupTransaction(void)
/*
* do abort cleanup processing
*/
AtCleanup_Memory();
AtCleanup_Portals(); /* now safe to release portal memory */
AtCleanup_Memory(); /* and transaction memory */
/*
* done with abort processing, set current transaction state back to
@ -1220,11 +1208,11 @@ StartTransactionCommand(bool preventChain)
}
/*
* We must switch to TransactionCommandContext before returning. This
* We must switch to TopTransactionContext before returning. This
* is already done if we called StartTransaction, otherwise not.
*/
Assert(TransactionCommandContext != NULL);
MemoryContextSwitchTo(TransactionCommandContext);
Assert(TopTransactionContext != NULL);
MemoryContextSwitchTo(TopTransactionContext);
}
/*
@ -1266,7 +1254,6 @@ CommitTransactionCommand(bool forceCommit)
Assert(s->blockState == TBLOCK_INPROGRESS);
/* This code must match the TBLOCK_INPROGRESS case below: */
CommandCounterIncrement();
MemoryContextResetAndDeleteChildren(TransactionCommandContext);
}
break;
@ -1283,15 +1270,10 @@ CommitTransactionCommand(bool forceCommit)
/*
* This is the case when we have finished executing a command
* someplace within a transaction block. We increment the
* command counter and return. Someday we may free resources
* local to the command.
*
* That someday is today, at least for memory allocated in
* TransactionCommandContext. - vadim 03/25/97
* command counter and return.
*/
case TBLOCK_INPROGRESS:
CommandCounterIncrement();
MemoryContextResetAndDeleteChildren(TransactionCommandContext);
break;
/*

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.107 2003/03/20 03:34:55 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.108 2003/05/02 20:54:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -189,10 +189,10 @@ cluster(ClusterStmt *stmt)
/*
* Create special memory context for cross-transaction storage.
*
* Since it is a child of QueryContext, it will go away even in case
* Since it is a child of PortalContext, it will go away even in case
* of error.
*/
cluster_context = AllocSetContextCreate(QueryContext,
cluster_context = AllocSetContextCreate(PortalContext,
"Cluster",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.97 2003/01/23 15:18:40 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.98 2003/05/02 20:54:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -720,11 +720,11 @@ ReindexDatabase(const char *dbname, bool force, bool all)
/*
* Create a memory context that will survive forced transaction
* commits we do below. Since it is a child of QueryContext, it will
* commits we do below. Since it is a child of PortalContext, it will
* go away eventually even if we suffer an error; there's no need for
* special abort cleanup logic.
*/
private_context = AllocSetContextCreate(QueryContext,
private_context = AllocSetContextCreate(PortalContext,
"ReindexDatabase",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,

View File

@ -1,14 +1,20 @@
/*-------------------------------------------------------------------------
*
* portalcmds.c
* portal support code
* Utility commands affecting portals (that is, SQL cursor commands)
*
* Note: see also tcop/pquery.c, which implements portal operations for
* the FE/BE protocol. This module uses pquery.c for some operations.
* And both modules depend on utils/mmgr/portalmem.c, which controls
* storage management for portals (but doesn't run any queries in them).
*
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.12 2003/04/29 03:21:29 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.13 2003/05/02 20:54:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,18 +28,10 @@
#include "executor/executor.h"
#include "optimizer/planner.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/pquery.h"
#include "utils/memutils.h"
static long DoRelativeFetch(Portal portal,
bool forward,
long count,
CommandDest dest);
static uint32 RunFromStore(Portal portal, ScanDirection direction, long count);
static void DoPortalRewind(Portal portal);
static Portal PreparePortal(DeclareCursorStmt *stmt);
/*
* PerformCursorOpen
* Execute SQL DECLARE CURSOR command.
@ -46,8 +44,13 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
Plan *plan;
Portal portal;
MemoryContext oldContext;
char *cursorName;
QueryDesc *queryDesc;
/*
* Disallow empty-string cursor name (conflicts with protocol-level
* unnamed portal).
*/
if (strlen(stmt->portalname) == 0)
elog(ERROR, "Invalid cursor name: must not be empty");
/*
* If this is a non-holdable cursor, we require that this statement
@ -77,38 +80,53 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest)
plan = planner(query, true, stmt->options);
/* If binary cursor, switch to alternate output format */
if ((stmt->options & CURSOR_OPT_BINARY) && dest == Remote)
dest = RemoteInternal;
/*
* Create a portal and copy the query and plan into its memory context.
* (If a duplicate cursor name already exists, warn and drop it.)
*/
portal = PreparePortal(stmt);
portal = CreatePortal(stmt->portalname, true, false);
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
query = copyObject(query);
plan = copyObject(plan);
/*
* Create the QueryDesc object in the portal context, too.
*/
cursorName = pstrdup(stmt->portalname);
queryDesc = CreateQueryDesc(query, plan, dest, cursorName, NULL, false);
PortalDefineQuery(portal,
NULL, /* unfortunately don't have sourceText */
"SELECT", /* cursor's query is always a SELECT */
makeList1(query),
makeList1(plan),
PortalGetHeapMemory(portal));
MemoryContextSwitchTo(oldContext);
/*
* call ExecStart to prepare the plan for execution
* Set up options for portal.
*
* If the user didn't specify a SCROLL type, allow or disallow
* scrolling based on whether it would require any additional
* runtime overhead to do so.
*/
ExecutorStart(queryDesc);
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;
}
/* Arrange to shut down the executor if portal is dropped */
PortalSetQuery(portal, queryDesc);
/*
* Start execution --- never any params for a cursor.
*/
PortalStart(portal, NULL);
Assert(portal->strategy == PORTAL_ONE_SELECT);
/*
* We're done; the query won't actually be run until PerformPortalFetch
* is called.
*/
MemoryContextSwitchTo(oldContext);
}
/*
@ -130,10 +148,6 @@ PerformPortalFetch(FetchStmt *stmt,
Portal portal;
long nprocessed;
/* initialize completion status in case of early exit */
if (completionTag)
strcpy(completionTag, stmt->ismove ? "MOVE 0" : "FETCH 0");
/* get the portal from the portal name */
portal = GetPortalByName(stmt->portalname);
if (!PortalIsValid(portal))
@ -141,14 +155,27 @@ PerformPortalFetch(FetchStmt *stmt,
/* FIXME: shouldn't this be an ERROR? */
elog(WARNING, "PerformPortalFetch: portal \"%s\" not found",
stmt->portalname);
if (completionTag)
strcpy(completionTag, stmt->ismove ? "MOVE 0" : "FETCH 0");
return;
}
/*
* Adjust dest if needed. MOVE wants dest = None.
*
* If fetching from a binary cursor and the requested destination is
* Remote, change it to RemoteInternal.
*/
if (stmt->ismove)
dest = None;
else if (dest == Remote && (portal->cursorOptions & CURSOR_OPT_BINARY))
dest = RemoteInternal;
/* Do it */
nprocessed = DoPortalFetch(portal,
stmt->direction,
stmt->howMany,
stmt->ismove ? None : dest);
nprocessed = PortalRunFetch(portal,
stmt->direction,
stmt->howMany,
dest);
/* Return command status if wanted */
if (completionTag)
@ -157,416 +184,6 @@ PerformPortalFetch(FetchStmt *stmt,
nprocessed);
}
/*
* DoPortalFetch
* Guts of PerformPortalFetch --- shared with SPI cursor operations.
* Caller must already have validated the Portal.
*
* Returns number of rows processed (suitable for use in result tag)
*/
long
DoPortalFetch(Portal portal,
FetchDirection fdirection,
long count,
CommandDest dest)
{
bool forward;
switch (fdirection)
{
case FETCH_FORWARD:
if (count < 0)
{
fdirection = FETCH_BACKWARD;
count = -count;
}
/* fall out of switch to share code with FETCH_BACKWARD */
break;
case FETCH_BACKWARD:
if (count < 0)
{
fdirection = FETCH_FORWARD;
count = -count;
}
/* fall out of switch to share code with FETCH_FORWARD */
break;
case FETCH_ABSOLUTE:
if (count > 0)
{
/*
* Definition: Rewind to start, advance count-1 rows, return
* next row (if any). In practice, if the goal is less than
* halfway back to the start, it's better to scan from where
* we are. In any case, we arrange to fetch the target row
* going forwards.
*/
if (portal->posOverflow || portal->portalPos == LONG_MAX ||
count-1 <= portal->portalPos / 2)
{
DoPortalRewind(portal);
if (count > 1)
DoRelativeFetch(portal, true, count-1, None);
}
else
{
long pos = portal->portalPos;
if (portal->atEnd)
pos++; /* need one extra fetch if off end */
if (count <= pos)
DoRelativeFetch(portal, false, pos-count+1, None);
else if (count > pos+1)
DoRelativeFetch(portal, true, count-pos-1, None);
}
return DoRelativeFetch(portal, true, 1L, dest);
}
else if (count < 0)
{
/*
* Definition: Advance to end, back up abs(count)-1 rows,
* return prior row (if any). We could optimize this if we
* knew in advance where the end was, but typically we won't.
* (Is it worth considering case where count > half of size
* of query? We could rewind once we know the size ...)
*/
DoRelativeFetch(portal, true, FETCH_ALL, None);
if (count < -1)
DoRelativeFetch(portal, false, -count-1, None);
return DoRelativeFetch(portal, false, 1L, dest);
}
else /* count == 0 */
{
/* Rewind to start, return zero rows */
DoPortalRewind(portal);
return DoRelativeFetch(portal, true, 0L, dest);
}
break;
case FETCH_RELATIVE:
if (count > 0)
{
/*
* Definition: advance count-1 rows, return next row (if any).
*/
if (count > 1)
DoRelativeFetch(portal, true, count-1, None);
return DoRelativeFetch(portal, true, 1L, dest);
}
else if (count < 0)
{
/*
* Definition: back up abs(count)-1 rows, return prior row
* (if any).
*/
if (count < -1)
DoRelativeFetch(portal, false, -count-1, None);
return DoRelativeFetch(portal, false, 1L, dest);
}
else /* count == 0 */
{
/* Same as FETCH FORWARD 0, so fall out of switch */
fdirection = FETCH_FORWARD;
}
break;
default:
elog(ERROR, "DoPortalFetch: bogus direction");
break;
}
/*
* Get here with fdirection == FETCH_FORWARD or FETCH_BACKWARD,
* and count >= 0.
*/
forward = (fdirection == FETCH_FORWARD);
/*
* Zero count means to re-fetch the current row, if any (per SQL92)
*/
if (count == 0)
{
bool on_row;
/* Are we sitting on a row? */
on_row = (!portal->atStart && !portal->atEnd);
if (dest == None)
{
/* MOVE 0 returns 0/1 based on if FETCH 0 would return a row */
return on_row ? 1L : 0L;
}
else
{
/*
* If we are sitting on a row, back up one so we can re-fetch it.
* If we are not sitting on a row, we still have to start up and
* shut down the executor so that the destination is initialized
* and shut down correctly; so keep going. To DoRelativeFetch,
* count == 0 means we will retrieve no row.
*/
if (on_row)
{
DoRelativeFetch(portal, false, 1L, None);
/* Set up to fetch one row forward */
count = 1;
forward = true;
}
}
}
/*
* Optimize MOVE BACKWARD ALL into a Rewind.
*/
if (!forward && count == FETCH_ALL && dest == None)
{
long result = portal->portalPos;
if (result > 0 && !portal->atEnd)
result--;
DoPortalRewind(portal);
/* result is bogus if pos had overflowed, but it's best we can do */
return result;
}
return DoRelativeFetch(portal, forward, count, dest);
}
/*
* DoRelativeFetch
* Do fetch for a simple N-rows-forward-or-backward case.
*
* count <= 0 is interpreted as a no-op: the destination gets started up
* and shut down, but nothing else happens. Also, count == FETCH_ALL is
* interpreted as "all rows".
*
* Caller must already have validated the Portal.
*
* Returns number of rows processed (suitable for use in result tag)
*/
static long
DoRelativeFetch(Portal portal,
bool forward,
long count,
CommandDest dest)
{
QueryDesc *queryDesc;
QueryDesc temp_queryDesc;
ScanDirection direction;
uint32 nprocessed;
queryDesc = PortalGetQueryDesc(portal);
/*
* If the requested destination is not the same as the query's
* original destination, make a temporary QueryDesc with the proper
* destination. This supports MOVE, for example, which will pass in
* dest = None.
*
* EXCEPTION: if the query's original dest is RemoteInternal (ie, it's a
* binary cursor) and the request is Remote, we do NOT override the
* original dest. This is necessary since a FETCH command will pass
* dest = Remote, not knowing whether the cursor is binary or not.
*/
if (dest != queryDesc->dest &&
!(queryDesc->dest == RemoteInternal && dest == Remote))
{
memcpy(&temp_queryDesc, queryDesc, sizeof(QueryDesc));
temp_queryDesc.dest = dest;
queryDesc = &temp_queryDesc;
}
/*
* Determine which direction to go in, and check to see if we're
* already at the end of the available tuples in that direction. If
* so, set the direction to NoMovement to avoid trying to fetch any
* tuples. (This check exists because not all plan node types are
* robust about being called again if they've already returned NULL
* once.) Then call the executor (we must not skip this, because the
* destination needs to see a setup and shutdown even if no tuples are
* available). Finally, update the portal position state depending on
* the number of tuples that were retrieved.
*/
if (forward)
{
if (portal->atEnd || count <= 0)
direction = NoMovementScanDirection;
else
direction = ForwardScanDirection;
/* In the executor, zero count processes all rows */
if (count == FETCH_ALL)
count = 0;
if (portal->holdStore)
nprocessed = RunFromStore(portal, direction, count);
else
{
Assert(portal->executorRunning);
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
if (direction != NoMovementScanDirection)
{
long oldPos;
if (nprocessed > 0)
portal->atStart = false; /* OK to go backward now */
if (count == 0 ||
(unsigned long) nprocessed < (unsigned long) count)
portal->atEnd = true; /* we retrieved 'em all */
oldPos = portal->portalPos;
portal->portalPos += nprocessed;
/* portalPos doesn't advance when we fall off the end */
if (portal->portalPos < oldPos)
portal->posOverflow = true;
}
}
else
{
if (portal->scrollType == DISABLE_SCROLL)
elog(ERROR, "Cursor can only scan forward"
"\n\tDeclare it with SCROLL option to enable backward scan");
if (portal->atStart || count <= 0)
direction = NoMovementScanDirection;
else
direction = BackwardScanDirection;
/* In the executor, zero count processes all rows */
if (count == FETCH_ALL)
count = 0;
if (portal->holdStore)
nprocessed = RunFromStore(portal, direction, count);
else
{
Assert(portal->executorRunning);
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
if (direction != NoMovementScanDirection)
{
if (nprocessed > 0 && portal->atEnd)
{
portal->atEnd = false; /* OK to go forward now */
portal->portalPos++; /* adjust for endpoint case */
}
if (count == 0 ||
(unsigned long) nprocessed < (unsigned long) count)
{
portal->atStart = true; /* we retrieved 'em all */
portal->portalPos = 0;
portal->posOverflow = false;
}
else
{
long oldPos;
oldPos = portal->portalPos;
portal->portalPos -= nprocessed;
if (portal->portalPos > oldPos ||
portal->portalPos <= 0)
portal->posOverflow = true;
}
}
}
return nprocessed;
}
/*
* RunFromStore
* Fetch tuples from the portal's tuple store.
*
* Calling conventions are similar to ExecutorRun, except that we
* do not depend on having an estate, and therefore return the number
* of tuples processed as the result, not in estate->es_processed.
*
* One difference from ExecutorRun is that the destination receiver functions
* are run in the caller's memory context (since we have no estate). Watch
* out for memory leaks.
*/
static uint32
RunFromStore(Portal portal, ScanDirection direction, long count)
{
QueryDesc *queryDesc = PortalGetQueryDesc(portal);
DestReceiver *destfunc;
long current_tuple_count = 0;
destfunc = DestToFunction(queryDesc->dest);
(*destfunc->setup) (destfunc, queryDesc->operation,
queryDesc->portalName, queryDesc->tupDesc);
if (direction == NoMovementScanDirection)
{
/* do nothing except start/stop the destination */
}
else
{
bool forward = (direction == ForwardScanDirection);
for (;;)
{
MemoryContext oldcontext;
HeapTuple tup;
bool should_free;
oldcontext = MemoryContextSwitchTo(portal->holdContext);
tup = tuplestore_getheaptuple(portal->holdStore, forward,
&should_free);
MemoryContextSwitchTo(oldcontext);
if (tup == NULL)
break;
(*destfunc->receiveTuple) (tup, queryDesc->tupDesc, destfunc);
if (should_free)
pfree(tup);
/*
* check our tuple count.. if we've processed the proper number
* then quit, else loop again and process more tuples. Zero
* count means no limit.
*/
current_tuple_count++;
if (count && count == current_tuple_count)
break;
}
}
(*destfunc->cleanup) (destfunc);
return (uint32) current_tuple_count;
}
/*
* DoPortalRewind - rewind a Portal to starting point
*/
static void
DoPortalRewind(Portal portal)
{
if (portal->holdStore)
{
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(portal->holdContext);
tuplestore_rescan(portal->holdStore);
MemoryContextSwitchTo(oldcontext);
}
if (portal->executorRunning)
{
ExecutorRewind(PortalGetQueryDesc(portal));
}
portal->atStart = true;
portal->atEnd = false;
portal->portalPos = 0;
portal->posOverflow = false;
}
/*
* PerformPortalClose
* Close a cursor.
@ -593,53 +210,6 @@ PerformPortalClose(char *name)
PortalDrop(portal, false);
}
/*
* PreparePortal
* Given a DECLARE CURSOR statement, returns the Portal data
* structure based on that statement that is used to manage the
* Portal internally. If a portal with specified name already
* exists, it is replaced.
*/
static Portal
PreparePortal(DeclareCursorStmt *stmt)
{
Portal portal;
/*
* Check for already-in-use portal name.
*/
portal = GetPortalByName(stmt->portalname);
if (PortalIsValid(portal))
{
/*
* XXX Should we raise an error rather than closing the old
* portal?
*/
elog(WARNING, "Closing pre-existing portal \"%s\"",
stmt->portalname);
PortalDrop(portal, false);
}
/*
* Create the new portal.
*/
portal = CreatePortal(stmt->portalname);
/*
* Modify the newly created portal based on the options specified in
* the DECLARE CURSOR statement.
*/
if (stmt->options & CURSOR_OPT_SCROLL)
portal->scrollType = ENABLE_SCROLL;
else if (stmt->options & CURSOR_OPT_NO_SCROLL)
portal->scrollType = DISABLE_SCROLL;
if (stmt->options & CURSOR_OPT_HOLD)
portal->holdOpen = true;
return portal;
}
/*
* PortalCleanup
*
@ -649,6 +219,8 @@ PreparePortal(DeclareCursorStmt *stmt)
void
PortalCleanup(Portal portal, bool isError)
{
QueryDesc *queryDesc;
/*
* sanity checks
*/
@ -658,7 +230,8 @@ PortalCleanup(Portal portal, bool isError)
/*
* Delete tuplestore if present. (Note: portalmem.c is responsible
* for removing holdContext.) We should do this even under error
* conditions.
* conditions; since the tuplestore would have been using cross-
* transaction storage, its temp files need to be explicitly deleted.
*/
if (portal->holdStore)
{
@ -674,11 +247,12 @@ PortalCleanup(Portal portal, bool isError)
* abort, since other mechanisms will take care of releasing executor
* resources, and we can't be sure that ExecutorEnd itself wouldn't fail.
*/
if (portal->executorRunning)
queryDesc = PortalGetQueryDesc(portal);
if (queryDesc)
{
portal->executorRunning = false;
portal->queryDesc = NULL;
if (!isError)
ExecutorEnd(PortalGetQueryDesc(portal));
ExecutorEnd(queryDesc);
}
}
@ -694,9 +268,10 @@ void
PersistHoldablePortal(Portal portal)
{
QueryDesc *queryDesc = PortalGetQueryDesc(portal);
Portal saveCurrentPortal;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
MemoryContext oldcxt;
CommandDest olddest;
TupleDesc tupdesc;
/*
* If we're preserving a holdable portal, we had better be
@ -704,6 +279,9 @@ PersistHoldablePortal(Portal portal)
*/
Assert(portal->createXact == GetCurrentTransactionId());
Assert(portal->holdStore == NULL);
Assert(queryDesc != NULL);
Assert(portal->portalReady);
Assert(!portal->portalDone);
/*
* This context is used to store the tuple set.
@ -715,6 +293,34 @@ PersistHoldablePortal(Portal portal)
/* XXX: Should SortMem be used for this? */
portal->holdStore = tuplestore_begin_heap(true, true, SortMem);
/*
* Before closing down the executor, we must copy the tupdesc, since
* it was created in executor memory. Note we are copying it into
* the holdContext.
*/
portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
MemoryContextSwitchTo(oldcxt);
/*
* Check for improper portal use, and mark portal active.
*/
if (portal->portalActive)
elog(ERROR, "Portal \"%s\" already active", portal->name);
portal->portalActive = true;
/*
* Set global portal and context pointers.
*/
saveCurrentPortal = CurrentPortal;
CurrentPortal = portal;
savePortalContext = PortalContext;
PortalContext = PortalGetHeapMemory(portal);
saveQueryContext = QueryContext;
QueryContext = portal->queryContext;
MemoryContextSwitchTo(PortalContext);
/*
* Rewind the executor: we need to store the entire result set in
* the tuplestore, so that subsequent backward FETCHs can be
@ -723,40 +329,40 @@ PersistHoldablePortal(Portal portal)
ExecutorRewind(queryDesc);
/* Set the destination to output to the tuplestore */
olddest = queryDesc->dest;
queryDesc->dest = Tuplestore;
/* Fetch the result set into the tuplestore */
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
queryDesc->dest = olddest;
/*
* Before closing down the executor, we must copy the tupdesc, since
* it was created in executor memory.
*/
tupdesc = CreateTupleDescCopy(queryDesc->tupDesc);
/*
* Now shut down the inner executor.
*/
portal->executorRunning = false;
portal->queryDesc = NULL; /* prevent double shutdown */
ExecutorEnd(queryDesc);
/* ExecutorEnd clears this, so must wait to save copied pointer */
queryDesc->tupDesc = tupdesc;
/* Mark portal not active */
portal->portalActive = false;
CurrentPortal = saveCurrentPortal;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
/*
* Reset the position in the result set: ideally, this could be
* implemented by just skipping straight to the tuple # that we need
* to be at, but the tuplestore API doesn't support that. So we
* start at the beginning of the tuplestore and iterate through it
* until we reach where we need to be.
* until we reach where we need to be. FIXME someday?
*/
MemoryContextSwitchTo(portal->holdContext);
if (!portal->atEnd)
{
long store_pos;
if (portal->posOverflow) /* oops, cannot trust portalPos */
elog(ERROR, "Unable to reposition held cursor");
tuplestore_rescan(portal->holdStore);
for (store_pos = 0; store_pos < portal->portalPos; store_pos++)
@ -777,4 +383,12 @@ PersistHoldablePortal(Portal portal)
}
MemoryContextSwitchTo(oldcxt);
/*
* We can now release any subsidiary memory of the portal's heap
* context; we'll never use it again. The executor already dropped
* its context, but this will clean up anything that glommed onto
* the portal's heap via PortalContext.
*/
MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
}

View File

@ -6,7 +6,7 @@
* Copyright (c) 2002, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.13 2003/02/02 23:46:38 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.14 2003/05/02 20:54:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -59,9 +59,8 @@ static ParamListInfo EvaluateParams(EState *estate,
void
PrepareQuery(PrepareStmt *stmt)
{
List *plan_list = NIL;
List *query_list,
*query_list_item;
*plan_list;
if (!stmt->name)
elog(ERROR, "No statement name given");
@ -69,19 +68,18 @@ PrepareQuery(PrepareStmt *stmt)
if (stmt->query->commandType == CMD_UTILITY)
elog(ERROR, "Utility statements cannot be prepared");
/*
* Parse analysis is already done, but we must still rewrite and plan
* the query.
*/
/* Rewrite the query. The result could be 0, 1, or many queries. */
query_list = QueryRewrite(stmt->query);
foreach(query_list_item, query_list)
{
Query *query = (Query *) lfirst(query_list_item);
Plan *plan;
plan = pg_plan_query(query);
plan_list = lappend(plan_list, plan);
}
/* Generate plans for queries. Snapshot is already set. */
plan_list = pg_plan_queries(query_list, false);
/* Save the results. */
StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids);
}
@ -92,17 +90,19 @@ void
ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
{
QueryHashEntry *entry;
List *l,
*query_list,
List *query_list,
*plan_list;
MemoryContext qcontext;
ParamListInfo paramLI = NULL;
EState *estate = NULL;
Portal portal;
/* Look it up in the hash table */
entry = FetchQuery(stmt->name);
query_list = entry->query_list;
plan_list = entry->plan_list;
qcontext = entry->context;
Assert(length(query_list) == length(plan_list));
@ -117,58 +117,54 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest)
paramLI = EvaluateParams(estate, stmt->params, entry->argtype_list);
}
/* Execute each query */
foreach(l, query_list)
/*
* Create a new portal to run the query in
*/
portal = CreateNewPortal();
/*
* For EXECUTE INTO, make a copy of the stored query so that we can
* modify its destination (yech, but INTO has always been ugly).
* For regular EXECUTE we can just use the stored query where it sits,
* since the executor is read-only.
*/
if (stmt->into)
{
Query *query = (Query *) lfirst(l);
Plan *plan = (Plan *) lfirst(plan_list);
bool is_last_query;
MemoryContext oldContext;
Query *query;
plan_list = lnext(plan_list);
is_last_query = (plan_list == NIL);
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
if (query->commandType == CMD_UTILITY)
ProcessUtility(query->utilityStmt, outputDest, NULL);
else
{
QueryDesc *qdesc;
query_list = copyObject(query_list);
plan_list = copyObject(plan_list);
qcontext = PortalGetHeapMemory(portal);
if (log_executor_stats)
ResetUsage();
if (length(query_list) != 1)
elog(ERROR, "INTO clause specified for non-SELECT query");
query = (Query *) lfirst(query_list);
if (query->commandType != CMD_SELECT)
elog(ERROR, "INTO clause specified for non-SELECT query");
query->into = copyObject(stmt->into);
qdesc = CreateQueryDesc(query, plan, outputDest, NULL,
paramLI, false);
if (stmt->into)
{
if (qdesc->operation != CMD_SELECT)
elog(ERROR, "INTO clause specified for non-SELECT query");
query->into = stmt->into;
qdesc->dest = None;
}
ExecutorStart(qdesc);
ExecutorRun(qdesc, ForwardScanDirection, 0L);
ExecutorEnd(qdesc);
FreeQueryDesc(qdesc);
if (log_executor_stats)
ShowUsage("EXECUTOR STATISTICS");
}
/*
* If we're processing multiple queries, we need to increment the
* command counter between them. For the last query, there's no
* need to do this, it's done automatically.
*/
if (!is_last_query)
CommandCounterIncrement();
MemoryContextSwitchTo(oldContext);
}
PortalDefineQuery(portal,
NULL, /* XXX fixme: can we save query text? */
NULL, /* no command tag known either */
query_list,
plan_list,
qcontext);
/*
* Run the portal to completion.
*/
PortalStart(portal, paramLI);
(void) PortalRun(portal, FETCH_ALL, outputDest, outputDest, NULL);
PortalDrop(portal, false);
if (estate)
FreeExecutorState(estate);
@ -377,8 +373,11 @@ DeallocateQuery(DeallocateStmt *stmt)
/* Find the query's hash table entry */
entry = FetchQuery(stmt->name);
/* Flush the context holding the subsidiary data */
/* Drop any open portals that depend on this prepared statement */
Assert(MemoryContextIsValid(entry->context));
DropDependentPortals(entry->context);
/* Flush the context holding the subsidiary data */
MemoryContextDelete(entry->context);
/* Now we can remove the hash table entry */

View File

@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.251 2003/03/04 21:51:20 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.252 2003/05/02 20:54:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -189,11 +189,11 @@ vacuum(VacuumStmt *vacstmt)
/*
* Create special memory context for cross-transaction storage.
*
* Since it is a child of QueryContext, it will go away eventually even
* Since it is a child of PortalContext, it will go away eventually even
* if we suffer an error; there's no need for special abort cleanup
* logic.
*/
vac_context = AllocSetContextCreate(QueryContext,
vac_context = AllocSetContextCreate(PortalContext,
"Vacuum",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
@ -205,7 +205,7 @@ vacuum(VacuumStmt *vacstmt)
* lifetime.
*/
if (vacstmt->analyze && !vacstmt->vacuum)
anl_context = AllocSetContextCreate(QueryContext,
anl_context = AllocSetContextCreate(PortalContext,
"Analyze",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.128 2003/04/08 23:20:00 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.129 2003/05/02 20:54:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -377,7 +377,7 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull)
* XXX this is a horrid crock: since the pointer to the slot might live
* longer than the current evaluation context, we are forced to copy
* the tuple and slot into a long-lived context --- we use
* TransactionCommandContext which should be safe enough. This
* the econtext's per-query memory which should be safe enough. This
* represents a serious memory leak if many such tuples are processed
* in one command, however. We ought to redesign the representation
* of whole-tuple datums so that this is not necessary.
@ -391,7 +391,7 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull)
TupleTableSlot *tempSlot;
HeapTuple tup;
oldContext = MemoryContextSwitchTo(TransactionCommandContext);
oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
tempSlot = MakeTupleTableSlot();
tup = heap_copytuple(heapTuple);
ExecStoreTuple(tup, tempSlot, InvalidBuffer, true);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.93 2003/04/29 22:13:09 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.94 2003/05/02 20:54:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -16,7 +16,6 @@
#include "access/printtup.h"
#include "catalog/heap.h"
#include "commands/portalcmds.h"
#include "executor/spi_priv.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"
@ -91,7 +90,13 @@ SPI_connect(void)
_SPI_current->processed = 0;
_SPI_current->tuptable = NULL;
/* Create memory contexts for this procedure */
/*
* Create memory contexts for this procedure
*
* XXX it would be better to use PortalContext as the parent context,
* but we may not be inside a portal (consider deferred-trigger
* execution).
*/
_SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext,
"SPI Proc",
ALLOCSET_DEFAULT_MINSIZE,
@ -703,18 +708,14 @@ SPI_freetuptable(SPITupleTable *tuptable)
Portal
SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
{
static int unnamed_portal_count = 0;
_SPI_plan *spiplan = (_SPI_plan *) plan;
List *qtlist = spiplan->qtlist;
List *ptlist = spiplan->ptlist;
Query *queryTree;
Plan *planTree;
ParamListInfo paramLI;
QueryDesc *queryDesc;
MemoryContext oldcontext;
Portal portal;
char portalname[64];
int k;
/* Ensure that the plan contains only one regular SELECT query */
@ -737,32 +738,18 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
_SPI_current->processed = 0;
_SPI_current->tuptable = NULL;
if (name == NULL)
/* Create the portal */
if (name == NULL || name[0] == '\0')
{
/* Make up a portal name if none given */
for (;;)
{
unnamed_portal_count++;
if (unnamed_portal_count < 0)
unnamed_portal_count = 0;
sprintf(portalname, "<unnamed cursor %d>", unnamed_portal_count);
if (GetPortalByName(portalname) == NULL)
break;
}
name = portalname;
/* Use a random nonconflicting name */
portal = CreateNewPortal();
}
else
{
/* Ensure the portal doesn't exist already */
portal = GetPortalByName(name);
if (portal != NULL)
elog(ERROR, "cursor \"%s\" already in use", name);
/* In this path, error if portal of same name already exists */
portal = CreatePortal(name, false, false);
}
/* Create the portal */
portal = CreatePortal(name);
/* Switch to portals memory and copy the parsetree and plan to there */
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
queryTree = copyObject(queryTree);
@ -801,19 +788,34 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls)
else
paramLI = NULL;
/* Create the QueryDesc object */
queryDesc = CreateQueryDesc(queryTree, planTree, SPI, pstrdup(name),
paramLI, false);
/*
* Set up the portal.
*/
PortalDefineQuery(portal,
NULL, /* unfortunately don't have sourceText */
"SELECT", /* cursor's query is always a SELECT */
makeList1(queryTree),
makeList1(planTree),
PortalGetHeapMemory(portal));
/* Start the executor */
ExecutorStart(queryDesc);
/* Arrange to shut down the executor if portal is dropped */
PortalSetQuery(portal, queryDesc);
/* Switch back to the callers memory context */
MemoryContextSwitchTo(oldcontext);
/*
* Set up options for portal.
*/
portal->cursorOptions &= ~(CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL);
if (ExecSupportsBackwardScan(plan))
portal->cursorOptions |= CURSOR_OPT_SCROLL;
else
portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
/*
* Start portal execution.
*/
PortalStart(portal, paramLI);
Assert(portal->strategy == PORTAL_ONE_SELECT);
/* Return the created portal */
return portal;
}
@ -1008,39 +1010,15 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
foreach(list_item, raw_parsetree_list)
{
Node *parsetree = (Node *) lfirst(list_item);
CmdType origCmdType;
bool foundOriginalQuery = false;
List *query_list;
List *query_list_item;
switch (nodeTag(parsetree))
{
case T_InsertStmt:
origCmdType = CMD_INSERT;
break;
case T_DeleteStmt:
origCmdType = CMD_DELETE;
break;
case T_UpdateStmt:
origCmdType = CMD_UPDATE;
break;
case T_SelectStmt:
origCmdType = CMD_SELECT;
break;
default:
/* Otherwise, never match commandType */
origCmdType = CMD_UNKNOWN;
break;
}
if (plan)
plan->origCmdType = origCmdType;
query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs);
query_list_list = lappend(query_list_list, query_list);
/* Reset state for each original parsetree */
/* (at most one of its querytrees will be marked canSetTag) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
@ -1050,39 +1028,11 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
{
Query *queryTree = (Query *) lfirst(query_list_item);
Plan *planTree;
bool canSetResult;
QueryDesc *qdesc;
planTree = pg_plan_query(queryTree);
plan_list = lappend(plan_list, planTree);
/*
* This query can set the SPI result if it is the original
* query, or if it is an INSTEAD query of the same kind as the
* original and we haven't yet seen the original query.
*/
if (queryTree->querySource == QSRC_ORIGINAL)
{
canSetResult = true;
foundOriginalQuery = true;
}
else if (!foundOriginalQuery &&
queryTree->commandType == origCmdType &&
(queryTree->querySource == QSRC_INSTEAD_RULE ||
queryTree->querySource == QSRC_QUAL_INSTEAD_RULE))
canSetResult = true;
else
canSetResult = false;
/* Reset state if can set result */
if (canSetResult)
{
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
}
if (queryTree->commandType == CMD_UTILITY)
{
if (IsA(queryTree->utilityStmt, CopyStmt))
@ -1108,9 +1058,10 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
else if (plan == NULL)
{
qdesc = CreateQueryDesc(queryTree, planTree,
canSetResult ? SPI : None,
queryTree->canSetTag ? SPI : None,
NULL, NULL, false);
res = _SPI_pquery(qdesc, true, canSetResult ? tcount : 0);
res = _SPI_pquery(qdesc, true,
queryTree->canSetTag ? tcount : 0);
if (res < 0)
return res;
CommandCounterIncrement();
@ -1118,7 +1069,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
else
{
qdesc = CreateQueryDesc(queryTree, planTree,
canSetResult ? SPI : None,
queryTree->canSetTag ? SPI : None,
NULL, NULL, false);
res = _SPI_pquery(qdesc, false, 0);
if (res < 0)
@ -1145,10 +1096,31 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
List *query_list_list_item;
int nargs = plan->nargs;
int res = 0;
ParamListInfo paramLI;
/* Increment CommandCounter to see changes made by now */
CommandCounterIncrement();
/* Convert parameters to form wanted by executor */
if (nargs > 0)
{
int k;
paramLI = (ParamListInfo)
palloc0((nargs + 1) * sizeof(ParamListInfoData));
for (k = 0; k < nargs; k++)
{
paramLI[k].kind = PARAM_NUM;
paramLI[k].id = k + 1;
paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
paramLI[k].value = Values[k];
}
paramLI[k].kind = PARAM_INVALID;
}
else
paramLI = NULL;
/* Reset state (only needed in case string is empty) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
@ -1159,9 +1131,9 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
{
List *query_list = lfirst(query_list_list_item);
List *query_list_item;
bool foundOriginalQuery = false;
/* Reset state for each original parsetree */
/* (at most one of its querytrees will be marked canSetTag) */
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
@ -1171,72 +1143,24 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
{
Query *queryTree = (Query *) lfirst(query_list_item);
Plan *planTree;
bool canSetResult;
QueryDesc *qdesc;
planTree = lfirst(plan_list);
plan_list = lnext(plan_list);
/*
* This query can set the SPI result if it is the original
* query, or if it is an INSTEAD query of the same kind as the
* original and we haven't yet seen the original query.
*/
if (queryTree->querySource == QSRC_ORIGINAL)
{
canSetResult = true;
foundOriginalQuery = true;
}
else if (!foundOriginalQuery &&
queryTree->commandType == plan->origCmdType &&
(queryTree->querySource == QSRC_INSTEAD_RULE ||
queryTree->querySource == QSRC_QUAL_INSTEAD_RULE))
canSetResult = true;
else
canSetResult = false;
/* Reset state if can set result */
if (canSetResult)
{
SPI_processed = 0;
SPI_lastoid = InvalidOid;
SPI_tuptable = NULL;
_SPI_current->tuptable = NULL;
}
if (queryTree->commandType == CMD_UTILITY)
{
res = SPI_OK_UTILITY;
ProcessUtility(queryTree->utilityStmt, None, NULL);
res = SPI_OK_UTILITY;
CommandCounterIncrement();
}
else
{
ParamListInfo paramLI;
if (nargs > 0)
{
int k;
paramLI = (ParamListInfo)
palloc0((nargs + 1) * sizeof(ParamListInfoData));
for (k = 0; k < plan->nargs; k++)
{
paramLI[k].kind = PARAM_NUM;
paramLI[k].id = k + 1;
paramLI[k].isnull = (Nulls && Nulls[k] == 'n');
paramLI[k].value = Values[k];
}
paramLI[k].kind = PARAM_INVALID;
}
else
paramLI = NULL;
qdesc = CreateQueryDesc(queryTree, planTree,
canSetResult ? SPI : None,
queryTree->canSetTag ? SPI : None,
NULL, paramLI, false);
res = _SPI_pquery(qdesc, true, canSetResult ? tcount : 0);
res = _SPI_pquery(qdesc, true,
queryTree->canSetTag ? tcount : 0);
if (res < 0)
return res;
CommandCounterIncrement();
@ -1346,10 +1270,10 @@ _SPI_cursor_operation(Portal portal, bool forward, int count,
/* Run the cursor */
_SPI_current->processed =
DoPortalFetch(portal,
forward ? FETCH_FORWARD : FETCH_BACKWARD,
(long) count,
dest);
PortalRunFetch(portal,
forward ? FETCH_FORWARD : FETCH_BACKWARD,
(long) count,
dest);
if (dest == SPI && _SPI_checktuples())
elog(FATAL, "SPI_fetch: # of processed tuples check failed");
@ -1467,7 +1391,6 @@ _SPI_copy_plan(_SPI_plan *plan, int location)
}
else
newplan->argtypes = NULL;
newplan->origCmdType = plan->origCmdType;
MemoryContextSwitchTo(oldcxt);

View File

@ -9,7 +9,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/executor/tstoreReceiver.c,v 1.2 2003/04/29 03:21:29 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/executor/tstoreReceiver.c,v 1.3 2003/05/02 20:54:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -43,18 +43,18 @@ tstoreSetupReceiver(DestReceiver *self, int operation,
const char *portalname, TupleDesc typeinfo)
{
TStoreState *myState = (TStoreState *) self;
Portal portal;
if (operation != CMD_SELECT)
elog(ERROR, "Unexpected operation type: %d", operation);
/* Should only be called within a suitably-prepped portal */
if (CurrentPortal == NULL ||
CurrentPortal->holdStore == NULL)
elog(ERROR, "Tuplestore destination used in wrong context");
portal = GetPortalByName(portalname);
/* Debug check: make sure portal's result tuple desc is correct */
Assert(CurrentPortal->tupDesc != NULL);
Assert(equalTupleDescs(CurrentPortal->tupDesc, typeinfo));
if (portal == NULL)
elog(ERROR, "Specified portal does not exist: %s", portalname);
myState->tstore = portal->holdStore;
myState->cxt = portal->holdContext;
myState->tstore = CurrentPortal->holdStore;
myState->cxt = CurrentPortal->holdContext;
}
/*

View File

@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.248 2003/04/08 23:20:01 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.249 2003/05/02 20:54:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1476,6 +1476,7 @@ _copyQuery(Query *from)
COPY_SCALAR_FIELD(commandType);
COPY_SCALAR_FIELD(querySource);
COPY_SCALAR_FIELD(canSetTag);
COPY_NODE_FIELD(utilityStmt);
COPY_SCALAR_FIELD(resultRelation);
COPY_NODE_FIELD(into);

View File

@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.191 2003/04/08 23:20:01 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.192 2003/05/02 20:54:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -566,6 +566,7 @@ _equalQuery(Query *a, Query *b)
{
COMPARE_SCALAR_FIELD(commandType);
COMPARE_SCALAR_FIELD(querySource);
COMPARE_SCALAR_FIELD(canSetTag);
COMPARE_NODE_FIELD(utilityStmt);
COMPARE_SCALAR_FIELD(resultRelation);
COMPARE_NODE_FIELD(into);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.203 2003/04/24 21:16:43 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.204 2003/05/02 20:54:34 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@ -1177,6 +1177,7 @@ _outQuery(StringInfo str, Query *node)
WRITE_ENUM_FIELD(commandType, CmdType);
WRITE_ENUM_FIELD(querySource, QuerySource);
WRITE_BOOL_FIELD(canSetTag);
/*
* Hack to work around missing outfuncs routines for a lot of the

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.151 2003/04/08 23:20:01 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.152 2003/05/02 20:54:34 tgl Exp $
*
* NOTES
* Path and Plan nodes do not have any readfuncs support, because we
@ -195,6 +195,7 @@ _readQuery(void)
READ_ENUM_FIELD(commandType, CmdType);
READ_ENUM_FIELD(querySource, QuerySource);
READ_BOOL_FIELD(canSetTag);
READ_NODE_FIELD(utilityStmt);
READ_INT_FIELD(resultRelation);
READ_NODE_FIELD(into);

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Header: /cvsroot/pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.61 2003/01/20 18:54:49 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.62 2003/05/02 20:54:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -65,10 +65,10 @@ geqo_eval(Query *root, List *initial_rels, Gene *tour, int num_gene)
*
* Since geqo_eval() will be called many times, we can't afford to let
* all that memory go unreclaimed until end of statement. Note we
* make the temp context a child of TransactionCommandContext, so that
* make the temp context a child of the planner's normal context, so that
* it will be freed even if we abort via elog(ERROR).
*/
mycontext = AllocSetContextCreate(TransactionCommandContext,
mycontext = AllocSetContextCreate(CurrentMemoryContext,
"GEQO",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.268 2003/04/29 22:13:09 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.269 2003/05/02 20:54:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -237,13 +237,23 @@ do_parse_analyze(Node *parseTree, ParseState *pstate)
/*
* Make sure that only the original query is marked original. We have
* to do this explicitly since recursive calls of do_parse_analyze will
* have marked some of the added-on queries as "original".
* have marked some of the added-on queries as "original". Also mark
* only the original query as allowed to set the command-result tag.
*/
foreach(listscan, result)
{
Query *q = lfirst(listscan);
q->querySource = (q == query ? QSRC_ORIGINAL : QSRC_PARSER);
if (q == query)
{
q->querySource = QSRC_ORIGINAL;
q->canSetTag = true;
}
else
{
q->querySource = QSRC_PARSER;
q->canSetTag = false;
}
}
return result;
@ -399,6 +409,11 @@ transformStmt(ParseState *pstate, Node *parseTree,
result->utilityStmt = (Node *) parseTree;
break;
}
/* Mark as original query until we learn differently */
result->querySource = QSRC_ORIGINAL;
result->canSetTag = true;
return result;
}

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.119 2003/04/29 22:13:10 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.120 2003/05/02 20:54:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1016,6 +1016,7 @@ fireRules(Query *parsetree,
event_qual, rt_index, event);
rule_action->querySource = qsrc;
rule_action->canSetTag = false; /* might change later */
results = lappend(results, rule_action);
}
@ -1181,6 +1182,9 @@ QueryRewrite(Query *parsetree)
List *querylist;
List *results = NIL;
List *l;
CmdType origCmdType;
bool foundOriginalQuery;
Query *lastInstead;
/*
* Step 1
@ -1235,5 +1239,51 @@ QueryRewrite(Query *parsetree)
results = lappend(results, query);
}
/*
* Step 3
*
* Determine which, if any, of the resulting queries is supposed to set
* the command-result tag; and update the canSetTag fields accordingly.
*
* If the original query is still in the list, it sets the command tag.
* Otherwise, the last INSTEAD query of the same kind as the original
* is allowed to set the tag. (Note these rules can leave us with no
* query setting the tag. The tcop code has to cope with this by
* setting up a default tag based on the original un-rewritten query.)
*
* The Asserts verify that at most one query in the result list is marked
* canSetTag. If we aren't checking asserts, we can fall out of the loop
* as soon as we find the original query.
*/
origCmdType = parsetree->commandType;
foundOriginalQuery = false;
lastInstead = NULL;
foreach(l, results)
{
Query *query = (Query *) lfirst(l);
if (query->querySource == QSRC_ORIGINAL)
{
Assert(query->canSetTag);
Assert(!foundOriginalQuery);
foundOriginalQuery = true;
#ifndef USE_ASSERT_CHECKING
break;
#endif
}
else
{
Assert(!query->canSetTag);
if (query->commandType == origCmdType &&
(query->querySource == QSRC_INSTEAD_RULE ||
query->querySource == QSRC_QUAL_INSTEAD_RULE))
lastInstead = query;
}
}
if (!foundOriginalQuery && lastInstead != NULL)
lastInstead->canSetTag = true;
return results;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.59 2003/04/22 00:08:07 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.60 2003/05/02 20:54:35 tgl Exp $
*
* NOTES
* This cruft is the server side of PQfn.
@ -255,7 +255,7 @@ fetch_fp_info(Oid func_id, struct fp_info * fip)
*
* Note: palloc()s done here and in the called function do not need to be
* cleaned up explicitly. We are called from PostgresMain() in the
* QueryContext memory context, which will be automatically reset when
* MessageContext memory context, which will be automatically reset when
* control returns to PostgresMain.
*/
int

File diff suppressed because it is too large Load Diff

View File

@ -8,15 +8,35 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.59 2003/03/10 03:53:51 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.60 2003/05/02 20:54:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "tcop/tcopprot.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/guc.h"
#include "utils/memutils.h"
static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
CommandDest dest);
static long PortalRunSelect(Portal portal, bool forward, long count,
CommandDest dest);
static void PortalRunUtility(Portal portal, Query *query,
CommandDest dest, char *completionTag);
static void PortalRunMulti(Portal portal,
CommandDest dest, CommandDest altdest,
char *completionTag);
static long DoPortalRunFetch(Portal portal,
FetchDirection fdirection,
long count,
CommandDest dest);
static void DoPortalRewind(Portal portal);
/*
@ -63,19 +83,26 @@ FreeQueryDesc(QueryDesc *qdesc)
/*
* ProcessQuery
* Execute a query
* Execute a single query
*
* parsetree: the query tree
* plan: the plan tree for the query
* params: any parameters needed
* portalName: name of portal being used
* dest: where to send results
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
*
* completionTag may be NULL if caller doesn't want a status string.
*
* Must be called in a memory context that will be reset or deleted on
* error; otherwise the executor's memory usage will be leaked.
*/
void
ProcessQuery(Query *parsetree,
Plan *plan,
ParamListInfo params,
const char *portalName,
CommandDest dest,
char *completionTag)
{
@ -103,7 +130,8 @@ ProcessQuery(Query *parsetree,
/*
* Create the QueryDesc object
*/
queryDesc = CreateQueryDesc(parsetree, plan, dest, NULL, NULL, false);
queryDesc = CreateQueryDesc(parsetree, plan, dest, portalName, params,
false);
/*
* Call ExecStart to prepare the plan for execution
@ -111,7 +139,7 @@ ProcessQuery(Query *parsetree,
ExecutorStart(queryDesc);
/*
* And run the plan.
* Run the plan to completion.
*/
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
@ -156,3 +184,871 @@ ProcessQuery(Query *parsetree,
FreeQueryDesc(queryDesc);
}
/*
* PortalStart
* Prepare a portal for execution.
*
* Caller must already have created the portal, done PortalDefineQuery(),
* and adjusted portal options if needed. If parameters are needed by
* the query, they must be passed in here (caller is responsible for
* giving them appropriate lifetime).
*
* On return, portal is ready to accept PortalRun() calls, and the result
* tupdesc (if any) is known.
*/
void
PortalStart(Portal portal, ParamListInfo params)
{
MemoryContext oldContext;
Query *query = NULL;
QueryDesc *queryDesc;
AssertArg(PortalIsValid(portal));
AssertState(portal->queryContext != NULL); /* query defined? */
AssertState(!portal->portalReady); /* else extra PortalStart */
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
/* Must remember portal param list, if any */
portal->portalParams = params;
/*
* Determine the portal execution strategy (see comments in portal.h)
*/
portal->strategy = PORTAL_MULTI_QUERY; /* default assumption */
if (length(portal->parseTrees) == 1)
{
query = (Query *) lfirst(portal->parseTrees);
if (query->commandType == CMD_SELECT &&
query->canSetTag &&
query->into == NULL)
portal->strategy = PORTAL_ONE_SELECT;
else if (query->commandType == CMD_UTILITY &&
query->canSetTag &&
query->utilityStmt != NULL)
{
/* XXX check for things that can be PORTAL_UTIL_SELECT */
}
}
/*
* Fire her up according to the strategy
*/
switch (portal->strategy)
{
case PORTAL_ONE_SELECT:
/*
* Must set query snapshot before starting executor.
*/
SetQuerySnapshot();
/*
* Create QueryDesc in portal's context; for the moment, set
* the destination to None.
*/
queryDesc = CreateQueryDesc(query,
(Plan *) lfirst(portal->planTrees),
None,
portal->name,
params,
false);
/*
* Call ExecStart to prepare the plan for execution
*/
ExecutorStart(queryDesc);
/*
* This tells PortalCleanup to shut down the executor
*/
portal->queryDesc = queryDesc;
portal->tupDesc = queryDesc->tupDesc;
/*
* Reset cursor position data to "start of query"
*/
portal->atStart = true;
portal->atEnd = false; /* allow fetches */
portal->portalPos = 0;
portal->posOverflow = false;
break;
case PORTAL_UTIL_SELECT:
/* XXX implement later */
/* XXX query snapshot here? no, RunUtility will do it */
/* xxx what about Params? */
portal->tupDesc = NULL;
break;
case PORTAL_MULTI_QUERY:
/* Need do nothing now */
portal->tupDesc = NULL;
break;
}
MemoryContextSwitchTo(oldContext);
portal->portalReady = true;
}
/*
* PortalRun
* Run a portal's query or queries.
*
* count <= 0 is interpreted as a no-op: the destination gets started up
* and shut down, but nothing else happens. Also, count == FETCH_ALL is
* interpreted as "all rows". Note that count is ignored in multi-query
* situations, where we always run the portal to completion.
*
* dest: where to send output of primary (canSetTag) query
*
* altdest: where to send output of non-primary queries
*
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
* May be NULL if caller doesn't want a status string.
*
* Returns TRUE if the portal's execution is complete, FALSE if it was
* suspended due to exhaustion of the count parameter.
*/
bool
PortalRun(Portal portal, long count, CommandDest dest, CommandDest altdest,
char *completionTag)
{
bool result;
Portal saveCurrentPortal;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
MemoryContext oldContext;
AssertArg(PortalIsValid(portal));
AssertState(portal->portalReady); /* else no PortalStart */
/* Initialize completion tag to empty string */
if (completionTag)
completionTag[0] = '\0';
/*
* Check for improper portal use, and mark portal active.
*/
if (portal->portalDone)
elog(ERROR, "Portal \"%s\" cannot be run anymore", portal->name);
if (portal->portalActive)
elog(ERROR, "Portal \"%s\" already active", portal->name);
portal->portalActive = true;
/*
* Set global portal and context pointers.
*/
saveCurrentPortal = CurrentPortal;
CurrentPortal = portal;
savePortalContext = PortalContext;
PortalContext = PortalGetHeapMemory(portal);
saveQueryContext = QueryContext;
QueryContext = portal->queryContext;
oldContext = MemoryContextSwitchTo(PortalContext);
switch (portal->strategy)
{
case PORTAL_ONE_SELECT:
(void) PortalRunSelect(portal, true, count, dest);
/* we know the query is supposed to set the tag */
if (completionTag && portal->commandTag)
strcpy(completionTag, portal->commandTag);
/*
* Since it's a forward fetch, say DONE iff atEnd is now true.
*/
result = portal->atEnd;
break;
case PORTAL_UTIL_SELECT:
/*
* If we have not yet run the utility statement, do so,
* storing its results in the portal's tuplestore.
*/
if (!portal->portalUtilReady)
{
PortalRunUtility(portal, lfirst(portal->parseTrees),
Tuplestore, NULL);
portal->portalUtilReady = true;
}
/*
* Now fetch desired portion of results.
*/
(void) PortalRunSelect(portal, true, count, dest);
/*
* We know the query is supposed to set the tag; we assume
* only the default tag is needed.
*/
if (completionTag && portal->commandTag)
strcpy(completionTag, portal->commandTag);
/*
* Since it's a forward fetch, say DONE iff atEnd is now true.
*/
result = portal->atEnd;
break;
case PORTAL_MULTI_QUERY:
PortalRunMulti(portal, dest, altdest, completionTag);
/* Always complete at end of RunMulti */
result = true;
break;
default:
elog(ERROR, "PortalRun: bogus portal strategy");
result = false; /* keep compiler quiet */
break;
}
MemoryContextSwitchTo(oldContext);
/* Mark portal not active */
portal->portalActive = false;
CurrentPortal = saveCurrentPortal;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
return result;
}
/*
* PortalRunSelect
* Execute a portal's query in SELECT cases (also UTIL_SELECT).
*
* This handles simple N-rows-forward-or-backward cases. For more complex
* nonsequential access to a portal, see PortalRunFetch.
*
* count <= 0 is interpreted as a no-op: the destination gets started up
* and shut down, but nothing else happens. Also, count == FETCH_ALL is
* interpreted as "all rows".
*
* Caller must already have validated the Portal and done appropriate
* setup (cf. PortalRun).
*
* Returns number of rows processed (suitable for use in result tag)
*/
long
PortalRunSelect(Portal portal,
bool forward,
long count,
CommandDest dest)
{
QueryDesc *queryDesc;
ScanDirection direction;
uint32 nprocessed;
/*
* NB: queryDesc will be NULL if we are fetching from a held cursor
* or a completed utility query; can't use it in that path.
*/
queryDesc = PortalGetQueryDesc(portal);
/* Caller messed up if we have neither a ready query nor held data. */
Assert(queryDesc || portal->holdStore);
/*
* Force the queryDesc destination to the right thing. This supports
* MOVE, for example, which will pass in dest = None. This is okay to
* change as long as we do it on every fetch. (The Executor must not
* assume that dest never changes.)
*/
if (queryDesc)
queryDesc->dest = dest;
/*
* Determine which direction to go in, and check to see if we're
* already at the end of the available tuples in that direction. If
* so, set the direction to NoMovement to avoid trying to fetch any
* tuples. (This check exists because not all plan node types are
* robust about being called again if they've already returned NULL
* once.) Then call the executor (we must not skip this, because the
* destination needs to see a setup and shutdown even if no tuples are
* available). Finally, update the portal position state depending on
* the number of tuples that were retrieved.
*/
if (forward)
{
if (portal->atEnd || count <= 0)
direction = NoMovementScanDirection;
else
direction = ForwardScanDirection;
/* In the executor, zero count processes all rows */
if (count == FETCH_ALL)
count = 0;
if (portal->holdStore)
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
if (direction != NoMovementScanDirection)
{
long oldPos;
if (nprocessed > 0)
portal->atStart = false; /* OK to go backward now */
if (count == 0 ||
(unsigned long) nprocessed < (unsigned long) count)
portal->atEnd = true; /* we retrieved 'em all */
oldPos = portal->portalPos;
portal->portalPos += nprocessed;
/* portalPos doesn't advance when we fall off the end */
if (portal->portalPos < oldPos)
portal->posOverflow = true;
}
}
else
{
if (portal->cursorOptions & CURSOR_OPT_NO_SCROLL)
elog(ERROR, "Cursor can only scan forward"
"\n\tDeclare it with SCROLL option to enable backward scan");
if (portal->atStart || count <= 0)
direction = NoMovementScanDirection;
else
direction = BackwardScanDirection;
/* In the executor, zero count processes all rows */
if (count == FETCH_ALL)
count = 0;
if (portal->holdStore)
nprocessed = RunFromStore(portal, direction, count, dest);
else
{
ExecutorRun(queryDesc, direction, count);
nprocessed = queryDesc->estate->es_processed;
}
if (direction != NoMovementScanDirection)
{
if (nprocessed > 0 && portal->atEnd)
{
portal->atEnd = false; /* OK to go forward now */
portal->portalPos++; /* adjust for endpoint case */
}
if (count == 0 ||
(unsigned long) nprocessed < (unsigned long) count)
{
portal->atStart = true; /* we retrieved 'em all */
portal->portalPos = 0;
portal->posOverflow = false;
}
else
{
long oldPos;
oldPos = portal->portalPos;
portal->portalPos -= nprocessed;
if (portal->portalPos > oldPos ||
portal->portalPos <= 0)
portal->posOverflow = true;
}
}
}
return nprocessed;
}
/*
* RunFromStore
* Fetch tuples from the portal's tuple store.
*
* Calling conventions are similar to ExecutorRun, except that we
* do not depend on having a queryDesc or estate. Therefore we return the
* number of tuples processed as the result, not in estate->es_processed.
*
* One difference from ExecutorRun is that the destination receiver functions
* are run in the caller's memory context (since we have no estate). Watch
* out for memory leaks.
*/
static uint32
RunFromStore(Portal portal, ScanDirection direction, long count,
CommandDest dest)
{
DestReceiver *destfunc;
long current_tuple_count = 0;
destfunc = DestToFunction(dest);
(*destfunc->setup) (destfunc, CMD_SELECT, portal->name, portal->tupDesc);
if (direction == NoMovementScanDirection)
{
/* do nothing except start/stop the destination */
}
else
{
bool forward = (direction == ForwardScanDirection);
for (;;)
{
MemoryContext oldcontext;
HeapTuple tup;
bool should_free;
oldcontext = MemoryContextSwitchTo(portal->holdContext);
tup = tuplestore_getheaptuple(portal->holdStore, forward,
&should_free);
MemoryContextSwitchTo(oldcontext);
if (tup == NULL)
break;
(*destfunc->receiveTuple) (tup, portal->tupDesc, destfunc);
if (should_free)
pfree(tup);
/*
* check our tuple count.. if we've processed the proper number
* then quit, else loop again and process more tuples. Zero
* count means no limit.
*/
current_tuple_count++;
if (count && count == current_tuple_count)
break;
}
}
(*destfunc->cleanup) (destfunc);
return (uint32) current_tuple_count;
}
/*
* PortalRunUtility
* Execute a utility statement inside a portal.
*/
static void
PortalRunUtility(Portal portal, Query *query,
CommandDest dest, char *completionTag)
{
Node *utilityStmt = query->utilityStmt;
elog(DEBUG2, "ProcessUtility");
/*
* Set snapshot if utility stmt needs one. Most reliable
* way to do this seems to be to enumerate those that do not
* need one; this is a short list. Transaction control,
* LOCK, and SET must *not* set a snapshot since they need
* to be executable at the start of a serializable transaction
* without freezing a snapshot. By extension we allow SHOW
* not to set a snapshot. The other stmts listed are just
* efficiency hacks. Beware of listing anything that can
* modify the database --- if, say, it has to update a
* functional index, then it had better have a snapshot.
*/
if (! (IsA(utilityStmt, TransactionStmt) ||
IsA(utilityStmt, LockStmt) ||
IsA(utilityStmt, VariableSetStmt) ||
IsA(utilityStmt, VariableShowStmt) ||
IsA(utilityStmt, VariableResetStmt) ||
IsA(utilityStmt, ConstraintsSetStmt) ||
/* efficiency hacks from here down */
IsA(utilityStmt, FetchStmt) ||
IsA(utilityStmt, ListenStmt) ||
IsA(utilityStmt, NotifyStmt) ||
IsA(utilityStmt, UnlistenStmt) ||
IsA(utilityStmt, CheckPointStmt)))
SetQuerySnapshot();
if (query->canSetTag)
{
/* utility statement can override default tag string */
ProcessUtility(utilityStmt, dest, completionTag);
if (completionTag && completionTag[0] == '\0' && portal->commandTag)
strcpy(completionTag, portal->commandTag); /* use the default */
}
else
{
/* utility added by rewrite cannot set tag */
ProcessUtility(utilityStmt, dest, NULL);
}
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));
}
/*
* PortalRunMulti
* Execute a portal's queries in the general case (multi queries).
*/
static void
PortalRunMulti(Portal portal,
CommandDest dest, CommandDest altdest,
char *completionTag)
{
List *plantree_list = portal->planTrees;
List *querylist_item;
/*
* Loop to handle the individual queries generated from a
* single parsetree by analysis and rewrite.
*/
foreach(querylist_item, portal->parseTrees)
{
Query *query = (Query *) lfirst(querylist_item);
Plan *plan = (Plan *) lfirst(plantree_list);
plantree_list = lnext(plantree_list);
/*
* If we got a cancel signal in prior command, quit
*/
CHECK_FOR_INTERRUPTS();
if (query->commandType == CMD_UTILITY)
{
/*
* process utility functions (create, destroy, etc..)
*/
Assert(plan == NULL);
PortalRunUtility(portal, query,
query->canSetTag ? dest : altdest,
completionTag);
}
else
{
/*
* process a plannable query.
*/
elog(DEBUG2, "ProcessQuery");
/* Must always set snapshot for plannable queries */
SetQuerySnapshot();
/*
* execute the plan
*/
if (log_executor_stats)
ResetUsage();
if (query->canSetTag)
{
/* statement can set tag string */
ProcessQuery(query, plan,
portal->portalParams, portal->name,
dest, completionTag);
}
else
{
/* stmt added by rewrite cannot set tag */
ProcessQuery(query, plan,
portal->portalParams, portal->name,
altdest, NULL);
}
if (log_executor_stats)
ShowUsage("EXECUTOR STATISTICS");
}
/*
* Increment command counter between queries, but not after the
* last one.
*/
if (plantree_list != NIL)
CommandCounterIncrement();
/*
* Clear subsidiary contexts to recover temporary memory.
*/
Assert(PortalGetHeapMemory(portal) == CurrentMemoryContext);
MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
}
/*
* If a command completion tag was supplied, use it. Otherwise
* use the portal's commandTag as the default completion tag.
*
* Exception: clients will expect INSERT/UPDATE/DELETE tags to
* have counts, so fake something up if necessary. (This could
* happen if the original query was replaced by a DO INSTEAD rule.)
*/
if (completionTag && completionTag[0] == '\0')
{
if (portal->commandTag)
strcpy(completionTag, portal->commandTag);
if (strcmp(completionTag, "INSERT") == 0)
strcpy(completionTag, "INSERT 0 0");
else if (strcmp(completionTag, "UPDATE") == 0)
strcpy(completionTag, "UPDATE 0");
else if (strcmp(completionTag, "DELETE") == 0)
strcpy(completionTag, "DELETE 0");
}
/* Prevent portal's commands from being re-executed */
portal->portalDone = true;
}
/*
* PortalRunFetch
* Variant form of PortalRun that supports SQL FETCH directions.
*
* Returns number of rows processed (suitable for use in result tag)
*/
long
PortalRunFetch(Portal portal,
FetchDirection fdirection,
long count,
CommandDest dest)
{
long result;
Portal saveCurrentPortal;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
MemoryContext oldContext;
AssertArg(PortalIsValid(portal));
AssertState(portal->portalReady); /* else no PortalStart */
/*
* Check for improper portal use, and mark portal active.
*/
if (portal->portalDone)
elog(ERROR, "Portal \"%s\" cannot be run anymore", portal->name);
if (portal->portalActive)
elog(ERROR, "Portal \"%s\" already active", portal->name);
portal->portalActive = true;
/*
* Set global portal and context pointers.
*/
saveCurrentPortal = CurrentPortal;
CurrentPortal = portal;
savePortalContext = PortalContext;
PortalContext = PortalGetHeapMemory(portal);
saveQueryContext = QueryContext;
QueryContext = portal->queryContext;
oldContext = MemoryContextSwitchTo(PortalContext);
switch (portal->strategy)
{
case PORTAL_ONE_SELECT:
result = DoPortalRunFetch(portal, fdirection, count, dest);
break;
default:
elog(ERROR, "PortalRunFetch: unsupported portal strategy");
result = 0; /* keep compiler quiet */
break;
}
MemoryContextSwitchTo(oldContext);
/* Mark portal not active */
portal->portalActive = false;
CurrentPortal = saveCurrentPortal;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
return result;
}
/*
* DoPortalRunFetch
* Guts of PortalRunFetch --- the portal context is already set up
*
* Returns number of rows processed (suitable for use in result tag)
*/
static long
DoPortalRunFetch(Portal portal,
FetchDirection fdirection,
long count,
CommandDest dest)
{
bool forward;
Assert(portal->strategy == PORTAL_ONE_SELECT);
switch (fdirection)
{
case FETCH_FORWARD:
if (count < 0)
{
fdirection = FETCH_BACKWARD;
count = -count;
}
/* fall out of switch to share code with FETCH_BACKWARD */
break;
case FETCH_BACKWARD:
if (count < 0)
{
fdirection = FETCH_FORWARD;
count = -count;
}
/* fall out of switch to share code with FETCH_FORWARD */
break;
case FETCH_ABSOLUTE:
if (count > 0)
{
/*
* Definition: Rewind to start, advance count-1 rows, return
* next row (if any). In practice, if the goal is less than
* halfway back to the start, it's better to scan from where
* we are. In any case, we arrange to fetch the target row
* going forwards.
*/
if (portal->posOverflow || portal->portalPos == LONG_MAX ||
count-1 <= portal->portalPos / 2)
{
DoPortalRewind(portal);
if (count > 1)
PortalRunSelect(portal, true, count-1, None);
}
else
{
long pos = portal->portalPos;
if (portal->atEnd)
pos++; /* need one extra fetch if off end */
if (count <= pos)
PortalRunSelect(portal, false, pos-count+1, None);
else if (count > pos+1)
PortalRunSelect(portal, true, count-pos-1, None);
}
return PortalRunSelect(portal, true, 1L, dest);
}
else if (count < 0)
{
/*
* Definition: Advance to end, back up abs(count)-1 rows,
* return prior row (if any). We could optimize this if we
* knew in advance where the end was, but typically we won't.
* (Is it worth considering case where count > half of size
* of query? We could rewind once we know the size ...)
*/
PortalRunSelect(portal, true, FETCH_ALL, None);
if (count < -1)
PortalRunSelect(portal, false, -count-1, None);
return PortalRunSelect(portal, false, 1L, dest);
}
else /* count == 0 */
{
/* Rewind to start, return zero rows */
DoPortalRewind(portal);
return PortalRunSelect(portal, true, 0L, dest);
}
break;
case FETCH_RELATIVE:
if (count > 0)
{
/*
* Definition: advance count-1 rows, return next row (if any).
*/
if (count > 1)
PortalRunSelect(portal, true, count-1, None);
return PortalRunSelect(portal, true, 1L, dest);
}
else if (count < 0)
{
/*
* Definition: back up abs(count)-1 rows, return prior row
* (if any).
*/
if (count < -1)
PortalRunSelect(portal, false, -count-1, None);
return PortalRunSelect(portal, false, 1L, dest);
}
else /* count == 0 */
{
/* Same as FETCH FORWARD 0, so fall out of switch */
fdirection = FETCH_FORWARD;
}
break;
default:
elog(ERROR, "PortalRunFetch: bogus direction");
break;
}
/*
* Get here with fdirection == FETCH_FORWARD or FETCH_BACKWARD,
* and count >= 0.
*/
forward = (fdirection == FETCH_FORWARD);
/*
* Zero count means to re-fetch the current row, if any (per SQL92)
*/
if (count == 0)
{
bool on_row;
/* Are we sitting on a row? */
on_row = (!portal->atStart && !portal->atEnd);
if (dest == None)
{
/* MOVE 0 returns 0/1 based on if FETCH 0 would return a row */
return on_row ? 1L : 0L;
}
else
{
/*
* If we are sitting on a row, back up one so we can re-fetch it.
* If we are not sitting on a row, we still have to start up and
* shut down the executor so that the destination is initialized
* and shut down correctly; so keep going. To PortalRunSelect,
* count == 0 means we will retrieve no row.
*/
if (on_row)
{
PortalRunSelect(portal, false, 1L, None);
/* Set up to fetch one row forward */
count = 1;
forward = true;
}
}
}
/*
* Optimize MOVE BACKWARD ALL into a Rewind.
*/
if (!forward && count == FETCH_ALL && dest == None)
{
long result = portal->portalPos;
if (result > 0 && !portal->atEnd)
result--;
DoPortalRewind(portal);
/* result is bogus if pos had overflowed, but it's best we can do */
return result;
}
return PortalRunSelect(portal, forward, count, dest);
}
/*
* DoPortalRewind - rewind a Portal to starting point
*/
static void
DoPortalRewind(Portal portal)
{
if (portal->holdStore)
{
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(portal->holdContext);
tuplestore_rescan(portal->holdStore);
MemoryContextSwitchTo(oldcontext);
}
if (PortalGetQueryDesc(portal))
{
ExecutorRewind(PortalGetQueryDesc(portal));
}
portal->atStart = true;
portal->atEnd = false;
portal->portalPos = 0;
portal->posOverflow = false;
}

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.197 2003/03/20 18:52:48 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.198 2003/05/02 20:54:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1027,3 +1027,386 @@ ProcessUtility(Node *parsetree,
break;
}
}
/*
* CreateCommandTag
* utility to get a string representation of the
* command operation, given a raw (un-analyzed) parsetree.
*
* This must handle all raw command types, but since the vast majority
* of 'em are utility commands, it seems sensible to keep it here.
*
* NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
* Also, the result must point at a true constant (permanent storage).
*/
const char *
CreateCommandTag(Node *parsetree)
{
const char *tag;
switch (nodeTag(parsetree))
{
case T_InsertStmt:
tag = "INSERT";
break;
case T_DeleteStmt:
tag = "DELETE";
break;
case T_UpdateStmt:
tag = "UPDATE";
break;
case T_SelectStmt:
tag = "SELECT";
break;
case T_TransactionStmt:
{
TransactionStmt *stmt = (TransactionStmt *) parsetree;
switch (stmt->kind)
{
case TRANS_STMT_BEGIN:
tag = "BEGIN";
break;
case TRANS_STMT_START:
tag = "START TRANSACTION";
break;
case TRANS_STMT_COMMIT:
tag = "COMMIT";
break;
case TRANS_STMT_ROLLBACK:
tag = "ROLLBACK";
break;
default:
tag = "???";
break;
}
}
break;
case T_DeclareCursorStmt:
tag = "DECLARE CURSOR";
break;
case T_ClosePortalStmt:
tag = "CLOSE CURSOR";
break;
case T_FetchStmt:
{
FetchStmt *stmt = (FetchStmt *) parsetree;
tag = (stmt->ismove) ? "MOVE" : "FETCH";
}
break;
case T_CreateDomainStmt:
tag = "CREATE DOMAIN";
break;
case T_CreateSchemaStmt:
tag = "CREATE SCHEMA";
break;
case T_CreateStmt:
tag = "CREATE TABLE";
break;
case T_DropStmt:
switch (((DropStmt *) parsetree)->removeType)
{
case DROP_TABLE:
tag = "DROP TABLE";
break;
case DROP_SEQUENCE:
tag = "DROP SEQUENCE";
break;
case DROP_VIEW:
tag = "DROP VIEW";
break;
case DROP_INDEX:
tag = "DROP INDEX";
break;
case DROP_TYPE:
tag = "DROP TYPE";
break;
case DROP_DOMAIN:
tag = "DROP DOMAIN";
break;
case DROP_CONVERSION:
tag = "DROP CONVERSION";
break;
case DROP_SCHEMA:
tag = "DROP SCHEMA";
break;
default:
tag = "???";
}
break;
case T_TruncateStmt:
tag = "TRUNCATE TABLE";
break;
case T_CommentStmt:
tag = "COMMENT";
break;
case T_CopyStmt:
tag = "COPY";
break;
case T_RenameStmt:
if (((RenameStmt *) parsetree)->renameType == RENAME_TRIGGER)
tag = "ALTER TRIGGER";
else
tag = "ALTER TABLE";
break;
case T_AlterTableStmt:
tag = "ALTER TABLE";
break;
case T_AlterDomainStmt:
tag = "ALTER DOMAIN";
break;
case T_GrantStmt:
{
GrantStmt *stmt = (GrantStmt *) parsetree;
tag = (stmt->is_grant) ? "GRANT" : "REVOKE";
}
break;
case T_DefineStmt:
switch (((DefineStmt *) parsetree)->kind)
{
case DEFINE_STMT_AGGREGATE:
tag = "CREATE AGGREGATE";
break;
case DEFINE_STMT_OPERATOR:
tag = "CREATE OPERATOR";
break;
case DEFINE_STMT_TYPE:
tag = "CREATE TYPE";
break;
default:
tag = "???";
}
break;
case T_CompositeTypeStmt:
tag = "CREATE TYPE";
break;
case T_ViewStmt:
tag = "CREATE VIEW";
break;
case T_CreateFunctionStmt:
tag = "CREATE FUNCTION";
break;
case T_IndexStmt:
tag = "CREATE INDEX";
break;
case T_RuleStmt:
tag = "CREATE RULE";
break;
case T_CreateSeqStmt:
tag = "CREATE SEQUENCE";
break;
case T_AlterSeqStmt:
tag = "ALTER SEQUENCE";
break;
case T_RemoveAggrStmt:
tag = "DROP AGGREGATE";
break;
case T_RemoveFuncStmt:
tag = "DROP FUNCTION";
break;
case T_RemoveOperStmt:
tag = "DROP OPERATOR";
break;
case T_CreatedbStmt:
tag = "CREATE DATABASE";
break;
case T_AlterDatabaseSetStmt:
tag = "ALTER DATABASE";
break;
case T_DropdbStmt:
tag = "DROP DATABASE";
break;
case T_NotifyStmt:
tag = "NOTIFY";
break;
case T_ListenStmt:
tag = "LISTEN";
break;
case T_UnlistenStmt:
tag = "UNLISTEN";
break;
case T_LoadStmt:
tag = "LOAD";
break;
case T_ClusterStmt:
tag = "CLUSTER";
break;
case T_VacuumStmt:
if (((VacuumStmt *) parsetree)->vacuum)
tag = "VACUUM";
else
tag = "ANALYZE";
break;
case T_ExplainStmt:
tag = "EXPLAIN";
break;
case T_VariableSetStmt:
tag = "SET";
break;
case T_VariableShowStmt:
tag = "SHOW";
break;
case T_VariableResetStmt:
tag = "RESET";
break;
case T_CreateTrigStmt:
tag = "CREATE TRIGGER";
break;
case T_DropPropertyStmt:
switch (((DropPropertyStmt *) parsetree)->removeType)
{
case DROP_TRIGGER:
tag = "DROP TRIGGER";
break;
case DROP_RULE:
tag = "DROP RULE";
break;
default:
tag = "???";
}
break;
case T_CreatePLangStmt:
tag = "CREATE LANGUAGE";
break;
case T_DropPLangStmt:
tag = "DROP LANGUAGE";
break;
case T_CreateUserStmt:
tag = "CREATE USER";
break;
case T_AlterUserStmt:
tag = "ALTER USER";
break;
case T_AlterUserSetStmt:
tag = "ALTER USER";
break;
case T_DropUserStmt:
tag = "DROP USER";
break;
case T_LockStmt:
tag = "LOCK TABLE";
break;
case T_ConstraintsSetStmt:
tag = "SET CONSTRAINTS";
break;
case T_CreateGroupStmt:
tag = "CREATE GROUP";
break;
case T_AlterGroupStmt:
tag = "ALTER GROUP";
break;
case T_DropGroupStmt:
tag = "DROP GROUP";
break;
case T_CheckPointStmt:
tag = "CHECKPOINT";
break;
case T_ReindexStmt:
tag = "REINDEX";
break;
case T_CreateConversionStmt:
tag = "CREATE CONVERSION";
break;
case T_CreateCastStmt:
tag = "CREATE CAST";
break;
case T_DropCastStmt:
tag = "DROP CAST";
break;
case T_CreateOpClassStmt:
tag = "CREATE OPERATOR CLASS";
break;
case T_RemoveOpClassStmt:
tag = "DROP OPERATOR CLASS";
break;
case T_PrepareStmt:
tag = "PREPARE";
break;
case T_ExecuteStmt:
tag = "EXECUTE";
break;
case T_DeallocateStmt:
tag = "DEALLOCATE";
break;
default:
elog(LOG, "CreateCommandTag: unknown parse node type %d",
nodeTag(parsetree));
tag = "???";
break;
}
return tag;
}

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/mcxt.c,v 1.39 2003/03/27 16:51:29 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/mcxt.c,v 1.40 2003/05/02 20:54:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -43,9 +43,11 @@ MemoryContext TopMemoryContext = NULL;
MemoryContext ErrorContext = NULL;
MemoryContext PostmasterContext = NULL;
MemoryContext CacheMemoryContext = NULL;
MemoryContext QueryContext = NULL;
MemoryContext MessageContext = NULL;
MemoryContext TopTransactionContext = NULL;
MemoryContext TransactionCommandContext = NULL;
/* These two are transient links to contexts owned by other objects: */
MemoryContext QueryContext = NULL;
MemoryContext PortalContext = NULL;
/*****************************************************************************

View File

@ -1,36 +1,21 @@
/*-------------------------------------------------------------------------
*
* portalmem.c
* backend portal memory context management stuff
* backend portal memory management
*
* Portals are objects representing the execution state of a query.
* This module provides memory management services for portals, but it
* doesn't actually run the executor for them.
*
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.55 2003/04/29 03:21:29 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.56 2003/05/02 20:54:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* NOTES
* A "Portal" is a structure used to keep track of cursor queries.
*
* When the backend sees a "declare cursor" query, it allocates a
* "PortalData" structure, plans the query and then stores the query
* in the portal without executing it. Later, when the backend
* sees a
* fetch 1 from foo
* the system looks up the portal named "foo" in the portal table,
* gets the planned query and then calls the executor with a count
* of 1. The executor then runs the query and returns a single
* tuple. The problem is that we have to hold onto the state of the
* portal query until we see a "close". This means we have to be
* careful about memory management.
*
* I hope this makes things clearer to whoever reads this -cim 2/22/91
*/
#include "postgres.h"
#include "commands/portalcmds.h"
@ -51,6 +36,9 @@
* ----------------
*/
Portal CurrentPortal = NULL; /* globally visible pointer */
#define MAX_PORTALNAME_LEN NAMEDATALEN
typedef struct portalhashent
@ -66,7 +54,7 @@ do { \
PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
\
MemSet(key, 0, MAX_PORTALNAME_LEN); \
snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", NAME); \
StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
key, HASH_FIND, NULL); \
if (hentry) \
@ -75,12 +63,12 @@ do { \
PORTAL = NULL; \
} while(0)
#define PortalHashTableInsert(PORTAL) \
#define PortalHashTableInsert(PORTAL, NAME) \
do { \
PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
\
MemSet(key, 0, MAX_PORTALNAME_LEN); \
snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
key, HASH_ENTER, &found); \
if (hentry == NULL) \
@ -88,6 +76,8 @@ do { \
if (found) \
elog(WARNING, "trying to insert a portal name that exists."); \
hentry->portal = PORTAL; \
/* To avoid duplicate storage, make PORTAL->name point to htab entry */ \
PORTAL->name = hentry->portalname; \
} while(0)
#define PortalHashTableDelete(PORTAL) \
@ -95,7 +85,7 @@ do { \
PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
\
MemSet(key, 0, MAX_PORTALNAME_LEN); \
snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \
hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
key, HASH_REMOVE, NULL); \
if (hentry == NULL) \
@ -155,50 +145,17 @@ GetPortalByName(const char *name)
return portal;
}
/*
* PortalSetQuery
* Attaches a QueryDesc to the specified portal. This should be
* called only after successfully doing ExecutorStart for the query.
*
* Note that in the case of DECLARE CURSOR, some Portal options have
* already been set in portalcmds.c's PreparePortal(). This is grotty.
*/
void
PortalSetQuery(Portal portal, QueryDesc *queryDesc)
{
AssertArg(PortalIsValid(portal));
/*
* If the user didn't specify a SCROLL type, allow or disallow
* scrolling based on whether it would require any additional
* runtime overhead to do so.
*/
if (portal->scrollType == DEFAULT_SCROLL)
{
if (ExecSupportsBackwardScan(queryDesc->plantree))
portal->scrollType = ENABLE_SCROLL;
else
portal->scrollType = DISABLE_SCROLL;
}
portal->queryDesc = queryDesc;
portal->executorRunning = true; /* now need to shut down executor */
portal->atStart = true;
portal->atEnd = false; /* allow fetches */
portal->portalPos = 0;
portal->posOverflow = false;
}
/*
* CreatePortal
* Returns a new portal given a name.
*
* An elog(WARNING) is emitted if portal name is in use (existing
* portal is returned!)
* allowDup: if true, automatically drop any pre-existing portal of the
* same name (if false, an error is raised).
*
* dupSilent: if true, don't even emit a WARNING.
*/
Portal
CreatePortal(const char *name)
CreatePortal(const char *name, bool allowDup, bool dupSilent)
{
Portal portal;
@ -207,43 +164,90 @@ CreatePortal(const char *name)
portal = GetPortalByName(name);
if (PortalIsValid(portal))
{
elog(WARNING, "CreatePortal: portal \"%s\" already exists", name);
return portal;
if (!allowDup)
elog(ERROR, "Portal \"%s\" already exists", name);
if (!dupSilent)
elog(WARNING, "Closing pre-existing portal \"%s\"", name);
PortalDrop(portal, false);
}
/* make new portal structure */
portal = (Portal) MemoryContextAlloc(PortalMemory, sizeof *portal);
portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal);
/* initialize portal name */
portal->name = MemoryContextStrdup(PortalMemory, name);
/* initialize portal heap context */
/* initialize portal heap context; typically it won't store much */
portal->heap = AllocSetContextCreate(PortalMemory,
"PortalHeapMemory",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
/* initialize portal query */
portal->queryDesc = NULL;
/* initialize portal fields that don't start off zero */
portal->cleanup = PortalCleanup;
portal->scrollType = DEFAULT_SCROLL;
portal->executorRunning = false;
portal->holdOpen = false;
portal->createXact = GetCurrentTransactionId();
portal->holdStore = NULL;
portal->holdContext = NULL;
portal->strategy = PORTAL_MULTI_QUERY;
portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
portal->atStart = true;
portal->atEnd = true; /* disallow fetches until query is set */
portal->portalPos = 0;
portal->posOverflow = false;
/* put portal in table */
PortalHashTableInsert(portal);
/* put portal in table (sets portal->name) */
PortalHashTableInsert(portal, name);
return portal;
}
/*
* CreateNewPortal
* Create a new portal, assigning it a random nonconflicting name.
*/
Portal
CreateNewPortal(void)
{
static unsigned int unnamed_portal_count = 0;
char portalname[MAX_PORTALNAME_LEN];
/* Select a nonconflicting name */
for (;;)
{
unnamed_portal_count++;
sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count);
if (GetPortalByName(portalname) == NULL)
break;
}
return CreatePortal(portalname, false, false);
}
/*
* PortalDefineQuery
* A simple subroutine to establish a portal's query.
*
* Notes: the passed commandTag must be a pointer to a constant string,
* since it is not copied. The caller is responsible for ensuring that
* the passed sourceText (if any), parse and plan trees have adequate
* lifetime. Also, queryContext must accurately describe the location
* of the parse and plan trees.
*/
void
PortalDefineQuery(Portal portal,
const char *sourceText,
const char *commandTag,
List *parseTrees,
List *planTrees,
MemoryContext queryContext)
{
AssertArg(PortalIsValid(portal));
AssertState(portal->queryContext == NULL); /* else defined already */
Assert(length(parseTrees) == length(planTrees));
portal->sourceText = sourceText;
portal->commandTag = commandTag;
portal->parseTrees = parseTrees;
portal->planTrees = planTrees;
portal->queryContext = queryContext;
}
/*
* PortalDrop
* Destroy the portal.
@ -256,6 +260,10 @@ PortalDrop(Portal portal, bool isError)
{
AssertArg(PortalIsValid(portal));
/* Not sure if this case can validly happen or not... */
if (portal->portalActive)
elog(ERROR, "PortalDrop: can't drop active portal");
/*
* Remove portal from hash table. Because we do this first, we will
* not come back to try to remove the portal again if there's any error
@ -273,21 +281,43 @@ PortalDrop(Portal portal, bool isError)
MemoryContextDelete(portal->holdContext);
/* release subsidiary storage */
if (PortalGetHeapMemory(portal))
MemoryContextDelete(PortalGetHeapMemory(portal));
MemoryContextDelete(PortalGetHeapMemory(portal));
/* release name and portal data (both are in PortalMemory) */
pfree(portal->name);
/* release portal struct (it's in PortalMemory) */
pfree(portal);
}
/*
* Cleanup the portals created in the current transaction. If the
* transaction was aborted, all the portals created in this transaction
* should be removed. If the transaction was successfully committed, any
* holdable cursors created in this transaction need to be kept
* open. In any case, portals remaining from prior transactions should
* be left untouched.
* DropDependentPortals
* Drop any portals using the specified context as queryContext.
*
* This is normally used to make sure we can safely drop a prepared statement.
*/
void
DropDependentPortals(MemoryContext queryContext)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
{
Portal portal = hentry->portal;
if (portal->queryContext == queryContext)
PortalDrop(portal, false);
}
}
/*
* Pre-commit processing for portals.
*
* Any holdable cursors created in this transaction need to be converted to
* materialized form, since we are going to close down the executor and
* release locks. Remove all other portals created in this transaction.
* Portals remaining from prior transactions should be left untouched.
*
* XXX This assumes that portals can be deleted in a random order, ie,
* no portal has a reference to any other (at least not one that will be
@ -296,7 +326,7 @@ PortalDrop(Portal portal, bool isError)
* references...
*/
void
AtEOXact_portals(bool isCommit)
AtCommit_Portals(void)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
@ -308,11 +338,21 @@ AtEOXact_portals(bool isCommit)
{
Portal portal = hentry->portal;
if (portal->createXact != xact)
/*
* Do not touch active portals --- this can only happen in the case of
* a multi-transaction utility command, such as VACUUM.
*/
if (portal->portalActive)
continue;
if (portal->holdOpen && isCommit)
if (portal->cursorOptions & CURSOR_OPT_HOLD)
{
/*
* Do nothing to cursors held over from a previous transaction.
*/
if (portal->createXact != xact)
continue;
/*
* We are exiting the transaction that created a holdable
* cursor. Instead of dropping the portal, prepare it for
@ -321,7 +361,8 @@ AtEOXact_portals(bool isCommit)
/*
* Create the memory context that is used for storage of
* the held cursor's tuple set.
* the held cursor's tuple set. Note this is NOT a child
* of the portal's heap memory.
*/
portal->holdContext =
AllocSetContextCreate(PortalMemory,
@ -341,7 +382,91 @@ AtEOXact_portals(bool isCommit)
}
else
{
PortalDrop(portal, !isCommit);
/* Zap all non-holdable portals */
PortalDrop(portal, false);
}
}
}
/*
* Abort processing for portals.
*
* At this point we reset the "active" flags and run the cleanup hook if
* present, but we can't release memory until the cleanup call.
*
* The reason we need to reset active is so that we can replace the unnamed
* portal, else we'll fail to execute ROLLBACK when it arrives. Also, we
* want to run the cleanup hook now to be certain it knows that we had an
* error abort and not successful conclusion.
*/
void
AtAbort_Portals(void)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
TransactionId xact = GetCurrentTransactionId();
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
{
Portal portal = hentry->portal;
portal->portalActive = false;
/*
* Do nothing else to cursors held over from a previous transaction.
* (This test must include checking CURSOR_OPT_HOLD, else we will
* fail to clean up a VACUUM portal if it fails after its first
* sub-transaction.)
*/
if (portal->createXact != xact &&
(portal->cursorOptions & CURSOR_OPT_HOLD))
continue;
/* let portalcmds.c clean up the state it knows about */
if (PointerIsValid(portal->cleanup))
{
(*portal->cleanup) (portal, true);
portal->cleanup = NULL;
}
}
}
/*
* Post-abort cleanup for portals.
*
* Delete all portals not held over from prior transactions.
*/
void
AtCleanup_Portals(void)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
TransactionId xact = GetCurrentTransactionId();
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
{
Portal portal = hentry->portal;
/*
* Let's just make sure no one's active...
*/
portal->portalActive = false;
/*
* Do nothing else to cursors held over from a previous transaction.
* (This test must include checking CURSOR_OPT_HOLD, else we will
* fail to clean up a VACUUM portal if it fails after its first
* sub-transaction.)
*/
if (portal->createXact != xact &&
(portal->cursorOptions & CURSOR_OPT_HOLD))
continue;
/* Else zap it with prejudice. */
PortalDrop(portal, true);
}
}

View File

@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: catversion.h,v 1.184 2003/04/08 23:20:02 tgl Exp $
* $Id: catversion.h,v 1.185 2003/05/02 20:54:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200304071
#define CATALOG_VERSION_NO 200305011
#endif

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: portalcmds.h,v 1.7 2003/04/29 03:21:30 tgl Exp $
* $Id: portalcmds.h,v 1.8 2003/05/02 20:54:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,13 +22,10 @@ extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest);
extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest,
char *completionTag);
extern long DoPortalFetch(Portal portal,
FetchDirection fdirection,
long count,
CommandDest dest);
extern void PerformPortalClose(char *name);
extern void PortalCleanup(Portal portal, bool isError);
extern void PersistHoldablePortal(Portal portal);
#endif /* PORTALCMDS_H */

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: spi_priv.h,v 1.13 2002/10/14 23:49:20 tgl Exp $
* $Id: spi_priv.h,v 1.14 2003/05/02 20:54:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -36,8 +36,6 @@ typedef struct
/* Argument types, if a prepared plan */
int nargs;
Oid *argtypes;
/* Command type of last original parsetree */
CmdType origCmdType;
} _SPI_plan;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: parsenodes.h,v 1.236 2003/03/27 16:51:29 momjian Exp $
* $Id: parsenodes.h,v 1.237 2003/05/02 20:54:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -47,6 +47,8 @@ typedef struct Query
QuerySource querySource; /* where did I come from? */
bool canSetTag; /* do I set the command result tag? */
Node *utilityStmt; /* non-null if this is a non-optimizable
* statement */

View File

@ -7,17 +7,32 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: pquery.h,v 1.24 2003/03/10 03:53:52 tgl Exp $
* $Id: pquery.h,v 1.25 2003/05/02 20:54:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef PQUERY_H
#define PQUERY_H
#include "executor/execdesc.h"
#include "utils/portal.h"
extern void ProcessQuery(Query *parsetree, Plan *plan, CommandDest dest,
char *completionTag);
extern void ProcessQuery(Query *parsetree,
Plan *plan,
ParamListInfo params,
const char *portalName,
CommandDest dest,
char *completionTag);
extern void PortalStart(Portal portal, ParamListInfo params);
extern bool PortalRun(Portal portal, long count,
CommandDest dest, CommandDest altdest,
char *completionTag);
extern long PortalRunFetch(Portal portal,
FetchDirection fdirection,
long count,
CommandDest dest);
#endif /* PQUERY_H */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: tcopprot.h,v 1.55 2003/04/29 22:13:11 tgl Exp $
* $Id: tcopprot.h,v 1.56 2003/05/02 20:54:36 tgl Exp $
*
* OLD COMMENTS
* This file was created so that other c files could get the two
@ -41,6 +41,7 @@ extern List *pg_analyze_and_rewrite(Node *parsetree,
extern List *pg_parse_and_rewrite(const char *query_string,
Oid *paramTypes, int numParams);
extern Plan *pg_plan_query(Query *querytree);
extern List *pg_plan_queries(List *querytrees, bool needSnapshot);
#endif /* BOOTSTRAP_INCLUDE */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: utility.h,v 1.16 2002/09/04 20:31:45 momjian Exp $
* $Id: utility.h,v 1.17 2003/05/02 20:54:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -19,4 +19,6 @@
extern void ProcessUtility(Node *parsetree, CommandDest dest,
char *completionTag);
extern const char *CreateCommandTag(Node *parsetree);
#endif /* UTILITY_H */

View File

@ -10,7 +10,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: memutils.h,v 1.50 2002/12/16 16:22:46 tgl Exp $
* $Id: memutils.h,v 1.51 2003/05/02 20:54:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -68,9 +68,11 @@ extern DLLIMPORT MemoryContext TopMemoryContext;
extern DLLIMPORT MemoryContext ErrorContext;
extern DLLIMPORT MemoryContext PostmasterContext;
extern DLLIMPORT MemoryContext CacheMemoryContext;
extern DLLIMPORT MemoryContext QueryContext;
extern DLLIMPORT MemoryContext MessageContext;
extern DLLIMPORT MemoryContext TopTransactionContext;
extern DLLIMPORT MemoryContext TransactionCommandContext;
/* These two are transient links to contexts owned by other objects: */
extern DLLIMPORT MemoryContext QueryContext;
extern DLLIMPORT MemoryContext PortalContext;
/*

View File

@ -4,12 +4,42 @@
* POSTGRES portal definitions.
*
* A portal is an abstraction which represents the execution state of
* a running query (specifically, a CURSOR).
* a running or runnable query. Portals support both SQL-level CURSORs
* and protocol-level portals.
*
* Scrolling (nonsequential access) and suspension of execution are allowed
* only for portals that contain a single SELECT-type query. We do not want
* to let the client suspend an update-type query partway through! Because
* the query rewriter does not allow arbitrary ON SELECT rewrite rules,
* only queries that were originally update-type could produce multiple
* parse/plan trees; so the restriction to a single query is not a problem
* in practice.
*
* For SQL cursors, we support three kinds of scroll behavior:
*
* (1) Neither NO SCROLL nor SCROLL was specified: to remain backward
* compatible, we allow backward fetches here, unless it would
* impose additional runtime overhead to do so.
*
* (2) NO SCROLL was specified: don't allow any backward fetches.
*
* (3) SCROLL was specified: allow all kinds of backward fetches, even
* if we need to take a performance hit to do so. (The planner sticks
* a Materialize node atop the query plan if needed.)
*
* Case #1 is converted to #2 or #3 by looking at the query itself and
* determining if scrollability can be supported without additional
* overhead.
*
* Protocol-level portals have no nonsequential-fetch API and so the
* distinction doesn't matter for them. They are always initialized
* to look like NO SCROLL cursors.
*
*
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: portal.h,v 1.41 2003/04/29 03:21:30 tgl Exp $
* $Id: portal.h,v 1.42 2003/05/02 20:54:36 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -20,41 +50,81 @@
#include "nodes/memnodes.h"
#include "utils/tuplestore.h"
/*
* We support three kinds of scroll behavior:
* We have several execution strategies for Portals, depending on what
* query or queries are to be executed. (Note: in all cases, a Portal
* executes just a single source-SQL query, and thus produces just a
* single result from the user's viewpoint. However, the rule rewriter
* may expand the single source query to zero or many actual queries.)
*
* (1) Neither NO SCROLL nor SCROLL was specified: to remain backward
* compatible, we allow backward fetches here, unless it would
* impose additional runtime overhead to do so.
* PORTAL_ONE_SELECT: the portal contains one single SELECT query. We run
* the Executor incrementally as results are demanded. This strategy also
* supports holdable cursors (the Executor results can be dumped into a
* tuplestore for access after transaction completion).
*
* (2) NO SCROLL was specified: don't allow any backward fetches.
* PORTAL_UTIL_SELECT: the portal contains a utility statement that returns
* a SELECT-like result (for example, EXPLAIN or SHOW). On first execution,
* we run the statement and dump its results into the portal tuplestore;
* the results are then returned to the client as demanded.
*
* (3) SCROLL was specified: allow all kinds of backward fetches, even
* if we need to take a slight performance hit to do so.
*
* Case #1 is converted to #2 or #3 by looking at the query itself and
* determining if scrollability can be supported without additional
* overhead.
* PORTAL_MULTI_QUERY: all other cases. Here, we do not support partial
* execution: the portal's queries will be run to completion on first call.
*/
typedef enum
typedef enum PortalStrategy
{
DEFAULT_SCROLL,
DISABLE_SCROLL,
ENABLE_SCROLL
} ScrollType;
PORTAL_ONE_SELECT,
PORTAL_UTIL_SELECT,
PORTAL_MULTI_QUERY
} PortalStrategy;
typedef struct PortalData *Portal;
typedef struct PortalData
{
char *name; /* Portal's name */
MemoryContext heap; /* subsidiary memory */
QueryDesc *queryDesc; /* Info about query associated with portal */
void (*cleanup) (Portal portal, bool isError); /* Cleanup hook */
ScrollType scrollType; /* Allow backward fetches? */
bool executorRunning; /* T if we need to call ExecutorEnd */
bool holdOpen; /* hold open after xact ends? */
/* Bookkeeping data */
const char *name; /* portal's name */
MemoryContext heap; /* subsidiary memory for portal */
void (*cleanup) (Portal portal, bool isError); /* cleanup hook */
TransactionId createXact; /* the xid of the creating xact */
/* The query or queries the portal will execute */
const char *sourceText; /* text of query, if known (may be NULL) */
const char *commandTag; /* command tag for original query */
List *parseTrees; /* parse tree(s) */
List *planTrees; /* plan tree(s) */
MemoryContext queryContext; /* where the above trees live */
/*
* Note: queryContext effectively identifies which prepared statement
* the portal depends on, if any. The queryContext is *not* owned by
* the portal and is not to be deleted by portal destruction. (But for
* a cursor it is the same as "heap", and that context is deleted by
* portal destruction.)
*/
ParamListInfo portalParams; /* params to pass to query */
/* Features/options */
PortalStrategy strategy; /* see above */
int cursorOptions; /* DECLARE CURSOR option bits */
/* Status data */
bool portalReady; /* PortalStart complete? */
bool portalUtilReady; /* PortalRunUtility complete? */
bool portalActive; /* portal is running (can't delete it) */
bool portalDone; /* portal is finished (don't re-run it) */
/* If not NULL, Executor is active; call ExecutorEnd eventually: */
QueryDesc *queryDesc; /* info needed for executor invocation */
/* If portal returns tuples, this is their tupdesc: */
TupleDesc tupDesc; /* descriptor for result tuples */
/*
* Where we store tuples for a held cursor or a PORTAL_UTIL_SELECT query.
* (A cursor held past the end of its transaction no longer has any
* active executor state.)
*/
Tuplestorestate *holdStore; /* store for holdable cursors */
MemoryContext holdContext; /* memory containing holdStore */
@ -86,12 +156,25 @@ typedef struct PortalData
#define PortalGetHeapMemory(portal) ((portal)->heap)
/* Currently executing Portal, if any */
extern DLLIMPORT Portal CurrentPortal;
/* Prototypes for functions in utils/mmgr/portalmem.c */
extern void EnablePortalManager(void);
extern void AtEOXact_portals(bool isCommit);
extern Portal CreatePortal(const char *name);
extern void AtCommit_Portals(void);
extern void AtAbort_Portals(void);
extern void AtCleanup_Portals(void);
extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent);
extern Portal CreateNewPortal(void);
extern void PortalDrop(Portal portal, bool isError);
extern void DropDependentPortals(MemoryContext queryContext);
extern Portal GetPortalByName(const char *name);
extern void PortalSetQuery(Portal portal, QueryDesc *queryDesc);
extern void PersistHoldablePortal(Portal portal);
extern void PortalDefineQuery(Portal portal,
const char *sourceText,
const char *commandTag,
List *parseTrees,
List *planTrees,
MemoryContext queryContext);
#endif /* PORTAL_H */