]> granicus.if.org Git - postgresql/commitdiff
PL/pgSQL: Nested CALL with transactions
authorPeter Eisentraut <peter_e@gmx.net>
Sat, 24 Mar 2018 14:05:06 +0000 (10:05 -0400)
committerPeter Eisentraut <peter_e@gmx.net>
Wed, 28 Mar 2018 17:31:27 +0000 (13:31 -0400)
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>
13 files changed:
doc/src/sgml/plpgsql.sgml
src/backend/executor/spi.c
src/backend/tcop/utility.c
src/include/executor/spi_priv.h
src/include/tcop/utility.h
src/pl/plpgsql/src/expected/plpgsql_transaction.out
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/pl_gram.y
src/pl/plpgsql/src/pl_handler.c
src/pl/plpgsql/src/pl_scanner.c
src/pl/plpgsql/src/plpgsql.h
src/pl/plpgsql/src/sql/plpgsql_transaction.sql

index 7ed926fd51e65b481bb567fd1fb7fe1e75b8d76b..b63b8496c7b615c9ed69564f9f11c15fd3022cd6 100644 (file)
@@ -3463,9 +3463,9 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
    <title>Transaction Management</title>
 
    <para>
-    In procedures invoked by the <command>CALL</command> command from the top
-    level as well as in anonymous code blocks (<command>DO</command> command)
-    called from the top level, it is possible to end transactions using the
+    In procedures invoked by the <command>CALL</command> command
+    as well as in anonymous code blocks (<command>DO</command> command),
+    it is possible to end transactions using the
     commands <command>COMMIT</command> and <command>ROLLBACK</command>.  A new
     transaction is started automatically after a transaction is ended using
     these commands, so there is no separate <command>START
@@ -3495,6 +3495,20 @@ CALL transaction_test1();
 </programlisting>
    </para>
 
+   <para>
+    Transaction control is only possible in <command>CALL</command> or
+    <command>DO</command> invocations from the top level or nested
+    <command>CALL</command> or <command>DO</command> invocations without any
+    other intervening command.  For example, if the call stack is
+    <command>CALL proc1()</command> &rarr; <command>CALL proc2()</command>
+    &rarr; <command>CALL proc3()</command>, then the second and third
+    procedures can perform transaction control actions.  But if the call stack
+    is <command>CALL proc1()</command> &rarr; <command>SELECT
+    func2()</command> &rarr; <command>CALL proc3()</command>, then the last
+    procedure cannot do transaction control, because of the
+    <command>SELECT</command> in between.
+   </para>
+
    <para>
     A transaction cannot be ended inside a loop over a query result, nor
     inside a block with exception handlers.
index 9fc4431b80c317aacbc2ede2f9639488eee8912d..08f6f67a15c89f822cbfdcb22a85f63735765f70 100644 (file)
@@ -2041,8 +2041,11 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
         *
         * In the first two cases, we can just push the snap onto the stack once
         * for the whole plan list.
+        *
+        * But if the plan has no_snapshots set to true, then don't manage
+        * snapshots at all.  The caller should then take care of that.
         */
-       if (snapshot != InvalidSnapshot)
+       if (snapshot != InvalidSnapshot && !plan->no_snapshots)
        {
                if (read_only)
                {
@@ -2121,7 +2124,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                 * In the default non-read-only case, get a new snapshot, replacing
                 * any that we pushed in a previous cycle.
                 */
-               if (snapshot == InvalidSnapshot && !read_only)
+               if (snapshot == InvalidSnapshot && !read_only && !plan->no_snapshots)
                {
                        if (pushed_active_snap)
                                PopActiveSnapshot();
@@ -2172,7 +2175,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                         * If not read-only mode, advance the command counter before each
                         * command and update the snapshot.
                         */
-                       if (!read_only)
+                       if (!read_only && !plan->no_snapshots)
                        {
                                CommandCounterIncrement();
                                UpdateActiveSnapshotCommandId();
@@ -2203,10 +2206,23 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                        else
                        {
                                char            completionTag[COMPLETION_TAG_BUFSIZE];
+                               ProcessUtilityContext context;
+
+                               /*
+                                * If the SPI context is atomic, or we are asked to manage
+                                * snapshots, then we are in an atomic execution context.
+                                * Conversely, to propagate a nonatomic execution context, the
+                                * caller must be in a nonatomic SPI context and manage
+                                * snapshots itself.
+                                */
+                               if (_SPI_current->atomic || !plan->no_snapshots)
+                                       context = PROCESS_UTILITY_QUERY;
+                               else
+                                       context = PROCESS_UTILITY_QUERY_NONATOMIC;
 
                                ProcessUtility(stmt,
                                                           plansource->query_string,
-                                                          PROCESS_UTILITY_QUERY,
+                                                          context,
                                                           paramLI,
                                                           _SPI_current->queryEnv,
                                                           dest,
@@ -2638,11 +2654,8 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan)
        oldcxt = MemoryContextSwitchTo(plancxt);
 
        /* Copy the SPI_plan struct and subsidiary data into the new context */
-       newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
+       newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
        newplan->magic = _SPI_PLAN_MAGIC;
-       newplan->saved = false;
-       newplan->oneshot = false;
-       newplan->plancache_list = NIL;
        newplan->plancxt = plancxt;
        newplan->cursor_options = plan->cursor_options;
        newplan->nargs = plan->nargs;
@@ -2705,11 +2718,8 @@ _SPI_save_plan(SPIPlanPtr plan)
        oldcxt = MemoryContextSwitchTo(plancxt);
 
        /* Copy the SPI plan into its own context */
-       newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan));
+       newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
        newplan->magic = _SPI_PLAN_MAGIC;
-       newplan->saved = false;
-       newplan->oneshot = false;
-       newplan->plancache_list = NIL;
        newplan->plancxt = plancxt;
        newplan->cursor_options = plan->cursor_options;
        newplan->nargs = plan->nargs;
index e144583bd1f1285420ba864f4ae82823ddc7282a..d355bef6060092fc30b31c7f8f2858fa306f0157 100644 (file)
@@ -382,7 +382,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 {
        Node       *parsetree = pstmt->utilityStmt;
        bool            isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
-       bool            isAtomicContext = (context != PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock());
+       bool            isAtomicContext = (!(context == PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC) || IsTransactionBlock());
        ParseState *pstate;
 
        check_xact_readonly(parsetree);
index 263c8f145397f9d3cc5be456ae6b7e5f5abde7e6..376fae0bbc4a0f9dc63a8c8ad7d286d4fb419a61 100644 (file)
@@ -86,6 +86,7 @@ typedef struct _SPI_plan
        int                     magic;                  /* should equal _SPI_PLAN_MAGIC */
        bool            saved;                  /* saved or unsaved plan? */
        bool            oneshot;                /* one-shot plan? */
+       bool            no_snapshots;   /* let the caller handle the snapshots */
        List       *plancache_list; /* one CachedPlanSource per parsetree */
        MemoryContext plancxt;          /* Context containing _SPI_plan and data */
        int                     cursor_options; /* Cursor options used for planning */
index 55500557109d2cee54e6281c03947e5cad8f84f3..880d19311a640c4ea33f2980535255a0d9a29467 100644 (file)
@@ -20,6 +20,7 @@ typedef enum
 {
        PROCESS_UTILITY_TOPLEVEL,       /* toplevel interactive command */
        PROCESS_UTILITY_QUERY,          /* a complete query, but not toplevel */
+       PROCESS_UTILITY_QUERY_NONATOMIC, /* a complete query, nonatomic execution context */
        PROCESS_UTILITY_SUBCOMMAND      /* a portion of a query */
 } ProcessUtilityContext;
 
index ce6648713702fd96a7874340c9717a1af8c0da82..b601f6aef651bfd5937ced4eb6d42a9370c3746e 100644 (file)
@@ -1,10 +1,10 @@
 CREATE TABLE test1 (a int, b text);
-CREATE PROCEDURE transaction_test1()
+CREATE PROCEDURE transaction_test1(x int, y text)
 LANGUAGE plpgsql
 AS $$
 BEGIN
-    FOR i IN 0..9 LOOP
-        INSERT INTO test1 (a) VALUES (i);
+    FOR i IN 0..x LOOP
+        INSERT INTO test1 (a, b) VALUES (i, y);
         IF i % 2 = 0 THEN
             COMMIT;
         ELSE
@@ -13,15 +13,15 @@ BEGIN
     END LOOP;
 END
 $$;
-CALL transaction_test1();
+CALL transaction_test1(9, 'foo');
 SELECT * FROM test1;
- a | b 
----+---
- 0 | 
- 2 | 
- 4 | 
- 6 | 
- 8 | 
+ a |  b  
+---+-----
+ 0 | foo
+ 2 | foo
+ 4 | foo
+ 6 | foo
+ 8 | foo
 (5 rows)
 
 TRUNCATE test1;
@@ -51,9 +51,9 @@ SELECT * FROM test1;
 
 -- transaction commands not allowed when called in transaction block
 START TRANSACTION;
-CALL transaction_test1();
+CALL transaction_test1(9, 'error');
 ERROR:  invalid transaction termination
-CONTEXT:  PL/pgSQL function transaction_test1() line 6 at COMMIT
+CONTEXT:  PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
 COMMIT;
 START TRANSACTION;
 DO LANGUAGE plpgsql $$ BEGIN COMMIT; END $$;
@@ -90,14 +90,14 @@ CREATE FUNCTION transaction_test3() RETURNS int
 LANGUAGE plpgsql
 AS $$
 BEGIN
-    CALL transaction_test1();
+    CALL transaction_test1(9, 'error');
     RETURN 1;
 END;
 $$;
 SELECT transaction_test3();
 ERROR:  invalid transaction termination
-CONTEXT:  PL/pgSQL function transaction_test1() line 6 at COMMIT
-SQL statement "CALL transaction_test1()"
+CONTEXT:  PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
+SQL statement "CALL transaction_test1(9, 'error')"
 PL/pgSQL function transaction_test3() line 3 at CALL
 SELECT * FROM test1;
  a | b 
@@ -130,6 +130,57 @@ $$;
 CALL transaction_test5();
 ERROR:  invalid transaction termination
 CONTEXT:  PL/pgSQL function transaction_test5() line 3 at COMMIT
+TRUNCATE test1;
+-- nested procedure calls
+CREATE PROCEDURE transaction_test6(c text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    CALL transaction_test1(9, c);
+END;
+$$;
+CALL transaction_test6('bar');
+SELECT * FROM test1;
+ a |  b  
+---+-----
+ 0 | bar
+ 2 | bar
+ 4 | bar
+ 6 | bar
+ 8 | bar
+(5 rows)
+
+TRUNCATE test1;
+CREATE PROCEDURE transaction_test7()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    DO 'BEGIN CALL transaction_test1(9, $x$baz$x$); END;';
+END;
+$$;
+CALL transaction_test7();
+SELECT * FROM test1;
+ a |  b  
+---+-----
+ 0 | baz
+ 2 | baz
+ 4 | baz
+ 6 | baz
+ 8 | baz
+(5 rows)
+
+CREATE PROCEDURE transaction_test8()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    EXECUTE 'CALL transaction_test1(10, $x$baz$x$)';
+END;
+$$;
+CALL transaction_test8();
+ERROR:  invalid transaction termination
+CONTEXT:  PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
+SQL statement "CALL transaction_test1(10, $x$baz$x$)"
+PL/pgSQL function transaction_test8() line 3 at EXECUTE
 -- commit inside cursor loop
 CREATE TABLE test2 (x int);
 INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
index 38ea7a091f7f37e77f055fd7382e5d60ae1045d2..fc0f0f0480a94d255925097331c9d5c53cf6dd73 100644 (file)
@@ -22,6 +22,7 @@
 #include "access/tupconvert.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/defrem.h"
 #include "executor/execExpr.h"
 #include "executor/spi.h"
 #include "executor/spi_priv.h"
@@ -33,6 +34,7 @@
 #include "parser/scansup.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
+#include "tcop/utility.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
@@ -311,7 +313,8 @@ static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
 static void exec_eval_cleanup(PLpgSQL_execstate *estate);
 
 static void exec_prepare_plan(PLpgSQL_execstate *estate,
-                                 PLpgSQL_expr *expr, int cursorOptions);
+                                                         PLpgSQL_expr *expr, int cursorOptions,
+                                                         bool keepplan);
 static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
 static void exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan);
 static void exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno);
@@ -440,7 +443,7 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
  */
 Datum
 plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
-                                         EState *simple_eval_estate)
+                                         EState *simple_eval_estate, bool atomic)
 {
        PLpgSQL_execstate estate;
        ErrorContextCallback plerrcontext;
@@ -452,6 +455,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
         */
        plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo,
                                                 simple_eval_estate);
+       estate.atomic = atomic;
 
        /*
         * Setup error traceback support for ereport()
@@ -2057,20 +2061,48 @@ exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
 {
        PLpgSQL_expr *expr = stmt->expr;
        ParamListInfo paramLI;
+       LocalTransactionId before_lxid;
+       LocalTransactionId after_lxid;
        int                     rc;
 
        if (expr->plan == NULL)
-               exec_prepare_plan(estate, expr, 0);
+       {
+               /*
+                * Don't save the plan if not in atomic context.  Otherwise,
+                * transaction ends would cause warnings about plan leaks.
+                */
+               exec_prepare_plan(estate, expr, 0, estate->atomic);
+
+               /*
+                * The procedure call could end transactions, which would upset the
+                * snapshot management in SPI_execute*, so don't let it do it.
+                */
+               expr->plan->no_snapshots = true;
+       }
 
        paramLI = setup_param_list(estate, expr);
 
+       before_lxid = MyProc->lxid;
+
        rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
                                                                                 estate->readonly_func, 0);
 
+       after_lxid = MyProc->lxid;
+
        if (rc < 0)
                elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
                         expr->query, SPI_result_code_string(rc));
 
+       /*
+        * If we are in a new transaction after the call, we need to reset some
+        * internal state.
+        */
+       if (before_lxid != after_lxid)
+       {
+               estate->simple_eval_estate = NULL;
+               plpgsql_create_econtext(estate);
+       }
+
        if (SPI_processed == 1)
        {
                SPITupleTable *tuptab = SPI_tuptable;
@@ -2705,7 +2737,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
        Assert(query);
 
        if (query->plan == NULL)
-               exec_prepare_plan(estate, query, curvar->cursor_options);
+               exec_prepare_plan(estate, query, curvar->cursor_options, true);
 
        /*
         * Set up ParamListInfo for this query
@@ -3719,6 +3751,7 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
        estate->retisset = func->fn_retset;
 
        estate->readonly_func = func->fn_readonly;
+       estate->atomic = true;
 
        estate->exitlabel = NULL;
        estate->cur_error = NULL;
@@ -3863,7 +3896,8 @@ exec_eval_cleanup(PLpgSQL_execstate *estate)
  */
 static void
 exec_prepare_plan(PLpgSQL_execstate *estate,
-                                 PLpgSQL_expr *expr, int cursorOptions)
+                                 PLpgSQL_expr *expr, int cursorOptions,
+                                 bool keepplan)
 {
        SPIPlanPtr      plan;
 
@@ -3899,7 +3933,8 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
                                         expr->query, SPI_result_code_string(SPI_result));
                }
        }
-       SPI_keepplan(plan);
+       if (keepplan)
+               SPI_keepplan(plan);
        expr->plan = plan;
 
        /* Check to see if it's a simple expression */
@@ -3938,7 +3973,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
        {
                ListCell   *l;
 
-               exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
+               exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
                stmt->mod_stmt = false;
                foreach(l, SPI_plan_get_plan_sources(expr->plan))
                {
@@ -4396,7 +4431,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
                 */
                query = stmt->query;
                if (query->plan == NULL)
-                       exec_prepare_plan(estate, query, stmt->cursor_options);
+                       exec_prepare_plan(estate, query, stmt->cursor_options, true);
        }
        else if (stmt->dynquery != NULL)
        {
@@ -4467,7 +4502,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
 
                query = curvar->cursor_explicit_expr;
                if (query->plan == NULL)
-                       exec_prepare_plan(estate, query, curvar->cursor_options);
+                       exec_prepare_plan(estate, query, curvar->cursor_options, true);
        }
 
        /*
@@ -4707,7 +4742,7 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
         */
        if (expr->plan == NULL)
        {
-               exec_prepare_plan(estate, expr, 0);
+               exec_prepare_plan(estate, expr, 0, true);
                if (target->dtype == PLPGSQL_DTYPE_VAR)
                        exec_check_rw_parameter(expr, target->dno);
        }
@@ -5566,7 +5601,7 @@ exec_eval_expr(PLpgSQL_execstate *estate,
         * If first time through, create a plan for this expression.
         */
        if (expr->plan == NULL)
-               exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
+               exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK, true);
 
        /*
         * If this is a simple expression, bypass SPI and use the executor
@@ -5652,7 +5687,7 @@ exec_run_select(PLpgSQL_execstate *estate,
         */
        if (expr->plan == NULL)
                exec_prepare_plan(estate, expr,
-                                                 portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0);
+                                                 portalP == NULL ? CURSOR_OPT_PARALLEL_OK : 0, true);
 
        /*
         * Set up ParamListInfo to pass to executor
@@ -7834,11 +7869,13 @@ plpgsql_create_econtext(PLpgSQL_execstate *estate)
        {
                MemoryContext oldcontext;
 
-               Assert(shared_simple_eval_estate == NULL);
-               oldcontext = MemoryContextSwitchTo(TopTransactionContext);
-               shared_simple_eval_estate = CreateExecutorState();
+               if (shared_simple_eval_estate == NULL)
+               {
+                       oldcontext = MemoryContextSwitchTo(TopTransactionContext);
+                       shared_simple_eval_estate = CreateExecutorState();
+                       MemoryContextSwitchTo(oldcontext);
+               }
                estate->simple_eval_estate = shared_simple_eval_estate;
-               MemoryContextSwitchTo(oldcontext);
        }
 
        /*
index 39d6a546632f9b4ff48347c859bfc8988b40328a..fc96fb5f4d10a7430e295ebdd813fa70d3370bc3 100644 (file)
@@ -285,7 +285,7 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
                case PLPGSQL_STMT_PERFORM:
                        return "PERFORM";
                case PLPGSQL_STMT_CALL:
-                       return "CALL";
+                       return ((PLpgSQL_stmt_call *) stmt)->is_call ? "CALL" : "DO";
                case PLPGSQL_STMT_COMMIT:
                        return "COMMIT";
                case PLPGSQL_STMT_ROLLBACK:
@@ -1295,7 +1295,7 @@ static void
 dump_call(PLpgSQL_stmt_call *stmt)
 {
        dump_ind();
-       printf("CALL expr = ");
+       printf("%s expr = ", stmt->is_call ? "CALL" : "DO");
        dump_expr(stmt->expr);
        printf("\n");
 }
index 4c80936678f9378d637d6005e26beaa2ffc23de9..b8562ca8b4ea529874ce2237b1d3aae36d865f42 100644 (file)
@@ -276,6 +276,7 @@ static      void                    check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>       K_DEFAULT
 %token <keyword>       K_DETAIL
 %token <keyword>       K_DIAGNOSTICS
+%token <keyword>       K_DO
 %token <keyword>       K_DUMP
 %token <keyword>       K_ELSE
 %token <keyword>       K_ELSIF
@@ -914,8 +915,24 @@ stmt_call          : K_CALL
                                                new->cmd_type = PLPGSQL_STMT_CALL;
                                                new->lineno = plpgsql_location_to_lineno(@1);
                                                new->expr = read_sql_stmt("CALL ");
+                                               new->is_call = true;
 
                                                $$ = (PLpgSQL_stmt *)new;
+
+                                       }
+                               | K_DO
+                                       {
+                                               /* use the same structures as for CALL, for simplicity */
+                                               PLpgSQL_stmt_call *new;
+
+                                               new = palloc0(sizeof(PLpgSQL_stmt_call));
+                                               new->cmd_type = PLPGSQL_STMT_CALL;
+                                               new->lineno = plpgsql_location_to_lineno(@1);
+                                               new->expr = read_sql_stmt("DO ");
+                                               new->is_call = false;
+
+                                               $$ = (PLpgSQL_stmt *)new;
+
                                        }
                                ;
 
@@ -2434,6 +2451,7 @@ unreserved_keyword        :
                                | K_DEFAULT
                                | K_DETAIL
                                | K_DIAGNOSTICS
+                               | K_DO
                                | K_DUMP
                                | K_ELSIF
                                | K_ERRCODE
index f38ef0407748b10d305142ac154dcceb3359f632..61452d9f7fd6ddda7d7f1b26aebd7fa86c8241d7 100644 (file)
@@ -260,7 +260,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
                        retval = (Datum) 0;
                }
                else
-                       retval = plpgsql_exec_function(func, fcinfo, NULL);
+                       retval = plpgsql_exec_function(func, fcinfo, NULL, !nonatomic);
        }
        PG_CATCH();
        {
@@ -332,7 +332,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS)
        /* And run the function */
        PG_TRY();
        {
-               retval = plpgsql_exec_function(func, &fake_fcinfo, simple_eval_estate);
+               retval = plpgsql_exec_function(func, &fake_fcinfo, simple_eval_estate, codeblock->atomic);
        }
        PG_CATCH();
        {
index 65774f9902842b14bb414e5ad9248dd0d04ca846..08614a89a8773877310c93cb48f5d4aaab088204 100644 (file)
@@ -119,6 +119,7 @@ static const ScanKeyword unreserved_keywords[] = {
        PG_KEYWORD("default", K_DEFAULT, UNRESERVED_KEYWORD)
        PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD)
        PG_KEYWORD("diagnostics", K_DIAGNOSTICS, UNRESERVED_KEYWORD)
+       PG_KEYWORD("do", K_DO, UNRESERVED_KEYWORD)
        PG_KEYWORD("dump", K_DUMP, UNRESERVED_KEYWORD)
        PG_KEYWORD("elseif", K_ELSIF, UNRESERVED_KEYWORD)
        PG_KEYWORD("elsif", K_ELSIF, UNRESERVED_KEYWORD)
index f7619a63f9bc0f3345802244dda0381d62806d15..dc90fe532fd101f7e36149170a272f9bfa97e34e 100644 (file)
@@ -517,6 +517,7 @@ typedef struct PLpgSQL_stmt_call
        PLpgSQL_stmt_type cmd_type;
        int                     lineno;
        PLpgSQL_expr *expr;
+       bool            is_call;
        PLpgSQL_variable *target;
 } PLpgSQL_stmt_call;
 
@@ -979,6 +980,7 @@ typedef struct PLpgSQL_execstate
        bool            retisset;
 
        bool            readonly_func;
+       bool            atomic;
 
        char       *exitlabel;          /* the "target" label of the current EXIT or
                                                                 * CONTINUE stmt, if any */
@@ -1194,7 +1196,8 @@ extern void _PG_init(void);
  */
 extern Datum plpgsql_exec_function(PLpgSQL_function *func,
                                          FunctionCallInfo fcinfo,
-                                         EState *simple_eval_estate);
+                                         EState *simple_eval_estate,
+                                         bool atomic);
 extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
                                         TriggerData *trigdata);
 extern void plpgsql_exec_event_trigger(PLpgSQL_function *func,
index 02ee7350795e6af354846ced827ba648dccaa756..a718f50f89fe45d1c3b1f21fb7660e778b8b0681 100644 (file)
@@ -1,12 +1,12 @@
 CREATE TABLE test1 (a int, b text);
 
 
-CREATE PROCEDURE transaction_test1()
+CREATE PROCEDURE transaction_test1(x int, y text)
 LANGUAGE plpgsql
 AS $$
 BEGIN
-    FOR i IN 0..9 LOOP
-        INSERT INTO test1 (a) VALUES (i);
+    FOR i IN 0..x LOOP
+        INSERT INTO test1 (a, b) VALUES (i, y);
         IF i % 2 = 0 THEN
             COMMIT;
         ELSE
@@ -16,7 +16,7 @@ BEGIN
 END
 $$;
 
-CALL transaction_test1();
+CALL transaction_test1(9, 'foo');
 
 SELECT * FROM test1;
 
@@ -43,7 +43,7 @@ SELECT * FROM test1;
 
 -- transaction commands not allowed when called in transaction block
 START TRANSACTION;
-CALL transaction_test1();
+CALL transaction_test1(9, 'error');
 COMMIT;
 
 START TRANSACTION;
@@ -80,7 +80,7 @@ CREATE FUNCTION transaction_test3() RETURNS int
 LANGUAGE plpgsql
 AS $$
 BEGIN
-    CALL transaction_test1();
+    CALL transaction_test1(9, 'error');
     RETURN 1;
 END;
 $$;
@@ -116,6 +116,46 @@ $$;
 CALL transaction_test5();
 
 
+TRUNCATE test1;
+
+-- nested procedure calls
+CREATE PROCEDURE transaction_test6(c text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    CALL transaction_test1(9, c);
+END;
+$$;
+
+CALL transaction_test6('bar');
+
+SELECT * FROM test1;
+
+TRUNCATE test1;
+
+CREATE PROCEDURE transaction_test7()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    DO 'BEGIN CALL transaction_test1(9, $x$baz$x$); END;';
+END;
+$$;
+
+CALL transaction_test7();
+
+SELECT * FROM test1;
+
+CREATE PROCEDURE transaction_test8()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+    EXECUTE 'CALL transaction_test1(10, $x$baz$x$)';
+END;
+$$;
+
+CALL transaction_test8();
+
+
 -- commit inside cursor loop
 CREATE TABLE test2 (x int);
 INSERT INTO test2 VALUES (0), (1), (2), (3), (4);