/*
* 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)
/* 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;
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;
/* 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;
<!--
-$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
-->
<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>
<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>
</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>
*
*
* 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:
* * 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:
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,
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);
}
Assert(TopTransactionContext != NULL);
MemoryContextDelete(TopTransactionContext);
TopTransactionContext = NULL;
- TransactionCommandContext = NULL;
}
/* ----------------------------------------------------------------
{
/*
* 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);
if (TopTransactionContext != NULL)
MemoryContextDelete(TopTransactionContext);
TopTransactionContext = NULL;
- TransactionCommandContext = NULL;
}
* 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 */
* do abort processing
*/
DeferredTriggerAbortXact();
- AtEOXact_portals(false);
+ AtAbort_Portals();
lo_commit(false); /* 'false' means it's abort */
AtAbort_Notify();
AtEOXact_UpdatePasswordFile(false);
/*
* 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
}
/*
- * 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);
}
/*
Assert(s->blockState == TBLOCK_INPROGRESS);
/* This code must match the TBLOCK_INPROGRESS case below: */
CommandCounterIncrement();
- MemoryContextResetAndDeleteChildren(TransactionCommandContext);
}
break;
/*
* 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;
/*
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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,
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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,
/*-------------------------------------------------------------------------
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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.
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
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);
+ PortalDefineQuery(portal,
+ NULL, /* unfortunately don't have sourceText */
+ "SELECT", /* cursor's query is always a SELECT */
+ makeList1(query),
+ makeList1(plan),
+ PortalGetHeapMemory(portal));
+
+ MemoryContextSwitchTo(oldContext);
+
/*
- * Create the QueryDesc object in the portal context, too.
+ * 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.
*/
- cursorName = pstrdup(stmt->portalname);
- queryDesc = CreateQueryDesc(query, plan, dest, cursorName, NULL, false);
+ 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;
+ }
/*
- * call ExecStart to prepare the plan for execution
+ * Start execution --- never any params for a cursor.
*/
- ExecutorStart(queryDesc);
+ PortalStart(portal, NULL);
- /* Arrange to shut down the executor if portal is dropped */
- PortalSetQuery(portal, queryDesc);
+ Assert(portal->strategy == PORTAL_ONE_SELECT);
/*
* We're done; the query won't actually be run until PerformPortalFetch
* is called.
*/
- MemoryContextSwitchTo(oldContext);
}
/*
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))
/* 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)
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.
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
*
void
PortalCleanup(Portal portal, bool isError)
{
+ QueryDesc *queryDesc;
+
/*
* sanity checks
*/
/*
* 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)
{
* 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);
}
}
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
*/
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.
/* 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
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++)
}
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));
}
* 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 $
*
*-------------------------------------------------------------------------
*/
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");
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);
}
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));
paramLI = EvaluateParams(estate, stmt->params, entry->argtype_list);
}
- /* Execute each query */
- foreach(l, query_list)
- {
- Query *query = (Query *) lfirst(l);
- Plan *plan = (Plan *) lfirst(plan_list);
- bool is_last_query;
-
- plan_list = lnext(plan_list);
- is_last_query = (plan_list == NIL);
-
- if (query->commandType == CMD_UTILITY)
- ProcessUtility(query->utilityStmt, outputDest, NULL);
- else
- {
- QueryDesc *qdesc;
-
- if (log_executor_stats)
- ResetUsage();
+ /*
+ * Create a new portal to run the query in
+ */
+ portal = CreateNewPortal();
- qdesc = CreateQueryDesc(query, plan, outputDest, NULL,
- paramLI, false);
+ /*
+ * 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)
+ {
+ MemoryContext oldContext;
+ Query *query;
- if (stmt->into)
- {
- if (qdesc->operation != CMD_SELECT)
- elog(ERROR, "INTO clause specified for non-SELECT query");
+ oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
- query->into = stmt->into;
- qdesc->dest = None;
- }
+ query_list = copyObject(query_list);
+ plan_list = copyObject(plan_list);
+ qcontext = PortalGetHeapMemory(portal);
- ExecutorStart(qdesc);
+ 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);
- ExecutorRun(qdesc, ForwardScanDirection, 0L);
+ MemoryContextSwitchTo(oldContext);
+ }
- ExecutorEnd(qdesc);
+ PortalDefineQuery(portal,
+ NULL, /* XXX fixme: can we save query text? */
+ NULL, /* no command tag known either */
+ query_list,
+ plan_list,
+ qcontext);
- FreeQueryDesc(qdesc);
+ /*
+ * Run the portal to completion.
+ */
+ PortalStart(portal, paramLI);
- if (log_executor_stats)
- ShowUsage("EXECUTOR STATISTICS");
- }
+ (void) PortalRun(portal, FETCH_ALL, outputDest, outputDest, NULL);
- /*
- * 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();
- }
+ PortalDrop(portal, false);
if (estate)
FreeExecutorState(estate);
/* 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 */
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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,
* lifetime.
*/
if (vacstmt->analyze && !vacstmt->vacuum)
- anl_context = AllocSetContextCreate(QueryContext,
+ anl_context = AllocSetContextCreate(PortalContext,
"Analyze",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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.
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);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
_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,
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 */
_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);
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);
+ 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;
- /* Arrange to shut down the executor if portal is dropped */
- PortalSetQuery(portal, queryDesc);
+ /*
+ * Start portal execution.
+ */
+ PortalStart(portal, paramLI);
- /* Switch back to the callers memory context */
- MemoryContextSwitchTo(oldcontext);
+ Assert(portal->strategy == PORTAL_ONE_SELECT);
/* Return the created portal */
return portal;
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;
{
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))
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();
else
{
qdesc = CreateQueryDesc(queryTree, planTree,
- canSetResult ? SPI : None,
+ queryTree->canSetTag ? SPI : None,
NULL, NULL, false);
res = _SPI_pquery(qdesc, false, 0);
if (res < 0)
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;
{
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;
{
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();
/* 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");
}
else
newplan->argtypes = NULL;
- newplan->origCmdType = plan->origCmdType;
MemoryContextSwitchTo(oldcxt);
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
}
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
COPY_SCALAR_FIELD(commandType);
COPY_SCALAR_FIELD(querySource);
+ COPY_SCALAR_FIELD(canSetTag);
COPY_NODE_FIELD(utilityStmt);
COPY_SCALAR_FIELD(resultRelation);
COPY_NODE_FIELD(into);
* 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 $
*
*-------------------------------------------------------------------------
*/
{
COMPARE_SCALAR_FIELD(commandType);
COMPARE_SCALAR_FIELD(querySource);
+ COMPARE_SCALAR_FIELD(canSetTag);
COMPARE_NODE_FIELD(utilityStmt);
COMPARE_SCALAR_FIELD(resultRelation);
COMPARE_NODE_FIELD(into);
*
*
* 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*
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
*
*
* 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
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
*
* 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,
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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;
result->utilityStmt = (Node *) parseTree;
break;
}
+
+ /* Mark as original query until we learn differently */
+ result->querySource = QSRC_ORIGINAL;
+ result->canSetTag = true;
+
return result;
}
* 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 $
*
*-------------------------------------------------------------------------
*/
event_qual, rt_index, event);
rule_action->querySource = qsrc;
+ rule_action->canSetTag = false; /* might change later */
results = lappend(results, rule_action);
}
List *querylist;
List *results = NIL;
List *l;
+ CmdType origCmdType;
+ bool foundOriginalQuery;
+ Query *lastInstead;
/*
* Step 1
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;
}
*
*
* 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.
*
* 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
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.326 2003/04/29 22:13:11 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.327 2003/05/02 20:54:35 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
/* Note: whereToSendOutput is initialized for the bootstrap/standalone case */
CommandDest whereToSendOutput = Debug;
-static bool dontExecute = false;
-
/* note: these declarations had better match tcopprot.h */
sigjmp_buf Warn_restart;
static void finish_xact_command(bool forceCommit);
static void SigHupHandler(SIGNAL_ARGS);
static void FloatExceptionHandler(SIGNAL_ARGS);
-static const char *CreateCommandTag(Node *parsetree);
/* ----------------------------------------------------------------
/* ----------------
* ReadCommand reads a command from either the frontend or
- * standard input, places it in inBuf, and returns a char
- * representing whether the string is a 'Q'uery or a 'F'astpath
- * call. EOF is returned if end of file.
+ * standard input, places it in inBuf, and returns the
+ * message type code (first byte of the message).
+ * EOF is returned if end of file.
* ----------------
*/
static int
}
-/* Generate a plan for a single query. */
+/* Generate a plan for a single already-rewritten query. */
Plan *
pg_plan_query(Query *querytree)
{
return plan;
}
-
-/* ----------------------------------------------------------------
- * pg_exec_query_string()
- *
- * Takes a querystring, runs the parser/utilities or
- * parser/planner/executor over it as necessary.
- *
- * Assumptions:
- *
- * At call, we are not inside a transaction command.
- *
- * The CurrentMemoryContext after starting a transaction command must be
- * appropriate for execution of individual queries (typically this will be
- * TransactionCommandContext). Note that this routine resets that context
- * after each individual query, so don't store anything there that
- * must outlive the call!
- *
- * parse_context references a context suitable for holding the
- * parse/rewrite trees (typically this will be QueryContext).
- * This context *must* be longer-lived than the transaction context!
- * In fact, if the query string might contain BEGIN/COMMIT commands,
- * parse_context had better outlive TopTransactionContext!
+/*
+ * Generate plans for a list of already-rewritten queries.
*
- * We could have hard-wired knowledge about QueryContext and
- * TransactionCommandContext into this routine, but it seems better
- * not to, in case callers from outside this module need to use some
- * other contexts.
+ * If needSnapshot is TRUE, we haven't yet set a snapshot for the current
+ * query. A snapshot must be set before invoking the planner, since it
+ * might try to evaluate user-defined functions. But we must not set a
+ * snapshot if the list contains only utility statements, because some
+ * utility statements depend on not having frozen the snapshot yet.
+ * (We assume that such statements cannot appear together with plannable
+ * statements in the rewriter's output.)
+ */
+List *
+pg_plan_queries(List *querytrees, bool needSnapshot)
+{
+ List *plan_list = NIL;
+ List *query_list;
+
+ foreach(query_list, querytrees)
+ {
+ Query *query = (Query *) lfirst(query_list);
+ Plan *plan;
+
+ if (query->commandType == CMD_UTILITY)
+ {
+ /* Utility commands have no plans. */
+ plan = NULL;
+ }
+ else
+ {
+ if (needSnapshot)
+ {
+ SetQuerySnapshot();
+ needSnapshot = false;
+ }
+ plan = pg_plan_query(query);
+ }
+
+ plan_list = lappend(plan_list, plan);
+ }
+
+ return plan_list;
+}
+
+
+/*
+ * exec_simple_query()
*
- * ----------------------------------------------------------------
+ * Execute a "simple Query" protocol message.
*/
static void
-pg_exec_query_string(const char *query_string, /* string to execute */
- CommandDest dest, /* where results should go */
- MemoryContext parse_context) /* context for
- * parsetrees */
+exec_simple_query(const char *query_string, /* string to execute */
+ CommandDest dest) /* where results should go */
{
bool xact_started;
MemoryContext oldcontext;
struct timeval start_t,
stop_t;
bool save_log_duration = log_duration;
+ bool save_log_statement_stats = log_statement_stats;
+ /*
+ * Report query to various monitoring facilities.
+ */
debug_query_string = query_string;
+ pgstat_report_activity(query_string);
+
/*
* We use save_log_duration so "SET log_duration = true" doesn't
* report incorrect time because gettimeofday() wasn't called.
+ * Similarly, log_statement_stats has to be captured once.
*/
if (save_log_duration)
gettimeofday(&start_t, NULL);
+ if (save_log_statement_stats)
+ ResetUsage();
+
/*
* Start up a transaction command. All queries generated by the
* query_string will be in this same command block, *unless* we find a
* BEGIN/COMMIT/ABORT statement; we have to force a new xact command
* after one of those, else bad things will happen in xact.c. (Note
- * that this will possibly change current memory context.)
+ * that this will normally change current memory context.)
*/
start_xact_command();
xact_started = true;
- /*
- * parse_context *must* be different from the execution memory
- * context, else the context reset at the bottom of the loop will
- * destroy the parsetree list. (We really ought to check that
- * parse_context isn't a child of CurrentMemoryContext either, but
- * that would take more cycles than it's likely to be worth.)
- */
- Assert(parse_context != CurrentMemoryContext);
-
/*
* Switch to appropriate context for constructing parsetrees.
*/
- oldcontext = MemoryContextSwitchTo(parse_context);
+ oldcontext = MemoryContextSwitchTo(MessageContext);
/*
* Do basic parsing of the query or queries (this should be safe even
parsetree_list = pg_parse_query(query_string);
/*
- * Switch back to execution context to enter the loop.
+ * Switch back to transaction context to enter the loop.
*/
MemoryContextSwitchTo(oldcontext);
/*
- * Run through the parsetree(s) and process each one.
+ * Run through the raw parsetree(s) and process each one.
*/
foreach(parsetree_item, parsetree_list)
{
Node *parsetree = (Node *) lfirst(parsetree_item);
const char *commandTag;
char completionTag[COMPLETION_TAG_BUFSIZE];
- CmdType origCmdType;
- bool foundOriginalQuery = false;
List *querytree_list,
- *querytree_item;
+ *plantree_list;
+ Portal portal;
/*
- * First we set the command-completion tag to the main query (as
- * opposed to each of the others that may be generated by analyze
- * and rewrite). Also set ps_status and do any special
- * start-of-SQL-command processing needed by the destination.
+ * Get the command name for use in status display (it also becomes the
+ * default completion tag, down inside PortalRun). Set ps_status and
+ * do any special start-of-SQL-command processing needed by the
+ * destination.
*/
commandTag = CreateCommandTag(parsetree);
- 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;
- }
-
set_ps_display(commandTag);
BeginCommand(commandTag, dest);
/*
- * If we are in an aborted transaction, ignore all commands except
+ * If we are in an aborted transaction, reject all commands except
* COMMIT/ABORT. It is important that this test occur before we
* try to do parse analysis, rewrite, or planning, since all those
* phases try to do database accesses, which may fail in abort
CHECK_FOR_INTERRUPTS();
/*
- * OK to analyze and rewrite this query.
+ * OK to analyze, rewrite, and plan this query.
*
* Switch to appropriate context for constructing querytrees (again,
* these must outlive the execution context).
*/
- oldcontext = MemoryContextSwitchTo(parse_context);
+ oldcontext = MemoryContextSwitchTo(MessageContext);
querytree_list = pg_analyze_and_rewrite(parsetree, NULL, 0);
+ plantree_list = pg_plan_queries(querytree_list, true);
+
+ /* If we got a cancel signal in analysis or planning, quit */
+ CHECK_FOR_INTERRUPTS();
+
/*
- * Switch back to execution context for planning and execution.
+ * Switch back to transaction context for execution.
*/
MemoryContextSwitchTo(oldcontext);
/*
- * Inner loop handles the individual queries generated from a
- * single parsetree by analysis and rewrite.
+ * Create unnamed portal to run the query or queries in.
+ * If there already is one, silently drop it.
*/
- foreach(querytree_item, querytree_list)
- {
- Query *querytree = (Query *) lfirst(querytree_item);
- bool endTransactionBlock = false;
- bool canSetTag;
-
- /* Make sure we are in a transaction command */
- if (!xact_started)
- {
- start_xact_command();
- xact_started = true;
- }
-
- /*
- * If we got a cancel signal in analysis or prior command,
- * quit
- */
- CHECK_FOR_INTERRUPTS();
-
- /*
- * This query can set the completion tag 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)
- {
- canSetTag = true;
- foundOriginalQuery = true;
- }
- else if (!foundOriginalQuery &&
- querytree->commandType == origCmdType &&
- (querytree->querySource == QSRC_INSTEAD_RULE ||
- querytree->querySource == QSRC_QUAL_INSTEAD_RULE))
- canSetTag = true;
- else
- canSetTag = false;
-
- if (querytree->commandType == CMD_UTILITY)
- {
- /*
- * process utility functions (create, destroy, etc..)
- */
- Node *utilityStmt = querytree->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();
-
- /* end transaction block if transaction or variable stmt */
- if (IsA(utilityStmt, TransactionStmt) ||
- IsA(utilityStmt, VariableSetStmt) ||
- IsA(utilityStmt, VariableShowStmt) ||
- IsA(utilityStmt, VariableResetStmt))
- endTransactionBlock = true;
-
- if (canSetTag)
- {
- /* utility statement can override default tag string */
- ProcessUtility(utilityStmt, dest, completionTag);
- if (completionTag[0])
- commandTag = completionTag;
- }
- else
- {
- /* utility added by rewrite cannot override tag */
- ProcessUtility(utilityStmt, dest, NULL);
- }
- }
- else
- {
- /*
- * process a plannable query.
- */
- Plan *plan;
-
- /*
- * Initialize snapshot state for query. This has to
- * be done before running the planner, because it might
- * try to evaluate immutable or stable functions, which
- * in turn might run queries.
- */
- SetQuerySnapshot();
-
- /* Make the plan */
- plan = pg_plan_query(querytree);
+ portal = CreatePortal("", true, true);
- /* if we got a cancel signal whilst planning, quit */
- CHECK_FOR_INTERRUPTS();
-
- /*
- * execute the plan
- */
- if (log_executor_stats)
- ResetUsage();
-
- if (dontExecute)
- {
- /* don't execute it, just show the query plan */
- print_plan(plan, querytree);
- }
- else
- {
- elog(DEBUG2, "ProcessQuery");
-
- if (canSetTag)
- {
- /* statement can override default tag string */
- ProcessQuery(querytree, plan, dest, completionTag);
- commandTag = completionTag;
- }
- else
- {
- /* stmt added by rewrite cannot override tag */
- ProcessQuery(querytree, plan, dest, NULL);
- }
- }
+ PortalDefineQuery(portal,
+ query_string,
+ commandTag,
+ querytree_list,
+ plantree_list,
+ MessageContext);
- if (log_executor_stats)
- ShowUsage("EXECUTOR STATISTICS");
- }
-
- /*
- * In a query block, we want to increment the command counter
- * between queries so that the effects of early queries are
- * visible to subsequent ones. In particular we'd better do
- * so before checking constraints.
- */
- if (!endTransactionBlock)
- CommandCounterIncrement();
-
- /*
- * Clear the execution context to recover temporary memory
- * used by the query. NOTE: if query string contains
- * BEGIN/COMMIT transaction commands, execution context may
- * now be different from what we were originally passed; so be
- * careful to clear current context not "oldcontext".
- */
- Assert(parse_context != CurrentMemoryContext);
+ /*
+ * Run the portal to completion, and then drop it.
+ */
+ PortalStart(portal, NULL);
- MemoryContextResetAndDeleteChildren(CurrentMemoryContext);
+ (void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
- /*
- * If this was a transaction control statement or a variable
- * set/show/reset statement, commit it and arrange to start a
- * new xact command for the next command (if any).
- */
- if (endTransactionBlock)
- {
- finish_xact_command(true);
- xact_started = false;
- }
- } /* end loop over queries generated from a
- * parsetree */
+ PortalDrop(portal, false);
+ /*
+ * If this was a transaction control statement or a variable
+ * set/show/reset statement, commit it and arrange to start a
+ * new xact command for the next command (if any).
+ */
+ if (IsA(parsetree, TransactionStmt) ||
+ IsA(parsetree, VariableSetStmt) ||
+ IsA(parsetree, VariableShowStmt) ||
+ IsA(parsetree, VariableResetStmt))
+ {
+ finish_xact_command(true);
+ xact_started = false;
+ }
/*
* If this is the last parsetree of the query string, close down
* transaction statement before reporting command-complete. This
* historical Postgres behavior, we do not force a transaction
* boundary between queries appearing in a single query string.
*/
- if ((lnext(parsetree_item) == NIL || !autocommit) && xact_started)
+ else if (lnext(parsetree_item) == NIL || !autocommit)
{
finish_xact_command(false);
xact_started = false;
}
-
- /*
- * It is possible that the original query was removed due to a DO
- * INSTEAD rewrite rule. If so, and if we found no INSTEAD query
- * matching the command type, we will still have the default
- * completion tag. This is fine for most purposes, but it
- * may confuse clients if it's INSERT/UPDATE/DELETE. Clients
- * expect those tags to have counts after them (cf. ProcessQuery).
- */
- if (!foundOriginalQuery)
+ else
{
- if (strcmp(commandTag, "INSERT") == 0)
- commandTag = "INSERT 0 0";
- else if (strcmp(commandTag, "UPDATE") == 0)
- commandTag = "UPDATE 0";
- else if (strcmp(commandTag, "DELETE") == 0)
- commandTag = "DELETE 0";
+ /*
+ * We need a CommandCounterIncrement after every query,
+ * except those that start or end a transaction block.
+ */
+ CommandCounterIncrement();
}
/*
* (But a command aborted by error will not send an EndCommand
* report at all.)
*/
- EndCommand(commandTag, dest);
+ EndCommand(completionTag, dest);
} /* end loop over parsetrees */
- /* No parsetree - return empty result */
+ /*
+ * If there were no parsetrees, return EmptyQueryResponse message.
+ */
if (!parsetree_list)
NullCommand(dest);
/*
- * Close down transaction statement, if one is open. (Note that this
- * will only happen if the querystring was empty.)
+ * Close down transaction statement, if one is open.
*/
if (xact_started)
finish_xact_command(false);
+ /*
+ * Finish up monitoring.
+ */
if (save_log_duration)
{
gettimeofday(&stop_t, NULL);
(long) (stop_t.tv_usec - start_t.tv_usec));
}
+ if (save_log_statement_stats)
+ ShowUsage("QUERY STATISTICS");
+
debug_query_string = NULL;
}
SetConfigOption(tmp, "false", ctx, gucsource);
break;
- case 'i':
- dontExecute = true;
- break;
-
case 'N':
/*
if (!IsUnderPostmaster)
{
puts("\nPOSTGRES backend interactive interface ");
- puts("$Revision: 1.326 $ $Date: 2003/04/29 22:13:11 $\n");
+ puts("$Revision: 1.327 $ $Date: 2003/05/02 20:54:35 $\n");
}
/*
* Create the memory context we will use in the main loop.
*
- * QueryContext is reset once per iteration of the main loop, ie, upon
- * completion of processing of each supplied query string. It can
- * therefore be used for any data that should live just as long as the
- * query string --- parse trees, for example.
+ * MessageContext is reset once per iteration of the main loop, ie, upon
+ * completion of processing of each command message from the client.
*/
- QueryContext = AllocSetContextCreate(TopMemoryContext,
- "QueryContext",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
+ MessageContext = AllocSetContextCreate(TopMemoryContext,
+ "MessageContext",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
/* ----------
* Tell the statistics collector that we're alive and
*/
MemoryContextSwitchTo(TopMemoryContext);
MemoryContextResetAndDeleteChildren(ErrorContext);
+ CurrentPortal = NULL;
+ PortalContext = NULL;
+ QueryContext = NULL;
/*
* Clear flag to indicate that we got out of error recovery mode
{
/*
* Release storage left over from prior query cycle, and create a
- * new query input buffer in the cleared QueryContext.
+ * new query input buffer in the cleared MessageContext.
*/
- MemoryContextSwitchTo(QueryContext);
- MemoryContextResetAndDeleteChildren(QueryContext);
+ MemoryContextSwitchTo(MessageContext);
+ MemoryContextResetAndDeleteChildren(MessageContext);
input_message = makeStringInfo();
{
case 'Q': /* simple query */
{
- /*
- * Process the query string.
- *
- * Note: transaction command start/end is now done within
- * pg_exec_query_string(), not here.
- */
const char *query_string = pq_getmsgstring(input_message);
- if (log_statement_stats)
- ResetUsage();
-
- pgstat_report_activity(query_string);
-
- pg_exec_query_string(query_string,
- whereToSendOutput,
- QueryContext);
-
- if (log_statement_stats)
- ShowUsage("QUERY STATISTICS");
+ exec_simple_query(query_string, whereToSendOutput);
send_rfq = true;
}
pfree(str.data);
}
-
-/* ----------------------------------------------------------------
- * CreateCommandTag
- *
- * utility to get a string representation of the
- * command operation.
- * ----------------------------------------------------------------
- */
-static 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;
-}
*
*
* 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);
/*
/*
* 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)
{
/*
* 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
ExecutorStart(queryDesc);
/*
- * And run the plan.
+ * Run the plan to completion.
*/
ExecutorRun(queryDesc, ForwardScanDirection, 0L);
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;
+}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
+}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
/*****************************************************************************
/*-------------------------------------------------------------------------
*
* 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"
* ----------------
*/
+Portal CurrentPortal = NULL; /* globally visible pointer */
+
+
#define MAX_PORTALNAME_LEN NAMEDATALEN
typedef struct portalhashent
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) \
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) \
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) \
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) \
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;
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.
{
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
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
* references...
*/
void
-AtEOXact_portals(bool isCommit)
+AtCommit_Portals(void)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
{
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
/*
* 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,
}
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);
+ }
+}
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200304071
+#define CATALOG_VERSION_NO 200305011
#endif
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
/* Argument types, if a prepared plan */
int nargs;
Oid *argtypes;
- /* Command type of last original parsetree */
- CmdType origCmdType;
} _SPI_plan;
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
* 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 */
* 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
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
extern void ProcessUtility(Node *parsetree, CommandDest dest,
char *completionTag);
+extern const char *CreateCommandTag(Node *parsetree);
+
#endif /* UTILITY_H */
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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.
- *
- * (3) SCROLL was specified: allow all kinds of backward fetches, even
- * if we need to take a slight performance hit to do so.
+ * 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.
*
- * 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 */
#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 */