diff options
Diffstat (limited to 'src/backend/access/transam/xact.c')
-rw-r--r-- | src/backend/access/transam/xact.c | 160 |
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: |