From de28dc9a04c4df5d711815b7a518501b43535a26 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 2 May 2003 20:54:36 +0000 Subject: [PATCH] Portal and memory management infrastructure for extended query protocol. Both plannable queries and utility commands are now always executed within Portals, which have been revamped so that they can handle the load (they used to be good only for single SELECT queries). Restructure code to push command-completion-tag selection logic out of postgres.c, so that it won't have to be duplicated between simple and extended queries. initdb forced due to addition of a field to Query nodes. --- contrib/intagg/int_aggregate.c | 52 +- doc/src/sgml/ref/postgres-ref.sgml | 13 +- src/backend/access/transam/xact.c | 54 +- src/backend/commands/cluster.c | 6 +- src/backend/commands/indexcmds.c | 6 +- src/backend/commands/portalcmds.c | 634 ++++------------- src/backend/commands/prepare.c | 113 ++- src/backend/commands/vacuum.c | 8 +- src/backend/executor/execQual.c | 6 +- src/backend/executor/spi.c | 223 ++---- src/backend/executor/tstoreReceiver.c | 20 +- src/backend/nodes/copyfuncs.c | 3 +- src/backend/nodes/equalfuncs.c | 3 +- src/backend/nodes/outfuncs.c | 3 +- src/backend/nodes/readfuncs.c | 3 +- src/backend/optimizer/geqo/geqo_eval.c | 6 +- src/backend/parser/analyze.c | 21 +- src/backend/rewrite/rewriteHandler.c | 52 +- src/backend/tcop/fastpath.c | 4 +- src/backend/tcop/postgres.c | 834 ++++------------------- src/backend/tcop/pquery.c | 906 ++++++++++++++++++++++++- src/backend/tcop/utility.c | 385 ++++++++++- src/backend/utils/mmgr/mcxt.c | 8 +- src/backend/utils/mmgr/portalmem.c | 325 ++++++--- src/include/catalog/catversion.h | 4 +- src/include/commands/portalcmds.h | 9 +- src/include/executor/spi_priv.h | 4 +- src/include/nodes/parsenodes.h | 4 +- src/include/tcop/pquery.h | 23 +- src/include/tcop/tcopprot.h | 3 +- src/include/tcop/utility.h | 4 +- src/include/utils/memutils.h | 8 +- src/include/utils/portal.h | 141 +++- 33 files changed, 2200 insertions(+), 1688 deletions(-) diff --git a/contrib/intagg/int_aggregate.c b/contrib/intagg/int_aggregate.c index 1c14e7c2db..9f4b5d0918 100644 --- a/contrib/intagg/int_aggregate.c +++ b/contrib/intagg/int_aggregate.c @@ -77,7 +77,9 @@ PG_FUNCTION_INFO_V1(int_enum); /* * Manage the aggregation state of the array - * You need to specify the correct memory context, or it will vanish! + * + * Need to specify a suitably long-lived memory context, or it will vanish! + * PortalContext isn't really right, but it's close enough. */ static PGARRAY * GetPGArray(int4 state, int fAdd) @@ -89,14 +91,7 @@ GetPGArray(int4 state, int fAdd) /* New array */ int cb = PGARRAY_SIZE(START_NUM); - p = (PGARRAY *) MemoryContextAlloc(TopTransactionContext, cb); - - if (!p) - { - elog(ERROR, "Integer aggregator, cant allocate TopTransactionContext memory"); - return 0; - } - + p = (PGARRAY *) MemoryContextAlloc(PortalContext, cb); p->a.size = cb; p->a.ndim = 0; p->a.flags = 0; @@ -115,18 +110,6 @@ GetPGArray(int4 state, int fAdd) int cbNew = PGARRAY_SIZE(n); pn = (PGARRAY *) repalloc(p, cbNew); - - if (!pn) - { /* Realloc failed! Reallocate new block. */ - pn = (PGARRAY *) MemoryContextAlloc(TopTransactionContext, cbNew); - if (!pn) - { - elog(ERROR, "Integer aggregator, REALLY REALLY can't alloc memory"); - return (PGARRAY *) NULL; - } - memcpy(pn, p, p->a.size); - pfree(p); - } pn->a.size = cbNew; pn->lower = n; return pn; @@ -149,24 +132,19 @@ ShrinkPGArray(PGARRAY * p) /* use current transaction context */ pnew = palloc(cb); - - if (pnew) - { - /* - * Fix up the fields in the new structure, so Postgres - * understands - */ - memcpy(pnew, p, cb); - pnew->a.size = cb; - pnew->a.ndim = 1; - pnew->a.flags = 0; + /* + * Fix up the fields in the new structure, so Postgres + * understands + */ + memcpy(pnew, p, cb); + pnew->a.size = cb; + pnew->a.ndim = 1; + pnew->a.flags = 0; #ifndef PG_7_2 - pnew->a.elemtype = INT4OID; + pnew->a.elemtype = INT4OID; #endif - pnew->lower = 0; - } - else - elog(ERROR, "Integer aggregator, can't allocate memory"); + pnew->lower = 0; + pfree(p); } return pnew; diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml index 6532cc1c89..e09c523b1a 100644 --- a/doc/src/sgml/ref/postgres-ref.sgml +++ b/doc/src/sgml/ref/postgres-ref.sgml @@ -1,5 +1,5 @@ @@ -28,7 +28,6 @@ PostgreSQL documentation -E -fsitnmh -F - -i -N -o filename -O @@ -52,7 +51,6 @@ PostgreSQL documentation -e -fsitnmh -F - -i -o filename -O -p database @@ -281,15 +279,6 @@ PostgreSQL documentation - - - - - Prevents query execution, but shows the plan tree. - - - - diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index a15985d3bd..59b43656d5 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.146 2003/04/26 20:22:59 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.147 2003/05/02 20:54:33 tgl Exp $ * * NOTES * Transaction aborts can now occur two ways: @@ -49,7 +49,7 @@ * * CleanupTransaction() executes when we finally see a user COMMIT * or ROLLBACK command; it cleans things up and gets us out of * the transaction internally. In particular, we mustn't destroy - * TransactionCommandContext until this point. + * TopTransactionContext until this point. * * NOTES * The essential aspects of the transaction system are: @@ -456,13 +456,12 @@ static void AtStart_Memory(void) { /* - * We shouldn't have any transaction contexts already. + * We shouldn't have a transaction context already. */ Assert(TopTransactionContext == NULL); - Assert(TransactionCommandContext == NULL); /* - * Create a toplevel context for the transaction. + * Create a toplevel context for the transaction, and make it active. */ TopTransactionContext = AllocSetContextCreate(TopMemoryContext, @@ -471,16 +470,7 @@ AtStart_Memory(void) ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - /* - * Create a statement-level context and make it active. - */ - TransactionCommandContext = - AllocSetContextCreate(TopTransactionContext, - "TransactionCommandContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); - MemoryContextSwitchTo(TransactionCommandContext); + MemoryContextSwitchTo(TopTransactionContext); } @@ -659,7 +649,6 @@ AtCommit_Memory(void) Assert(TopTransactionContext != NULL); MemoryContextDelete(TopTransactionContext); TopTransactionContext = NULL; - TransactionCommandContext = NULL; } /* ---------------------------------------------------------------- @@ -769,19 +758,18 @@ AtAbort_Memory(void) { /* * Make sure we are in a valid context (not a child of - * TransactionCommandContext...). Note that it is possible for this + * TopTransactionContext...). Note that it is possible for this * code to be called when we aren't in a transaction at all; go * directly to TopMemoryContext in that case. */ - if (TransactionCommandContext != NULL) + if (TopTransactionContext != NULL) { - MemoryContextSwitchTo(TransactionCommandContext); + MemoryContextSwitchTo(TopTransactionContext); /* - * We do not want to destroy transaction contexts yet, but it - * should be OK to delete any command-local memory. + * We do not want to destroy the transaction's global state yet, + * so we can't free any memory here. */ - MemoryContextResetAndDeleteChildren(TransactionCommandContext); } else MemoryContextSwitchTo(TopMemoryContext); @@ -812,7 +800,6 @@ AtCleanup_Memory(void) if (TopTransactionContext != NULL) MemoryContextDelete(TopTransactionContext); TopTransactionContext = NULL; - TransactionCommandContext = NULL; } @@ -926,7 +913,7 @@ CommitTransaction(void) * access, and in fact could still cause an error...) */ - AtEOXact_portals(true); + AtCommit_Portals(); /* handle commit for large objects [ PA, 7/17/98 ] */ /* XXX probably this does not belong here */ @@ -1057,7 +1044,7 @@ AbortTransaction(void) * do abort processing */ DeferredTriggerAbortXact(); - AtEOXact_portals(false); + AtAbort_Portals(); lo_commit(false); /* 'false' means it's abort */ AtAbort_Notify(); AtEOXact_UpdatePasswordFile(false); @@ -1126,7 +1113,8 @@ CleanupTransaction(void) /* * do abort cleanup processing */ - AtCleanup_Memory(); + AtCleanup_Portals(); /* now safe to release portal memory */ + AtCleanup_Memory(); /* and transaction memory */ /* * done with abort processing, set current transaction state back to @@ -1220,11 +1208,11 @@ StartTransactionCommand(bool preventChain) } /* - * We must switch to TransactionCommandContext before returning. This + * We must switch to TopTransactionContext before returning. This * is already done if we called StartTransaction, otherwise not. */ - Assert(TransactionCommandContext != NULL); - MemoryContextSwitchTo(TransactionCommandContext); + Assert(TopTransactionContext != NULL); + MemoryContextSwitchTo(TopTransactionContext); } /* @@ -1266,7 +1254,6 @@ CommitTransactionCommand(bool forceCommit) Assert(s->blockState == TBLOCK_INPROGRESS); /* This code must match the TBLOCK_INPROGRESS case below: */ CommandCounterIncrement(); - MemoryContextResetAndDeleteChildren(TransactionCommandContext); } break; @@ -1283,15 +1270,10 @@ CommitTransactionCommand(bool forceCommit) /* * This is the case when we have finished executing a command * someplace within a transaction block. We increment the - * command counter and return. Someday we may free resources - * local to the command. - * - * That someday is today, at least for memory allocated in - * TransactionCommandContext. - vadim 03/25/97 + * command counter and return. */ case TBLOCK_INPROGRESS: CommandCounterIncrement(); - MemoryContextResetAndDeleteChildren(TransactionCommandContext); break; /* diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 0c1a7d3c70..6c01094c5a 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.107 2003/03/20 03:34:55 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.108 2003/05/02 20:54:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -189,10 +189,10 @@ cluster(ClusterStmt *stmt) /* * Create special memory context for cross-transaction storage. * - * Since it is a child of QueryContext, it will go away even in case + * Since it is a child of PortalContext, it will go away even in case * of error. */ - cluster_context = AllocSetContextCreate(QueryContext, + cluster_context = AllocSetContextCreate(PortalContext, "Cluster", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 10d240d6d1..5232faadc5 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.97 2003/01/23 15:18:40 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/indexcmds.c,v 1.98 2003/05/02 20:54:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -720,11 +720,11 @@ ReindexDatabase(const char *dbname, bool force, bool all) /* * Create a memory context that will survive forced transaction - * commits we do below. Since it is a child of QueryContext, it will + * commits we do below. Since it is a child of PortalContext, it will * go away eventually even if we suffer an error; there's no need for * special abort cleanup logic. */ - private_context = AllocSetContextCreate(QueryContext, + private_context = AllocSetContextCreate(PortalContext, "ReindexDatabase", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 9dc7583f06..35ed8a270b 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -1,14 +1,20 @@ /*------------------------------------------------------------------------- * * portalcmds.c - * portal support code + * Utility commands affecting portals (that is, SQL cursor commands) + * + * Note: see also tcop/pquery.c, which implements portal operations for + * the FE/BE protocol. This module uses pquery.c for some operations. + * And both modules depend on utils/mmgr/portalmem.c, which controls + * storage management for portals (but doesn't run any queries in them). + * * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.12 2003/04/29 03:21:29 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/portalcmds.c,v 1.13 2003/05/02 20:54:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,18 +28,10 @@ #include "executor/executor.h" #include "optimizer/planner.h" #include "rewrite/rewriteHandler.h" +#include "tcop/pquery.h" #include "utils/memutils.h" -static long DoRelativeFetch(Portal portal, - bool forward, - long count, - CommandDest dest); -static uint32 RunFromStore(Portal portal, ScanDirection direction, long count); -static void DoPortalRewind(Portal portal); -static Portal PreparePortal(DeclareCursorStmt *stmt); - - /* * PerformCursorOpen * Execute SQL DECLARE CURSOR command. @@ -46,8 +44,13 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest) Plan *plan; Portal portal; MemoryContext oldContext; - char *cursorName; - QueryDesc *queryDesc; + + /* + * Disallow empty-string cursor name (conflicts with protocol-level + * unnamed portal). + */ + if (strlen(stmt->portalname) == 0) + elog(ERROR, "Invalid cursor name: must not be empty"); /* * If this is a non-holdable cursor, we require that this statement @@ -77,38 +80,53 @@ PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest) plan = planner(query, true, stmt->options); - /* If binary cursor, switch to alternate output format */ - if ((stmt->options & CURSOR_OPT_BINARY) && dest == Remote) - dest = RemoteInternal; - /* * Create a portal and copy the query and plan into its memory context. + * (If a duplicate cursor name already exists, warn and drop it.) */ - portal = PreparePortal(stmt); + portal = CreatePortal(stmt->portalname, true, false); oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); + query = copyObject(query); plan = copyObject(plan); + 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); } /* @@ -130,10 +148,6 @@ PerformPortalFetch(FetchStmt *stmt, Portal portal; long nprocessed; - /* initialize completion status in case of early exit */ - if (completionTag) - strcpy(completionTag, stmt->ismove ? "MOVE 0" : "FETCH 0"); - /* get the portal from the portal name */ portal = GetPortalByName(stmt->portalname); if (!PortalIsValid(portal)) @@ -141,14 +155,27 @@ PerformPortalFetch(FetchStmt *stmt, /* FIXME: shouldn't this be an ERROR? */ elog(WARNING, "PerformPortalFetch: portal \"%s\" not found", stmt->portalname); + if (completionTag) + strcpy(completionTag, stmt->ismove ? "MOVE 0" : "FETCH 0"); return; } + /* + * Adjust dest if needed. MOVE wants dest = None. + * + * If fetching from a binary cursor and the requested destination is + * Remote, change it to RemoteInternal. + */ + if (stmt->ismove) + dest = None; + else if (dest == Remote && (portal->cursorOptions & CURSOR_OPT_BINARY)) + dest = RemoteInternal; + /* Do it */ - nprocessed = DoPortalFetch(portal, - stmt->direction, - stmt->howMany, - stmt->ismove ? None : dest); + nprocessed = PortalRunFetch(portal, + stmt->direction, + stmt->howMany, + dest); /* Return command status if wanted */ if (completionTag) @@ -157,416 +184,6 @@ PerformPortalFetch(FetchStmt *stmt, nprocessed); } -/* - * DoPortalFetch - * Guts of PerformPortalFetch --- shared with SPI cursor operations. - * Caller must already have validated the Portal. - * - * Returns number of rows processed (suitable for use in result tag) - */ -long -DoPortalFetch(Portal portal, - FetchDirection fdirection, - long count, - CommandDest dest) -{ - bool forward; - - switch (fdirection) - { - case FETCH_FORWARD: - if (count < 0) - { - fdirection = FETCH_BACKWARD; - count = -count; - } - /* fall out of switch to share code with FETCH_BACKWARD */ - break; - case FETCH_BACKWARD: - if (count < 0) - { - fdirection = FETCH_FORWARD; - count = -count; - } - /* fall out of switch to share code with FETCH_FORWARD */ - break; - case FETCH_ABSOLUTE: - if (count > 0) - { - /* - * Definition: Rewind to start, advance count-1 rows, return - * next row (if any). In practice, if the goal is less than - * halfway back to the start, it's better to scan from where - * we are. In any case, we arrange to fetch the target row - * going forwards. - */ - if (portal->posOverflow || portal->portalPos == LONG_MAX || - count-1 <= portal->portalPos / 2) - { - DoPortalRewind(portal); - if (count > 1) - DoRelativeFetch(portal, true, count-1, None); - } - else - { - long pos = portal->portalPos; - - if (portal->atEnd) - pos++; /* need one extra fetch if off end */ - if (count <= pos) - DoRelativeFetch(portal, false, pos-count+1, None); - else if (count > pos+1) - DoRelativeFetch(portal, true, count-pos-1, None); - } - return DoRelativeFetch(portal, true, 1L, dest); - } - else if (count < 0) - { - /* - * Definition: Advance to end, back up abs(count)-1 rows, - * return prior row (if any). We could optimize this if we - * knew in advance where the end was, but typically we won't. - * (Is it worth considering case where count > half of size - * of query? We could rewind once we know the size ...) - */ - DoRelativeFetch(portal, true, FETCH_ALL, None); - if (count < -1) - DoRelativeFetch(portal, false, -count-1, None); - return DoRelativeFetch(portal, false, 1L, dest); - } - else /* count == 0 */ - { - /* Rewind to start, return zero rows */ - DoPortalRewind(portal); - return DoRelativeFetch(portal, true, 0L, dest); - } - break; - case FETCH_RELATIVE: - if (count > 0) - { - /* - * Definition: advance count-1 rows, return next row (if any). - */ - if (count > 1) - DoRelativeFetch(portal, true, count-1, None); - return DoRelativeFetch(portal, true, 1L, dest); - } - else if (count < 0) - { - /* - * Definition: back up abs(count)-1 rows, return prior row - * (if any). - */ - if (count < -1) - DoRelativeFetch(portal, false, -count-1, None); - return DoRelativeFetch(portal, false, 1L, dest); - } - else /* count == 0 */ - { - /* Same as FETCH FORWARD 0, so fall out of switch */ - fdirection = FETCH_FORWARD; - } - break; - default: - elog(ERROR, "DoPortalFetch: bogus direction"); - break; - } - - /* - * Get here with fdirection == FETCH_FORWARD or FETCH_BACKWARD, - * and count >= 0. - */ - forward = (fdirection == FETCH_FORWARD); - - /* - * Zero count means to re-fetch the current row, if any (per SQL92) - */ - if (count == 0) - { - bool on_row; - - /* Are we sitting on a row? */ - on_row = (!portal->atStart && !portal->atEnd); - - if (dest == None) - { - /* MOVE 0 returns 0/1 based on if FETCH 0 would return a row */ - return on_row ? 1L : 0L; - } - else - { - /* - * If we are sitting on a row, back up one so we can re-fetch it. - * If we are not sitting on a row, we still have to start up and - * shut down the executor so that the destination is initialized - * and shut down correctly; so keep going. To DoRelativeFetch, - * count == 0 means we will retrieve no row. - */ - if (on_row) - { - DoRelativeFetch(portal, false, 1L, None); - /* Set up to fetch one row forward */ - count = 1; - forward = true; - } - } - } - - /* - * Optimize MOVE BACKWARD ALL into a Rewind. - */ - if (!forward && count == FETCH_ALL && dest == None) - { - long result = portal->portalPos; - - if (result > 0 && !portal->atEnd) - result--; - DoPortalRewind(portal); - /* result is bogus if pos had overflowed, but it's best we can do */ - return result; - } - - return DoRelativeFetch(portal, forward, count, dest); -} - -/* - * DoRelativeFetch - * Do fetch for a simple N-rows-forward-or-backward case. - * - * count <= 0 is interpreted as a no-op: the destination gets started up - * and shut down, but nothing else happens. Also, count == FETCH_ALL is - * interpreted as "all rows". - * - * Caller must already have validated the Portal. - * - * Returns number of rows processed (suitable for use in result tag) - */ -static long -DoRelativeFetch(Portal portal, - bool forward, - long count, - CommandDest dest) -{ - QueryDesc *queryDesc; - QueryDesc temp_queryDesc; - ScanDirection direction; - uint32 nprocessed; - - queryDesc = PortalGetQueryDesc(portal); - - /* - * If the requested destination is not the same as the query's - * original destination, make a temporary QueryDesc with the proper - * destination. This supports MOVE, for example, which will pass in - * dest = None. - * - * EXCEPTION: if the query's original dest is RemoteInternal (ie, it's a - * binary cursor) and the request is Remote, we do NOT override the - * original dest. This is necessary since a FETCH command will pass - * dest = Remote, not knowing whether the cursor is binary or not. - */ - if (dest != queryDesc->dest && - !(queryDesc->dest == RemoteInternal && dest == Remote)) - { - memcpy(&temp_queryDesc, queryDesc, sizeof(QueryDesc)); - temp_queryDesc.dest = dest; - queryDesc = &temp_queryDesc; - } - - /* - * Determine which direction to go in, and check to see if we're - * already at the end of the available tuples in that direction. If - * so, set the direction to NoMovement to avoid trying to fetch any - * tuples. (This check exists because not all plan node types are - * robust about being called again if they've already returned NULL - * once.) Then call the executor (we must not skip this, because the - * destination needs to see a setup and shutdown even if no tuples are - * available). Finally, update the portal position state depending on - * the number of tuples that were retrieved. - */ - if (forward) - { - if (portal->atEnd || count <= 0) - direction = NoMovementScanDirection; - else - direction = ForwardScanDirection; - - /* In the executor, zero count processes all rows */ - if (count == FETCH_ALL) - count = 0; - - if (portal->holdStore) - nprocessed = RunFromStore(portal, direction, count); - else - { - Assert(portal->executorRunning); - ExecutorRun(queryDesc, direction, count); - nprocessed = queryDesc->estate->es_processed; - } - - if (direction != NoMovementScanDirection) - { - long oldPos; - - if (nprocessed > 0) - portal->atStart = false; /* OK to go backward now */ - if (count == 0 || - (unsigned long) nprocessed < (unsigned long) count) - portal->atEnd = true; /* we retrieved 'em all */ - oldPos = portal->portalPos; - portal->portalPos += nprocessed; - /* portalPos doesn't advance when we fall off the end */ - if (portal->portalPos < oldPos) - portal->posOverflow = true; - } - } - else - { - if (portal->scrollType == DISABLE_SCROLL) - elog(ERROR, "Cursor can only scan forward" - "\n\tDeclare it with SCROLL option to enable backward scan"); - - if (portal->atStart || count <= 0) - direction = NoMovementScanDirection; - else - direction = BackwardScanDirection; - - /* In the executor, zero count processes all rows */ - if (count == FETCH_ALL) - count = 0; - - if (portal->holdStore) - nprocessed = RunFromStore(portal, direction, count); - else - { - Assert(portal->executorRunning); - ExecutorRun(queryDesc, direction, count); - nprocessed = queryDesc->estate->es_processed; - } - - if (direction != NoMovementScanDirection) - { - if (nprocessed > 0 && portal->atEnd) - { - portal->atEnd = false; /* OK to go forward now */ - portal->portalPos++; /* adjust for endpoint case */ - } - if (count == 0 || - (unsigned long) nprocessed < (unsigned long) count) - { - portal->atStart = true; /* we retrieved 'em all */ - portal->portalPos = 0; - portal->posOverflow = false; - } - else - { - long oldPos; - - oldPos = portal->portalPos; - portal->portalPos -= nprocessed; - if (portal->portalPos > oldPos || - portal->portalPos <= 0) - portal->posOverflow = true; - } - } - } - - return nprocessed; -} - -/* - * RunFromStore - * Fetch tuples from the portal's tuple store. - * - * Calling conventions are similar to ExecutorRun, except that we - * do not depend on having an estate, and therefore return the number - * of tuples processed as the result, not in estate->es_processed. - * - * One difference from ExecutorRun is that the destination receiver functions - * are run in the caller's memory context (since we have no estate). Watch - * out for memory leaks. - */ -static uint32 -RunFromStore(Portal portal, ScanDirection direction, long count) -{ - QueryDesc *queryDesc = PortalGetQueryDesc(portal); - DestReceiver *destfunc; - long current_tuple_count = 0; - - destfunc = DestToFunction(queryDesc->dest); - (*destfunc->setup) (destfunc, queryDesc->operation, - queryDesc->portalName, queryDesc->tupDesc); - - if (direction == NoMovementScanDirection) - { - /* do nothing except start/stop the destination */ - } - else - { - bool forward = (direction == ForwardScanDirection); - - for (;;) - { - MemoryContext oldcontext; - HeapTuple tup; - bool should_free; - - oldcontext = MemoryContextSwitchTo(portal->holdContext); - - tup = tuplestore_getheaptuple(portal->holdStore, forward, - &should_free); - - MemoryContextSwitchTo(oldcontext); - - if (tup == NULL) - break; - - (*destfunc->receiveTuple) (tup, queryDesc->tupDesc, destfunc); - - if (should_free) - pfree(tup); - - /* - * check our tuple count.. if we've processed the proper number - * then quit, else loop again and process more tuples. Zero - * count means no limit. - */ - current_tuple_count++; - if (count && count == current_tuple_count) - break; - } - } - - (*destfunc->cleanup) (destfunc); - - return (uint32) current_tuple_count; -} - -/* - * DoPortalRewind - rewind a Portal to starting point - */ -static void -DoPortalRewind(Portal portal) -{ - if (portal->holdStore) - { - MemoryContext oldcontext; - - oldcontext = MemoryContextSwitchTo(portal->holdContext); - tuplestore_rescan(portal->holdStore); - MemoryContextSwitchTo(oldcontext); - } - if (portal->executorRunning) - { - ExecutorRewind(PortalGetQueryDesc(portal)); - } - - portal->atStart = true; - portal->atEnd = false; - portal->portalPos = 0; - portal->posOverflow = false; -} - /* * PerformPortalClose * Close a cursor. @@ -593,53 +210,6 @@ PerformPortalClose(char *name) PortalDrop(portal, false); } -/* - * PreparePortal - * Given a DECLARE CURSOR statement, returns the Portal data - * structure based on that statement that is used to manage the - * Portal internally. If a portal with specified name already - * exists, it is replaced. - */ -static Portal -PreparePortal(DeclareCursorStmt *stmt) -{ - Portal portal; - - /* - * Check for already-in-use portal name. - */ - portal = GetPortalByName(stmt->portalname); - if (PortalIsValid(portal)) - { - /* - * XXX Should we raise an error rather than closing the old - * portal? - */ - elog(WARNING, "Closing pre-existing portal \"%s\"", - stmt->portalname); - PortalDrop(portal, false); - } - - /* - * Create the new portal. - */ - portal = CreatePortal(stmt->portalname); - - /* - * Modify the newly created portal based on the options specified in - * the DECLARE CURSOR statement. - */ - if (stmt->options & CURSOR_OPT_SCROLL) - portal->scrollType = ENABLE_SCROLL; - else if (stmt->options & CURSOR_OPT_NO_SCROLL) - portal->scrollType = DISABLE_SCROLL; - - if (stmt->options & CURSOR_OPT_HOLD) - portal->holdOpen = true; - - return portal; -} - /* * PortalCleanup * @@ -649,6 +219,8 @@ PreparePortal(DeclareCursorStmt *stmt) void PortalCleanup(Portal portal, bool isError) { + QueryDesc *queryDesc; + /* * sanity checks */ @@ -658,7 +230,8 @@ PortalCleanup(Portal portal, bool isError) /* * Delete tuplestore if present. (Note: portalmem.c is responsible * for removing holdContext.) We should do this even under error - * conditions. + * conditions; since the tuplestore would have been using cross- + * transaction storage, its temp files need to be explicitly deleted. */ if (portal->holdStore) { @@ -674,11 +247,12 @@ PortalCleanup(Portal portal, bool isError) * abort, since other mechanisms will take care of releasing executor * resources, and we can't be sure that ExecutorEnd itself wouldn't fail. */ - if (portal->executorRunning) + queryDesc = PortalGetQueryDesc(portal); + if (queryDesc) { - portal->executorRunning = false; + portal->queryDesc = NULL; if (!isError) - ExecutorEnd(PortalGetQueryDesc(portal)); + ExecutorEnd(queryDesc); } } @@ -694,9 +268,10 @@ void PersistHoldablePortal(Portal portal) { QueryDesc *queryDesc = PortalGetQueryDesc(portal); + Portal saveCurrentPortal; + MemoryContext savePortalContext; + MemoryContext saveQueryContext; MemoryContext oldcxt; - CommandDest olddest; - TupleDesc tupdesc; /* * If we're preserving a holdable portal, we had better be @@ -704,6 +279,9 @@ PersistHoldablePortal(Portal portal) */ Assert(portal->createXact == GetCurrentTransactionId()); Assert(portal->holdStore == NULL); + Assert(queryDesc != NULL); + Assert(portal->portalReady); + Assert(!portal->portalDone); /* * This context is used to store the tuple set. @@ -715,6 +293,34 @@ PersistHoldablePortal(Portal portal) /* XXX: Should SortMem be used for this? */ portal->holdStore = tuplestore_begin_heap(true, true, SortMem); + /* + * Before closing down the executor, we must copy the tupdesc, since + * it was created in executor memory. Note we are copying it into + * the holdContext. + */ + portal->tupDesc = CreateTupleDescCopy(portal->tupDesc); + + MemoryContextSwitchTo(oldcxt); + + /* + * Check for improper portal use, and mark portal active. + */ + if (portal->portalActive) + elog(ERROR, "Portal \"%s\" already active", portal->name); + portal->portalActive = true; + + /* + * Set global portal and context pointers. + */ + saveCurrentPortal = CurrentPortal; + CurrentPortal = portal; + savePortalContext = PortalContext; + PortalContext = PortalGetHeapMemory(portal); + saveQueryContext = QueryContext; + QueryContext = portal->queryContext; + + MemoryContextSwitchTo(PortalContext); + /* * Rewind the executor: we need to store the entire result set in * the tuplestore, so that subsequent backward FETCHs can be @@ -723,40 +329,40 @@ PersistHoldablePortal(Portal portal) ExecutorRewind(queryDesc); /* Set the destination to output to the tuplestore */ - olddest = queryDesc->dest; queryDesc->dest = Tuplestore; /* Fetch the result set into the tuplestore */ ExecutorRun(queryDesc, ForwardScanDirection, 0L); - queryDesc->dest = olddest; - - /* - * Before closing down the executor, we must copy the tupdesc, since - * it was created in executor memory. - */ - tupdesc = CreateTupleDescCopy(queryDesc->tupDesc); - /* * Now shut down the inner executor. */ - portal->executorRunning = false; + portal->queryDesc = NULL; /* prevent double shutdown */ ExecutorEnd(queryDesc); - /* ExecutorEnd clears this, so must wait to save copied pointer */ - queryDesc->tupDesc = tupdesc; + /* Mark portal not active */ + portal->portalActive = false; + + CurrentPortal = saveCurrentPortal; + PortalContext = savePortalContext; + QueryContext = saveQueryContext; /* * Reset the position in the result set: ideally, this could be * implemented by just skipping straight to the tuple # that we need * to be at, but the tuplestore API doesn't support that. So we * start at the beginning of the tuplestore and iterate through it - * until we reach where we need to be. + * until we reach where we need to be. FIXME someday? */ + MemoryContextSwitchTo(portal->holdContext); + if (!portal->atEnd) { long store_pos; + if (portal->posOverflow) /* oops, cannot trust portalPos */ + elog(ERROR, "Unable to reposition held cursor"); + tuplestore_rescan(portal->holdStore); for (store_pos = 0; store_pos < portal->portalPos; store_pos++) @@ -777,4 +383,12 @@ PersistHoldablePortal(Portal portal) } MemoryContextSwitchTo(oldcxt); + + /* + * We can now release any subsidiary memory of the portal's heap + * context; we'll never use it again. The executor already dropped + * its context, but this will clean up anything that glommed onto + * the portal's heap via PortalContext. + */ + MemoryContextDeleteChildren(PortalGetHeapMemory(portal)); } diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index f6cd5d0a80..5a3e3f589d 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -6,7 +6,7 @@ * Copyright (c) 2002, PostgreSQL Global Development Group * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.13 2003/02/02 23:46:38 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/prepare.c,v 1.14 2003/05/02 20:54:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -59,9 +59,8 @@ static ParamListInfo EvaluateParams(EState *estate, void PrepareQuery(PrepareStmt *stmt) { - List *plan_list = NIL; List *query_list, - *query_list_item; + *plan_list; if (!stmt->name) elog(ERROR, "No statement name given"); @@ -69,19 +68,18 @@ PrepareQuery(PrepareStmt *stmt) if (stmt->query->commandType == CMD_UTILITY) elog(ERROR, "Utility statements cannot be prepared"); + /* + * Parse analysis is already done, but we must still rewrite and plan + * the query. + */ + /* Rewrite the query. The result could be 0, 1, or many queries. */ query_list = QueryRewrite(stmt->query); - foreach(query_list_item, query_list) - { - Query *query = (Query *) lfirst(query_list_item); - Plan *plan; - - plan = pg_plan_query(query); - - plan_list = lappend(plan_list, plan); - } + /* Generate plans for queries. Snapshot is already set. */ + plan_list = pg_plan_queries(query_list, false); + /* Save the results. */ StoreQuery(stmt->name, query_list, plan_list, stmt->argtype_oids); } @@ -92,17 +90,19 @@ void ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) { QueryHashEntry *entry; - List *l, - *query_list, + List *query_list, *plan_list; + MemoryContext qcontext; ParamListInfo paramLI = NULL; EState *estate = NULL; + Portal portal; /* Look it up in the hash table */ entry = FetchQuery(stmt->name); query_list = entry->query_list; plan_list = entry->plan_list; + qcontext = entry->context; Assert(length(query_list) == length(plan_list)); @@ -117,57 +117,53 @@ ExecuteQuery(ExecuteStmt *stmt, CommandDest outputDest) 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); @@ -377,8 +373,11 @@ DeallocateQuery(DeallocateStmt *stmt) /* Find the query's hash table entry */ entry = FetchQuery(stmt->name); - /* Flush the context holding the subsidiary data */ + /* Drop any open portals that depend on this prepared statement */ Assert(MemoryContextIsValid(entry->context)); + DropDependentPortals(entry->context); + + /* Flush the context holding the subsidiary data */ MemoryContextDelete(entry->context); /* Now we can remove the hash table entry */ diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 03bd3de328..9667eabb8b 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.251 2003/03/04 21:51:20 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.252 2003/05/02 20:54:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -189,11 +189,11 @@ vacuum(VacuumStmt *vacstmt) /* * Create special memory context for cross-transaction storage. * - * Since it is a child of QueryContext, it will go away eventually even + * Since it is a child of PortalContext, it will go away eventually even * if we suffer an error; there's no need for special abort cleanup * logic. */ - vac_context = AllocSetContextCreate(QueryContext, + vac_context = AllocSetContextCreate(PortalContext, "Vacuum", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, @@ -205,7 +205,7 @@ vacuum(VacuumStmt *vacstmt) * lifetime. */ if (vacstmt->analyze && !vacstmt->vacuum) - anl_context = AllocSetContextCreate(QueryContext, + anl_context = AllocSetContextCreate(PortalContext, "Analyze", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 8c6a7c04b6..a2d0a8346f 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.128 2003/04/08 23:20:00 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execQual.c,v 1.129 2003/05/02 20:54:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -377,7 +377,7 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) * XXX this is a horrid crock: since the pointer to the slot might live * longer than the current evaluation context, we are forced to copy * the tuple and slot into a long-lived context --- we use - * TransactionCommandContext which should be safe enough. This + * the econtext's per-query memory which should be safe enough. This * represents a serious memory leak if many such tuples are processed * in one command, however. We ought to redesign the representation * of whole-tuple datums so that this is not necessary. @@ -391,7 +391,7 @@ ExecEvalVar(Var *variable, ExprContext *econtext, bool *isNull) TupleTableSlot *tempSlot; HeapTuple tup; - oldContext = MemoryContextSwitchTo(TransactionCommandContext); + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); tempSlot = MakeTupleTableSlot(); tup = heap_copytuple(heapTuple); ExecStoreTuple(tup, tempSlot, InvalidBuffer, true); diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index aca27d0aeb..3b1e6c4bb3 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.93 2003/04/29 22:13:09 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/spi.c,v 1.94 2003/05/02 20:54:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,7 +16,6 @@ #include "access/printtup.h" #include "catalog/heap.h" -#include "commands/portalcmds.h" #include "executor/spi_priv.h" #include "tcop/tcopprot.h" #include "utils/lsyscache.h" @@ -91,7 +90,13 @@ SPI_connect(void) _SPI_current->processed = 0; _SPI_current->tuptable = NULL; - /* Create memory contexts for this procedure */ + /* + * Create memory contexts for this procedure + * + * XXX it would be better to use PortalContext as the parent context, + * but we may not be inside a portal (consider deferred-trigger + * execution). + */ _SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext, "SPI Proc", ALLOCSET_DEFAULT_MINSIZE, @@ -703,18 +708,14 @@ SPI_freetuptable(SPITupleTable *tuptable) Portal SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls) { - static int unnamed_portal_count = 0; - _SPI_plan *spiplan = (_SPI_plan *) plan; List *qtlist = spiplan->qtlist; List *ptlist = spiplan->ptlist; Query *queryTree; Plan *planTree; ParamListInfo paramLI; - QueryDesc *queryDesc; MemoryContext oldcontext; Portal portal; - char portalname[64]; int k; /* Ensure that the plan contains only one regular SELECT query */ @@ -737,32 +738,18 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls) _SPI_current->processed = 0; _SPI_current->tuptable = NULL; - if (name == NULL) + /* Create the portal */ + if (name == NULL || name[0] == '\0') { - /* Make up a portal name if none given */ - for (;;) - { - unnamed_portal_count++; - if (unnamed_portal_count < 0) - unnamed_portal_count = 0; - sprintf(portalname, "", unnamed_portal_count); - if (GetPortalByName(portalname) == NULL) - break; - } - - name = portalname; + /* Use a random nonconflicting name */ + portal = CreateNewPortal(); } else { - /* Ensure the portal doesn't exist already */ - portal = GetPortalByName(name); - if (portal != NULL) - elog(ERROR, "cursor \"%s\" already in use", name); + /* In this path, error if portal of same name already exists */ + portal = CreatePortal(name, false, false); } - /* Create the portal */ - portal = CreatePortal(name); - /* Switch to portals memory and copy the parsetree and plan to there */ oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); queryTree = copyObject(queryTree); @@ -801,18 +788,33 @@ SPI_cursor_open(const char *name, void *plan, Datum *Values, const char *Nulls) else paramLI = NULL; - /* Create the QueryDesc object */ - queryDesc = CreateQueryDesc(queryTree, planTree, SPI, pstrdup(name), - paramLI, false); + /* + * Set up the portal. + */ + PortalDefineQuery(portal, + NULL, /* unfortunately don't have sourceText */ + "SELECT", /* cursor's query is always a SELECT */ + makeList1(queryTree), + makeList1(planTree), + PortalGetHeapMemory(portal)); - /* Start the executor */ - ExecutorStart(queryDesc); + 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; @@ -1008,39 +1010,15 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan) foreach(list_item, raw_parsetree_list) { Node *parsetree = (Node *) lfirst(list_item); - CmdType origCmdType; - bool foundOriginalQuery = false; List *query_list; List *query_list_item; - switch (nodeTag(parsetree)) - { - case T_InsertStmt: - origCmdType = CMD_INSERT; - break; - case T_DeleteStmt: - origCmdType = CMD_DELETE; - break; - case T_UpdateStmt: - origCmdType = CMD_UPDATE; - break; - case T_SelectStmt: - origCmdType = CMD_SELECT; - break; - default: - /* Otherwise, never match commandType */ - origCmdType = CMD_UNKNOWN; - break; - } - - if (plan) - plan->origCmdType = origCmdType; - query_list = pg_analyze_and_rewrite(parsetree, argtypes, nargs); query_list_list = lappend(query_list_list, query_list); /* Reset state for each original parsetree */ + /* (at most one of its querytrees will be marked canSetTag) */ SPI_processed = 0; SPI_lastoid = InvalidOid; SPI_tuptable = NULL; @@ -1050,39 +1028,11 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan) { Query *queryTree = (Query *) lfirst(query_list_item); Plan *planTree; - bool canSetResult; QueryDesc *qdesc; planTree = pg_plan_query(queryTree); plan_list = lappend(plan_list, planTree); - /* - * This query can set the SPI result if it is the original - * query, or if it is an INSTEAD query of the same kind as the - * original and we haven't yet seen the original query. - */ - if (queryTree->querySource == QSRC_ORIGINAL) - { - canSetResult = true; - foundOriginalQuery = true; - } - else if (!foundOriginalQuery && - queryTree->commandType == origCmdType && - (queryTree->querySource == QSRC_INSTEAD_RULE || - queryTree->querySource == QSRC_QUAL_INSTEAD_RULE)) - canSetResult = true; - else - canSetResult = false; - - /* Reset state if can set result */ - if (canSetResult) - { - SPI_processed = 0; - SPI_lastoid = InvalidOid; - SPI_tuptable = NULL; - _SPI_current->tuptable = NULL; - } - if (queryTree->commandType == CMD_UTILITY) { if (IsA(queryTree->utilityStmt, CopyStmt)) @@ -1108,9 +1058,10 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan) else if (plan == NULL) { qdesc = CreateQueryDesc(queryTree, planTree, - canSetResult ? SPI : None, + queryTree->canSetTag ? SPI : None, NULL, NULL, false); - res = _SPI_pquery(qdesc, true, canSetResult ? tcount : 0); + res = _SPI_pquery(qdesc, true, + queryTree->canSetTag ? tcount : 0); if (res < 0) return res; CommandCounterIncrement(); @@ -1118,7 +1069,7 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan) else { qdesc = CreateQueryDesc(queryTree, planTree, - canSetResult ? SPI : None, + queryTree->canSetTag ? SPI : None, NULL, NULL, false); res = _SPI_pquery(qdesc, false, 0); if (res < 0) @@ -1145,10 +1096,31 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls, List *query_list_list_item; int nargs = plan->nargs; int res = 0; + ParamListInfo paramLI; /* Increment CommandCounter to see changes made by now */ CommandCounterIncrement(); + /* Convert parameters to form wanted by executor */ + if (nargs > 0) + { + int k; + + paramLI = (ParamListInfo) + palloc0((nargs + 1) * sizeof(ParamListInfoData)); + + for (k = 0; k < nargs; k++) + { + paramLI[k].kind = PARAM_NUM; + paramLI[k].id = k + 1; + paramLI[k].isnull = (Nulls && Nulls[k] == 'n'); + paramLI[k].value = Values[k]; + } + paramLI[k].kind = PARAM_INVALID; + } + else + paramLI = NULL; + /* Reset state (only needed in case string is empty) */ SPI_processed = 0; SPI_lastoid = InvalidOid; @@ -1159,9 +1131,9 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls, { List *query_list = lfirst(query_list_list_item); List *query_list_item; - bool foundOriginalQuery = false; /* Reset state for each original parsetree */ + /* (at most one of its querytrees will be marked canSetTag) */ SPI_processed = 0; SPI_lastoid = InvalidOid; SPI_tuptable = NULL; @@ -1171,72 +1143,24 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls, { Query *queryTree = (Query *) lfirst(query_list_item); Plan *planTree; - bool canSetResult; QueryDesc *qdesc; planTree = lfirst(plan_list); plan_list = lnext(plan_list); - /* - * This query can set the SPI result if it is the original - * query, or if it is an INSTEAD query of the same kind as the - * original and we haven't yet seen the original query. - */ - if (queryTree->querySource == QSRC_ORIGINAL) - { - canSetResult = true; - foundOriginalQuery = true; - } - else if (!foundOriginalQuery && - queryTree->commandType == plan->origCmdType && - (queryTree->querySource == QSRC_INSTEAD_RULE || - queryTree->querySource == QSRC_QUAL_INSTEAD_RULE)) - canSetResult = true; - else - canSetResult = false; - - /* Reset state if can set result */ - if (canSetResult) - { - SPI_processed = 0; - SPI_lastoid = InvalidOid; - SPI_tuptable = NULL; - _SPI_current->tuptable = NULL; - } - if (queryTree->commandType == CMD_UTILITY) { - res = SPI_OK_UTILITY; ProcessUtility(queryTree->utilityStmt, None, NULL); + res = SPI_OK_UTILITY; CommandCounterIncrement(); } else { - ParamListInfo paramLI; - - if (nargs > 0) - { - int k; - - paramLI = (ParamListInfo) - palloc0((nargs + 1) * sizeof(ParamListInfoData)); - - for (k = 0; k < plan->nargs; k++) - { - paramLI[k].kind = PARAM_NUM; - paramLI[k].id = k + 1; - paramLI[k].isnull = (Nulls && Nulls[k] == 'n'); - paramLI[k].value = Values[k]; - } - paramLI[k].kind = PARAM_INVALID; - } - else - paramLI = NULL; - qdesc = CreateQueryDesc(queryTree, planTree, - canSetResult ? SPI : None, + queryTree->canSetTag ? SPI : None, NULL, paramLI, false); - res = _SPI_pquery(qdesc, true, canSetResult ? tcount : 0); + res = _SPI_pquery(qdesc, true, + queryTree->canSetTag ? tcount : 0); if (res < 0) return res; CommandCounterIncrement(); @@ -1346,10 +1270,10 @@ _SPI_cursor_operation(Portal portal, bool forward, int count, /* Run the cursor */ _SPI_current->processed = - DoPortalFetch(portal, - forward ? FETCH_FORWARD : FETCH_BACKWARD, - (long) count, - dest); + PortalRunFetch(portal, + forward ? FETCH_FORWARD : FETCH_BACKWARD, + (long) count, + dest); if (dest == SPI && _SPI_checktuples()) elog(FATAL, "SPI_fetch: # of processed tuples check failed"); @@ -1467,7 +1391,6 @@ _SPI_copy_plan(_SPI_plan *plan, int location) } else newplan->argtypes = NULL; - newplan->origCmdType = plan->origCmdType; MemoryContextSwitchTo(oldcxt); diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c index 01fffed816..c4d16ef5e9 100644 --- a/src/backend/executor/tstoreReceiver.c +++ b/src/backend/executor/tstoreReceiver.c @@ -9,7 +9,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/tstoreReceiver.c,v 1.2 2003/04/29 03:21:29 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/tstoreReceiver.c,v 1.3 2003/05/02 20:54:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,18 +43,18 @@ tstoreSetupReceiver(DestReceiver *self, int operation, const char *portalname, TupleDesc typeinfo) { TStoreState *myState = (TStoreState *) self; - Portal portal; - if (operation != CMD_SELECT) - elog(ERROR, "Unexpected operation type: %d", operation); + /* Should only be called within a suitably-prepped portal */ + if (CurrentPortal == NULL || + CurrentPortal->holdStore == NULL) + elog(ERROR, "Tuplestore destination used in wrong context"); - portal = GetPortalByName(portalname); + /* Debug check: make sure portal's result tuple desc is correct */ + Assert(CurrentPortal->tupDesc != NULL); + Assert(equalTupleDescs(CurrentPortal->tupDesc, typeinfo)); - if (portal == NULL) - elog(ERROR, "Specified portal does not exist: %s", portalname); - - myState->tstore = portal->holdStore; - myState->cxt = portal->holdContext; + myState->tstore = CurrentPortal->holdStore; + myState->cxt = CurrentPortal->holdContext; } /* diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index cbf61970d4..4c773657fe 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.248 2003/04/08 23:20:01 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.249 2003/05/02 20:54:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1476,6 +1476,7 @@ _copyQuery(Query *from) COPY_SCALAR_FIELD(commandType); COPY_SCALAR_FIELD(querySource); + COPY_SCALAR_FIELD(canSetTag); COPY_NODE_FIELD(utilityStmt); COPY_SCALAR_FIELD(resultRelation); COPY_NODE_FIELD(into); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 31e5319711..e4c10ed968 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.191 2003/04/08 23:20:01 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.192 2003/05/02 20:54:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -566,6 +566,7 @@ _equalQuery(Query *a, Query *b) { COMPARE_SCALAR_FIELD(commandType); COMPARE_SCALAR_FIELD(querySource); + COMPARE_SCALAR_FIELD(canSetTag); COMPARE_NODE_FIELD(utilityStmt); COMPARE_SCALAR_FIELD(resultRelation); COMPARE_NODE_FIELD(into); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 45c7a2a301..654905b096 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.203 2003/04/24 21:16:43 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.204 2003/05/02 20:54:34 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1177,6 +1177,7 @@ _outQuery(StringInfo str, Query *node) WRITE_ENUM_FIELD(commandType, CmdType); WRITE_ENUM_FIELD(querySource, QuerySource); + WRITE_BOOL_FIELD(canSetTag); /* * Hack to work around missing outfuncs routines for a lot of the diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 4f75942169..3c8e7501f4 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.151 2003/04/08 23:20:01 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.152 2003/05/02 20:54:34 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -195,6 +195,7 @@ _readQuery(void) READ_ENUM_FIELD(commandType, CmdType); READ_ENUM_FIELD(querySource, QuerySource); + READ_BOOL_FIELD(canSetTag); READ_NODE_FIELD(utilityStmt); READ_INT_FIELD(resultRelation); READ_NODE_FIELD(into); diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c index d53a160a4e..1be69e93f3 100644 --- a/src/backend/optimizer/geqo/geqo_eval.c +++ b/src/backend/optimizer/geqo/geqo_eval.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.61 2003/01/20 18:54:49 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/optimizer/geqo/geqo_eval.c,v 1.62 2003/05/02 20:54:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -65,10 +65,10 @@ geqo_eval(Query *root, List *initial_rels, Gene *tour, int num_gene) * * Since geqo_eval() will be called many times, we can't afford to let * all that memory go unreclaimed until end of statement. Note we - * make the temp context a child of TransactionCommandContext, so that + * make the temp context a child of the planner's normal context, so that * it will be freed even if we abort via elog(ERROR). */ - mycontext = AllocSetContextCreate(TransactionCommandContext, + mycontext = AllocSetContextCreate(CurrentMemoryContext, "GEQO", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index c2159a70e0..a488d1d91e 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.268 2003/04/29 22:13:09 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.269 2003/05/02 20:54:34 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -237,13 +237,23 @@ do_parse_analyze(Node *parseTree, ParseState *pstate) /* * Make sure that only the original query is marked original. We have * to do this explicitly since recursive calls of do_parse_analyze will - * have marked some of the added-on queries as "original". + * have marked some of the added-on queries as "original". Also mark + * only the original query as allowed to set the command-result tag. */ foreach(listscan, result) { Query *q = lfirst(listscan); - q->querySource = (q == query ? QSRC_ORIGINAL : QSRC_PARSER); + if (q == query) + { + q->querySource = QSRC_ORIGINAL; + q->canSetTag = true; + } + else + { + q->querySource = QSRC_PARSER; + q->canSetTag = false; + } } return result; @@ -399,6 +409,11 @@ transformStmt(ParseState *pstate, Node *parseTree, result->utilityStmt = (Node *) parseTree; break; } + + /* Mark as original query until we learn differently */ + result->querySource = QSRC_ORIGINAL; + result->canSetTag = true; + return result; } diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 8ca7fb954f..5d481a3f0a 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.119 2003/04/29 22:13:10 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.120 2003/05/02 20:54:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1016,6 +1016,7 @@ fireRules(Query *parsetree, event_qual, rt_index, event); rule_action->querySource = qsrc; + rule_action->canSetTag = false; /* might change later */ results = lappend(results, rule_action); } @@ -1181,6 +1182,9 @@ QueryRewrite(Query *parsetree) List *querylist; List *results = NIL; List *l; + CmdType origCmdType; + bool foundOriginalQuery; + Query *lastInstead; /* * Step 1 @@ -1235,5 +1239,51 @@ QueryRewrite(Query *parsetree) results = lappend(results, query); } + /* + * Step 3 + * + * Determine which, if any, of the resulting queries is supposed to set + * the command-result tag; and update the canSetTag fields accordingly. + * + * If the original query is still in the list, it sets the command tag. + * Otherwise, the last INSTEAD query of the same kind as the original + * is allowed to set the tag. (Note these rules can leave us with no + * query setting the tag. The tcop code has to cope with this by + * setting up a default tag based on the original un-rewritten query.) + * + * The Asserts verify that at most one query in the result list is marked + * canSetTag. If we aren't checking asserts, we can fall out of the loop + * as soon as we find the original query. + */ + origCmdType = parsetree->commandType; + foundOriginalQuery = false; + lastInstead = NULL; + + foreach(l, results) + { + Query *query = (Query *) lfirst(l); + + if (query->querySource == QSRC_ORIGINAL) + { + Assert(query->canSetTag); + Assert(!foundOriginalQuery); + foundOriginalQuery = true; +#ifndef USE_ASSERT_CHECKING + break; +#endif + } + else + { + Assert(!query->canSetTag); + if (query->commandType == origCmdType && + (query->querySource == QSRC_INSTEAD_RULE || + query->querySource == QSRC_QUAL_INSTEAD_RULE)) + lastInstead = query; + } + } + + if (!foundOriginalQuery && lastInstead != NULL) + lastInstead->canSetTag = true; + return results; } diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c index b875095734..78fcfdb7e0 100644 --- a/src/backend/tcop/fastpath.c +++ b/src/backend/tcop/fastpath.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.59 2003/04/22 00:08:07 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/fastpath.c,v 1.60 2003/05/02 20:54:35 tgl Exp $ * * NOTES * This cruft is the server side of PQfn. @@ -255,7 +255,7 @@ fetch_fp_info(Oid func_id, struct fp_info * fip) * * Note: palloc()s done here and in the called function do not need to be * cleaned up explicitly. We are called from PostgresMain() in the - * QueryContext memory context, which will be automatically reset when + * MessageContext memory context, which will be automatically reset when * control returns to PostgresMain. */ int diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index bc98d1d91e..6c2e456ccc 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * 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 @@ -74,8 +74,6 @@ const char *debug_query_string; /* for pgmonitor 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; @@ -122,7 +120,6 @@ static void start_xact_command(void); static void finish_xact_command(bool forceCommit); static void SigHupHandler(SIGNAL_ARGS); static void FloatExceptionHandler(SIGNAL_ARGS); -static const char *CreateCommandTag(Node *parsetree); /* ---------------------------------------------------------------- @@ -310,9 +307,9 @@ SocketBackend(StringInfo inBuf) /* ---------------- * 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 @@ -487,7 +484,7 @@ pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams) } -/* Generate a plan for a single query. */ +/* Generate a plan for a single already-rewritten query. */ Plan * pg_plan_query(Query *querytree) { @@ -534,41 +531,58 @@ 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; @@ -577,39 +591,40 @@ pg_exec_query_string(const char *query_string, /* string to execute */ 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 @@ -618,57 +633,36 @@ pg_exec_query_string(const char *query_string, /* string to execute */ 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 @@ -704,202 +698,60 @@ pg_exec_query_string(const char *query_string, /* string to execute */ 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 @@ -910,28 +762,18 @@ pg_exec_query_string(const char *query_string, /* string to execute */ * 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(); } /* @@ -941,20 +783,24 @@ pg_exec_query_string(const char *query_string, /* string to execute */ * (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); @@ -968,6 +814,9 @@ pg_exec_query_string(const char *query_string, /* string to execute */ (long) (stop_t.tv_usec - start_t.tv_usec)); } + if (save_log_statement_stats) + ShowUsage("QUERY STATISTICS"); + debug_query_string = NULL; } @@ -1431,10 +1280,6 @@ PostgresMain(int argc, char *argv[], const char *username) SetConfigOption(tmp, "false", ctx, gucsource); break; - case 'i': - dontExecute = true; - break; - case 'N': /* @@ -1827,22 +1672,20 @@ PostgresMain(int argc, char *argv[], const char *username) 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 @@ -1897,6 +1740,9 @@ PostgresMain(int argc, char *argv[], const char *username) */ MemoryContextSwitchTo(TopMemoryContext); MemoryContextResetAndDeleteChildren(ErrorContext); + CurrentPortal = NULL; + PortalContext = NULL; + QueryContext = NULL; /* * Clear flag to indicate that we got out of error recovery mode @@ -1924,10 +1770,10 @@ PostgresMain(int argc, char *argv[], const char *username) { /* * 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(); @@ -2006,25 +1852,9 @@ PostgresMain(int argc, char *argv[], const char *username) { 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; } @@ -2225,381 +2055,3 @@ ShowUsage(const char *title) 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; -} diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 29d5018440..f70b913224 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -8,15 +8,35 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.59 2003/03/10 03:53:51 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.60 2003/05/02 20:54:35 tgl Exp $ * *------------------------------------------------------------------------- */ - #include "postgres.h" #include "executor/executor.h" +#include "miscadmin.h" +#include "tcop/tcopprot.h" #include "tcop/pquery.h" +#include "tcop/utility.h" +#include "utils/guc.h" +#include "utils/memutils.h" + + +static uint32 RunFromStore(Portal portal, ScanDirection direction, long count, + CommandDest dest); +static long PortalRunSelect(Portal portal, bool forward, long count, + CommandDest dest); +static void PortalRunUtility(Portal portal, Query *query, + CommandDest dest, char *completionTag); +static void PortalRunMulti(Portal portal, + CommandDest dest, CommandDest altdest, + char *completionTag); +static long DoPortalRunFetch(Portal portal, + FetchDirection fdirection, + long count, + CommandDest dest); +static void DoPortalRewind(Portal portal); /* @@ -63,19 +83,26 @@ FreeQueryDesc(QueryDesc *qdesc) /* * ProcessQuery - * Execute a query + * Execute a single query * * parsetree: the query tree * plan: the plan tree for the query + * params: any parameters needed + * portalName: name of portal being used * dest: where to send results * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE * in which to store a command completion status string. * * completionTag may be NULL if caller doesn't want a status string. + * + * Must be called in a memory context that will be reset or deleted on + * error; otherwise the executor's memory usage will be leaked. */ void ProcessQuery(Query *parsetree, Plan *plan, + ParamListInfo params, + const char *portalName, CommandDest dest, char *completionTag) { @@ -103,7 +130,8 @@ ProcessQuery(Query *parsetree, /* * Create the QueryDesc object */ - queryDesc = CreateQueryDesc(parsetree, plan, dest, NULL, NULL, false); + queryDesc = CreateQueryDesc(parsetree, plan, dest, portalName, params, + false); /* * Call ExecStart to prepare the plan for execution @@ -111,7 +139,7 @@ ProcessQuery(Query *parsetree, ExecutorStart(queryDesc); /* - * And run the plan. + * Run the plan to completion. */ ExecutorRun(queryDesc, ForwardScanDirection, 0L); @@ -156,3 +184,871 @@ ProcessQuery(Query *parsetree, FreeQueryDesc(queryDesc); } + + +/* + * PortalStart + * Prepare a portal for execution. + * + * Caller must already have created the portal, done PortalDefineQuery(), + * and adjusted portal options if needed. If parameters are needed by + * the query, they must be passed in here (caller is responsible for + * giving them appropriate lifetime). + * + * On return, portal is ready to accept PortalRun() calls, and the result + * tupdesc (if any) is known. + */ +void +PortalStart(Portal portal, ParamListInfo params) +{ + MemoryContext oldContext; + Query *query = NULL; + QueryDesc *queryDesc; + + AssertArg(PortalIsValid(portal)); + AssertState(portal->queryContext != NULL); /* query defined? */ + AssertState(!portal->portalReady); /* else extra PortalStart */ + + oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); + + /* Must remember portal param list, if any */ + portal->portalParams = params; + + /* + * Determine the portal execution strategy (see comments in portal.h) + */ + portal->strategy = PORTAL_MULTI_QUERY; /* default assumption */ + if (length(portal->parseTrees) == 1) + { + query = (Query *) lfirst(portal->parseTrees); + if (query->commandType == CMD_SELECT && + query->canSetTag && + query->into == NULL) + portal->strategy = PORTAL_ONE_SELECT; + else if (query->commandType == CMD_UTILITY && + query->canSetTag && + query->utilityStmt != NULL) + { + /* XXX check for things that can be PORTAL_UTIL_SELECT */ + } + } + + /* + * Fire her up according to the strategy + */ + switch (portal->strategy) + { + case PORTAL_ONE_SELECT: + /* + * Must set query snapshot before starting executor. + */ + SetQuerySnapshot(); + /* + * Create QueryDesc in portal's context; for the moment, set + * the destination to None. + */ + queryDesc = CreateQueryDesc(query, + (Plan *) lfirst(portal->planTrees), + None, + portal->name, + params, + false); + /* + * Call ExecStart to prepare the plan for execution + */ + ExecutorStart(queryDesc); + /* + * This tells PortalCleanup to shut down the executor + */ + portal->queryDesc = queryDesc; + portal->tupDesc = queryDesc->tupDesc; + /* + * Reset cursor position data to "start of query" + */ + portal->atStart = true; + portal->atEnd = false; /* allow fetches */ + portal->portalPos = 0; + portal->posOverflow = false; + break; + + case PORTAL_UTIL_SELECT: + /* XXX implement later */ + /* XXX query snapshot here? no, RunUtility will do it */ + /* xxx what about Params? */ + portal->tupDesc = NULL; + break; + + case PORTAL_MULTI_QUERY: + /* Need do nothing now */ + portal->tupDesc = NULL; + break; + } + + MemoryContextSwitchTo(oldContext); + + portal->portalReady = true; +} + +/* + * PortalRun + * Run a portal's query or queries. + * + * count <= 0 is interpreted as a no-op: the destination gets started up + * and shut down, but nothing else happens. Also, count == FETCH_ALL is + * interpreted as "all rows". Note that count is ignored in multi-query + * situations, where we always run the portal to completion. + * + * dest: where to send output of primary (canSetTag) query + * + * altdest: where to send output of non-primary queries + * + * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE + * in which to store a command completion status string. + * May be NULL if caller doesn't want a status string. + * + * Returns TRUE if the portal's execution is complete, FALSE if it was + * suspended due to exhaustion of the count parameter. + */ +bool +PortalRun(Portal portal, long count, CommandDest dest, CommandDest altdest, + char *completionTag) +{ + bool result; + Portal saveCurrentPortal; + MemoryContext savePortalContext; + MemoryContext saveQueryContext; + MemoryContext oldContext; + + AssertArg(PortalIsValid(portal)); + AssertState(portal->portalReady); /* else no PortalStart */ + + /* Initialize completion tag to empty string */ + if (completionTag) + completionTag[0] = '\0'; + + /* + * Check for improper portal use, and mark portal active. + */ + if (portal->portalDone) + elog(ERROR, "Portal \"%s\" cannot be run anymore", portal->name); + if (portal->portalActive) + elog(ERROR, "Portal \"%s\" already active", portal->name); + portal->portalActive = true; + + /* + * Set global portal and context pointers. + */ + saveCurrentPortal = CurrentPortal; + CurrentPortal = portal; + savePortalContext = PortalContext; + PortalContext = PortalGetHeapMemory(portal); + saveQueryContext = QueryContext; + QueryContext = portal->queryContext; + + oldContext = MemoryContextSwitchTo(PortalContext); + + switch (portal->strategy) + { + case PORTAL_ONE_SELECT: + (void) PortalRunSelect(portal, true, count, dest); + /* we know the query is supposed to set the tag */ + if (completionTag && portal->commandTag) + strcpy(completionTag, portal->commandTag); + /* + * Since it's a forward fetch, say DONE iff atEnd is now true. + */ + result = portal->atEnd; + break; + + case PORTAL_UTIL_SELECT: + /* + * If we have not yet run the utility statement, do so, + * storing its results in the portal's tuplestore. + */ + if (!portal->portalUtilReady) + { + PortalRunUtility(portal, lfirst(portal->parseTrees), + Tuplestore, NULL); + portal->portalUtilReady = true; + } + /* + * Now fetch desired portion of results. + */ + (void) PortalRunSelect(portal, true, count, dest); + /* + * We know the query is supposed to set the tag; we assume + * only the default tag is needed. + */ + if (completionTag && portal->commandTag) + strcpy(completionTag, portal->commandTag); + /* + * Since it's a forward fetch, say DONE iff atEnd is now true. + */ + result = portal->atEnd; + break; + + case PORTAL_MULTI_QUERY: + PortalRunMulti(portal, dest, altdest, completionTag); + /* Always complete at end of RunMulti */ + result = true; + break; + + default: + elog(ERROR, "PortalRun: bogus portal strategy"); + result = false; /* keep compiler quiet */ + break; + } + + MemoryContextSwitchTo(oldContext); + + /* Mark portal not active */ + portal->portalActive = false; + + CurrentPortal = saveCurrentPortal; + PortalContext = savePortalContext; + QueryContext = saveQueryContext; + + return result; +} + +/* + * PortalRunSelect + * Execute a portal's query in SELECT cases (also UTIL_SELECT). + * + * This handles simple N-rows-forward-or-backward cases. For more complex + * nonsequential access to a portal, see PortalRunFetch. + * + * count <= 0 is interpreted as a no-op: the destination gets started up + * and shut down, but nothing else happens. Also, count == FETCH_ALL is + * interpreted as "all rows". + * + * Caller must already have validated the Portal and done appropriate + * setup (cf. PortalRun). + * + * Returns number of rows processed (suitable for use in result tag) + */ +long +PortalRunSelect(Portal portal, + bool forward, + long count, + CommandDest dest) +{ + QueryDesc *queryDesc; + ScanDirection direction; + uint32 nprocessed; + + /* + * NB: queryDesc will be NULL if we are fetching from a held cursor + * or a completed utility query; can't use it in that path. + */ + queryDesc = PortalGetQueryDesc(portal); + + /* Caller messed up if we have neither a ready query nor held data. */ + Assert(queryDesc || portal->holdStore); + + /* + * Force the queryDesc destination to the right thing. This supports + * MOVE, for example, which will pass in dest = None. This is okay to + * change as long as we do it on every fetch. (The Executor must not + * assume that dest never changes.) + */ + if (queryDesc) + queryDesc->dest = dest; + + /* + * Determine which direction to go in, and check to see if we're + * already at the end of the available tuples in that direction. If + * so, set the direction to NoMovement to avoid trying to fetch any + * tuples. (This check exists because not all plan node types are + * robust about being called again if they've already returned NULL + * once.) Then call the executor (we must not skip this, because the + * destination needs to see a setup and shutdown even if no tuples are + * available). Finally, update the portal position state depending on + * the number of tuples that were retrieved. + */ + if (forward) + { + if (portal->atEnd || count <= 0) + direction = NoMovementScanDirection; + else + direction = ForwardScanDirection; + + /* In the executor, zero count processes all rows */ + if (count == FETCH_ALL) + count = 0; + + if (portal->holdStore) + nprocessed = RunFromStore(portal, direction, count, dest); + else + { + ExecutorRun(queryDesc, direction, count); + nprocessed = queryDesc->estate->es_processed; + } + + if (direction != NoMovementScanDirection) + { + long oldPos; + + if (nprocessed > 0) + portal->atStart = false; /* OK to go backward now */ + if (count == 0 || + (unsigned long) nprocessed < (unsigned long) count) + portal->atEnd = true; /* we retrieved 'em all */ + oldPos = portal->portalPos; + portal->portalPos += nprocessed; + /* portalPos doesn't advance when we fall off the end */ + if (portal->portalPos < oldPos) + portal->posOverflow = true; + } + } + else + { + if (portal->cursorOptions & CURSOR_OPT_NO_SCROLL) + elog(ERROR, "Cursor can only scan forward" + "\n\tDeclare it with SCROLL option to enable backward scan"); + + if (portal->atStart || count <= 0) + direction = NoMovementScanDirection; + else + direction = BackwardScanDirection; + + /* In the executor, zero count processes all rows */ + if (count == FETCH_ALL) + count = 0; + + if (portal->holdStore) + nprocessed = RunFromStore(portal, direction, count, dest); + else + { + ExecutorRun(queryDesc, direction, count); + nprocessed = queryDesc->estate->es_processed; + } + + if (direction != NoMovementScanDirection) + { + if (nprocessed > 0 && portal->atEnd) + { + portal->atEnd = false; /* OK to go forward now */ + portal->portalPos++; /* adjust for endpoint case */ + } + if (count == 0 || + (unsigned long) nprocessed < (unsigned long) count) + { + portal->atStart = true; /* we retrieved 'em all */ + portal->portalPos = 0; + portal->posOverflow = false; + } + else + { + long oldPos; + + oldPos = portal->portalPos; + portal->portalPos -= nprocessed; + if (portal->portalPos > oldPos || + portal->portalPos <= 0) + portal->posOverflow = true; + } + } + } + + return nprocessed; +} + +/* + * RunFromStore + * Fetch tuples from the portal's tuple store. + * + * Calling conventions are similar to ExecutorRun, except that we + * do not depend on having a queryDesc or estate. Therefore we return the + * number of tuples processed as the result, not in estate->es_processed. + * + * One difference from ExecutorRun is that the destination receiver functions + * are run in the caller's memory context (since we have no estate). Watch + * out for memory leaks. + */ +static uint32 +RunFromStore(Portal portal, ScanDirection direction, long count, + CommandDest dest) +{ + DestReceiver *destfunc; + long current_tuple_count = 0; + + destfunc = DestToFunction(dest); + (*destfunc->setup) (destfunc, CMD_SELECT, portal->name, portal->tupDesc); + + if (direction == NoMovementScanDirection) + { + /* do nothing except start/stop the destination */ + } + else + { + bool forward = (direction == ForwardScanDirection); + + for (;;) + { + MemoryContext oldcontext; + HeapTuple tup; + bool should_free; + + oldcontext = MemoryContextSwitchTo(portal->holdContext); + + tup = tuplestore_getheaptuple(portal->holdStore, forward, + &should_free); + + MemoryContextSwitchTo(oldcontext); + + if (tup == NULL) + break; + + (*destfunc->receiveTuple) (tup, portal->tupDesc, destfunc); + + if (should_free) + pfree(tup); + + /* + * check our tuple count.. if we've processed the proper number + * then quit, else loop again and process more tuples. Zero + * count means no limit. + */ + current_tuple_count++; + if (count && count == current_tuple_count) + break; + } + } + + (*destfunc->cleanup) (destfunc); + + return (uint32) current_tuple_count; +} + +/* + * PortalRunUtility + * Execute a utility statement inside a portal. + */ +static void +PortalRunUtility(Portal portal, Query *query, + CommandDest dest, char *completionTag) +{ + Node *utilityStmt = query->utilityStmt; + + elog(DEBUG2, "ProcessUtility"); + + /* + * Set snapshot if utility stmt needs one. Most reliable + * way to do this seems to be to enumerate those that do not + * need one; this is a short list. Transaction control, + * LOCK, and SET must *not* set a snapshot since they need + * to be executable at the start of a serializable transaction + * without freezing a snapshot. By extension we allow SHOW + * not to set a snapshot. The other stmts listed are just + * efficiency hacks. Beware of listing anything that can + * modify the database --- if, say, it has to update a + * functional index, then it had better have a snapshot. + */ + if (! (IsA(utilityStmt, TransactionStmt) || + IsA(utilityStmt, LockStmt) || + IsA(utilityStmt, VariableSetStmt) || + IsA(utilityStmt, VariableShowStmt) || + IsA(utilityStmt, VariableResetStmt) || + IsA(utilityStmt, ConstraintsSetStmt) || + /* efficiency hacks from here down */ + IsA(utilityStmt, FetchStmt) || + IsA(utilityStmt, ListenStmt) || + IsA(utilityStmt, NotifyStmt) || + IsA(utilityStmt, UnlistenStmt) || + IsA(utilityStmt, CheckPointStmt))) + SetQuerySnapshot(); + + if (query->canSetTag) + { + /* utility statement can override default tag string */ + ProcessUtility(utilityStmt, dest, completionTag); + if (completionTag && completionTag[0] == '\0' && portal->commandTag) + strcpy(completionTag, portal->commandTag); /* use the default */ + } + else + { + /* utility added by rewrite cannot set tag */ + ProcessUtility(utilityStmt, dest, NULL); + } + + /* Some utility statements may change context on us */ + MemoryContextSwitchTo(PortalGetHeapMemory(portal)); +} + +/* + * PortalRunMulti + * Execute a portal's queries in the general case (multi queries). + */ +static void +PortalRunMulti(Portal portal, + CommandDest dest, CommandDest altdest, + char *completionTag) +{ + List *plantree_list = portal->planTrees; + List *querylist_item; + + /* + * Loop to handle the individual queries generated from a + * single parsetree by analysis and rewrite. + */ + foreach(querylist_item, portal->parseTrees) + { + Query *query = (Query *) lfirst(querylist_item); + Plan *plan = (Plan *) lfirst(plantree_list); + + plantree_list = lnext(plantree_list); + + /* + * If we got a cancel signal in prior command, quit + */ + CHECK_FOR_INTERRUPTS(); + + if (query->commandType == CMD_UTILITY) + { + /* + * process utility functions (create, destroy, etc..) + */ + Assert(plan == NULL); + + PortalRunUtility(portal, query, + query->canSetTag ? dest : altdest, + completionTag); + } + else + { + /* + * process a plannable query. + */ + elog(DEBUG2, "ProcessQuery"); + + /* Must always set snapshot for plannable queries */ + SetQuerySnapshot(); + + /* + * execute the plan + */ + if (log_executor_stats) + ResetUsage(); + + if (query->canSetTag) + { + /* statement can set tag string */ + ProcessQuery(query, plan, + portal->portalParams, portal->name, + dest, completionTag); + } + else + { + /* stmt added by rewrite cannot set tag */ + ProcessQuery(query, plan, + portal->portalParams, portal->name, + altdest, NULL); + } + + if (log_executor_stats) + ShowUsage("EXECUTOR STATISTICS"); + } + + /* + * Increment command counter between queries, but not after the + * last one. + */ + if (plantree_list != NIL) + CommandCounterIncrement(); + + /* + * Clear subsidiary contexts to recover temporary memory. + */ + Assert(PortalGetHeapMemory(portal) == CurrentMemoryContext); + + MemoryContextDeleteChildren(PortalGetHeapMemory(portal)); + } + + /* + * If a command completion tag was supplied, use it. Otherwise + * use the portal's commandTag as the default completion tag. + * + * Exception: clients will expect INSERT/UPDATE/DELETE tags to + * have counts, so fake something up if necessary. (This could + * happen if the original query was replaced by a DO INSTEAD rule.) + */ + if (completionTag && completionTag[0] == '\0') + { + if (portal->commandTag) + strcpy(completionTag, portal->commandTag); + if (strcmp(completionTag, "INSERT") == 0) + strcpy(completionTag, "INSERT 0 0"); + else if (strcmp(completionTag, "UPDATE") == 0) + strcpy(completionTag, "UPDATE 0"); + else if (strcmp(completionTag, "DELETE") == 0) + strcpy(completionTag, "DELETE 0"); + } + + /* Prevent portal's commands from being re-executed */ + portal->portalDone = true; +} + +/* + * PortalRunFetch + * Variant form of PortalRun that supports SQL FETCH directions. + * + * Returns number of rows processed (suitable for use in result tag) + */ +long +PortalRunFetch(Portal portal, + FetchDirection fdirection, + long count, + CommandDest dest) +{ + long result; + Portal saveCurrentPortal; + MemoryContext savePortalContext; + MemoryContext saveQueryContext; + MemoryContext oldContext; + + AssertArg(PortalIsValid(portal)); + AssertState(portal->portalReady); /* else no PortalStart */ + + /* + * Check for improper portal use, and mark portal active. + */ + if (portal->portalDone) + elog(ERROR, "Portal \"%s\" cannot be run anymore", portal->name); + if (portal->portalActive) + elog(ERROR, "Portal \"%s\" already active", portal->name); + portal->portalActive = true; + + /* + * Set global portal and context pointers. + */ + saveCurrentPortal = CurrentPortal; + CurrentPortal = portal; + savePortalContext = PortalContext; + PortalContext = PortalGetHeapMemory(portal); + saveQueryContext = QueryContext; + QueryContext = portal->queryContext; + + oldContext = MemoryContextSwitchTo(PortalContext); + + switch (portal->strategy) + { + case PORTAL_ONE_SELECT: + result = DoPortalRunFetch(portal, fdirection, count, dest); + break; + + default: + elog(ERROR, "PortalRunFetch: unsupported portal strategy"); + result = 0; /* keep compiler quiet */ + break; + } + + MemoryContextSwitchTo(oldContext); + + /* Mark portal not active */ + portal->portalActive = false; + + CurrentPortal = saveCurrentPortal; + PortalContext = savePortalContext; + QueryContext = saveQueryContext; + + return result; +} + +/* + * DoPortalRunFetch + * Guts of PortalRunFetch --- the portal context is already set up + * + * Returns number of rows processed (suitable for use in result tag) + */ +static long +DoPortalRunFetch(Portal portal, + FetchDirection fdirection, + long count, + CommandDest dest) +{ + bool forward; + + Assert(portal->strategy == PORTAL_ONE_SELECT); + + switch (fdirection) + { + case FETCH_FORWARD: + if (count < 0) + { + fdirection = FETCH_BACKWARD; + count = -count; + } + /* fall out of switch to share code with FETCH_BACKWARD */ + break; + case FETCH_BACKWARD: + if (count < 0) + { + fdirection = FETCH_FORWARD; + count = -count; + } + /* fall out of switch to share code with FETCH_FORWARD */ + break; + case FETCH_ABSOLUTE: + if (count > 0) + { + /* + * Definition: Rewind to start, advance count-1 rows, return + * next row (if any). In practice, if the goal is less than + * halfway back to the start, it's better to scan from where + * we are. In any case, we arrange to fetch the target row + * going forwards. + */ + if (portal->posOverflow || portal->portalPos == LONG_MAX || + count-1 <= portal->portalPos / 2) + { + DoPortalRewind(portal); + if (count > 1) + PortalRunSelect(portal, true, count-1, None); + } + else + { + long pos = portal->portalPos; + + if (portal->atEnd) + pos++; /* need one extra fetch if off end */ + if (count <= pos) + PortalRunSelect(portal, false, pos-count+1, None); + else if (count > pos+1) + PortalRunSelect(portal, true, count-pos-1, None); + } + return PortalRunSelect(portal, true, 1L, dest); + } + else if (count < 0) + { + /* + * Definition: Advance to end, back up abs(count)-1 rows, + * return prior row (if any). We could optimize this if we + * knew in advance where the end was, but typically we won't. + * (Is it worth considering case where count > half of size + * of query? We could rewind once we know the size ...) + */ + PortalRunSelect(portal, true, FETCH_ALL, None); + if (count < -1) + PortalRunSelect(portal, false, -count-1, None); + return PortalRunSelect(portal, false, 1L, dest); + } + else /* count == 0 */ + { + /* Rewind to start, return zero rows */ + DoPortalRewind(portal); + return PortalRunSelect(portal, true, 0L, dest); + } + break; + case FETCH_RELATIVE: + if (count > 0) + { + /* + * Definition: advance count-1 rows, return next row (if any). + */ + if (count > 1) + PortalRunSelect(portal, true, count-1, None); + return PortalRunSelect(portal, true, 1L, dest); + } + else if (count < 0) + { + /* + * Definition: back up abs(count)-1 rows, return prior row + * (if any). + */ + if (count < -1) + PortalRunSelect(portal, false, -count-1, None); + return PortalRunSelect(portal, false, 1L, dest); + } + else /* count == 0 */ + { + /* Same as FETCH FORWARD 0, so fall out of switch */ + fdirection = FETCH_FORWARD; + } + break; + default: + elog(ERROR, "PortalRunFetch: bogus direction"); + break; + } + + /* + * Get here with fdirection == FETCH_FORWARD or FETCH_BACKWARD, + * and count >= 0. + */ + forward = (fdirection == FETCH_FORWARD); + + /* + * Zero count means to re-fetch the current row, if any (per SQL92) + */ + if (count == 0) + { + bool on_row; + + /* Are we sitting on a row? */ + on_row = (!portal->atStart && !portal->atEnd); + + if (dest == None) + { + /* MOVE 0 returns 0/1 based on if FETCH 0 would return a row */ + return on_row ? 1L : 0L; + } + else + { + /* + * If we are sitting on a row, back up one so we can re-fetch it. + * If we are not sitting on a row, we still have to start up and + * shut down the executor so that the destination is initialized + * and shut down correctly; so keep going. To PortalRunSelect, + * count == 0 means we will retrieve no row. + */ + if (on_row) + { + PortalRunSelect(portal, false, 1L, None); + /* Set up to fetch one row forward */ + count = 1; + forward = true; + } + } + } + + /* + * Optimize MOVE BACKWARD ALL into a Rewind. + */ + if (!forward && count == FETCH_ALL && dest == None) + { + long result = portal->portalPos; + + if (result > 0 && !portal->atEnd) + result--; + DoPortalRewind(portal); + /* result is bogus if pos had overflowed, but it's best we can do */ + return result; + } + + return PortalRunSelect(portal, forward, count, dest); +} + +/* + * DoPortalRewind - rewind a Portal to starting point + */ +static void +DoPortalRewind(Portal portal) +{ + if (portal->holdStore) + { + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(portal->holdContext); + tuplestore_rescan(portal->holdStore); + MemoryContextSwitchTo(oldcontext); + } + if (PortalGetQueryDesc(portal)) + { + ExecutorRewind(PortalGetQueryDesc(portal)); + } + + portal->atStart = true; + portal->atEnd = false; + portal->portalPos = 0; + portal->posOverflow = false; +} diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 390adeb9cb..a0431f350c 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.197 2003/03/20 18:52:48 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.198 2003/05/02 20:54:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1027,3 +1027,386 @@ ProcessUtility(Node *parsetree, break; } } + + +/* + * CreateCommandTag + * utility to get a string representation of the + * command operation, given a raw (un-analyzed) parsetree. + * + * This must handle all raw command types, but since the vast majority + * of 'em are utility commands, it seems sensible to keep it here. + * + * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE. + * Also, the result must point at a true constant (permanent storage). + */ +const char * +CreateCommandTag(Node *parsetree) +{ + const char *tag; + + switch (nodeTag(parsetree)) + { + case T_InsertStmt: + tag = "INSERT"; + break; + + case T_DeleteStmt: + tag = "DELETE"; + break; + + case T_UpdateStmt: + tag = "UPDATE"; + break; + + case T_SelectStmt: + tag = "SELECT"; + break; + + case T_TransactionStmt: + { + TransactionStmt *stmt = (TransactionStmt *) parsetree; + + switch (stmt->kind) + { + case TRANS_STMT_BEGIN: + tag = "BEGIN"; + break; + + case TRANS_STMT_START: + tag = "START TRANSACTION"; + break; + + case TRANS_STMT_COMMIT: + tag = "COMMIT"; + break; + + case TRANS_STMT_ROLLBACK: + tag = "ROLLBACK"; + break; + + default: + tag = "???"; + break; + } + } + break; + + case T_DeclareCursorStmt: + tag = "DECLARE CURSOR"; + break; + + case T_ClosePortalStmt: + tag = "CLOSE CURSOR"; + break; + + case T_FetchStmt: + { + FetchStmt *stmt = (FetchStmt *) parsetree; + + tag = (stmt->ismove) ? "MOVE" : "FETCH"; + } + break; + + case T_CreateDomainStmt: + tag = "CREATE DOMAIN"; + break; + + case T_CreateSchemaStmt: + tag = "CREATE SCHEMA"; + break; + + case T_CreateStmt: + tag = "CREATE TABLE"; + break; + + case T_DropStmt: + switch (((DropStmt *) parsetree)->removeType) + { + case DROP_TABLE: + tag = "DROP TABLE"; + break; + case DROP_SEQUENCE: + tag = "DROP SEQUENCE"; + break; + case DROP_VIEW: + tag = "DROP VIEW"; + break; + case DROP_INDEX: + tag = "DROP INDEX"; + break; + case DROP_TYPE: + tag = "DROP TYPE"; + break; + case DROP_DOMAIN: + tag = "DROP DOMAIN"; + break; + case DROP_CONVERSION: + tag = "DROP CONVERSION"; + break; + case DROP_SCHEMA: + tag = "DROP SCHEMA"; + break; + default: + tag = "???"; + } + break; + + case T_TruncateStmt: + tag = "TRUNCATE TABLE"; + break; + + case T_CommentStmt: + tag = "COMMENT"; + break; + + case T_CopyStmt: + tag = "COPY"; + break; + + case T_RenameStmt: + if (((RenameStmt *) parsetree)->renameType == RENAME_TRIGGER) + tag = "ALTER TRIGGER"; + else + tag = "ALTER TABLE"; + break; + + case T_AlterTableStmt: + tag = "ALTER TABLE"; + break; + + case T_AlterDomainStmt: + tag = "ALTER DOMAIN"; + break; + + case T_GrantStmt: + { + GrantStmt *stmt = (GrantStmt *) parsetree; + + tag = (stmt->is_grant) ? "GRANT" : "REVOKE"; + } + break; + + case T_DefineStmt: + switch (((DefineStmt *) parsetree)->kind) + { + case DEFINE_STMT_AGGREGATE: + tag = "CREATE AGGREGATE"; + break; + case DEFINE_STMT_OPERATOR: + tag = "CREATE OPERATOR"; + break; + case DEFINE_STMT_TYPE: + tag = "CREATE TYPE"; + break; + default: + tag = "???"; + } + break; + + case T_CompositeTypeStmt: + tag = "CREATE TYPE"; + break; + + case T_ViewStmt: + tag = "CREATE VIEW"; + break; + + case T_CreateFunctionStmt: + tag = "CREATE FUNCTION"; + break; + + case T_IndexStmt: + tag = "CREATE INDEX"; + break; + + case T_RuleStmt: + tag = "CREATE RULE"; + break; + + case T_CreateSeqStmt: + tag = "CREATE SEQUENCE"; + break; + + case T_AlterSeqStmt: + tag = "ALTER SEQUENCE"; + break; + + case T_RemoveAggrStmt: + tag = "DROP AGGREGATE"; + break; + + case T_RemoveFuncStmt: + tag = "DROP FUNCTION"; + break; + + case T_RemoveOperStmt: + tag = "DROP OPERATOR"; + break; + + case T_CreatedbStmt: + tag = "CREATE DATABASE"; + break; + + case T_AlterDatabaseSetStmt: + tag = "ALTER DATABASE"; + break; + + case T_DropdbStmt: + tag = "DROP DATABASE"; + break; + + case T_NotifyStmt: + tag = "NOTIFY"; + break; + + case T_ListenStmt: + tag = "LISTEN"; + break; + + case T_UnlistenStmt: + tag = "UNLISTEN"; + break; + + case T_LoadStmt: + tag = "LOAD"; + break; + + case T_ClusterStmt: + tag = "CLUSTER"; + break; + + case T_VacuumStmt: + if (((VacuumStmt *) parsetree)->vacuum) + tag = "VACUUM"; + else + tag = "ANALYZE"; + break; + + case T_ExplainStmt: + tag = "EXPLAIN"; + break; + + case T_VariableSetStmt: + tag = "SET"; + break; + + case T_VariableShowStmt: + tag = "SHOW"; + break; + + case T_VariableResetStmt: + tag = "RESET"; + break; + + case T_CreateTrigStmt: + tag = "CREATE TRIGGER"; + break; + + case T_DropPropertyStmt: + switch (((DropPropertyStmt *) parsetree)->removeType) + { + case DROP_TRIGGER: + tag = "DROP TRIGGER"; + break; + case DROP_RULE: + tag = "DROP RULE"; + break; + default: + tag = "???"; + } + break; + + case T_CreatePLangStmt: + tag = "CREATE LANGUAGE"; + break; + + case T_DropPLangStmt: + tag = "DROP LANGUAGE"; + break; + + case T_CreateUserStmt: + tag = "CREATE USER"; + break; + + case T_AlterUserStmt: + tag = "ALTER USER"; + break; + + case T_AlterUserSetStmt: + tag = "ALTER USER"; + break; + + case T_DropUserStmt: + tag = "DROP USER"; + break; + + case T_LockStmt: + tag = "LOCK TABLE"; + break; + + case T_ConstraintsSetStmt: + tag = "SET CONSTRAINTS"; + break; + + case T_CreateGroupStmt: + tag = "CREATE GROUP"; + break; + + case T_AlterGroupStmt: + tag = "ALTER GROUP"; + break; + + case T_DropGroupStmt: + tag = "DROP GROUP"; + break; + + case T_CheckPointStmt: + tag = "CHECKPOINT"; + break; + + case T_ReindexStmt: + tag = "REINDEX"; + break; + + case T_CreateConversionStmt: + tag = "CREATE CONVERSION"; + break; + + case T_CreateCastStmt: + tag = "CREATE CAST"; + break; + + case T_DropCastStmt: + tag = "DROP CAST"; + break; + + case T_CreateOpClassStmt: + tag = "CREATE OPERATOR CLASS"; + break; + + case T_RemoveOpClassStmt: + tag = "DROP OPERATOR CLASS"; + break; + + case T_PrepareStmt: + tag = "PREPARE"; + break; + + case T_ExecuteStmt: + tag = "EXECUTE"; + break; + + case T_DeallocateStmt: + tag = "DEALLOCATE"; + break; + + default: + elog(LOG, "CreateCommandTag: unknown parse node type %d", + nodeTag(parsetree)); + tag = "???"; + break; + } + + return tag; +} diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 729137a1b9..ba4c48f71b 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/mcxt.c,v 1.39 2003/03/27 16:51:29 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/mcxt.c,v 1.40 2003/05/02 20:54:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,9 +43,11 @@ MemoryContext TopMemoryContext = NULL; MemoryContext ErrorContext = NULL; MemoryContext PostmasterContext = NULL; MemoryContext CacheMemoryContext = NULL; -MemoryContext QueryContext = NULL; +MemoryContext MessageContext = NULL; MemoryContext TopTransactionContext = NULL; -MemoryContext TransactionCommandContext = NULL; +/* These two are transient links to contexts owned by other objects: */ +MemoryContext QueryContext = NULL; +MemoryContext PortalContext = NULL; /***************************************************************************** diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 0b093b2613..974e69a2f1 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -1,36 +1,21 @@ /*------------------------------------------------------------------------- * * portalmem.c - * backend portal memory context management stuff + * backend portal memory management + * + * Portals are objects representing the execution state of a query. + * This module provides memory management services for portals, but it + * doesn't actually run the executor for them. + * * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.55 2003/04/29 03:21:29 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.56 2003/05/02 20:54:35 tgl Exp $ * *------------------------------------------------------------------------- */ -/* - * NOTES - * A "Portal" is a structure used to keep track of cursor queries. - * - * When the backend sees a "declare cursor" query, it allocates a - * "PortalData" structure, plans the query and then stores the query - * in the portal without executing it. Later, when the backend - * sees a - * fetch 1 from foo - * the system looks up the portal named "foo" in the portal table, - * gets the planned query and then calls the executor with a count - * of 1. The executor then runs the query and returns a single - * tuple. The problem is that we have to hold onto the state of the - * portal query until we see a "close". This means we have to be - * careful about memory management. - * - * I hope this makes things clearer to whoever reads this -cim 2/22/91 - */ - #include "postgres.h" #include "commands/portalcmds.h" @@ -51,6 +36,9 @@ * ---------------- */ +Portal CurrentPortal = NULL; /* globally visible pointer */ + + #define MAX_PORTALNAME_LEN NAMEDATALEN typedef struct portalhashent @@ -66,7 +54,7 @@ do { \ PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \ \ MemSet(key, 0, MAX_PORTALNAME_LEN); \ - snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", NAME); \ + StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \ hentry = (PortalHashEnt*)hash_search(PortalHashTable, \ key, HASH_FIND, NULL); \ if (hentry) \ @@ -75,12 +63,12 @@ do { \ PORTAL = NULL; \ } while(0) -#define PortalHashTableInsert(PORTAL) \ +#define PortalHashTableInsert(PORTAL, NAME) \ do { \ PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \ \ MemSet(key, 0, MAX_PORTALNAME_LEN); \ - snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \ + StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \ hentry = (PortalHashEnt*)hash_search(PortalHashTable, \ key, HASH_ENTER, &found); \ if (hentry == NULL) \ @@ -88,6 +76,8 @@ do { \ if (found) \ elog(WARNING, "trying to insert a portal name that exists."); \ hentry->portal = PORTAL; \ + /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \ + PORTAL->name = hentry->portalname; \ } while(0) #define PortalHashTableDelete(PORTAL) \ @@ -95,7 +85,7 @@ do { \ PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \ \ MemSet(key, 0, MAX_PORTALNAME_LEN); \ - snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \ + StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \ hentry = (PortalHashEnt*)hash_search(PortalHashTable, \ key, HASH_REMOVE, NULL); \ if (hentry == NULL) \ @@ -155,50 +145,17 @@ GetPortalByName(const char *name) return portal; } -/* - * PortalSetQuery - * Attaches a QueryDesc to the specified portal. This should be - * called only after successfully doing ExecutorStart for the query. - * - * Note that in the case of DECLARE CURSOR, some Portal options have - * already been set in portalcmds.c's PreparePortal(). This is grotty. - */ -void -PortalSetQuery(Portal portal, QueryDesc *queryDesc) -{ - AssertArg(PortalIsValid(portal)); - - /* - * If the user didn't specify a SCROLL type, allow or disallow - * scrolling based on whether it would require any additional - * runtime overhead to do so. - */ - if (portal->scrollType == DEFAULT_SCROLL) - { - if (ExecSupportsBackwardScan(queryDesc->plantree)) - portal->scrollType = ENABLE_SCROLL; - else - portal->scrollType = DISABLE_SCROLL; - } - - portal->queryDesc = queryDesc; - portal->executorRunning = true; /* now need to shut down executor */ - - portal->atStart = true; - portal->atEnd = false; /* allow fetches */ - portal->portalPos = 0; - portal->posOverflow = false; -} - /* * CreatePortal * Returns a new portal given a name. * - * An elog(WARNING) is emitted if portal name is in use (existing - * portal is returned!) + * allowDup: if true, automatically drop any pre-existing portal of the + * same name (if false, an error is raised). + * + * dupSilent: if true, don't even emit a WARNING. */ Portal -CreatePortal(const char *name) +CreatePortal(const char *name, bool allowDup, bool dupSilent) { Portal portal; @@ -207,43 +164,90 @@ CreatePortal(const char *name) portal = GetPortalByName(name); if (PortalIsValid(portal)) { - elog(WARNING, "CreatePortal: portal \"%s\" already exists", name); - return portal; + if (!allowDup) + elog(ERROR, "Portal \"%s\" already exists", name); + if (!dupSilent) + elog(WARNING, "Closing pre-existing portal \"%s\"", name); + PortalDrop(portal, false); } /* make new portal structure */ - portal = (Portal) MemoryContextAlloc(PortalMemory, sizeof *portal); + portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal); - /* initialize portal name */ - portal->name = MemoryContextStrdup(PortalMemory, name); - - /* initialize portal heap context */ + /* initialize portal heap context; typically it won't store much */ portal->heap = AllocSetContextCreate(PortalMemory, "PortalHeapMemory", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_SMALL_MAXSIZE); - /* initialize portal query */ - portal->queryDesc = NULL; + /* initialize portal fields that don't start off zero */ portal->cleanup = PortalCleanup; - portal->scrollType = DEFAULT_SCROLL; - portal->executorRunning = false; - portal->holdOpen = false; portal->createXact = GetCurrentTransactionId(); - portal->holdStore = NULL; - portal->holdContext = NULL; + portal->strategy = PORTAL_MULTI_QUERY; + portal->cursorOptions = CURSOR_OPT_NO_SCROLL; portal->atStart = true; portal->atEnd = true; /* disallow fetches until query is set */ - portal->portalPos = 0; - portal->posOverflow = false; - /* put portal in table */ - PortalHashTableInsert(portal); + /* put portal in table (sets portal->name) */ + PortalHashTableInsert(portal, name); return portal; } +/* + * CreateNewPortal + * Create a new portal, assigning it a random nonconflicting name. + */ +Portal +CreateNewPortal(void) +{ + static unsigned int unnamed_portal_count = 0; + + char portalname[MAX_PORTALNAME_LEN]; + + /* Select a nonconflicting name */ + for (;;) + { + unnamed_portal_count++; + sprintf(portalname, "", unnamed_portal_count); + if (GetPortalByName(portalname) == NULL) + break; + } + + return CreatePortal(portalname, false, false); +} + +/* + * PortalDefineQuery + * A simple subroutine to establish a portal's query. + * + * Notes: the passed commandTag must be a pointer to a constant string, + * since it is not copied. The caller is responsible for ensuring that + * the passed sourceText (if any), parse and plan trees have adequate + * lifetime. Also, queryContext must accurately describe the location + * of the parse and plan trees. + */ +void +PortalDefineQuery(Portal portal, + const char *sourceText, + const char *commandTag, + List *parseTrees, + List *planTrees, + MemoryContext queryContext) +{ + AssertArg(PortalIsValid(portal)); + AssertState(portal->queryContext == NULL); /* else defined already */ + + Assert(length(parseTrees) == length(planTrees)); + + portal->sourceText = sourceText; + portal->commandTag = commandTag; + portal->parseTrees = parseTrees; + portal->planTrees = planTrees; + portal->queryContext = queryContext; +} + /* * PortalDrop * Destroy the portal. @@ -256,6 +260,10 @@ PortalDrop(Portal portal, bool isError) { AssertArg(PortalIsValid(portal)); + /* Not sure if this case can validly happen or not... */ + if (portal->portalActive) + elog(ERROR, "PortalDrop: can't drop active portal"); + /* * Remove portal from hash table. Because we do this first, we will * not come back to try to remove the portal again if there's any error @@ -273,21 +281,43 @@ PortalDrop(Portal portal, bool isError) MemoryContextDelete(portal->holdContext); /* release subsidiary storage */ - if (PortalGetHeapMemory(portal)) - MemoryContextDelete(PortalGetHeapMemory(portal)); + MemoryContextDelete(PortalGetHeapMemory(portal)); - /* release name and portal data (both are in PortalMemory) */ - pfree(portal->name); + /* release portal struct (it's in PortalMemory) */ pfree(portal); } /* - * Cleanup the portals created in the current transaction. If the - * transaction was aborted, all the portals created in this transaction - * should be removed. If the transaction was successfully committed, any - * holdable cursors created in this transaction need to be kept - * open. In any case, portals remaining from prior transactions should - * be left untouched. + * DropDependentPortals + * Drop any portals using the specified context as queryContext. + * + * This is normally used to make sure we can safely drop a prepared statement. + */ +void +DropDependentPortals(MemoryContext queryContext) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->queryContext == queryContext) + PortalDrop(portal, false); + } +} + + +/* + * Pre-commit processing for portals. + * + * Any holdable cursors created in this transaction need to be converted to + * materialized form, since we are going to close down the executor and + * release locks. Remove all other portals created in this transaction. + * Portals remaining from prior transactions should be left untouched. * * XXX This assumes that portals can be deleted in a random order, ie, * no portal has a reference to any other (at least not one that will be @@ -296,7 +326,7 @@ PortalDrop(Portal portal, bool isError) * references... */ void -AtEOXact_portals(bool isCommit) +AtCommit_Portals(void) { HASH_SEQ_STATUS status; PortalHashEnt *hentry; @@ -308,11 +338,21 @@ AtEOXact_portals(bool isCommit) { Portal portal = hentry->portal; - if (portal->createXact != xact) + /* + * Do not touch active portals --- this can only happen in the case of + * a multi-transaction utility command, such as VACUUM. + */ + if (portal->portalActive) continue; - if (portal->holdOpen && isCommit) + if (portal->cursorOptions & CURSOR_OPT_HOLD) { + /* + * Do nothing to cursors held over from a previous transaction. + */ + if (portal->createXact != xact) + continue; + /* * We are exiting the transaction that created a holdable * cursor. Instead of dropping the portal, prepare it for @@ -321,7 +361,8 @@ AtEOXact_portals(bool isCommit) /* * Create the memory context that is used for storage of - * the held cursor's tuple set. + * the held cursor's tuple set. Note this is NOT a child + * of the portal's heap memory. */ portal->holdContext = AllocSetContextCreate(PortalMemory, @@ -341,7 +382,91 @@ AtEOXact_portals(bool isCommit) } else { - PortalDrop(portal, !isCommit); + /* Zap all non-holdable portals */ + PortalDrop(portal, false); + } + } +} + +/* + * Abort processing for portals. + * + * At this point we reset the "active" flags and run the cleanup hook if + * present, but we can't release memory until the cleanup call. + * + * The reason we need to reset active is so that we can replace the unnamed + * portal, else we'll fail to execute ROLLBACK when it arrives. Also, we + * want to run the cleanup hook now to be certain it knows that we had an + * error abort and not successful conclusion. + */ +void +AtAbort_Portals(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + TransactionId xact = GetCurrentTransactionId(); + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + portal->portalActive = false; + + /* + * Do nothing else to cursors held over from a previous transaction. + * (This test must include checking CURSOR_OPT_HOLD, else we will + * fail to clean up a VACUUM portal if it fails after its first + * sub-transaction.) + */ + if (portal->createXact != xact && + (portal->cursorOptions & CURSOR_OPT_HOLD)) + continue; + + /* let portalcmds.c clean up the state it knows about */ + if (PointerIsValid(portal->cleanup)) + { + (*portal->cleanup) (portal, true); + portal->cleanup = NULL; } } } + +/* + * Post-abort cleanup for portals. + * + * Delete all portals not held over from prior transactions. + */ +void +AtCleanup_Portals(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + TransactionId xact = GetCurrentTransactionId(); + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + /* + * Let's just make sure no one's active... + */ + portal->portalActive = false; + + /* + * Do nothing else to cursors held over from a previous transaction. + * (This test must include checking CURSOR_OPT_HOLD, else we will + * fail to clean up a VACUUM portal if it fails after its first + * sub-transaction.) + */ + if (portal->createXact != xact && + (portal->cursorOptions & CURSOR_OPT_HOLD)) + continue; + + /* Else zap it with prejudice. */ + PortalDrop(portal, true); + } +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index c1aaadaac3..0c14811682 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: catversion.h,v 1.184 2003/04/08 23:20:02 tgl Exp $ + * $Id: catversion.h,v 1.185 2003/05/02 20:54:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200304071 +#define CATALOG_VERSION_NO 200305011 #endif diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h index 66bfca2b8b..efa60869fa 100644 --- a/src/include/commands/portalcmds.h +++ b/src/include/commands/portalcmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: portalcmds.h,v 1.7 2003/04/29 03:21:30 tgl Exp $ + * $Id: portalcmds.h,v 1.8 2003/05/02 20:54:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,13 +22,10 @@ extern void PerformCursorOpen(DeclareCursorStmt *stmt, CommandDest dest); extern void PerformPortalFetch(FetchStmt *stmt, CommandDest dest, char *completionTag); -extern long DoPortalFetch(Portal portal, - FetchDirection fdirection, - long count, - CommandDest dest); - extern void PerformPortalClose(char *name); extern void PortalCleanup(Portal portal, bool isError); +extern void PersistHoldablePortal(Portal portal); + #endif /* PORTALCMDS_H */ diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h index 6a2acd7b21..5dd0ff2a6e 100644 --- a/src/include/executor/spi_priv.h +++ b/src/include/executor/spi_priv.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: spi_priv.h,v 1.13 2002/10/14 23:49:20 tgl Exp $ + * $Id: spi_priv.h,v 1.14 2003/05/02 20:54:35 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -36,8 +36,6 @@ typedef struct /* Argument types, if a prepared plan */ int nargs; Oid *argtypes; - /* Command type of last original parsetree */ - CmdType origCmdType; } _SPI_plan; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 3d4b235e56..4ad35b683a 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: parsenodes.h,v 1.236 2003/03/27 16:51:29 momjian Exp $ + * $Id: parsenodes.h,v 1.237 2003/05/02 20:54:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -47,6 +47,8 @@ typedef struct Query QuerySource querySource; /* where did I come from? */ + bool canSetTag; /* do I set the command result tag? */ + Node *utilityStmt; /* non-null if this is a non-optimizable * statement */ diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h index c992306a9a..bf3344d2bd 100644 --- a/src/include/tcop/pquery.h +++ b/src/include/tcop/pquery.h @@ -7,17 +7,32 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: pquery.h,v 1.24 2003/03/10 03:53:52 tgl Exp $ + * $Id: pquery.h,v 1.25 2003/05/02 20:54:36 tgl Exp $ * *------------------------------------------------------------------------- */ #ifndef PQUERY_H #define PQUERY_H -#include "executor/execdesc.h" +#include "utils/portal.h" -extern void ProcessQuery(Query *parsetree, Plan *plan, CommandDest dest, - char *completionTag); +extern void ProcessQuery(Query *parsetree, + Plan *plan, + ParamListInfo params, + const char *portalName, + CommandDest dest, + char *completionTag); + +extern void PortalStart(Portal portal, ParamListInfo params); + +extern bool PortalRun(Portal portal, long count, + CommandDest dest, CommandDest altdest, + char *completionTag); + +extern long PortalRunFetch(Portal portal, + FetchDirection fdirection, + long count, + CommandDest dest); #endif /* PQUERY_H */ diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index b81df05320..c1fa9c1a6d 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: tcopprot.h,v 1.55 2003/04/29 22:13:11 tgl Exp $ + * $Id: tcopprot.h,v 1.56 2003/05/02 20:54:36 tgl Exp $ * * OLD COMMENTS * This file was created so that other c files could get the two @@ -41,6 +41,7 @@ extern List *pg_analyze_and_rewrite(Node *parsetree, extern List *pg_parse_and_rewrite(const char *query_string, Oid *paramTypes, int numParams); extern Plan *pg_plan_query(Query *querytree); +extern List *pg_plan_queries(List *querytrees, bool needSnapshot); #endif /* BOOTSTRAP_INCLUDE */ diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h index 9cca85415f..96c0dc43c5 100644 --- a/src/include/tcop/utility.h +++ b/src/include/tcop/utility.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: utility.h,v 1.16 2002/09/04 20:31:45 momjian Exp $ + * $Id: utility.h,v 1.17 2003/05/02 20:54:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -19,4 +19,6 @@ extern void ProcessUtility(Node *parsetree, CommandDest dest, char *completionTag); +extern const char *CreateCommandTag(Node *parsetree); + #endif /* UTILITY_H */ diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index 09198d1c6e..6934d10962 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: memutils.h,v 1.50 2002/12/16 16:22:46 tgl Exp $ + * $Id: memutils.h,v 1.51 2003/05/02 20:54:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -68,9 +68,11 @@ extern DLLIMPORT MemoryContext TopMemoryContext; extern DLLIMPORT MemoryContext ErrorContext; extern DLLIMPORT MemoryContext PostmasterContext; extern DLLIMPORT MemoryContext CacheMemoryContext; -extern DLLIMPORT MemoryContext QueryContext; +extern DLLIMPORT MemoryContext MessageContext; extern DLLIMPORT MemoryContext TopTransactionContext; -extern DLLIMPORT MemoryContext TransactionCommandContext; +/* These two are transient links to contexts owned by other objects: */ +extern DLLIMPORT MemoryContext QueryContext; +extern DLLIMPORT MemoryContext PortalContext; /* diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index 2615db0490..2353ba01d4 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -4,12 +4,42 @@ * POSTGRES portal definitions. * * A portal is an abstraction which represents the execution state of - * a running query (specifically, a CURSOR). + * a running or runnable query. Portals support both SQL-level CURSORs + * and protocol-level portals. + * + * Scrolling (nonsequential access) and suspension of execution are allowed + * only for portals that contain a single SELECT-type query. We do not want + * to let the client suspend an update-type query partway through! Because + * the query rewriter does not allow arbitrary ON SELECT rewrite rules, + * only queries that were originally update-type could produce multiple + * parse/plan trees; so the restriction to a single query is not a problem + * in practice. + * + * For SQL cursors, we support three kinds of scroll behavior: + * + * (1) Neither NO SCROLL nor SCROLL was specified: to remain backward + * compatible, we allow backward fetches here, unless it would + * impose additional runtime overhead to do so. + * + * (2) NO SCROLL was specified: don't allow any backward fetches. + * + * (3) SCROLL was specified: allow all kinds of backward fetches, even + * if we need to take a performance hit to do so. (The planner sticks + * a Materialize node atop the query plan if needed.) + * + * Case #1 is converted to #2 or #3 by looking at the query itself and + * determining if scrollability can be supported without additional + * overhead. + * + * Protocol-level portals have no nonsequential-fetch API and so the + * distinction doesn't matter for them. They are always initialized + * to look like NO SCROLL cursors. + * * * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: portal.h,v 1.41 2003/04/29 03:21:30 tgl Exp $ + * $Id: portal.h,v 1.42 2003/05/02 20:54:36 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,41 +50,81 @@ #include "nodes/memnodes.h" #include "utils/tuplestore.h" + /* - * We support three kinds of scroll behavior: + * We have several execution strategies for Portals, depending on what + * query or queries are to be executed. (Note: in all cases, a Portal + * executes just a single source-SQL query, and thus produces just a + * single result from the user's viewpoint. However, the rule rewriter + * may expand the single source query to zero or many actual queries.) * - * (1) Neither NO SCROLL nor SCROLL was specified: to remain backward - * compatible, we allow backward fetches here, unless it would - * impose additional runtime overhead to do so. + * PORTAL_ONE_SELECT: the portal contains one single SELECT query. We run + * the Executor incrementally as results are demanded. This strategy also + * supports holdable cursors (the Executor results can be dumped into a + * tuplestore for access after transaction completion). * - * (2) NO SCROLL was specified: don't allow any backward fetches. - * - * (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 */ @@ -86,12 +156,25 @@ typedef struct PortalData #define PortalGetHeapMemory(portal) ((portal)->heap) +/* Currently executing Portal, if any */ +extern DLLIMPORT Portal CurrentPortal; + + +/* Prototypes for functions in utils/mmgr/portalmem.c */ extern void EnablePortalManager(void); -extern void AtEOXact_portals(bool isCommit); -extern Portal CreatePortal(const char *name); +extern void AtCommit_Portals(void); +extern void AtAbort_Portals(void); +extern void AtCleanup_Portals(void); +extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent); +extern Portal CreateNewPortal(void); extern void PortalDrop(Portal portal, bool isError); +extern void DropDependentPortals(MemoryContext queryContext); extern Portal GetPortalByName(const char *name); -extern void PortalSetQuery(Portal portal, QueryDesc *queryDesc); -extern void PersistHoldablePortal(Portal portal); +extern void PortalDefineQuery(Portal portal, + const char *sourceText, + const char *commandTag, + List *parseTrees, + List *planTrees, + MemoryContext queryContext); #endif /* PORTAL_H */ -- GitLab