aboutsummaryrefslogtreecommitdiff
path: root/src/backend/access/transam/xact.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/access/transam/xact.c')
-rw-r--r--src/backend/access/transam/xact.c160
1 files changed, 145 insertions, 15 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 5e7e8122003..bc07354f9a6 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -145,6 +145,7 @@ typedef enum TBlockState
/* transaction block states */
TBLOCK_BEGIN, /* starting transaction block */
TBLOCK_INPROGRESS, /* live transaction */
+ TBLOCK_IMPLICIT_INPROGRESS, /* live transaction after implicit BEGIN */
TBLOCK_PARALLEL_INPROGRESS, /* live transaction inside parallel worker */
TBLOCK_END, /* COMMIT received */
TBLOCK_ABORT, /* failed xact, awaiting ROLLBACK */
@@ -2700,6 +2701,7 @@ StartTransactionCommand(void)
* previous CommitTransactionCommand.)
*/
case TBLOCK_INPROGRESS:
+ case TBLOCK_IMPLICIT_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
break;
@@ -2790,6 +2792,7 @@ CommitTransactionCommand(void)
* counter and return.
*/
case TBLOCK_INPROGRESS:
+ case TBLOCK_IMPLICIT_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
CommandCounterIncrement();
break;
@@ -3014,10 +3017,12 @@ AbortCurrentTransaction(void)
break;
/*
- * if we aren't in a transaction block, we just do the basic abort
- * & cleanup transaction.
+ * If we aren't in a transaction block, we just do the basic abort
+ * & cleanup transaction. For this purpose, we treat an implicit
+ * transaction block as if it were a simple statement.
*/
case TBLOCK_STARTED:
+ case TBLOCK_IMPLICIT_INPROGRESS:
AbortTransaction();
CleanupTransaction();
s->blockState = TBLOCK_DEFAULT;
@@ -3148,9 +3153,8 @@ AbortCurrentTransaction(void)
* completes). Subtransactions are verboten too.
*
* isTopLevel: passed down from ProcessUtility to determine whether we are
- * inside a function or multi-query querystring. (We will always fail if
- * this is false, but it's convenient to centralize the check here instead of
- * making callers do it.)
+ * inside a function. (We will always fail if this is false, but it's
+ * convenient to centralize the check here instead of making callers do it.)
* stmtType: statement type name, for error messages.
*/
void
@@ -3183,8 +3187,7 @@ PreventTransactionChain(bool isTopLevel, const char *stmtType)
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
/* translator: %s represents an SQL statement name */
- errmsg("%s cannot be executed from a function or multi-command string",
- stmtType)));
+ errmsg("%s cannot be executed from a function", stmtType)));
/* If we got past IsTransactionBlock test, should be in default state */
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
@@ -3429,6 +3432,15 @@ BeginTransactionBlock(void)
break;
/*
+ * BEGIN converts an implicit transaction block to a regular one.
+ * (Note that we allow this even if we've already done some
+ * commands, which is a bit odd but matches historical practice.)
+ */
+ case TBLOCK_IMPLICIT_INPROGRESS:
+ s->blockState = TBLOCK_BEGIN;
+ break;
+
+ /*
* Already a transaction block in progress.
*/
case TBLOCK_INPROGRESS:
@@ -3503,7 +3515,8 @@ PrepareTransactionBlock(char *gid)
* ignore case where we are not in a transaction;
* EndTransactionBlock already issued a warning.
*/
- Assert(s->blockState == TBLOCK_STARTED);
+ Assert(s->blockState == TBLOCK_STARTED ||
+ s->blockState == TBLOCK_IMPLICIT_INPROGRESS);
/* Don't send back a PREPARE result tag... */
result = false;
}
@@ -3542,6 +3555,18 @@ EndTransactionBlock(void)
break;
/*
+ * In an implicit transaction block, commit, but issue a warning
+ * because there was no explicit BEGIN before this.
+ */
+ case TBLOCK_IMPLICIT_INPROGRESS:
+ ereport(WARNING,
+ (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
+ errmsg("there is no transaction in progress")));
+ s->blockState = TBLOCK_END;
+ result = true;
+ break;
+
+ /*
* We are in a failed transaction block. Tell
* CommitTransactionCommand it's time to exit the block.
*/
@@ -3705,8 +3730,14 @@ UserAbortTransactionBlock(void)
* WARNING and go to abort state. The upcoming call to
* CommitTransactionCommand() will then put us back into the
* default state.
+ *
+ * We do the same thing with ABORT inside an implicit transaction,
+ * although in this case we might be rolling back actual database
+ * state changes. (It's debatable whether we should issue a
+ * WARNING in this case, but we have done so historically.)
*/
case TBLOCK_STARTED:
+ case TBLOCK_IMPLICIT_INPROGRESS:
ereport(WARNING,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
errmsg("there is no transaction in progress")));
@@ -3744,6 +3775,58 @@ UserAbortTransactionBlock(void)
}
/*
+ * BeginImplicitTransactionBlock
+ * Start an implicit transaction block if we're not already in one.
+ *
+ * Unlike BeginTransactionBlock, this is called directly from the main loop
+ * in postgres.c, not within a Portal. So we can just change blockState
+ * without a lot of ceremony. We do not expect caller to do
+ * CommitTransactionCommand/StartTransactionCommand.
+ */
+void
+BeginImplicitTransactionBlock(void)
+{
+ TransactionState s = CurrentTransactionState;
+
+ /*
+ * If we are in STARTED state (that is, no transaction block is open),
+ * switch to IMPLICIT_INPROGRESS state, creating an implicit transaction
+ * block.
+ *
+ * For caller convenience, we consider all other transaction states as
+ * legal here; otherwise the caller would need its own state check, which
+ * seems rather pointless.
+ */
+ if (s->blockState == TBLOCK_STARTED)
+ s->blockState = TBLOCK_IMPLICIT_INPROGRESS;
+}
+
+/*
+ * EndImplicitTransactionBlock
+ * End an implicit transaction block, if we're in one.
+ *
+ * Like EndTransactionBlock, we just make any needed blockState change here.
+ * The real work will be done in the upcoming CommitTransactionCommand().
+ */
+void
+EndImplicitTransactionBlock(void)
+{
+ TransactionState s = CurrentTransactionState;
+
+ /*
+ * If we are in IMPLICIT_INPROGRESS state, switch back to STARTED state,
+ * allowing CommitTransactionCommand to commit whatever happened during
+ * the implicit transaction block as though it were a single statement.
+ *
+ * For caller convenience, we consider all other transaction states as
+ * legal here; otherwise the caller would need its own state check, which
+ * seems rather pointless.
+ */
+ if (s->blockState == TBLOCK_IMPLICIT_INPROGRESS)
+ s->blockState = TBLOCK_STARTED;
+}
+
+/*
* DefineSavepoint
* This executes a SAVEPOINT command.
*/
@@ -3780,6 +3863,28 @@ DefineSavepoint(char *name)
s->name = MemoryContextStrdup(TopTransactionContext, name);
break;
+ /*
+ * We disallow savepoint commands in implicit transaction blocks.
+ * There would be no great difficulty in allowing them so far as
+ * this module is concerned, but a savepoint seems inconsistent
+ * with exec_simple_query's behavior of abandoning the whole query
+ * string upon error. Also, the point of an implicit transaction
+ * block (as opposed to a regular one) is to automatically close
+ * after an error, so it's hard to see how a savepoint would fit
+ * into that.
+ *
+ * The error messages for this are phrased as if there were no
+ * active transaction block at all, which is historical but
+ * perhaps could be improved.
+ */
+ case TBLOCK_IMPLICIT_INPROGRESS:
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
+ /* translator: %s represents an SQL statement name */
+ errmsg("%s can only be used in transaction blocks",
+ "SAVEPOINT")));
+ break;
+
/* These cases are invalid. */
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
@@ -3834,8 +3939,7 @@ ReleaseSavepoint(List *options)
switch (s->blockState)
{
/*
- * We can't rollback to a savepoint if there is no savepoint
- * defined.
+ * We can't release a savepoint if there is no savepoint defined.
*/
case TBLOCK_INPROGRESS:
ereport(ERROR,
@@ -3843,6 +3947,15 @@ ReleaseSavepoint(List *options)
errmsg("no such savepoint")));
break;
+ case TBLOCK_IMPLICIT_INPROGRESS:
+ /* See comment about implicit transactions in DefineSavepoint */
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
+ /* translator: %s represents an SQL statement name */
+ errmsg("%s can only be used in transaction blocks",
+ "RELEASE SAVEPOINT")));
+ break;
+
/*
* We are in a non-aborted subtransaction. This is the only valid
* case.
@@ -3957,6 +4070,15 @@ RollbackToSavepoint(List *options)
errmsg("no such savepoint")));
break;
+ case TBLOCK_IMPLICIT_INPROGRESS:
+ /* See comment about implicit transactions in DefineSavepoint */
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
+ /* translator: %s represents an SQL statement name */
+ errmsg("%s can only be used in transaction blocks",
+ "ROLLBACK TO SAVEPOINT")));
+ break;
+
/*
* There is at least one savepoint, so proceed.
*/
@@ -4046,11 +4168,12 @@ RollbackToSavepoint(List *options)
/*
* BeginInternalSubTransaction
* This is the same as DefineSavepoint except it allows TBLOCK_STARTED,
- * TBLOCK_END, and TBLOCK_PREPARE states, and therefore it can safely be
- * used in functions that might be called when not inside a BEGIN block
- * or when running deferred triggers at COMMIT/PREPARE time. Also, it
- * automatically does CommitTransactionCommand/StartTransactionCommand
- * instead of expecting the caller to do it.
+ * TBLOCK_IMPLICIT_INPROGRESS, TBLOCK_END, and TBLOCK_PREPARE states,
+ * and therefore it can safely be used in functions that might be called
+ * when not inside a BEGIN block or when running deferred triggers at
+ * COMMIT/PREPARE time. Also, it automatically does
+ * CommitTransactionCommand/StartTransactionCommand instead of expecting
+ * the caller to do it.
*/
void
BeginInternalSubTransaction(char *name)
@@ -4076,6 +4199,7 @@ BeginInternalSubTransaction(char *name)
{
case TBLOCK_STARTED:
case TBLOCK_INPROGRESS:
+ case TBLOCK_IMPLICIT_INPROGRESS:
case TBLOCK_END:
case TBLOCK_PREPARE:
case TBLOCK_SUBINPROGRESS:
@@ -4180,6 +4304,7 @@ RollbackAndReleaseCurrentSubTransaction(void)
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
+ case TBLOCK_IMPLICIT_INPROGRESS:
case TBLOCK_PARALLEL_INPROGRESS:
case TBLOCK_SUBBEGIN:
case TBLOCK_INPROGRESS:
@@ -4211,6 +4336,7 @@ RollbackAndReleaseCurrentSubTransaction(void)
s = CurrentTransactionState; /* changed by pop */
AssertState(s->blockState == TBLOCK_SUBINPROGRESS ||
s->blockState == TBLOCK_INPROGRESS ||
+ s->blockState == TBLOCK_IMPLICIT_INPROGRESS ||
s->blockState == TBLOCK_STARTED);
}
@@ -4259,6 +4385,7 @@ AbortOutOfAnyTransaction(void)
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_INPROGRESS:
+ case TBLOCK_IMPLICIT_INPROGRESS:
case TBLOCK_PARALLEL_INPROGRESS:
case TBLOCK_END:
case TBLOCK_ABORT_PENDING:
@@ -4369,6 +4496,7 @@ TransactionBlockStatusCode(void)
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
case TBLOCK_INPROGRESS:
+ case TBLOCK_IMPLICIT_INPROGRESS:
case TBLOCK_PARALLEL_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
case TBLOCK_END:
@@ -5036,6 +5164,8 @@ BlockStateAsString(TBlockState blockState)
return "BEGIN";
case TBLOCK_INPROGRESS:
return "INPROGRESS";
+ case TBLOCK_IMPLICIT_INPROGRESS:
+ return "IMPLICIT_INPROGRESS";
case TBLOCK_PARALLEL_INPROGRESS:
return "PARALLEL_INPROGRESS";
case TBLOCK_END: