mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03:00
These don't offer anything over plain Assert, and their usage had already been declared obsolescent. Author: Nathan Bossart <nathandbossart@gmail.com> Reviewed-by: Michael Paquier <michael@paquier.xyz> Discussion: https://www.postgresql.org/message-id/20221009210148.GA900071@nathanxps13
497 lines
13 KiB
C
497 lines
13 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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-2022, 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 "miscadmin.h"
|
|
#include "rewrite/rewriteHandler.h"
|
|
#include "tcop/pquery.h"
|
|
#include "tcop/tcopprot.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/snapmgr.h"
|
|
|
|
|
|
/*
|
|
* PerformCursorOpen
|
|
* Execute SQL DECLARE CURSOR command.
|
|
*/
|
|
void
|
|
PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo params,
|
|
bool isTopLevel)
|
|
{
|
|
Query *query = castNode(Query, cstmt->query);
|
|
List *rewritten;
|
|
PlannedStmt *plan;
|
|
Portal portal;
|
|
MemoryContext oldContext;
|
|
char *queryString;
|
|
|
|
/*
|
|
* 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))
|
|
RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
|
|
else if (InSecurityRestrictedOperation())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("cannot create a cursor WITH HOLD within security-restricted operation")));
|
|
|
|
/*
|
|
* Parse analysis was done already, but we still have to run the rule
|
|
* rewriter. We do not do AcquireRewriteLocks: we assume the query either
|
|
* came straight from the parser, or suitable locks were acquired by
|
|
* plancache.c.
|
|
*/
|
|
rewritten = QueryRewrite(query);
|
|
|
|
/* SELECT should never rewrite to more or less than one query */
|
|
if (list_length(rewritten) != 1)
|
|
elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
|
|
|
|
query = linitial_node(Query, rewritten);
|
|
|
|
if (query->commandType != CMD_SELECT)
|
|
elog(ERROR, "non-SELECT statement in DECLARE CURSOR");
|
|
|
|
/* Plan the query, applying the specified options */
|
|
plan = pg_plan_query(query, pstate->p_sourcetext, cstmt->options, params);
|
|
|
|
/*
|
|
* Create a portal and copy the plan and query string into its memory.
|
|
*/
|
|
portal = CreatePortal(cstmt->portalname, false, false);
|
|
|
|
oldContext = MemoryContextSwitchTo(portal->portalContext);
|
|
|
|
plan = copyObject(plan);
|
|
|
|
queryString = pstrdup(pstate->p_sourcetext);
|
|
|
|
PortalDefineQuery(portal,
|
|
NULL,
|
|
queryString,
|
|
CMDTAG_SELECT, /* cursor's query is always a SELECT */
|
|
list_make1(plan),
|
|
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 (plan->rowMarks == NIL &&
|
|
ExecSupportsBackwardScan(plan->planTree))
|
|
portal->cursorOptions |= CURSOR_OPT_SCROLL;
|
|
else
|
|
portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
|
|
}
|
|
|
|
/*
|
|
* Start execution, inserting parameters if any.
|
|
*/
|
|
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.
|
|
*/
|
|
}
|
|
|
|
/*
|
|
* PerformPortalFetch
|
|
* Execute SQL FETCH or MOVE command.
|
|
*
|
|
* stmt: parsetree node for command
|
|
* dest: where to send results
|
|
* qc: where to store a command completion status data.
|
|
*
|
|
* qc may be NULL if caller doesn't want status data.
|
|
*/
|
|
void
|
|
PerformPortalFetch(FetchStmt *stmt,
|
|
DestReceiver *dest,
|
|
QueryCompletion *qc)
|
|
{
|
|
Portal portal;
|
|
uint64 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 (qc)
|
|
SetQueryCompletion(qc, stmt->ismove ? CMDTAG_MOVE : CMDTAG_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, if not already done.
|
|
*/
|
|
PortalDrop(portal, false);
|
|
}
|
|
|
|
/*
|
|
* PortalCleanup
|
|
*
|
|
* 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)
|
|
{
|
|
QueryDesc *queryDesc;
|
|
|
|
/*
|
|
* sanity checks
|
|
*/
|
|
Assert(PortalIsValid(portal));
|
|
Assert(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 = portal->queryDesc;
|
|
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;
|
|
if (portal->resowner)
|
|
CurrentResourceOwner = portal->resowner;
|
|
|
|
ExecutorFinish(queryDesc);
|
|
ExecutorEnd(queryDesc);
|
|
FreeQueryDesc(queryDesc);
|
|
|
|
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 = portal->queryDesc;
|
|
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 ... but not a snapshot.
|
|
*/
|
|
Assert(portal->holdContext != NULL);
|
|
Assert(portal->holdStore != NULL);
|
|
Assert(portal->holdSnapshot == 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.
|
|
*/
|
|
MarkPortalActive(portal);
|
|
|
|
/*
|
|
* Set up global portal context pointers.
|
|
*/
|
|
saveActivePortal = ActivePortal;
|
|
saveResourceOwner = CurrentResourceOwner;
|
|
savePortalContext = PortalContext;
|
|
PG_TRY();
|
|
{
|
|
ScanDirection direction = ForwardScanDirection;
|
|
|
|
ActivePortal = portal;
|
|
if (portal->resowner)
|
|
CurrentResourceOwner = portal->resowner;
|
|
PortalContext = portal->portalContext;
|
|
|
|
MemoryContextSwitchTo(PortalContext);
|
|
|
|
PushActiveSnapshot(queryDesc->snapshot);
|
|
|
|
/*
|
|
* If the portal is marked scrollable, we need to store the entire
|
|
* result set in the tuplestore, so that subsequent backward FETCHs
|
|
* can be processed. Otherwise, store only the not-yet-fetched rows.
|
|
* (The latter is not only more efficient, but avoids semantic
|
|
* problems if the query's output isn't stable.)
|
|
*
|
|
* In the no-scroll case, tuple indexes in the tuplestore will not
|
|
* match the cursor's nominal position (portalPos). Currently this
|
|
* causes no difficulty because we only navigate in the tuplestore by
|
|
* relative position, except for the tuplestore_skiptuples call below
|
|
* and the tuplestore_rescan call in DoPortalRewind, both of which are
|
|
* disabled for no-scroll cursors. But someday we might need to track
|
|
* the offset between the holdStore and the cursor's nominal position
|
|
* explicitly.
|
|
*/
|
|
if (portal->cursorOptions & CURSOR_OPT_SCROLL)
|
|
{
|
|
ExecutorRewind(queryDesc);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If we already reached end-of-query, 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.) We'll
|
|
* still set up an empty tuplestore, though, to keep this from
|
|
* being a special case later.
|
|
*/
|
|
if (portal->atEnd)
|
|
direction = NoMovementScanDirection;
|
|
}
|
|
|
|
/*
|
|
* Change the destination to output to the tuplestore. Note we tell
|
|
* the tuplestore receiver to detoast all data passed through it; this
|
|
* makes it safe to not keep a snapshot associated with the data.
|
|
*/
|
|
queryDesc->dest = CreateDestReceiver(DestTuplestore);
|
|
SetTuplestoreDestReceiverParams(queryDesc->dest,
|
|
portal->holdStore,
|
|
portal->holdContext,
|
|
true,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* Fetch the result set into the tuplestore */
|
|
ExecutorRun(queryDesc, direction, 0L, false);
|
|
|
|
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)
|
|
{
|
|
/*
|
|
* 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
|
|
{
|
|
tuplestore_rescan(portal->holdStore);
|
|
|
|
/*
|
|
* In the no-scroll case, the start of the tuplestore is exactly
|
|
* where we want to be, so no repositioning is wanted.
|
|
*/
|
|
if (portal->cursorOptions & CURSOR_OPT_SCROLL)
|
|
{
|
|
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);
|
|
|
|
/* 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 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 context via
|
|
* PortalContext.
|
|
*/
|
|
MemoryContextDeleteChildren(portal->portalContext);
|
|
}
|