Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d92bc83

Browse files
committed
PL/pgSQL: Nested CALL with transactions
So far, a nested CALL or DO in PL/pgSQL would not establish a context where transaction control statements were allowed. This fixes that by handling CALL and DO specially in PL/pgSQL, passing the atomic/nonatomic execution context through and doing the required management around transaction boundaries. Reviewed-by: Tomas Vondra <tomas.vondra@2ndquadrant.com>
1 parent c2d4eb1 commit d92bc83

File tree

13 files changed

+235
-59
lines changed

13 files changed

+235
-59
lines changed

doc/src/sgml/plpgsql.sgml

+17-3
Original file line numberDiff line numberDiff line change
@@ -3463,9 +3463,9 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
34633463
<title>Transaction Management</title>
34643464

34653465
<para>
3466-
In procedures invoked by the <command>CALL</command> command from the top
3467-
level as well as in anonymous code blocks (<command>DO</command> command)
3468-
called from the top level, it is possible to end transactions using the
3466+
In procedures invoked by the <command>CALL</command> command
3467+
as well as in anonymous code blocks (<command>DO</command> command),
3468+
it is possible to end transactions using the
34693469
commands <command>COMMIT</command> and <command>ROLLBACK</command>. A new
34703470
transaction is started automatically after a transaction is ended using
34713471
these commands, so there is no separate <command>START
@@ -3495,6 +3495,20 @@ CALL transaction_test1();
34953495
</programlisting>
34963496
</para>
34973497

3498+
<para>
3499+
Transaction control is only possible in <command>CALL</command> or
3500+
<command>DO</command> invocations from the top level or nested
3501+
<command>CALL</command> or <command>DO</command> invocations without any
3502+
other intervening command. For example, if the call stack is
3503+
<command>CALL proc1()</command> &rarr; <command>CALL proc2()</command>
3504+
&rarr; <command>CALL proc3()</command>, then the second and third
3505+
procedures can perform transaction control actions. But if the call stack
3506+
is <command>CALL proc1()</command> &rarr; <command>SELECT
3507+
func2()</command> &rarr; <command>CALL proc3()</command>, then the last
3508+
procedure cannot do transaction control, because of the
3509+
<command>SELECT</command> in between.
3510+
</para>
3511+
34983512
<para>
34993513
A transaction cannot be ended inside a loop over a query result, nor
35003514
inside a block with exception handlers.

src/backend/executor/spi.c

+22-12
Original file line numberDiff line numberDiff line change
@@ -2041,8 +2041,11 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
20412041
*
20422042
* In the first two cases, we can just push the snap onto the stack once
20432043
* for the whole plan list.
2044+
*
2045+
* But if the plan has no_snapshots set to true, then don't manage
2046+
* snapshots at all. The caller should then take care of that.
20442047
*/
2045-
if (snapshot != InvalidSnapshot)
2048+
if (snapshot != InvalidSnapshot && !plan->no_snapshots)
20462049
{
20472050
if (read_only)
20482051
{
@@ -2121,7 +2124,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
21212124
* In the default non-read-only case, get a new snapshot, replacing
21222125
* any that we pushed in a previous cycle.
21232126
*/
2124-
if (snapshot == InvalidSnapshot && !read_only)
2127+
if (snapshot == InvalidSnapshot && !read_only && !plan->no_snapshots)
21252128
{
21262129
if (pushed_active_snap)
21272130
PopActiveSnapshot();
@@ -2172,7 +2175,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
21722175
* If not read-only mode, advance the command counter before each
21732176
* command and update the snapshot.
21742177
*/
2175-
if (!read_only)
2178+
if (!read_only && !plan->no_snapshots)
21762179
{
21772180
CommandCounterIncrement();
21782181
UpdateActiveSnapshotCommandId();
@@ -2203,10 +2206,23 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
22032206
else
22042207
{
22052208
char completionTag[COMPLETION_TAG_BUFSIZE];
2209+
ProcessUtilityContext context;
2210+
2211+
/*
2212+
* If the SPI context is atomic, or we are asked to manage
2213+
* snapshots, then we are in an atomic execution context.
2214+
* Conversely, to propagate a nonatomic execution context, the
2215+
* caller must be in a nonatomic SPI context and manage
2216+
* snapshots itself.
2217+
*/
2218+
if (_SPI_current->atomic || !plan->no_snapshots)
2219+
context = PROCESS_UTILITY_QUERY;
2220+
else
2221+
context = PROCESS_UTILITY_QUERY_NONATOMIC;
22062222

22072223
ProcessUtility(stmt,
22082224
plansource->query_string,
2209-
PROCESS_UTILITY_QUERY,
2225+
context,
22102226
paramLI,
22112227
_SPI_current->queryEnv,
22122228
dest,
@@ -2638,11 +2654,8 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan)
26382654
oldcxt = MemoryContextSwitchTo(plancxt);
26392655

26402656
/* Copy the SPI_plan struct and subsidiary data into the new context */
2641-
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
2657+
newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
26422658
newplan->magic = _SPI_PLAN_MAGIC;
2643-
newplan->saved = false;
2644-
newplan->oneshot = false;
2645-
newplan->plancache_list = NIL;
26462659
newplan->plancxt = plancxt;
26472660
newplan->cursor_options = plan->cursor_options;
26482661
newplan->nargs = plan->nargs;
@@ -2705,11 +2718,8 @@ _SPI_save_plan(SPIPlanPtr plan)
27052718
oldcxt = MemoryContextSwitchTo(plancxt);
27062719

27072720
/* Copy the SPI plan into its own context */
2708-
newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
2721+
newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
27092722
newplan->magic = _SPI_PLAN_MAGIC;
2710-
newplan->saved = false;
2711-
newplan->oneshot = false;
2712-
newplan->plancache_list = NIL;
27132723
newplan->plancxt = plancxt;
27142724
newplan->cursor_options = plan->cursor_options;
27152725
newplan->nargs = plan->nargs;

src/backend/tcop/utility.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
382382
{
383383
Node *parsetree = pstmt->utilityStmt;
384384
bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
385-
bool isAtomicContext = (context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock());
385+
bool isAtomicContext = (!(context == PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC) || IsTransactionBlock());
386386
ParseState *pstate;
387387

388388
check_xact_readonly(parsetree);

src/include/executor/spi_priv.h

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ typedef struct _SPI_plan
8686
int magic; /* should equal _SPI_PLAN_MAGIC */
8787
bool saved; /* saved or unsaved plan? */
8888
bool oneshot; /* one-shot plan? */
89+
bool no_snapshots; /* let the caller handle the snapshots */
8990
List *plancache_list; /* one CachedPlanSource per parsetree */
9091
MemoryContext plancxt; /* Context containing _SPI_plan and data */
9192
int cursor_options; /* Cursor options used for planning */

src/include/tcop/utility.h

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ typedef enum
2020
{
2121
PROCESS_UTILITY_TOPLEVEL, /* toplevel interactive command */
2222
PROCESS_UTILITY_QUERY, /* a complete query, but not toplevel */
23+
PROCESS_UTILITY_QUERY_NONATOMIC, /* a complete query, nonatomic execution context */
2324
PROCESS_UTILITY_SUBCOMMAND /* a portion of a query */
2425
} ProcessUtilityContext;
2526

src/pl/plpgsql/src/expected/plpgsql_transaction.out

+67-16
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
CREATE TABLE test1 (a int, b text);
2-
CREATE PROCEDURE transaction_test1()
2+
CREATE PROCEDURE transaction_test1(x int, y text)
33
LANGUAGE plpgsql
44
AS $$
55
BEGIN
6-
FOR i IN 0..9 LOOP
7-
INSERT INTO test1 (a) VALUES (i);
6+
FOR i IN 0..x LOOP
7+
INSERT INTO test1 (a, b) VALUES (i, y);
88
IF i % 2 = 0 THEN
99
COMMIT;
1010
ELSE
@@ -13,15 +13,15 @@ BEGIN
1313
END LOOP;
1414
END
1515
$$;
16-
CALL transaction_test1();
16+
CALL transaction_test1(9, 'foo');
1717
SELECT * FROM test1;
18-
a | b
19-
---+---
20-
0 |
21-
2 |
22-
4 |
23-
6 |
24-
8 |
18+
a | b
19+
---+-----
20+
0 | foo
21+
2 | foo
22+
4 | foo
23+
6 | foo
24+
8 | foo
2525
(5 rows)
2626

2727
TRUNCATE test1;
@@ -51,9 +51,9 @@ SELECT * FROM test1;
5151

5252
-- transaction commands not allowed when called in transaction block
5353
START TRANSACTION;
54-
CALL transaction_test1();
54+
CALL transaction_test1(9, 'error');
5555
ERROR: invalid transaction termination
56-
CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT
56+
CONTEXT: PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
5757
COMMIT;
5858
START TRANSACTION;
5959
DO LANGUAGE plpgsql $$ BEGIN COMMIT; END $$;
@@ -90,14 +90,14 @@ CREATE FUNCTION transaction_test3() RETURNS int
9090
LANGUAGE plpgsql
9191
AS $$
9292
BEGIN
93-
CALL transaction_test1();
93+
CALL transaction_test1(9, 'error');
9494
RETURN 1;
9595
END;
9696
$$;
9797
SELECT transaction_test3();
9898
ERROR: invalid transaction termination
99-
CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT
100-
SQL statement "CALL transaction_test1()"
99+
CONTEXT: PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
100+
SQL statement "CALL transaction_test1(9, 'error')"
101101
PL/pgSQL function transaction_test3() line 3 at CALL
102102
SELECT * FROM test1;
103103
a | b
@@ -130,6 +130,57 @@ $$;
130130
CALL transaction_test5();
131131
ERROR: invalid transaction termination
132132
CONTEXT: PL/pgSQL function transaction_test5() line 3 at COMMIT
133+
TRUNCATE test1;
134+
-- nested procedure calls
135+
CREATE PROCEDURE transaction_test6(c text)
136+
LANGUAGE plpgsql
137+
AS $$
138+
BEGIN
139+
CALL transaction_test1(9, c);
140+
END;
141+
$$;
142+
CALL transaction_test6('bar');
143+
SELECT * FROM test1;
144+
a | b
145+
---+-----
146+
0 | bar
147+
2 | bar
148+
4 | bar
149+
6 | bar
150+
8 | bar
151+
(5 rows)
152+
153+
TRUNCATE test1;
154+
CREATE PROCEDURE transaction_test7()
155+
LANGUAGE plpgsql
156+
AS $$
157+
BEGIN
158+
DO 'BEGIN CALL transaction_test1(9, $x$baz$x$); END;';
159+
END;
160+
$$;
161+
CALL transaction_test7();
162+
SELECT * FROM test1;
163+
a | b
164+
---+-----
165+
0 | baz
166+
2 | baz
167+
4 | baz
168+
6 | baz
169+
8 | baz
170+
(5 rows)
171+
172+
CREATE PROCEDURE transaction_test8()
173+
LANGUAGE plpgsql
174+
AS $$
175+
BEGIN
176+
EXECUTE 'CALL transaction_test1(10, $x$baz$x$)';
177+
END;
178+
$$;
179+
CALL transaction_test8();
180+
ERROR: invalid transaction termination
181+
CONTEXT: PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
182+
SQL statement "CALL transaction_test1(10, $x$baz$x$)"
183+
PL/pgSQL function transaction_test8() line 3 at EXECUTE
133184
-- commit inside cursor loop
134185
CREATE TABLE test2 (x int);
135186
INSERT INTO test2 VALUES (0), (1), (2), (3), (4);

0 commit comments

Comments
 (0)