/* * PortalCleanup * * Clean up a portal when it's dropped. This is the standard cleanup hook * for portals. */ void PortalCleanup(Portal portal, bool isError) { 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) { portal->queryDesc = NULL; if (!isError) ExecutorEnd(queryDesc); } }
/* * PerformPortalClose * Close a cursor. */ void PerformPortalClose(const char *name) { Portal portal; /* * Disallow empty-string cursor name (conflicts with protocol-level * unnamed portal). */ if (!name || 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); }
static PyObject * PLy_cursor_iternext(PyObject *self) { PLyCursorObject *cursor; PyObject *ret; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; Portal portal; cursor = (PLyCursorObject *) self; if (cursor->closed) { PLy_exception_set(PyExc_ValueError, "iterating a closed cursor"); return NULL; } portal = GetPortalByName(cursor->portalname); if (!PortalIsValid(portal)) { PLy_exception_set(PyExc_ValueError, "iterating a cursor in an aborted subtransaction"); return NULL; } oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { SPI_cursor_fetch(portal, true, 1); if (SPI_processed == 0) { PyErr_SetNone(PyExc_StopIteration); ret = NULL; } else { if (cursor->result.is_rowtype != 1) PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc); ret = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[0], SPI_tuptable->tupdesc); } SPI_freetuptable(SPI_tuptable); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); return ret; }
/* * SPI_cursor_close() * * Close a cursor */ void SPI_cursor_close(Portal portal) { if (!PortalIsValid(portal)) elog(ERROR, "invalid portal in SPI cursor operation"); 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 */ 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(); { 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; } } }
/* * CreatePortal * Returns a new portal given a name. * * 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, bool allowDup, bool dupSilent) { Portal portal; AssertArg(PointerIsValid(name)); portal = GetPortalByName(name); if (PortalIsValid(portal)) { if (!allowDup) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_CURSOR), errmsg("cursor \"%s\" already exists", name))); if (!dupSilent) ereport(WARNING, (errcode(ERRCODE_DUPLICATE_CURSOR), errmsg("closing existing cursor \"%s\"", name))); PortalDrop(portal, false); } /* make new portal structure */ portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal); /* initialize portal heap context; typically it won't store much */ portal->heap = AllocSetContextCreate(PortalMemory, "PortalHeapMemory", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); /* create a resource owner for the portal */ portal->resowner = ResourceOwnerCreate(CurTransactionResourceOwner, "Portal"); /* initialize portal fields that don't start off zero */ portal->cleanup = PortalCleanup; portal->createSubid = GetCurrentSubTransactionId(); portal->strategy = PORTAL_MULTI_QUERY; portal->cursorOptions = CURSOR_OPT_NO_SCROLL; portal->atStart = true; portal->atEnd = true; /* disallow fetches until query is set */ /* put portal in table (sets portal->name) */ PortalHashTableInsert(portal, name); return portal; }
/* * PortalDrop * Destroy the portal. * * isError: if true, we are destroying portals at the end of a failed * transaction. (This causes PortalCleanup to skip unneeded steps.) */ void PortalDrop(Portal portal, bool isError) { AssertArg(PortalIsValid(portal)); /* Not sure if this case can validly happen or not... */ if (portal->portalActive) elog(ERROR, "cannot 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 in the subsequent steps. Better to leak a little memory than * to get into an infinite error-recovery loop. */ PortalHashTableDelete(portal); /* let portalcmds.c clean up the state it knows about */ if (PointerIsValid(portal->cleanup)) (*portal->cleanup) (portal, isError); /* * Delete tuplestore if present. We should do this even under error * conditions; since the tuplestore would have been using cross- * transaction storage, its temp files need to be explicitly deleted. */ if (portal->holdStore) { MemoryContext oldcontext; oldcontext = MemoryContextSwitchTo(portal->holdContext); tuplestore_end(portal->holdStore); MemoryContextSwitchTo(oldcontext); portal->holdStore = NULL; } /* delete tuplestore storage, if any */ if (portal->holdContext) MemoryContextDelete(portal->holdContext); /* release subsidiary storage */ MemoryContextDelete(PortalGetHeapMemory(portal)); /* release portal struct (it's in PortalMemory) */ pfree(portal); }
/* * 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) { 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; /* we do not need AfterTriggerEndQuery() here */ ExecutorEnd(queryDesc); FreeQueryDesc(queryDesc); } PG_CATCH(); { /* Ensure CurrentResourceOwner is restored on error */ CurrentResourceOwner = saveResourceOwner; PG_RE_THROW(); } PG_END_TRY(); CurrentResourceOwner = saveResourceOwner; } } }
/* * 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); }
/* * PortalDefineQuery * A simple subroutine to establish a portal's query. * * Notes: commandTag shall be NULL if and only if the original query string * (before rewriting) was an empty string. Also, 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(list_length(parseTrees) == list_length(planTrees)); Assert(commandTag != NULL || parseTrees == NIL); portal->sourceText = sourceText; portal->commandTag = commandTag; portal->parseTrees = parseTrees; portal->planTrees = planTrees; portal->queryContext = queryContext; }
static PyObject * PLy_cursor_close(PyObject *self, PyObject *unused) { PLyCursorObject *cursor = (PLyCursorObject *) self; if (!cursor->closed) { Portal portal = GetPortalByName(cursor->portalname); if (!PortalIsValid(portal)) { PLy_exception_set(PyExc_ValueError, "closing a cursor in an aborted subtransaction"); return NULL; } SPI_cursor_close(portal); cursor->closed = true; } Py_INCREF(Py_None); return Py_None; }
static void PLy_cursor_dealloc(PyObject *arg) { PLyCursorObject *cursor; Portal portal; cursor = (PLyCursorObject *) arg; if (!cursor->closed) { portal = GetPortalByName(cursor->portalname); if (PortalIsValid(portal)) SPI_cursor_close(portal); cursor->closed = true; } if (cursor->mcxt) { MemoryContextDelete(cursor->mcxt); cursor->mcxt = NULL; } arg->ob_type->tp_free(arg); }
/* * PortalDrop * Destroy the portal. */ void PortalDrop(Portal portal, bool isTopCommit) { AssertArg(PortalIsValid(portal)); /* Not sure if this case can validly happen or not... */ if (portal->status == PORTAL_ACTIVE) elog(ERROR, "cannot 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 in the * subsequent steps. Better to leak a little memory than to get into an * infinite error-recovery loop. */ PortalHashTableDelete(portal); /* let portalcmds.c clean up the state it knows about */ if (PointerIsValid(portal->cleanup)) (*portal->cleanup) (portal); /* * Release any resources still attached to the portal. There are several * cases being covered here: * * Top transaction commit (indicated by isTopCommit): normally we should * do nothing here and let the regular end-of-transaction resource * releasing mechanism handle these resources too. However, if we have a * FAILED portal (eg, a cursor that got an error), we'd better clean up * its resources to avoid resource-leakage warning messages. * * Sub transaction commit: never comes here at all, since we don't kill * any portals in AtSubCommit_Portals(). * * Main or sub transaction abort: we will do nothing here because * portal->resowner was already set NULL; the resources were already * cleaned up in transaction abort. * * Ordinary portal drop: must release resources. However, if the portal * is not FAILED then we do not release its locks. The locks become the * responsibility of the transaction's ResourceOwner (since it is the * parent of the portal's owner) and will be released when the transaction * eventually ends. */ if (portal->resowner && (!isTopCommit || portal->status == PORTAL_FAILED)) { bool isCommit = (portal->status != PORTAL_FAILED); ResourceOwnerRelease(portal->resowner, RESOURCE_RELEASE_BEFORE_LOCKS, isCommit, false); ResourceOwnerRelease(portal->resowner, RESOURCE_RELEASE_LOCKS, isCommit, false); ResourceOwnerRelease(portal->resowner, RESOURCE_RELEASE_AFTER_LOCKS, isCommit, false); ResourceOwnerDelete(portal->resowner); } portal->resowner = NULL; /* * Delete tuplestore if present. We should do this even under error * conditions; since the tuplestore would have been using cross- * transaction storage, its temp files need to be explicitly deleted. */ if (portal->holdStore) { MemoryContext oldcontext; oldcontext = MemoryContextSwitchTo(portal->holdContext); tuplestore_end(portal->holdStore); MemoryContextSwitchTo(oldcontext); portal->holdStore = NULL; } /* delete tuplestore storage, if any */ if (portal->holdContext) MemoryContextDelete(portal->holdContext); /* release subsidiary storage */ MemoryContextDelete(PortalGetHeapMemory(portal)); /* release portal struct (it's in PortalMemory) */ pfree(portal); }
/* * execCurrentOf * * Given a CURRENT OF expression and the OID of a table, determine which row * of the table is currently being scanned by the cursor named by CURRENT OF, * and return the row's TID into *current_tid. * * Returns true if a row was identified. Returns false if the cursor is valid * for the table but is not currently scanning a row of the table (this is a * legal situation in inheritance cases). Raises error if cursor is not a * valid updatable scan of the specified table. */ bool execCurrentOf(CurrentOfExpr *cexpr, ExprContext *econtext, Oid table_oid, ItemPointer current_tid) { char *cursor_name; char *table_name; Portal portal; QueryDesc *queryDesc; /* Get the cursor name --- may have to look up a parameter reference */ if (cexpr->cursor_name) cursor_name = cexpr->cursor_name; else cursor_name = fetch_cursor_param_value(econtext, cexpr->cursor_param); /* Fetch table name for possible use in error messages */ table_name = get_rel_name(table_oid); if (table_name == NULL) elog(ERROR, "cache lookup failed for relation %u", table_oid); /* Find the cursor's portal */ portal = GetPortalByName(cursor_name); if (!PortalIsValid(portal)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_CURSOR), errmsg("cursor \"%s\" does not exist", cursor_name))); /* * We have to watch out for non-SELECT queries as well as held cursors, * both of which may have null queryDesc. */ if (portal->strategy != PORTAL_ONE_SELECT) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not a SELECT query", cursor_name))); queryDesc = portal->queryDesc; if (queryDesc == NULL || queryDesc->estate == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is held from a previous transaction", cursor_name))); /* * We have two different strategies depending on whether the cursor uses * FOR UPDATE/SHARE or not. The reason for supporting both is that the * FOR UPDATE code is able to identify a target table in many cases where * the other code can't, while the non-FOR-UPDATE case allows use of WHERE * CURRENT OF with an insensitive cursor. */ if (queryDesc->estate->es_rowmarks) { ExecRowMark *erm; Index i; /* * Here, the query must have exactly one FOR UPDATE/SHARE reference to * the target table, and we dig the ctid info out of that. */ erm = NULL; for (i = 0; i < queryDesc->estate->es_range_table_size; i++) { ExecRowMark *thiserm = queryDesc->estate->es_rowmarks[i]; if (thiserm == NULL || !RowMarkRequiresRowShareLock(thiserm->markType)) continue; /* ignore non-FOR UPDATE/SHARE items */ if (thiserm->relid == table_oid) { if (erm) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" has multiple FOR UPDATE/SHARE references to table \"%s\"", cursor_name, table_name))); erm = thiserm; } } if (erm == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" does not have a FOR UPDATE/SHARE reference to table \"%s\"", cursor_name, table_name))); /* * The cursor must have a current result row: per the SQL spec, it's * an error if not. */ if (portal->atStart || portal->atEnd) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not positioned on a row", cursor_name))); /* Return the currently scanned TID, if there is one */ if (ItemPointerIsValid(&(erm->curCtid))) { *current_tid = erm->curCtid; return true; } /* * This table didn't produce the cursor's current row; some other * inheritance child of the same parent must have. Signal caller to * do nothing on this table. */ return false; } else { /* * Without FOR UPDATE, we dig through the cursor's plan to find the * scan node. Fail if it's not there or buried underneath * aggregation. */ ScanState *scanstate; bool pending_rescan = false; scanstate = search_plan_tree(queryDesc->planstate, table_oid, &pending_rescan); if (!scanstate) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", cursor_name, table_name))); /* * The cursor must have a current result row: per the SQL spec, it's * an error if not. We test this at the top level, rather than at the * scan node level, because in inheritance cases any one table scan * could easily not be on a row. We want to return false, not raise * error, if the passed-in table OID is for one of the inactive scans. */ if (portal->atStart || portal->atEnd) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not positioned on a row", cursor_name))); /* * Now OK to return false if we found an inactive scan. It is * inactive either if it's not positioned on a row, or there's a * rescan pending for it. */ if (TupIsNull(scanstate->ss_ScanTupleSlot) || pending_rescan) return false; /* * Extract TID of the scan's current row. The mechanism for this is * in principle scan-type-dependent, but for most scan types, we can * just dig the TID out of the physical scan tuple. */ if (IsA(scanstate, IndexOnlyScanState)) { /* * For IndexOnlyScan, the tuple stored in ss_ScanTupleSlot may be * a virtual tuple that does not have the ctid column, so we have * to get the TID from xs_ctup.t_self. */ IndexScanDesc scan = ((IndexOnlyScanState *) scanstate)->ioss_ScanDesc; *current_tid = scan->xs_ctup.t_self; } else { /* * Default case: try to fetch TID from the scan node's current * tuple. As an extra cross-check, verify tableoid in the current * tuple. If the scan hasn't provided a physical tuple, we have * to fail. */ Datum ldatum; bool lisnull; ItemPointer tuple_tid; #ifdef USE_ASSERT_CHECKING if (!slot_getsysattr(scanstate->ss_ScanTupleSlot, TableOidAttributeNumber, &ldatum, &lisnull)) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", cursor_name, table_name))); Assert(!lisnull); Assert(DatumGetObjectId(ldatum) == table_oid); #endif if (!slot_getsysattr(scanstate->ss_ScanTupleSlot, SelfItemPointerAttributeNumber, &ldatum, &lisnull)) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", cursor_name, table_name))); Assert(!lisnull); tuple_tid = (ItemPointer) DatumGetPointer(ldatum); *current_tid = *tuple_tid; } Assert(ItemPointerIsValid(current_tid)); return true; } }
static PyObject * PLy_cursor_fetch(PyObject *self, PyObject *args) { PLyCursorObject *cursor; int count; PLyResultObject *ret; volatile MemoryContext oldcontext; volatile ResourceOwner oldowner; Portal portal; if (!PyArg_ParseTuple(args, "i", &count)) return NULL; cursor = (PLyCursorObject *) self; if (cursor->closed) { PLy_exception_set(PyExc_ValueError, "fetch from a closed cursor"); return NULL; } portal = GetPortalByName(cursor->portalname); if (!PortalIsValid(portal)) { PLy_exception_set(PyExc_ValueError, "iterating a cursor in an aborted subtransaction"); return NULL; } ret = (PLyResultObject *) PLy_result_new(); if (ret == NULL) return NULL; oldcontext = CurrentMemoryContext; oldowner = CurrentResourceOwner; PLy_spi_subtransaction_begin(oldcontext, oldowner); PG_TRY(); { SPI_cursor_fetch(portal, true, count); if (cursor->result.is_rowtype != 1) PLy_input_tuple_funcs(&cursor->result, SPI_tuptable->tupdesc); Py_DECREF(ret->status); ret->status = PyInt_FromLong(SPI_OK_FETCH); Py_DECREF(ret->nrows); ret->nrows = PyInt_FromLong(SPI_processed); if (SPI_processed != 0) { int i; Py_DECREF(ret->rows); ret->rows = PyList_New(SPI_processed); for (i = 0; i < SPI_processed; i++) { PyObject *row = PLyDict_FromTuple(&cursor->result, SPI_tuptable->vals[i], SPI_tuptable->tupdesc); PyList_SetItem(ret->rows, i, row); } } SPI_freetuptable(SPI_tuptable); PLy_spi_subtransaction_commit(oldcontext, oldowner); } PG_CATCH(); { PLy_spi_subtransaction_abort(oldcontext, oldowner); return NULL; } PG_END_TRY(); return (PyObject *) ret; }