aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/commands/functioncmds.c47
-rw-r--r--src/backend/executor/spi.c102
-rw-r--r--src/backend/tcop/utility.c6
-rw-r--r--src/backend/utils/mmgr/portalmem.c49
4 files changed, 174 insertions, 30 deletions
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index ea08c3237c1..df87dfeb543 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -65,6 +65,7 @@
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
+#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
@@ -2136,9 +2137,11 @@ IsThereFunctionInNamespace(const char *proname, int pronargs,
/*
* ExecuteDoStmt
* Execute inline procedural-language code
+ *
+ * See at ExecuteCallStmt() about the atomic argument.
*/
void
-ExecuteDoStmt(DoStmt *stmt)
+ExecuteDoStmt(DoStmt *stmt, bool atomic)
{
InlineCodeBlock *codeblock = makeNode(InlineCodeBlock);
ListCell *arg;
@@ -2200,6 +2203,7 @@ ExecuteDoStmt(DoStmt *stmt)
codeblock->langOid = HeapTupleGetOid(languageTuple);
languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
codeblock->langIsTrusted = languageStruct->lanpltrusted;
+ codeblock->atomic = atomic;
if (languageStruct->lanpltrusted)
{
@@ -2236,9 +2240,28 @@ ExecuteDoStmt(DoStmt *stmt)
/*
* Execute CALL statement
+ *
+ * Inside a top-level CALL statement, transaction-terminating commands such as
+ * COMMIT or a PL-specific equivalent are allowed. The terminology in the SQL
+ * standard is that CALL establishes a non-atomic execution context. Most
+ * other commands establish an atomic execution context, in which transaction
+ * control actions are not allowed. If there are nested executions of CALL,
+ * we want to track the execution context recursively, so that the nested
+ * CALLs can also do transaction control. Note, however, that for example in
+ * CALL -> SELECT -> CALL, the second call cannot do transaction control,
+ * because the SELECT in between establishes an atomic execution context.
+ *
+ * So when ExecuteCallStmt() is called from the top level, we pass in atomic =
+ * false (recall that that means transactions = yes). We then create a
+ * CallContext node with content atomic = false, which is passed in the
+ * fcinfo->context field to the procedure invocation. The language
+ * implementation should then take appropriate measures to allow or prevent
+ * transaction commands based on that information, e.g., call
+ * SPI_connect_ext(SPI_OPT_NONATOMIC). The language should also pass on the
+ * atomic flag to any nested invocations to CALL.
*/
void
-ExecuteCallStmt(ParseState *pstate, CallStmt *stmt)
+ExecuteCallStmt(ParseState *pstate, CallStmt *stmt, bool atomic)
{
List *targs;
ListCell *lc;
@@ -2249,6 +2272,8 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt)
AclResult aclresult;
FmgrInfo flinfo;
FunctionCallInfoData fcinfo;
+ CallContext *callcontext;
+ HeapTuple tp;
targs = NIL;
foreach(lc, stmt->funccall->args)
@@ -2284,8 +2309,24 @@ ExecuteCallStmt(ParseState *pstate, CallStmt *stmt)
FUNC_MAX_ARGS,
FUNC_MAX_ARGS)));
+ callcontext = makeNode(CallContext);
+ callcontext->atomic = atomic;
+
+ /*
+ * If proconfig is set we can't allow transaction commands because of the
+ * way the GUC stacking works: The transaction boundary would have to pop
+ * the proconfig setting off the stack. That restriction could be lifted
+ * by redesigning the GUC nesting mechanism a bit.
+ */
+ tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
+ if (!heap_attisnull(tp, Anum_pg_proc_proconfig))
+ callcontext->atomic = true;
+ ReleaseSysCache(tp);
+
fmgr_info(fexpr->funcid, &flinfo);
- InitFunctionCallInfoData(fcinfo, &flinfo, nargs, fexpr->inputcollid, NULL, NULL);
+ InitFunctionCallInfoData(fcinfo, &flinfo, nargs, fexpr->inputcollid, (Node *) callcontext, NULL);
i = 0;
foreach (lc, fexpr->args)
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 995f67d2662..9fc4431b80c 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -83,6 +83,12 @@ static bool _SPI_checktuples(void);
int
SPI_connect(void)
{
+ return SPI_connect_ext(0);
+}
+
+int
+SPI_connect_ext(int options)
+{
int newdepth;
/* Enlarge stack if necessary */
@@ -92,7 +98,7 @@ SPI_connect(void)
elog(ERROR, "SPI stack corrupted");
newdepth = 16;
_SPI_stack = (_SPI_connection *)
- MemoryContextAlloc(TopTransactionContext,
+ MemoryContextAlloc(TopMemoryContext,
newdepth * sizeof(_SPI_connection));
_SPI_stack_depth = newdepth;
}
@@ -124,19 +130,25 @@ SPI_connect(void)
_SPI_current->execCxt = NULL;
_SPI_current->connectSubid = GetCurrentSubTransactionId();
_SPI_current->queryEnv = NULL;
+ _SPI_current->atomic = (options & SPI_OPT_NONATOMIC ? false : true);
+ _SPI_current->internal_xact = false;
/*
* Create memory contexts for this procedure
*
- * XXX it would be better to use PortalContext as the parent context, but
- * we may not be inside a portal (consider deferred-trigger execution).
- * Perhaps CurTransactionContext would do? For now it doesn't matter
- * because we clean up explicitly in AtEOSubXact_SPI().
+ * In atomic contexts (the normal case), we use TopTransactionContext,
+ * otherwise PortalContext, so that it lives across transaction
+ * boundaries.
+ *
+ * XXX It could be better to use PortalContext as the parent context in
+ * all cases, but we may not be inside a portal (consider deferred-trigger
+ * execution). Perhaps CurTransactionContext could be an option? For now
+ * it doesn't matter because we clean up explicitly in AtEOSubXact_SPI().
*/
- _SPI_current->procCxt = AllocSetContextCreate(TopTransactionContext,
+ _SPI_current->procCxt = AllocSetContextCreate(_SPI_current->atomic ? TopTransactionContext : PortalContext,
"SPI Proc",
ALLOCSET_DEFAULT_SIZES);
- _SPI_current->execCxt = AllocSetContextCreate(TopTransactionContext,
+ _SPI_current->execCxt = AllocSetContextCreate(_SPI_current->atomic ? TopTransactionContext : _SPI_current->procCxt,
"SPI Exec",
ALLOCSET_DEFAULT_SIZES);
/* ... and switch to procedure's context */
@@ -181,6 +193,73 @@ SPI_finish(void)
return SPI_OK_FINISH;
}
+void
+SPI_start_transaction(void)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ StartTransactionCommand();
+ MemoryContextSwitchTo(oldcontext);
+}
+
+void
+SPI_commit(void)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ if (_SPI_current->atomic)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("invalid transaction termination")));
+
+ /*
+ * This restriction is required by PLs implemented on top of SPI. They
+ * use subtransactions to establish exception blocks that are supposed to
+ * be rolled back together if there is an error. Terminating the
+ * top-level transaction in such a block violates that idea. A future PL
+ * implementation might have different ideas about this, in which case
+ * this restriction would have to be refined or the check possibly be
+ * moved out of SPI into the PLs.
+ */
+ if (IsSubTransaction())
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("cannot commit while a subtransaction is active")));
+
+ _SPI_current->internal_xact = true;
+
+ if (ActiveSnapshotSet())
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ MemoryContextSwitchTo(oldcontext);
+
+ _SPI_current->internal_xact = false;
+}
+
+void
+SPI_rollback(void)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ if (_SPI_current->atomic)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("invalid transaction termination")));
+
+ /* see under SPI_commit() */
+ if (IsSubTransaction())
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
+ errmsg("cannot roll back while a subtransaction is active")));
+
+ _SPI_current->internal_xact = true;
+
+ AbortCurrentTransaction();
+ MemoryContextSwitchTo(oldcontext);
+
+ _SPI_current->internal_xact = false;
+}
+
/*
* Clean up SPI state at transaction commit or abort.
*/
@@ -188,6 +267,12 @@ void
AtEOXact_SPI(bool isCommit)
{
/*
+ * Do nothing if the transaction end was initiated by SPI.
+ */
+ if (_SPI_current && _SPI_current->internal_xact)
+ return;
+
+ /*
* Note that memory contexts belonging to SPI stack entries will be freed
* automatically, so we can ignore them here. We just need to restore our
* static variables to initial state.
@@ -224,6 +309,9 @@ AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
if (connection->connectSubid != mySubid)
break; /* couldn't be any underneath it either */
+ if (connection->internal_xact)
+ break;
+
found = true;
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 26df660f350..3abe7d6155a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -530,7 +530,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_DoStmt:
- ExecuteDoStmt((DoStmt *) parsetree);
+ ExecuteDoStmt((DoStmt *) parsetree,
+ (context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()));
break;
case T_CreateTableSpaceStmt:
@@ -659,7 +660,8 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
case T_CallStmt:
- ExecuteCallStmt(pstate, castNode(CallStmt, parsetree));
+ ExecuteCallStmt(pstate, castNode(CallStmt, parsetree),
+ (context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()));
break;
case T_ClusterStmt:
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 84c68ac1895..f3f0add1d6d 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -742,11 +742,8 @@ PreCommit_Portals(bool isPrepare)
/*
* Abort processing for portals.
*
- * At this point we reset "active" status and run the cleanup hook if
- * present, but we can't release the portal's memory until the cleanup call.
- *
- * The reason we need to reset active is so that we can replace the unnamed
- * portal, else we'll fail to execute ROLLBACK when it arrives.
+ * At this point we run the cleanup hook if present, but we can't release the
+ * portal's memory until the cleanup call.
*/
void
AtAbort_Portals(void)
@@ -761,17 +758,6 @@ AtAbort_Portals(void)
Portal portal = hentry->portal;
/*
- * See similar code in AtSubAbort_Portals(). This would fire if code
- * orchestrating multiple top-level transactions within a portal, such
- * as VACUUM, caught errors and continued under the same portal with a
- * fresh transaction. No part of core PostgreSQL functions that way.
- * XXX Such code would wish the portal to remain ACTIVE, as in
- * PreCommit_Portals().
- */
- if (portal->status == PORTAL_ACTIVE)
- MarkPortalFailed(portal);
-
- /*
* Do nothing else to cursors held over from a previous transaction.
*/
if (portal->createSubid == InvalidSubTransactionId)
@@ -810,9 +796,10 @@ AtAbort_Portals(void)
* Although we can't delete the portal data structure proper, we can
* release any memory in subsidiary contexts, such as executor state.
* The cleanup hook was the last thing that might have needed data
- * there.
+ * there. But leave active portals alone.
*/
- MemoryContextDeleteChildren(portal->portalContext);
+ if (portal->status != PORTAL_ACTIVE)
+ MemoryContextDeleteChildren(portal->portalContext);
}
}
@@ -832,6 +819,13 @@ AtCleanup_Portals(void)
{
Portal portal = hentry->portal;
+ /*
+ * Do not touch active portals --- this can only happen in the case of
+ * a multi-transaction command.
+ */
+ if (portal->status == PORTAL_ACTIVE)
+ continue;
+
/* Do nothing to cursors held over from a previous transaction */
if (portal->createSubid == InvalidSubTransactionId)
{
@@ -1161,3 +1155,22 @@ ThereAreNoReadyPortals(void)
return true;
}
+
+bool
+ThereArePinnedPortals(void)
+{
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ if (portal->portalPinned)
+ return true;
+ }
+
+ return false;
+}