Skip to content

Commit 5f0a073

Browse files
committed
Fix misevaluation of STABLE parameters in CALL within plpgsql.
Before commit 84f5c29, a STABLE function in a plpgsql CALL statement's argument list would see an up-to-date snapshot, because exec_stmt_call would push a new snapshot. I got rid of that because the possibility of the snapshot disappearing within COMMIT made it too hard to manage a snapshot across the CALL statement. That's fine so far as the procedure itself goes, but I forgot to think about the possibility of STABLE functions within the CALL argument list. As things now stand, those'll be executed with the Portal's snapshot as ActiveSnapshot, keeping them from seeing updates more recent than Portal startup. (VOLATILE functions don't have a problem because they take their own snapshots; which indeed is also why the procedure itself doesn't have a problem. There are no STABLE procedures.) We can fix this by pushing a new snapshot transiently within ExecuteCallStmt itself. Popping the snapshot before we get into the procedure proper eliminates the management problem. The possibly-useless extra snapshot-grab is slightly annoying, but it's no worse than what happened before 84f5c29. Per bug #17199 from Alexander Nawratil. Back-patch to v11, like the previous patch. Discussion: https://postgr.es/m/17199-1ab2561f0d94af92@postgresql.org
1 parent a1708ab commit 5f0a073

File tree

3 files changed

+62
-4
lines changed

3 files changed

+62
-4
lines changed

src/backend/commands/functioncmds.c

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
#include "utils/lsyscache.h"
7171
#include "utils/memutils.h"
7272
#include "utils/rel.h"
73+
#include "utils/snapmgr.h"
7374
#include "utils/syscache.h"
7475
#include "utils/typcache.h"
7576

@@ -2211,6 +2212,16 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
22112212
estate->es_param_list_info = params;
22122213
econtext = CreateExprContext(estate);
22132214

2215+
/*
2216+
* If we're called in non-atomic context, we also have to ensure that the
2217+
* argument expressions run with an up-to-date snapshot. Our caller will
2218+
* have provided a current snapshot in atomic contexts, but not in
2219+
* non-atomic contexts, because the possibility of a COMMIT/ROLLBACK
2220+
* destroying the snapshot makes higher-level management too complicated.
2221+
*/
2222+
if (!atomic)
2223+
PushActiveSnapshot(GetTransactionSnapshot());
2224+
22142225
i = 0;
22152226
foreach(lc, fexpr->args)
22162227
{
@@ -2228,20 +2239,23 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
22282239
i++;
22292240
}
22302241

2242+
/* Get rid of temporary snapshot for arguments, if we made one */
2243+
if (!atomic)
2244+
PopActiveSnapshot();
2245+
2246+
/* Here we actually call the procedure */
22312247
pgstat_init_function_usage(fcinfo, &fcusage);
22322248
retval = FunctionCallInvoke(fcinfo);
22332249
pgstat_end_function_usage(&fcusage, true);
22342250

2251+
/* Handle the procedure's outputs */
22352252
if (fexpr->funcresulttype == VOIDOID)
22362253
{
22372254
/* do nothing */
22382255
}
22392256
else if (fexpr->funcresulttype == RECORDOID)
22402257
{
2241-
/*
2242-
* send tuple to client
2243-
*/
2244-
2258+
/* send tuple to client */
22452259
HeapTupleHeader td;
22462260
Oid tupType;
22472261
int32 tupTypmod;

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,27 @@ SELECT * FROM test2;
600600
42
601601
(1 row)
602602

603+
-- another snapshot handling case: argument expressions of a CALL need
604+
-- to be evaluated with an up-to-date snapshot
605+
CREATE FUNCTION report_count() RETURNS int
606+
STABLE LANGUAGE sql
607+
AS $$ SELECT COUNT(*) FROM test2 $$;
608+
CREATE PROCEDURE transaction_test9b(cnt int)
609+
LANGUAGE plpgsql
610+
AS $$
611+
BEGIN
612+
RAISE NOTICE 'count = %', cnt;
613+
END
614+
$$;
615+
DO $$
616+
BEGIN
617+
CALL transaction_test9b(report_count());
618+
INSERT INTO test2 VALUES(43);
619+
CALL transaction_test9b(report_count());
620+
END
621+
$$;
622+
NOTICE: count = 1
623+
NOTICE: count = 2
603624
-- Test transaction in procedure with output parameters. This uses a
604625
-- different portal strategy and different code paths in pquery.c.
605626
CREATE PROCEDURE transaction_test10a(INOUT x int)

src/pl/plpgsql/src/sql/plpgsql_transaction.sql

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,29 @@ $$;
507507
SELECT * FROM test2;
508508

509509

510+
-- another snapshot handling case: argument expressions of a CALL need
511+
-- to be evaluated with an up-to-date snapshot
512+
CREATE FUNCTION report_count() RETURNS int
513+
STABLE LANGUAGE sql
514+
AS $$ SELECT COUNT(*) FROM test2 $$;
515+
516+
CREATE PROCEDURE transaction_test9b(cnt int)
517+
LANGUAGE plpgsql
518+
AS $$
519+
BEGIN
520+
RAISE NOTICE 'count = %', cnt;
521+
END
522+
$$;
523+
524+
DO $$
525+
BEGIN
526+
CALL transaction_test9b(report_count());
527+
INSERT INTO test2 VALUES(43);
528+
CALL transaction_test9b(report_count());
529+
END
530+
$$;
531+
532+
510533
-- Test transaction in procedure with output parameters. This uses a
511534
-- different portal strategy and different code paths in pquery.c.
512535
CREATE PROCEDURE transaction_test10a(INOUT x int)

0 commit comments

Comments
 (0)