Skip to content

Commit 4476bcb

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 ade24da commit 4476bcb

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
@@ -73,6 +73,7 @@
7373
#include "utils/lsyscache.h"
7474
#include "utils/memutils.h"
7575
#include "utils/rel.h"
76+
#include "utils/snapmgr.h"
7677
#include "utils/syscache.h"
7778
#include "utils/typcache.h"
7879

@@ -2250,6 +2251,16 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
22502251
estate->es_param_list_info = params;
22512252
econtext = CreateExprContext(estate);
22522253

2254+
/*
2255+
* If we're called in non-atomic context, we also have to ensure that the
2256+
* argument expressions run with an up-to-date snapshot. Our caller will
2257+
* have provided a current snapshot in atomic contexts, but not in
2258+
* non-atomic contexts, because the possibility of a COMMIT/ROLLBACK
2259+
* destroying the snapshot makes higher-level management too complicated.
2260+
*/
2261+
if (!atomic)
2262+
PushActiveSnapshot(GetTransactionSnapshot());
2263+
22532264
i = 0;
22542265
foreach(lc, fexpr->args)
22552266
{
@@ -2267,20 +2278,23 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
22672278
i++;
22682279
}
22692280

2281+
/* Get rid of temporary snapshot for arguments, if we made one */
2282+
if (!atomic)
2283+
PopActiveSnapshot();
2284+
2285+
/* Here we actually call the procedure */
22702286
pgstat_init_function_usage(fcinfo, &fcusage);
22712287
retval = FunctionCallInvoke(fcinfo);
22722288
pgstat_end_function_usage(&fcusage, true);
22732289

2290+
/* Handle the procedure's outputs */
22742291
if (fexpr->funcresulttype == VOIDOID)
22752292
{
22762293
/* do nothing */
22772294
}
22782295
else if (fexpr->funcresulttype == RECORDOID)
22792296
{
2280-
/*
2281-
* send tuple to client
2282-
*/
2283-
2297+
/* send tuple to client */
22842298
HeapTupleHeader td;
22852299
Oid tupType;
22862300
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)