So far, a nested CALL or DO in PL/pgSQL would not establish a context
where transaction control statements were allowed.  This patch 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.

This requires a new flag in SPI to run SPI_execute* without snapshot
management.  This currently poked directly into the plan struct,
requiring access to spi_priv.h.  This could use some refinement ideas.

Other PLs are currently not supported, but they could be extended in the
future using the principles being worked out here.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From fa13c429d1b401643af4fa87f553784edaa1515a Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@gmx.net>
Date: Wed, 28 Feb 2018 14:50:11 -0500
Subject: [PATCH v1] PL/pgSQL: Nested CALL with transactions

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.

XXX This requires a new flag in SPI to run SPI_execute* without snapshot
management.  This currently poked directly into the plan struct,
requiring access to spi_priv.h.  This could use some refinement ideas.
---
 doc/src/sgml/plpgsql.sgml                          | 13 +++-
 src/backend/executor/spi.c                         | 12 +--
 src/backend/tcop/utility.c                         |  4 +-
 src/include/executor/spi_priv.h                    |  1 +
 src/include/tcop/utility.h                         |  1 +
 .../plpgsql/src/expected/plpgsql_transaction.out   | 73 +++++++++++++-----
 src/pl/plpgsql/src/pl_exec.c                       | 86 ++++++++++++++++++++--
 src/pl/plpgsql/src/pl_funcs.c                      | 25 +++++++
 src/pl/plpgsql/src/pl_gram.y                       | 37 +++++++++-
 src/pl/plpgsql/src/pl_handler.c                    |  4 +-
 src/pl/plpgsql/src/pl_scanner.c                    |  2 +
 src/pl/plpgsql/src/plpgsql.h                       | 16 +++-
 src/pl/plpgsql/src/sql/plpgsql_transaction.sql     | 42 +++++++++--
 13 files changed, 273 insertions(+), 43 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index c1e3c6a19d..04aa1759cd 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -3447,9 +3447,9 @@ <title>Looping Through a Cursor's Result</title>
    <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
@@ -3479,6 +3479,13 @@ <title>Transaction Management</title>
 </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.
+   </para>
+
    <para>
     A transaction cannot be ended inside a loop over a query result, nor
     inside a block with exception handlers.
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 9fc4431b80..174ec6cd5a 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2042,7 +2042,7 @@ _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.
         */
-       if (snapshot != InvalidSnapshot)
+       if (snapshot != InvalidSnapshot && !plan->no_snapshots)
        {
                if (read_only)
                {
@@ -2121,7 +2121,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 +2172,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();
@@ -2206,7 +2206,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 
                                ProcessUtility(stmt,
                                                           
plansource->query_string,
-                                                          
PROCESS_UTILITY_QUERY,
+                                                          _SPI_current->atomic 
? PROCESS_UTILITY_QUERY : PROCESS_UTILITY_QUERY_NONATOMIC,
                                                           paramLI,
                                                           
_SPI_current->queryEnv,
                                                           dest,
@@ -2638,7 +2638,7 @@ _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;
@@ -2705,7 +2705,7 @@ _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;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f78efdf359..a282ec813b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -531,7 +531,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
                case T_DoStmt:
                        ExecuteDoStmt((DoStmt *) parsetree,
-                                                 (context != 
PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()));
+                                                 (!(context == 
PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC) || 
IsTransactionBlock()));
                        break;
 
                case T_CreateTableSpaceStmt:
@@ -661,7 +661,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
                case T_CallStmt:
                        ExecuteCallStmt(castNode(CallStmt, parsetree), params,
-                                                       (context != 
PROCESS_UTILITY_TOPLEVEL || IsTransactionBlock()));
+                                                       (!(context == 
PROCESS_UTILITY_TOPLEVEL || context == PROCESS_UTILITY_QUERY_NONATOMIC) || 
IsTransactionBlock()));
                        break;
 
                case T_ClusterStmt:
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
index 263c8f1453..376fae0bbc 100644
--- a/src/include/executor/spi_priv.h
+++ b/src/include/executor/spi_priv.h
@@ -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 */
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 5550055710..880d19311a 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -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;
 
diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out 
b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
index 8ec22c646c..583eac46a9 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
@@ -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,15 +90,15 @@ 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()"
-PL/pgSQL function transaction_test3() line 3 at SQL statement
+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,45 @@ $$;
 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)
+
 -- commit inside cursor loop
 CREATE TABLE test2 (x int);
 INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 4ff87e0879..8aebd62162 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -22,8 +22,10 @@
 #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"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
@@ -32,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"
@@ -253,6 +256,8 @@ static int exec_stmt_assign(PLpgSQL_execstate *estate,
                                 PLpgSQL_stmt_assign *stmt);
 static int exec_stmt_perform(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_perform *stmt);
+static int exec_stmt_call(PLpgSQL_execstate *estate,
+                                 PLpgSQL_stmt_call *stmt);
 static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
                                  PLpgSQL_stmt_getdiag *stmt);
 static int exec_stmt_if(PLpgSQL_execstate *estate,
@@ -436,7 +441,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;
@@ -448,6 +453,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()
@@ -1905,6 +1911,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
                        rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) 
stmt);
                        break;
 
+               case PLPGSQL_STMT_CALL:
+                       rc = exec_stmt_call(estate, (PLpgSQL_stmt_call *) stmt);
+                       break;
+
                case PLPGSQL_STMT_GETDIAG:
                        rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) 
stmt);
                        break;
@@ -2045,6 +2055,69 @@ exec_stmt_perform(PLpgSQL_execstate *estate, 
PLpgSQL_stmt_perform *stmt)
        return PLPGSQL_RC_OK;
 }
 
+/*
+ * exec_stmt_call
+ */
+static int
+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)
+       {
+               SPIPlanPtr      plan;
+
+               expr->func = estate->func;
+
+               plan = SPI_prepare_params(expr->query,
+                                                                 
(ParserSetupHook) plpgsql_parser_setup,
+                                                                 (void *) expr,
+                                                                 0);
+
+               if (plan == NULL)
+                       elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
+                                expr->query, 
SPI_result_code_string(SPI_result));
+
+               /*
+                * The procedure call could end transactions, which would upset 
the
+                * snapshot management in SPI_execute*, so don't let it do it.
+                */
+               plan->no_snapshots = true;
+
+               expr->plan = plan;
+               expr->rwparam = -1;
+       }
+
+       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);
+       }
+
+       return PLPGSQL_RC_OK;
+}
+
 /* ----------
  * exec_stmt_getdiag                                   Put internal PG 
information into
  *                                                                             
specified variables.
@@ -3599,6 +3672,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;
@@ -7714,11 +7788,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);
        }
 
        /*
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index b986fc39b3..fc96fb5f4d 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -284,6 +284,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
                        return "CLOSE";
                case PLPGSQL_STMT_PERFORM:
                        return "PERFORM";
+               case PLPGSQL_STMT_CALL:
+                       return ((PLpgSQL_stmt_call *) stmt)->is_call ? "CALL" : 
"DO";
                case PLPGSQL_STMT_COMMIT:
                        return "COMMIT";
                case PLPGSQL_STMT_ROLLBACK:
@@ -367,6 +369,7 @@ static void free_open(PLpgSQL_stmt_open *stmt);
 static void free_fetch(PLpgSQL_stmt_fetch *stmt);
 static void free_close(PLpgSQL_stmt_close *stmt);
 static void free_perform(PLpgSQL_stmt_perform *stmt);
+static void free_call(PLpgSQL_stmt_call *stmt);
 static void free_commit(PLpgSQL_stmt_commit *stmt);
 static void free_rollback(PLpgSQL_stmt_rollback *stmt);
 static void free_expr(PLpgSQL_expr *expr);
@@ -449,6 +452,9 @@ free_stmt(PLpgSQL_stmt *stmt)
                case PLPGSQL_STMT_PERFORM:
                        free_perform((PLpgSQL_stmt_perform *) stmt);
                        break;
+               case PLPGSQL_STMT_CALL:
+                       free_call((PLpgSQL_stmt_call *) stmt);
+                       break;
                case PLPGSQL_STMT_COMMIT:
                        free_commit((PLpgSQL_stmt_commit *) stmt);
                        break;
@@ -602,6 +608,12 @@ free_perform(PLpgSQL_stmt_perform *stmt)
        free_expr(stmt->expr);
 }
 
+static void
+free_call(PLpgSQL_stmt_call *stmt)
+{
+       free_expr(stmt->expr);
+}
+
 static void
 free_commit(PLpgSQL_stmt_commit *stmt)
 {
@@ -805,6 +817,7 @@ static void dump_fetch(PLpgSQL_stmt_fetch *stmt);
 static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
 static void dump_close(PLpgSQL_stmt_close *stmt);
 static void dump_perform(PLpgSQL_stmt_perform *stmt);
+static void dump_call(PLpgSQL_stmt_call *stmt);
 static void dump_commit(PLpgSQL_stmt_commit *stmt);
 static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
 static void dump_expr(PLpgSQL_expr *expr);
@@ -897,6 +910,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
                case PLPGSQL_STMT_PERFORM:
                        dump_perform((PLpgSQL_stmt_perform *) stmt);
                        break;
+               case PLPGSQL_STMT_CALL:
+                       dump_call((PLpgSQL_stmt_call *) stmt);
+                       break;
                case PLPGSQL_STMT_COMMIT:
                        dump_commit((PLpgSQL_stmt_commit *) stmt);
                        break;
@@ -1275,6 +1291,15 @@ dump_perform(PLpgSQL_stmt_perform *stmt)
        printf("\n");
 }
 
+static void
+dump_call(PLpgSQL_stmt_call *stmt)
+{
+       dump_ind();
+       printf("%s expr = ", stmt->is_call ? "CALL" : "DO");
+       dump_expr(stmt->expr);
+       printf("\n");
+}
+
 static void
 dump_commit(PLpgSQL_stmt_commit *stmt)
 {
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 688fbd6531..44d5b83e18 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -196,7 +196,7 @@ static      void                    
check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %type <stmt>   proc_stmt pl_block
 %type <stmt>   stmt_assign stmt_if stmt_loop stmt_while stmt_exit
 %type <stmt>   stmt_return stmt_raise stmt_assert stmt_execsql
-%type <stmt>   stmt_dynexecute stmt_for stmt_perform stmt_getdiag
+%type <stmt>   stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
 %type <stmt>   stmt_open stmt_fetch stmt_move stmt_close stmt_null
 %type <stmt>   stmt_commit stmt_rollback
 %type <stmt>   stmt_case stmt_foreach_a
@@ -256,6 +256,7 @@ static      void                    
check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>       K_BACKWARD
 %token <keyword>       K_BEGIN
 %token <keyword>       K_BY
+%token <keyword>       K_CALL
 %token <keyword>       K_CASE
 %token <keyword>       K_CLOSE
 %token <keyword>       K_COLLATE
@@ -274,6 +275,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
@@ -871,6 +873,8 @@ proc_stmt           : pl_block ';'
                                                { $$ = $1; }
                                | stmt_perform
                                                { $$ = $1; }
+                               | stmt_call
+                                               { $$ = $1; }
                                | stmt_getdiag
                                                { $$ = $1; }
                                | stmt_open
@@ -902,6 +906,35 @@ stmt_perform       : K_PERFORM expr_until_semi
                                        }
                                ;
 
+stmt_call              : K_CALL
+                                       {
+                                               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("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;
+
+                                       }
+                               ;
+
 stmt_assign            : assign_var assign_operator expr_until_semi
                                        {
                                                PLpgSQL_stmt_assign *new;
@@ -2400,6 +2433,7 @@ unreserved_keyword        :
                                | K_ARRAY
                                | K_ASSERT
                                | K_BACKWARD
+                               | K_CALL
                                | K_CLOSE
                                | K_COLLATE
                                | K_COLUMN
@@ -2416,6 +2450,7 @@ unreserved_keyword        :
                                | K_DEFAULT
                                | K_DETAIL
                                | K_DIAGNOSTICS
+                               | K_DO
                                | K_DUMP
                                | K_ELSIF
                                | K_ERRCODE
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index f38ef04077..61452d9f7f 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -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();
        {
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 12a3e6b818..08614a89a8 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -102,6 +102,7 @@ static const ScanKeyword unreserved_keywords[] = {
        PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
        PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
        PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
+       PG_KEYWORD("call", K_CALL, UNRESERVED_KEYWORD)
        PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
        PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
        PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
@@ -118,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)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 26a7344e9a..590df26f89 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -125,6 +125,7 @@ typedef enum PLpgSQL_stmt_type
        PLPGSQL_STMT_FETCH,
        PLPGSQL_STMT_CLOSE,
        PLPGSQL_STMT_PERFORM,
+       PLPGSQL_STMT_CALL,
        PLPGSQL_STMT_COMMIT,
        PLPGSQL_STMT_ROLLBACK
 } PLpgSQL_stmt_type;
@@ -508,6 +509,17 @@ typedef struct PLpgSQL_stmt_perform
        PLpgSQL_expr *expr;
 } PLpgSQL_stmt_perform;
 
+/*
+ * CALL statement
+ */
+typedef struct PLpgSQL_stmt_call
+{
+       PLpgSQL_stmt_type cmd_type;
+       int                     lineno;
+       PLpgSQL_expr *expr;
+       bool            is_call;
+} PLpgSQL_stmt_call;
+
 /*
  * COMMIT statement
  */
@@ -966,6 +978,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 */
@@ -1181,7 +1194,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,
diff --git a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql 
b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
index 02ee735079..3bbeab8978 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
@@ -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 @@ CREATE PROCEDURE transaction_test1()
 END
 $$;
 
-CALL transaction_test1();
+CALL transaction_test1(9, 'foo');
 
 SELECT * FROM test1;
 
@@ -43,7 +43,7 @@ CREATE PROCEDURE transaction_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,36 @@ CREATE PROCEDURE transaction_test5()
 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;
+
+
 -- commit inside cursor loop
 CREATE TABLE test2 (x int);
 INSERT INTO test2 VALUES (0), (1), (2), (3), (4);

base-commit: 51057feaa6bd24b51e6a4715c2090491ef037534
-- 
2.16.2

Reply via email to