From fa13c429d1b401643af4fa87f553784edaa1515a Mon Sep 17 00:00:00 2001 From: Peter Eisentraut 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 @@ Looping Through a Cursor's Result Transaction Management - In procedures invoked by the CALL command from the top - level as well as in anonymous code blocks (DO command) - called from the top level, it is possible to end transactions using the + In procedures invoked by the CALL command + as well as in anonymous code blocks (DO command), + it is possible to end transactions using the commands COMMIT and ROLLBACK. A new transaction is started automatically after a transaction is ended using these commands, so there is no separate START @@ -3479,6 +3479,13 @@ Transaction Management + + Transaction control is only possible in CALL or + DO invocations from the top level or nested + CALL or DO invocations without any + other intervening command. + + 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 proc_stmt pl_block %type stmt_assign stmt_if stmt_loop stmt_while stmt_exit %type stmt_return stmt_raise stmt_assert stmt_execsql -%type stmt_dynexecute stmt_for stmt_perform stmt_getdiag +%type stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag %type stmt_open stmt_fetch stmt_move stmt_close stmt_null %type stmt_commit stmt_rollback %type stmt_case stmt_foreach_a @@ -256,6 +256,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token K_BACKWARD %token K_BEGIN %token K_BY +%token K_CALL %token K_CASE %token K_CLOSE %token K_COLLATE @@ -274,6 +275,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token K_DEFAULT %token K_DETAIL %token K_DIAGNOSTICS +%token K_DO %token K_DUMP %token K_ELSE %token 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