Skip to content
Snippets Groups Projects
Select Git revision
  • a7801b62f21bd051444bd1119cd3745ecc8e14ec
  • master default
  • benchmark-tools
  • postgres-lambda
  • REL9_4_25
  • REL9_5_20
  • REL9_6_16
  • REL_10_11
  • REL_11_6
  • REL_12_1
  • REL_12_0
  • REL_12_RC1
  • REL_12_BETA4
  • REL9_4_24
  • REL9_5_19
  • REL9_6_15
  • REL_10_10
  • REL_11_5
  • REL_12_BETA3
  • REL9_4_23
  • REL9_5_18
  • REL9_6_14
  • REL_10_9
  • REL_11_4
24 results

portalcmds.c

Blame
  • user avatar
    Bruce Momjian authored
    bf50caf1
    History
    portalcmds.c 11.94 KiB
    /*-------------------------------------------------------------------------
     *
     * portalcmds.c
     *	  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-2011, PostgreSQL Global Development Group
     * Portions Copyright (c) 1994, Regents of the University of California
     *
     *
     * IDENTIFICATION
     *	  src/backend/commands/portalcmds.c
     *
     *-------------------------------------------------------------------------
     */
    
    #include "postgres.h"
    
    #include <limits.h>
    
    #include "access/xact.h"
    #include "commands/portalcmds.h"
    #include "executor/executor.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(PlannedStmt *stmt, ParamListInfo params,
    				  const char *queryString, bool isTopLevel)
    {
    	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 (!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 (!(cstmt->options & CURSOR_OPT_HOLD))
    		RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
    
    	/*
    	 * Create a portal and copy the plan and queryString into its memory.
    	 */
    	portal = CreatePortal(cstmt->portalname, false, false);
    
    	oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
    
    	stmt = copyObject(stmt);
    	stmt->utilityStmt = NULL;	/* make it look like plain SELECT */
    
    	queryString = pstrdup(queryString);
    
    	PortalDefineQuery(portal,
    					  NULL,
    					  queryString,
    					  "SELECT", /* cursor's query is always a SELECT */
    					  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.	Also, we disallow scrolling for FOR UPDATE cursors.
    	 */
    	portal->cursorOptions = cstmt->options;
    	if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL)))
    	{
    		if (stmt->rowMarks == NIL &&
    			ExecSupportsBackwardScan(stmt->planTree))
    			portal->cursorOptions |= CURSOR_OPT_SCROLL;
    		else
    			portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
    	}
    
    	/*
    	 * Start execution, inserting parameters if any.
    	 */
    	PortalStart(portal, params, GetActiveSnapshot());
    
    	Assert(portal->strategy == PORTAL_ONE_SELECT);
    
    	/*
    	 * We're done; the query won't actually be run until PerformPortalFetch is
    	 * called.
    	 */
    }
    
    /*
     * PerformPortalFetch
     *		Execute SQL FETCH or MOVE command.
     *
     *	stmt: parsetree node for command
     *	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.
     */
    void
    PerformPortalFetch(FetchStmt *stmt,
    				   DestReceiver *dest,
    				   char *completionTag)
    {
    	Portal		portal;
    	long		nprocessed;
    
    	/*
    	 * Disallow empty-string cursor name (conflicts with protocol-level
    	 * unnamed portal).
    	 */
    	if (!stmt->portalname || stmt->portalname[0] == '\0')
    		ereport(ERROR,
    				(errcode(ERRCODE_INVALID_CURSOR_NAME),
    				 errmsg("invalid cursor name: must not be empty")));
    
    	/* get the portal from the portal name */
    	portal = GetPortalByName(stmt->portalname);
    	if (!PortalIsValid(portal))
    	{
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_CURSOR),
    				 errmsg("cursor \"%s\" does not exist", stmt->portalname)));
    		return;					/* keep compiler happy */
    	}
    
    	/* Adjust dest if needed.  MOVE wants destination DestNone */
    	if (stmt->ismove)
    		dest = None_Receiver;
    
    	/* Do it */
    	nprocessed = PortalRunFetch(portal,
    								stmt->direction,
    								stmt->howMany,
    								dest);
    
    	/* Return command status if wanted */
    	if (completionTag)
    		snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "%s %ld",
    				 stmt->ismove ? "MOVE" : "FETCH",
    				 nprocessed);
    }
    
    /*
     * PerformPortalClose
     *		Close a cursor.
     */
    void
    PerformPortalClose(const char *name)
    {
    	Portal		portal;
    
    	/* NULL means CLOSE ALL */
    	if (name == NULL)
    	{
    		PortalHashTableDeleteAll();
    		return;
    	}
    
    	/*
    	 * Disallow empty-string cursor name (conflicts with protocol-level
    	 * unnamed portal).
    	 */
    	if (name[0] == '\0')
    		ereport(ERROR,
    				(errcode(ERRCODE_INVALID_CURSOR_NAME),
    				 errmsg("invalid cursor name: must not be empty")));
    
    	/*
    	 * get the portal from the portal name
    	 */
    	portal = GetPortalByName(name);
    	if (!PortalIsValid(portal))
    	{
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_CURSOR),
    				 errmsg("cursor \"%s\" does not exist", name)));
    		return;					/* keep compiler happy */
    	}
    
    	/*
    	 * Note: PortalCleanup is called as a side-effect
    	 */
    	PortalDrop(portal, false);
    }
    
    /*
     * PortalCleanup
     *
     * Clean up a portal when it's dropped.  This is the standard cleanup hook
     * for portals.
     */
    void
    PortalCleanup(Portal portal)
    {
    	QueryDesc  *queryDesc;
    
    	/*
    	 * sanity checks
    	 */
    	AssertArg(PortalIsValid(portal));
    	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.
    	 */
    	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 (portal->status != PORTAL_FAILED)
    		{
    			ResourceOwner saveResourceOwner;
    
    			/* We must make the portal's resource owner current */
    			saveResourceOwner = CurrentResourceOwner;
    			PG_TRY();
    			{
    				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
     *
     * Prepare the specified Portal for access outside of the current
     * transaction. When this function returns, all future accesses to the
     * portal must be done via the Tuplestore (not by invoking the
     * executor).
     */
    void
    PersistHoldablePortal(Portal portal)
    {
    	QueryDesc  *queryDesc = PortalGetQueryDesc(portal);
    	Portal		saveActivePortal;
    	ResourceOwner saveResourceOwner;
    	MemoryContext savePortalContext;
    	MemoryContext oldcxt;
    
    	/*
    	 * If we're preserving a holdable portal, we had better be inside the
    	 * transaction that originally created it.
    	 */
    	Assert(portal->createSubid != InvalidSubTransactionId);
    	Assert(queryDesc != NULL);
    
    	/*
    	 * Caller must have created the tuplestore already.
    	 */
    	Assert(portal->holdContext != NULL);
    	Assert(portal->holdStore != NULL);
    
    	/*
    	 * Before closing down the executor, we must copy the tupdesc into
    	 * long-term memory, since it was created in executor memory.
    	 */
    	oldcxt = MemoryContextSwitchTo(portal->holdContext);
    
    	portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);
    
    	MemoryContextSwitchTo(oldcxt);
    
    	/*
    	 * Check for improper portal use, and mark portal active.
    	 */
    	if (portal->status != PORTAL_READY)
    		ereport(ERROR,
    				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    				 errmsg("portal \"%s\" cannot be run", portal->name)));
    	portal->status = PORTAL_ACTIVE;
    
    	/*
    	 * Set up global portal context pointers.
    	 */
    	saveActivePortal = ActivePortal;
    	saveResourceOwner = CurrentResourceOwner;
    	savePortalContext = PortalContext;
    	PG_TRY();
    	{
    		ActivePortal = portal;
    		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: 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?  (Fortunately, the
    		 * typical case is that we're supposed to be at or near the start of
    		 * the result set, so this isn't as bad as it sounds.)
    		 */
    		MemoryContextSwitchTo(portal->holdContext);
    
    		if (portal->atEnd)
    		{
    			/* we can handle this case even if posOverflow */
    			while (tuplestore_advance(portal->holdStore, true))
    				 /* continue */ ;
    		}
    		else
    		{
    			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++)
    			{
    				if (!tuplestore_advance(portal->holdStore, true))
    					elog(ERROR, "unexpected end of tuple stream");
    			}
    		}
    	}
    	PG_CATCH();
    	{
    		/* Uncaught error while executing portal: mark it dead */
    		portal->status = PORTAL_FAILED;
    
    		/* Restore global vars and propagate error */
    		ActivePortal = saveActivePortal;
    		CurrentResourceOwner = saveResourceOwner;
    		PortalContext = savePortalContext;
    
    		PG_RE_THROW();
    	}
    	PG_END_TRY();
    
    	MemoryContextSwitchTo(oldcxt);
    
    	/* Mark portal not active */
    	portal->status = PORTAL_READY;
    
    	ActivePortal = saveActivePortal;
    	CurrentResourceOwner = saveResourceOwner;
    	PortalContext = savePortalContext;
    
    	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.
    	 */
    	MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
    }