* 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.
+ * 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-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2016, 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.23 2003/08/08 21:41:32 momjian Exp $
+ * src/backend/commands/portalcmds.c
*
*-------------------------------------------------------------------------
*/
#include <limits.h>
+#include "access/xact.h"
#include "commands/portalcmds.h"
#include "executor/executor.h"
-#include "optimizer/planner.h"
-#include "rewrite/rewriteHandler.h"
+#include "executor/tstoreReceiver.h"
#include "tcop/pquery.h"
#include "utils/memutils.h"
+#include "utils/snapmgr.h"
/*
* PerformCursorOpen
* Execute SQL DECLARE CURSOR command.
+ *
+ * The query has already been through parse analysis, rewriting, and planning.
+ * When it gets here, it looks like a SELECT PlannedStmt, except that the
+ * utilityStmt field is set.
*/
void
-PerformCursorOpen(DeclareCursorStmt *stmt)
+PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
+ const char *queryString, bool isTopLevel)
{
- List *rewritten;
- Query *query;
- Plan *plan;
+ DeclareCursorStmt *cstmt = (DeclareCursorStmt *) stmt->utilityStmt;
Portal portal;
MemoryContext oldContext;
+ if (cstmt == NULL || !IsA(cstmt, DeclareCursorStmt))
+ elog(ERROR, "PerformCursorOpen called for non-cursor query");
+
/*
* Disallow empty-string cursor name (conflicts with protocol-level
* unnamed portal).
*/
- if (!stmt->portalname || stmt->portalname[0] == '\0')
+ if (!cstmt->portalname || cstmt->portalname[0] == '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_NAME),
errmsg("invalid cursor name: must not be empty")));
/*
- * If this is a non-holdable cursor, we require that this statement
- * has been executed inside a transaction block (or else, it would
- * have no user-visible effect).
+ * If this is a non-holdable cursor, we require that this statement has
+ * been executed inside a transaction block (or else, it would have no
+ * user-visible effect).
*/
- if (!(stmt->options & CURSOR_OPT_HOLD))
- RequireTransactionChain((void *) stmt, "DECLARE CURSOR");
-
- /*
- * The query has been through parse analysis, but not rewriting or
- * planning as yet. Note that the grammar ensured we have a SELECT
- * query, so we are not expecting rule rewriting to do anything
- * strange.
- */
- rewritten = QueryRewrite((Query *) stmt->query);
- if (length(rewritten) != 1 || !IsA(lfirst(rewritten), Query))
- elog(ERROR, "unexpected rewrite result");
- query = (Query *) lfirst(rewritten);
- if (query->commandType != CMD_SELECT)
- elog(ERROR, "unexpected rewrite result");
-
- if (query->into)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("DECLARE CURSOR may not specify INTO")));
- if (query->rowMarks != NIL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("DECLARE CURSOR ... FOR UPDATE is not supported"),
- errdetail("Cursors must be READ ONLY.")));
-
- plan = planner(query, true, stmt->options);
+ if (!(cstmt->options & CURSOR_OPT_HOLD))
+ RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
/*
- * Create a portal and copy the query and plan into its memory
- * context. (If a duplicate cursor name already exists, warn and drop
- * it.)
+ * Create a portal and copy the plan and queryString into its memory.
*/
- portal = CreatePortal(stmt->portalname, true, false);
+ portal = CreatePortal(cstmt->portalname, false, false);
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
- query = copyObject(query);
- plan = copyObject(plan);
+ stmt = copyObject(stmt);
+ stmt->utilityStmt = NULL; /* make it look like plain SELECT */
+
+ queryString = pstrdup(queryString);
PortalDefineQuery(portal,
- NULL, /* unfortunately don't have sourceText */
+ NULL,
+ queryString,
"SELECT", /* cursor's query is always a SELECT */
- makeList1(query),
- makeList1(plan),
- PortalGetHeapMemory(portal));
+ list_make1(stmt),
+ NULL);
+
+ /*----------
+ * Also copy the outer portal's parameter list into the inner portal's
+ * memory context. We want to pass down the parameter values in case we
+ * had a command like
+ * DECLARE c CURSOR FOR SELECT ... WHERE foo = $1
+ * This will have been parsed using the outer parameter set and the
+ * parameter value needs to be preserved for use when the cursor is
+ * executed.
+ *----------
+ */
+ params = copyParamList(params);
MemoryContextSwitchTo(oldContext);
* 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.
+ * based on whether it would require any additional runtime overhead to do
+ * so. Also, we disallow scrolling for FOR UPDATE cursors.
*/
- portal->cursorOptions = stmt->options;
+ portal->cursorOptions = cstmt->options;
if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
{
- if (ExecSupportsBackwardScan(plan))
+ if (stmt->rowMarks == NIL &&
+ ExecSupportsBackwardScan(stmt->planTree))
portal->cursorOptions |= CURSOR_OPT_SCROLL;
else
portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
}
/*
- * Start execution --- never any params for a cursor.
+ * Start execution, inserting parameters if any.
*/
- PortalStart(portal, NULL);
+ PortalStart(portal, params, 0, GetActiveSnapshot());
Assert(portal->strategy == PORTAL_ONE_SELECT);
/*
- * We're done; the query won't actually be run until
- * PerformPortalFetch is called.
+ * We're done; the query won't actually be run until PerformPortalFetch is
+ * called.
*/
}
portal = GetPortalByName(stmt->portalname);
if (!PortalIsValid(portal))
{
- /* FIXME: shouldn't this be an ERROR? */
- ereport(WARNING,
+ ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
- errmsg("portal \"%s\" does not exist", stmt->portalname)));
- if (completionTag)
- strcpy(completionTag, stmt->ismove ? "MOVE 0" : "FETCH 0");
- return;
+ errmsg("cursor \"%s\" does not exist", stmt->portalname)));
+ return; /* keep compiler happy */
}
- /* Adjust dest if needed. MOVE wants destination None */
+ /* Adjust dest if needed. MOVE wants destination DestNone */
if (stmt->ismove)
dest = None_Receiver;
{
Portal portal;
+ /* NULL means CLOSE ALL */
+ if (name == NULL)
+ {
+ PortalHashTableDeleteAll();
+ return;
+ }
+
/*
* Disallow empty-string cursor name (conflicts with protocol-level
* unnamed portal).
*/
- if (!name || name[0] == '\0')
+ if (name[0] == '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_NAME),
errmsg("invalid cursor name: must not be empty")));
portal = GetPortalByName(name);
if (!PortalIsValid(portal))
{
- ereport(WARNING,
+ ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
- errmsg("portal \"%s\" does not exist", name),
- errfunction("PerformPortalClose"))); /* for ecpg */
- return;
+ errmsg("cursor \"%s\" does not exist", name)));
+ return; /* keep compiler happy */
}
/*
- * Note: PortalCleanup is called as a side-effect
+ * Note: PortalCleanup is called as a side-effect, if not already done.
*/
PortalDrop(portal, false);
}
*
* Clean up a portal when it's dropped. This is the standard cleanup hook
* for portals.
+ *
+ * Note: if portal->status is PORTAL_FAILED, we are probably being called
+ * during error abort, and must be careful to avoid doing anything that
+ * is likely to fail again.
*/
void
-PortalCleanup(Portal portal, bool isError)
+PortalCleanup(Portal portal)
{
QueryDesc *queryDesc;
AssertArg(portal->cleanup == PortalCleanup);
/*
- * Shut down executor, if still running. We skip this during error
- * abort, since other mechanisms will take care of releasing executor
- * resources, and we can't be sure that ExecutorEnd itself wouldn't
- * fail.
+ * Shut down executor, if still running. We skip this during error abort,
+ * since other mechanisms will take care of releasing executor resources,
+ * and we can't be sure that ExecutorEnd itself wouldn't fail.
*/
queryDesc = PortalGetQueryDesc(portal);
if (queryDesc)
{
+ /*
+ * Reset the queryDesc before anything else. This prevents us from
+ * trying to shut down the executor twice, in case of an error below.
+ * The transaction abort mechanisms will take care of resource cleanup
+ * in such a case.
+ */
portal->queryDesc = NULL;
- if (!isError)
- ExecutorEnd(queryDesc);
+
+ if (portal->status != PORTAL_FAILED)
+ {
+ ResourceOwner saveResourceOwner;
+
+ /* We must make the portal's resource owner current */
+ saveResourceOwner = CurrentResourceOwner;
+ PG_TRY();
+ {
+ if (portal->resowner)
+ CurrentResourceOwner = portal->resowner;
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+ FreeQueryDesc(queryDesc);
+ }
+ PG_CATCH();
+ {
+ /* Ensure CurrentResourceOwner is restored on error */
+ CurrentResourceOwner = saveResourceOwner;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ CurrentResourceOwner = saveResourceOwner;
+ }
}
}
PersistHoldablePortal(Portal portal)
{
QueryDesc *queryDesc = PortalGetQueryDesc(portal);
+ Portal saveActivePortal;
+ ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
- MemoryContext saveQueryContext;
MemoryContext oldcxt;
/*
* If we're preserving a holdable portal, we had better be inside the
* transaction that originally created it.
*/
- Assert(portal->createXact == GetCurrentTransactionId());
+ Assert(portal->createSubid != InvalidSubTransactionId);
Assert(queryDesc != NULL);
- Assert(portal->portalReady);
- Assert(!portal->portalDone);
/*
* Caller must have created the tuplestore already.
/*
* Check for improper portal use, and mark portal active.
*/
- if (portal->portalActive)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_IN_USE),
- errmsg("portal \"%s\" already active", portal->name)));
- portal->portalActive = true;
+ MarkPortalActive(portal);
/*
- * Set global portal context pointers.
+ * Set up global portal context pointers.
*/
+ saveActivePortal = ActivePortal;
+ saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
- PortalContext = PortalGetHeapMemory(portal);
- saveQueryContext = QueryContext;
- QueryContext = portal->queryContext;
-
- MemoryContextSwitchTo(PortalContext);
+ PG_TRY();
+ {
+ ActivePortal = portal;
+ if (portal->resowner)
+ CurrentResourceOwner = portal->resowner;
+ PortalContext = PortalGetHeapMemory(portal);
+
+ MemoryContextSwitchTo(PortalContext);
+
+ PushActiveSnapshot(queryDesc->snapshot);
+
+ /*
+ * Rewind the executor: we need to store the entire result set in the
+ * tuplestore, so that subsequent backward FETCHs can be processed.
+ */
+ ExecutorRewind(queryDesc);
+
+ /*
+ * Change the destination to output to the tuplestore. Note we tell
+ * the tuplestore receiver to detoast all data passed through it.
+ */
+ queryDesc->dest = CreateDestReceiver(DestTuplestore);
+ SetTuplestoreDestReceiverParams(queryDesc->dest,
+ portal->holdStore,
+ portal->holdContext,
+ true);
+
+ /* Fetch the result set into the tuplestore */
+ ExecutorRun(queryDesc, ForwardScanDirection, 0L);
+
+ (*queryDesc->dest->rDestroy) (queryDesc->dest);
+ queryDesc->dest = NULL;
+
+ /*
+ * Now shut down the inner executor.
+ */
+ portal->queryDesc = NULL; /* prevent double shutdown */
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+ FreeQueryDesc(queryDesc);
+
+ /*
+ * Set the position in the result set.
+ */
+ MemoryContextSwitchTo(portal->holdContext);
+
+ if (portal->atEnd)
+ {
+ /*
+ * We can handle this case even if posOverflow: just force the
+ * tuplestore forward to its end. The size of the skip request
+ * here is arbitrary.
+ */
+ while (tuplestore_skiptuples(portal->holdStore, 1000000, true))
+ /* continue */ ;
+ }
+ else
+ {
+ if (portal->posOverflow) /* oops, cannot trust portalPos */
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("could not reposition held cursor")));
- /*
- * Rewind the executor: we need to store the entire result set in the
- * tuplestore, so that subsequent backward FETCHs can be processed.
- */
- ExecutorRewind(queryDesc);
+ tuplestore_rescan(portal->holdStore);
- /* Change the destination to output to the tuplestore */
- queryDesc->dest = CreateDestReceiver(Tuplestore, portal);
+ if (!tuplestore_skiptuples(portal->holdStore,
+ portal->portalPos,
+ true))
+ elog(ERROR, "unexpected end of tuple stream");
+ }
+ }
+ PG_CATCH();
+ {
+ /* Uncaught error while executing portal: mark it dead */
+ MarkPortalFailed(portal);
- /* Fetch the result set into the tuplestore */
- ExecutorRun(queryDesc, ForwardScanDirection, 0L);
+ /* Restore global vars and propagate error */
+ ActivePortal = saveActivePortal;
+ CurrentResourceOwner = saveResourceOwner;
+ PortalContext = savePortalContext;
- (*queryDesc->dest->rDestroy) (queryDesc->dest);
- queryDesc->dest = NULL;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
- /*
- * Now shut down the inner executor.
- */
- portal->queryDesc = NULL; /* prevent double shutdown */
- ExecutorEnd(queryDesc);
+ MemoryContextSwitchTo(oldcxt);
/* Mark portal not active */
- portal->portalActive = false;
+ portal->status = PORTAL_READY;
+ ActivePortal = saveActivePortal;
+ CurrentResourceOwner = saveResourceOwner;
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. FIXME someday?
- */
- MemoryContextSwitchTo(portal->holdContext);
-
- if (!portal->atEnd)
- {
- long store_pos;
-
- if (portal->posOverflow) /* oops, cannot trust portalPos */
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("could not reposition held cursor")));
-
- tuplestore_rescan(portal->holdStore);
-
- for (store_pos = 0; store_pos < portal->portalPos; store_pos++)
- {
- HeapTuple tup;
- bool should_free;
- tup = tuplestore_gettuple(portal->holdStore, true,
- &should_free);
-
- if (tup == NULL)
- elog(ERROR, "unexpected end of tuple stream");
-
- if (should_free)
- pfree(tup);
- }
- }
-
- MemoryContextSwitchTo(oldcxt);
+ PopActiveSnapshot();
/*
- * 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.
+ * 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));
}