diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index 5f466d2f0b..2af4eaf746 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -733,7 +733,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) @@ -1874,7 +1876,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 9d214f92d1..47a6b5f599 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -2406,6 +2406,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; @@ -2413,6 +2414,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() */ @@ -2432,12 +2439,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. @@ -2447,6 +2459,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) { @@ -2592,7 +2605,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(); @@ -2692,14 +2705,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 5a99ac3bc7..e1b377c135 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_call.out +++ b/src/pl/plpgsql/src/expected/plpgsql_call.out @@ -552,3 +552,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 27624f1386..c1e301e04a 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_call.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql @@ -510,3 +510,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;