Skip to content

Commit ae47f8a

Browse files
committed
Rethink handling of [Prevent|Is]InTransactionBlock in pipeline mode.
Commits f929441 et al. made IsInTransactionBlock() set the XACT_FLAGS_NEEDIMMEDIATECOMMIT flag before returning "false", on the grounds that that kept its API promises equivalent to those of PreventInTransactionBlock(). This turns out to be a bad idea though, because it allows an ANALYZE in a pipelined series of commands to cause an immediate commit, which is unexpected. Furthermore, if we return "false" then we have another issue, which is that ANALYZE will decide it's allowed to do internal commit-and-start-transaction sequences, thus possibly unexpectedly committing the effects of previous commands in the pipeline. To fix the latter situation, invent another transaction state flag XACT_FLAGS_PIPELINING, which explicitly records the fact that we have executed some extended-protocol command and not yet seen a commit for it. Then, require that flag to not be set before allowing InTransactionBlock() to return "false". Having done that, we can remove its setting of NEEDIMMEDIATECOMMIT without fear of causing problems. This means that the API guarantees of IsInTransactionBlock now diverge from PreventInTransactionBlock, which is mildly annoying, but it seems OK given the very limited usage of IsInTransactionBlock. (In any case, a caller preferring the old behavior could always set NEEDIMMEDIATECOMMIT for itself.) For consistency also require XACT_FLAGS_PIPELINING to not be set in PreventInTransactionBlock. This too is meant to prevent commands such as CREATE DATABASE from silently committing previous commands in a pipeline. Per report from Peter Eisentraut. As before, back-patch to all supported branches (which sadly no longer includes v10). Discussion: https://postgr.es/m/65a899dd-aebc-f667-1d0a-abb89ff3abf8@enterprisedb.com
1 parent a18328b commit ae47f8a

File tree

5 files changed

+45
-16
lines changed

5 files changed

+45
-16
lines changed

doc/src/sgml/libpq.sgml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5024,10 +5024,11 @@ int PQflush(PGconn *conn);
50245024
</para>
50255025

50265026
<para>
5027-
While the pipeline API was introduced in
5027+
While <application>libpq</application>'s pipeline API was introduced in
50285028
<productname>PostgreSQL</productname> 14, it is a client-side feature
50295029
which doesn't require special server support and works on any server
5030-
that supports the v3 extended query protocol.
5030+
that supports the v3 extended query protocol. For more information see
5031+
<xref linkend="protocol-flow-pipelining"/>.
50315032
</para>
50325033

50335034
<sect2 id="libpq-pipeline-using">

doc/src/sgml/protocol.sgml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,17 +1093,18 @@ SELCT 1/0;<!-- this typo is intentional -->
10931093
implicit <command>ROLLBACK</command> if they failed. However, there
10941094
are a few DDL commands (such as <command>CREATE DATABASE</command>)
10951095
that cannot be executed inside a transaction block. If one of
1096-
these is executed in a pipeline, it will, upon success, force an
1097-
immediate commit to preserve database consistency.
1098-
A Sync immediately following one of these has no effect except to
1096+
these is executed in a pipeline, it will fail unless it is the first
1097+
command in the pipeline. Furthermore, upon success it will force an
1098+
immediate commit to preserve database consistency. Thus a Sync
1099+
immediately following one of these commands has no effect except to
10991100
respond with ReadyForQuery.
11001101
</para>
11011102

11021103
<para>
11031104
When using this method, completion of the pipeline must be determined
11041105
by counting ReadyForQuery messages and waiting for that to reach the
11051106
number of Syncs sent. Counting command completion responses is
1106-
unreliable, since some of the commands may not be executed and thus not
1107+
unreliable, since some of the commands may be skipped and thus not
11071108
produce a completion message.
11081109
</para>
11091110
</sect2>

src/backend/access/transam/xact.c

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3426,6 +3426,16 @@ PreventInTransactionBlock(bool isTopLevel, const char *stmtType)
34263426
errmsg("%s cannot run inside a subtransaction",
34273427
stmtType)));
34283428

3429+
/*
3430+
* inside a pipeline that has started an implicit transaction?
3431+
*/
3432+
if (MyXactFlags & XACT_FLAGS_PIPELINING)
3433+
ereport(ERROR,
3434+
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
3435+
/* translator: %s represents an SQL statement name */
3436+
errmsg("%s cannot be executed within a pipeline",
3437+
stmtType)));
3438+
34293439
/*
34303440
* inside a function call?
34313441
*/
@@ -3515,9 +3525,11 @@ CheckTransactionBlock(bool isTopLevel, bool throwError, const char *stmtType)
35153525
* a transaction block than when running as single commands. ANALYZE is
35163526
* currently the only example.
35173527
*
3518-
* If this routine returns "false", then the calling statement is
3519-
* guaranteed that if it completes without error, its results will be
3520-
* committed immediately.
3528+
* If this routine returns "false", then the calling statement is allowed
3529+
* to perform internal transaction-commit-and-start cycles; there is not a
3530+
* risk of messing up any transaction already in progress. (Note that this
3531+
* is not the identical guarantee provided by PreventInTransactionBlock,
3532+
* since we will not force a post-statement commit.)
35213533
*
35223534
* isTopLevel: passed down from ProcessUtility to determine whether we are
35233535
* inside a function.
@@ -3535,20 +3547,16 @@ IsInTransactionBlock(bool isTopLevel)
35353547
if (IsSubTransaction())
35363548
return true;
35373549

3550+
if (MyXactFlags & XACT_FLAGS_PIPELINING)
3551+
return true;
3552+
35383553
if (!isTopLevel)
35393554
return true;
35403555

35413556
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
35423557
CurrentTransactionState->blockState != TBLOCK_STARTED)
35433558
return true;
35443559

3545-
/*
3546-
* If we tell the caller we're not in a transaction block, then inform
3547-
* postgres.c that it had better commit when the statement is done.
3548-
* Otherwise our report could be a lie.
3549-
*/
3550-
MyXactFlags |= XACT_FLAGS_NEEDIMMEDIATECOMMIT;
3551-
35523560
return false;
35533561
}
35543562

src/backend/tcop/postgres.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,6 +2226,12 @@ exec_execute_message(const char *portal_name, long max_rows)
22262226
*/
22272227
CommandCounterIncrement();
22282228

2229+
/*
2230+
* Set XACT_FLAGS_PIPELINING whenever we complete an Execute
2231+
* message without immediately committing the transaction.
2232+
*/
2233+
MyXactFlags |= XACT_FLAGS_PIPELINING;
2234+
22292235
/*
22302236
* Disable statement timeout whenever we complete an Execute
22312237
* message. The next protocol message will start a fresh timeout.
@@ -2241,6 +2247,12 @@ exec_execute_message(const char *portal_name, long max_rows)
22412247
/* Portal run not complete, so send PortalSuspended */
22422248
if (whereToSendOutput == DestRemote)
22432249
pq_putemptymessage('s');
2250+
2251+
/*
2252+
* Set XACT_FLAGS_PIPELINING whenever we suspend an Execute message,
2253+
* too.
2254+
*/
2255+
MyXactFlags |= XACT_FLAGS_PIPELINING;
22442256
}
22452257

22462258
/*

src/include/access/xact.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ extern int MyXactFlags;
113113
*/
114114
#define XACT_FLAGS_NEEDIMMEDIATECOMMIT (1U << 2)
115115

116+
/*
117+
* XACT_FLAGS_PIPELINING - set when we complete an extended-query-protocol
118+
* Execute message. This is useful for detecting that an implicit transaction
119+
* block has been created via pipelining.
120+
*/
121+
#define XACT_FLAGS_PIPELINING (1U << 3)
122+
116123
/*
117124
* start- and end-of-transaction callbacks for dynamically loaded modules
118125
*/

0 commit comments

Comments
 (0)