diff --git a/contrib/intagg/int_aggregate.c b/contrib/intagg/int_aggregate.c index 1c14e7c2db7b82aeccb8fa0b2baa7cbf8bf7decd..9f4b5d09182c809aeaa38f1b9819ab822009832f 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 6532cc1c8942c59cba93930ffecf635c964445ed..e09c523b1ab2b236b3ef30a62040a72b40ed803c 100644 --- a/doc/src/sgml/ref/postgres-ref.sgml +++ b/doc/src/sgml/ref/postgres-ref.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/ref/postgres-ref.sgml,v 1.32 2003/03/25 16:15:43 petere Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/ref/postgres-ref.sgml,v 1.33 2003/05/02 20:54:33 tgl Exp $ PostgreSQL documentation --> @@ -28,7 +28,6 @@ PostgreSQL documentation <arg>-E</arg> <arg>-f<group choice="plain"><arg>s</arg><arg>i</arg><arg>t</arg><arg>n</arg><arg>m</arg><arg>h</arg></group></arg> <arg>-F</arg> - <arg>-i</arg> <arg>-N</arg> <arg>-o <replaceable>filename</replaceable></arg> <arg>-O</arg> @@ -52,7 +51,6 @@ PostgreSQL documentation <arg>-e</arg> <arg>-f<group choice="plain"><arg>s</arg><arg>i</arg><arg>t</arg><arg>n</arg><arg>m</arg><arg>h</arg></group></arg> <arg>-F</arg> - <arg>-i</arg> <arg>-o <replaceable>filename</replaceable></arg> <arg>-O</arg> <arg>-p <replaceable>database</replaceable></arg> @@ -281,15 +279,6 @@ PostgreSQL documentation </listitem> </varlistentry> - <varlistentry> - <term><option>-i</option></term> - <listitem> - <para> - Prevents query execution, but shows the plan tree. - </para> - </listitem> - </varlistentry> - <varlistentry> <term><option>-O</option></term> <listitem> diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index a15985d3bd787f808142ef00283c5b39f4ccf511..59b43656d5f049db763f24f68417c70cac962562 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 0c1a7d3c70c2d588ffa6c6208c95d40c4126ec3e..6c01094c5a9f74a7700e6dcd777e54de36f88619 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 10d240d6d148ba0d9e5593796dd24d0f29b3471c..5232faadc5c952c4cf721c0053650444e984d139 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 9dc7583f06c70bbaa1eab90e4895cbcacefcdef4..35ed8a270b5a475f090a418824c7c8aba800408d 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 f6cd5d0a802f0fef4e5b48e5b2594630a6cc9506..5a3e3f589d19ffc971cd239e75f9e7534ab5a16e 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 03bd3de32853a63ec69ea4240ac8f016e0604335..9667eabb8bf1f71b346633a62ac67a896165eec0 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 8c6a7c04b6603bc6794278ea3b4ab06b9b128ba3..a2d0a8346f1d1e776372d5373310dec6236edf69 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 aca27d0aebf3597900e057dbe046e6be9c53777c..3b1e6c4bb3f99472ebd7e7b624e910c41891c6c1 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 cursor %d>", unnamed_portal_count); - if (GetPortalByName(portalname) == NULL) - break; - } - - name = portalname; + /* Use a random nonconflicting name */ + portal = CreateNewPortal(); } else { - /* Ensure the portal doesn't exist already */ - portal = GetPortalByName(name); - if (portal != NULL) - elog(ERROR, "cursor \"%s\" already in use", name); + /* In this path, error if portal of same name already exists */ + portal = CreatePortal(name, false, false); } - /* Create the portal */ - portal = CreatePortal(name); - /* Switch to portals memory and copy the parsetree and plan to there */ oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); queryTree = copyObject(queryTree); @@ -801,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 01fffed8168168d6b31f9509ba38c411169b132c..c4d16ef5e9846081e799b42f1bef7313501fb6ca 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 cbf61970d4748aa71811cf9031fc2dbaee5dd126..4c773657fe49bfed6e3b159bcfd147255df465e9 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 31e5319711617f3e4ef126a54b2113a0e2b98160..e4c10ed968665f4759c3e8eaf5ed413bddf3eff2 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 45c7a2a301b67464e44dece5d3f6fdb9e096c401..654905b09628138f92dc6dc7ae8578b51e8bc1bb 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 4f75942169ba65d7dcf8b843d504fb5f53a83167..3c8e7501f434c6626e8b369a9918f44c33f31118 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 d53a160a4ebd2d3d40b3cf74bdf3f8ced10fea9b..1be69e93f352b201f06ba27ca43e7f5d139d0946 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 c2159a70e0e78f1e550030cc239f2f7c215e1cde..a488d1d91e5cfd4d2626fef40cf77a557784faa0 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 8ca7fb954f098817f29d1f3a35bcfd2b355a9018..5d481a3f0a5adb226a7cf022eff199bfce16b880 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 b87509573497abb51715cac3a9e0cf3cd3bcd4fe..78fcfdb7e0ef845230fb53c19fe40099973389cd 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 bc98d1d91ecd17f0a93d91d6b7ae883846799daa..6c2e456ccc17d0c94a2d2a8766665975a1da5593 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 29d5018440dee68ec5f58644aefa0c75f321de73..f70b91322445713f14921f0930037dd5b2b11acf 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 390adeb9cb9982146a2065ca04ca23a3c78b41d7..a0431f350c8f55d144b17ca980cfae4b4d845fbd 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 729137a1b9290c0a3dd6a4e865e16948e5be13fe..ba4c48f71ba019b5e3b986c5e30fb352a0e77e7b 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 0b093b2613c492213f64d7d8137cbdb566145deb..974e69a2f1aee9189c15f1ba12acf1ef69f0473b 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 %u>", unnamed_portal_count); + if (GetPortalByName(portalname) == NULL) + break; + } + + return CreatePortal(portalname, false, false); +} + +/* + * PortalDefineQuery + * A simple subroutine to establish a portal's query. + * + * Notes: the passed commandTag must be a pointer to a constant string, + * since it is not copied. The caller is responsible for ensuring that + * the passed sourceText (if any), parse and plan trees have adequate + * lifetime. Also, queryContext must accurately describe the location + * of the parse and plan trees. + */ +void +PortalDefineQuery(Portal portal, + const char *sourceText, + const char *commandTag, + List *parseTrees, + List *planTrees, + MemoryContext queryContext) +{ + AssertArg(PortalIsValid(portal)); + AssertState(portal->queryContext == NULL); /* else defined already */ + + Assert(length(parseTrees) == length(planTrees)); + + portal->sourceText = sourceText; + portal->commandTag = commandTag; + portal->parseTrees = parseTrees; + portal->planTrees = planTrees; + portal->queryContext = queryContext; +} + /* * PortalDrop * Destroy the portal. @@ -256,6 +260,10 @@ PortalDrop(Portal portal, bool isError) { AssertArg(PortalIsValid(portal)); + /* Not sure if this case can validly happen or not... */ + if (portal->portalActive) + elog(ERROR, "PortalDrop: can't drop active portal"); + /* * Remove portal from hash table. Because we do this first, we will * not come back to try to remove the portal again if there's any error @@ -273,21 +281,43 @@ PortalDrop(Portal portal, bool isError) MemoryContextDelete(portal->holdContext); /* release subsidiary storage */ - if (PortalGetHeapMemory(portal)) - MemoryContextDelete(PortalGetHeapMemory(portal)); + MemoryContextDelete(PortalGetHeapMemory(portal)); - /* release name and portal data (both are in PortalMemory) */ - pfree(portal->name); + /* release portal struct (it's in PortalMemory) */ pfree(portal); } /* - * Cleanup the portals created in the current transaction. If the - * transaction was aborted, all the portals created in this transaction - * should be removed. If the transaction was successfully committed, any - * holdable cursors created in this transaction need to be kept - * open. In any case, portals remaining from prior transactions should - * be left untouched. + * DropDependentPortals + * Drop any portals using the specified context as queryContext. + * + * This is normally used to make sure we can safely drop a prepared statement. + */ +void +DropDependentPortals(MemoryContext queryContext) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->queryContext == queryContext) + PortalDrop(portal, false); + } +} + + +/* + * Pre-commit processing for portals. + * + * Any holdable cursors created in this transaction need to be converted to + * materialized form, since we are going to close down the executor and + * release locks. Remove all other portals created in this transaction. + * Portals remaining from prior transactions should be left untouched. * * XXX This assumes that portals can be deleted in a random order, ie, * no portal has a reference to any other (at least not one that will be @@ -296,7 +326,7 @@ PortalDrop(Portal portal, bool isError) * references... */ void -AtEOXact_portals(bool isCommit) +AtCommit_Portals(void) { HASH_SEQ_STATUS status; PortalHashEnt *hentry; @@ -308,11 +338,21 @@ AtEOXact_portals(bool isCommit) { Portal portal = hentry->portal; - if (portal->createXact != xact) + /* + * Do not touch active portals --- this can only happen in the case of + * a multi-transaction utility command, such as VACUUM. + */ + if (portal->portalActive) continue; - if (portal->holdOpen && isCommit) + if (portal->cursorOptions & CURSOR_OPT_HOLD) { + /* + * Do nothing to cursors held over from a previous transaction. + */ + if (portal->createXact != xact) + continue; + /* * We are exiting the transaction that created a holdable * cursor. Instead of dropping the portal, prepare it for @@ -321,7 +361,8 @@ AtEOXact_portals(bool isCommit) /* * Create the memory context that is used for storage of - * the held cursor's tuple set. + * the held cursor's tuple set. Note this is NOT a child + * of the portal's heap memory. */ portal->holdContext = AllocSetContextCreate(PortalMemory, @@ -341,7 +382,91 @@ AtEOXact_portals(bool isCommit) } else { - PortalDrop(portal, !isCommit); + /* Zap all non-holdable portals */ + PortalDrop(portal, false); + } + } +} + +/* + * Abort processing for portals. + * + * At this point we reset the "active" flags and run the cleanup hook if + * present, but we can't release memory until the cleanup call. + * + * The reason we need to reset active is so that we can replace the unnamed + * portal, else we'll fail to execute ROLLBACK when it arrives. Also, we + * want to run the cleanup hook now to be certain it knows that we had an + * error abort and not successful conclusion. + */ +void +AtAbort_Portals(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + TransactionId xact = GetCurrentTransactionId(); + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + portal->portalActive = false; + + /* + * Do nothing else to cursors held over from a previous transaction. + * (This test must include checking CURSOR_OPT_HOLD, else we will + * fail to clean up a VACUUM portal if it fails after its first + * sub-transaction.) + */ + if (portal->createXact != xact && + (portal->cursorOptions & CURSOR_OPT_HOLD)) + continue; + + /* let portalcmds.c clean up the state it knows about */ + if (PointerIsValid(portal->cleanup)) + { + (*portal->cleanup) (portal, true); + portal->cleanup = NULL; } } } + +/* + * Post-abort cleanup for portals. + * + * Delete all portals not held over from prior transactions. + */ +void +AtCleanup_Portals(void) +{ + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + TransactionId xact = GetCurrentTransactionId(); + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + /* + * Let's just make sure no one's active... + */ + portal->portalActive = false; + + /* + * Do nothing else to cursors held over from a previous transaction. + * (This test must include checking CURSOR_OPT_HOLD, else we will + * fail to clean up a VACUUM portal if it fails after its first + * sub-transaction.) + */ + if (portal->createXact != xact && + (portal->cursorOptions & CURSOR_OPT_HOLD)) + continue; + + /* Else zap it with prejudice. */ + PortalDrop(portal, true); + } +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index c1aaadaac3df8a9cff2c67140819a348dc1d5ab2..0c14811682d4e053e8b5044031d168452043eb62 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 66bfca2b8b6b81c549ae8ddcfd1b22716d03f8cf..efa60869fa68e27c8479d3f761945ec1ef98ae6e 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 6a2acd7b21c680299eccdc816f2bad838e7beb56..5dd0ff2a6e32d3e4c34b1d0e6d423cdcb0bd0dac 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 3d4b235e5629e4b999b8a7236d5d1a33204ca610..4ad35b683a765496f6de09615c5bb3b137a3c2f0 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 c992306a9af377c9b5eb79edb04724c9ff87b3d1..bf3344d2bd7c5e71131a762e666684824da42ba6 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 b81df05320cd106a6f423a908b4ced14a4876033..c1fa9c1a6d737a27a7ff0d9006c3f89e5334a164 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 9cca85415f565cb49dbc26ed96f2de7db2efe6ce..96c0dc43c5c602b8d43077180f31577b3bdf80f1 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 09198d1c6e60bdc8cc2a85967928d300aa4ad955..6934d109623909e78bbd7649c00054247fe91214 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 2615db0490d56d623be7359483c9c82dfdc53c53..2353ba01d4cc23f1c27b6447f9b2d5b492051d51 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 */