diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 47f4b5b4317..5c36b534902 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -742,7 +742,9 @@ int SPI_execute_extended(const char *command,
true allows non-atomic execution of CALL and DO
- statements
+ statements (but this field is ignored unless
+ the SPI_OPT_NONATOMIC flag was passed
+ to SPI_connect_ext)
@@ -1883,7 +1885,9 @@ int SPI_execute_plan_extended(SPIPlanPtr plan,
true allows non-atomic execution of CALL and DO
- statements
+ statements (but this field is ignored unless
+ the SPI_OPT_NONATOMIC flag was passed
+ to SPI_connect_ext)
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 1edbd4353f6..ffb7fcadf73 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2398,6 +2398,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
uint64 my_processed = 0;
SPITupleTable *my_tuptable = NULL;
int res = 0;
+ bool allow_nonatomic;
bool pushed_active_snap = false;
ResourceOwner plan_owner = options->owner;
SPICallbackArg spicallbackarg;
@@ -2405,6 +2406,12 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
CachedPlan *cplan = NULL;
ListCell *lc1;
+ /*
+ * We allow nonatomic behavior only if options->allow_nonatomic is set
+ * *and* the SPI_OPT_NONATOMIC flag was given when connecting.
+ */
+ allow_nonatomic = options->allow_nonatomic && !_SPI_current->atomic;
+
/*
* Setup error traceback support for ereport()
*/
@@ -2424,12 +2431,17 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
* snapshot != InvalidSnapshot, read_only = false: use the given snapshot,
* modified by advancing its command ID before each querytree.
*
- * snapshot == InvalidSnapshot, read_only = true: use the entry-time
- * ActiveSnapshot, if any (if there isn't one, we run with no snapshot).
+ * snapshot == InvalidSnapshot, read_only = true: do nothing for queries
+ * that require no snapshot. For those that do, ensure that a Portal
+ * snapshot exists; then use that, or use the entry-time ActiveSnapshot if
+ * that exists and is different.
*
- * snapshot == InvalidSnapshot, read_only = false: take a full new
- * snapshot for each user command, and advance its command ID before each
- * querytree within the command.
+ * snapshot == InvalidSnapshot, read_only = false: do nothing for queries
+ * that require no snapshot. For those that do, ensure that a Portal
+ * snapshot exists; then, in atomic execution (!allow_nonatomic) take a
+ * full new snapshot for each user command, and advance its command ID
+ * before each querytree within the command. In allow_nonatomic mode we
+ * just use the Portal snapshot unmodified.
*
* In the first two cases, we can just push the snap onto the stack once
* for the whole plan list.
@@ -2439,6 +2451,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
*/
if (snapshot != InvalidSnapshot)
{
+ /* this intentionally tests the options field not the derived value */
Assert(!options->allow_nonatomic);
if (options->read_only)
{
@@ -2584,7 +2597,7 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
* Skip it when doing non-atomic execution, though (we rely
* entirely on the Portal snapshot in that case).
*/
- if (!options->read_only && !options->allow_nonatomic)
+ if (!options->read_only && !allow_nonatomic)
{
if (pushed_active_snap)
PopActiveSnapshot();
@@ -2684,14 +2697,13 @@ _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options,
QueryCompletion qc;
/*
- * If the SPI context is atomic, or we were not told to allow
- * nonatomic operations, tell ProcessUtility this is an atomic
- * execution context.
+ * If we're not allowing nonatomic operations, tell
+ * ProcessUtility this is an atomic execution context.
*/
- if (_SPI_current->atomic || !options->allow_nonatomic)
- context = PROCESS_UTILITY_QUERY;
- else
+ if (allow_nonatomic)
context = PROCESS_UTILITY_QUERY_NONATOMIC;
+ else
+ context = PROCESS_UTILITY_QUERY;
InitializeQueryCompletion(&qc);
ProcessUtility(stmt,
diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out
index 17235fca912..0a63b1d44ef 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_call.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_call.out
@@ -564,3 +564,53 @@ NOTICE: inner_p(44)
(1 row)
+-- Check that stable functions in CALL see the correct snapshot
+CREATE TABLE t_test (x int);
+INSERT INTO t_test VALUES (0);
+CREATE FUNCTION f_get_x () RETURNS int
+AS $$
+DECLARE l_result int;
+BEGIN
+ SELECT x INTO l_result FROM t_test;
+ RETURN l_result;
+END
+$$ LANGUAGE plpgsql STABLE;
+CREATE PROCEDURE f_print_x (x int)
+AS $$
+BEGIN
+ RAISE NOTICE 'f_print_x(%)', x;
+END
+$$ LANGUAGE plpgsql;
+-- test in non-atomic context
+DO $$
+BEGIN
+ UPDATE t_test SET x = x + 1;
+ RAISE NOTICE 'f_get_x(%)', f_get_x();
+ CALL f_print_x(f_get_x());
+ UPDATE t_test SET x = x + 1;
+ RAISE NOTICE 'f_get_x(%)', f_get_x();
+ CALL f_print_x(f_get_x());
+ ROLLBACK;
+END
+$$;
+NOTICE: f_get_x(1)
+NOTICE: f_print_x(1)
+NOTICE: f_get_x(2)
+NOTICE: f_print_x(2)
+-- test in atomic context
+BEGIN;
+DO $$
+BEGIN
+ UPDATE t_test SET x = x + 1;
+ RAISE NOTICE 'f_get_x(%)', f_get_x();
+ CALL f_print_x(f_get_x());
+ UPDATE t_test SET x = x + 1;
+ RAISE NOTICE 'f_get_x(%)', f_get_x();
+ CALL f_print_x(f_get_x());
+END
+$$;
+NOTICE: f_get_x(1)
+NOTICE: f_print_x(1)
+NOTICE: f_get_x(2)
+NOTICE: f_print_x(2)
+ROLLBACK;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql b/src/pl/plpgsql/src/sql/plpgsql_call.sql
index 869d021a075..4cbda0382e9 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_call.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql
@@ -522,3 +522,53 @@ CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 2 $$ LANGUAGE sql;
CALL outer_p(42);
SELECT outer_f(42);
+
+-- Check that stable functions in CALL see the correct snapshot
+
+CREATE TABLE t_test (x int);
+INSERT INTO t_test VALUES (0);
+
+CREATE FUNCTION f_get_x () RETURNS int
+AS $$
+DECLARE l_result int;
+BEGIN
+ SELECT x INTO l_result FROM t_test;
+ RETURN l_result;
+END
+$$ LANGUAGE plpgsql STABLE;
+
+CREATE PROCEDURE f_print_x (x int)
+AS $$
+BEGIN
+ RAISE NOTICE 'f_print_x(%)', x;
+END
+$$ LANGUAGE plpgsql;
+
+-- test in non-atomic context
+DO $$
+BEGIN
+ UPDATE t_test SET x = x + 1;
+ RAISE NOTICE 'f_get_x(%)', f_get_x();
+ CALL f_print_x(f_get_x());
+ UPDATE t_test SET x = x + 1;
+ RAISE NOTICE 'f_get_x(%)', f_get_x();
+ CALL f_print_x(f_get_x());
+ ROLLBACK;
+END
+$$;
+
+-- test in atomic context
+BEGIN;
+
+DO $$
+BEGIN
+ UPDATE t_test SET x = x + 1;
+ RAISE NOTICE 'f_get_x(%)', f_get_x();
+ CALL f_print_x(f_get_x());
+ UPDATE t_test SET x = x + 1;
+ RAISE NOTICE 'f_get_x(%)', f_get_x();
+ CALL f_print_x(f_get_x());
+END
+$$;
+
+ROLLBACK;