]> granicus.if.org Git - postgresql/blobdiff - src/backend/commands/portalcmds.c
Update copyright for 2016
[postgresql] / src / backend / commands / portalcmds.c
index c11b48db4c1ad1b803f52069c7477d3c6a23cf8e..8c045c090b453375c36648384a5ec8edbb06c2c9 100644 (file)
@@ -4,17 +4,17 @@
  *       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);
 
@@ -111,28 +105,29 @@ PerformCursorOpen(DeclareCursorStmt *stmt)
         * 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.
         */
 }
 
@@ -168,16 +163,13 @@ PerformPortalFetch(FetchStmt *stmt,
        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;
 
@@ -203,11 +195,18 @@ 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 || name[0] == '\0')
+       if (name[0] == '\0')
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_CURSOR_NAME),
                                 errmsg("invalid cursor name: must not be empty")));
@@ -218,15 +217,14 @@ PerformPortalClose(const char *name)
        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);
 }
@@ -236,9 +234,13 @@ PerformPortalClose(const char *name)
  *
  * 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;
 
@@ -249,17 +251,44 @@ PortalCleanup(Portal portal, bool isError)
        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;
+               }
        }
 }
 
@@ -275,18 +304,17 @@ void
 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.
@@ -307,92 +335,115 @@ PersistHoldablePortal(Portal portal)
        /*
         * 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));
 }