From 8b4d1ca1a90c18eb3a797b3938e804478022543a Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Mon, 26 Feb 2018 11:59:06 -0500 Subject: [PATCH v1] PL/pgSQL: Add support for SET TRANSACTION A normal SQL command run inside PL/pgSQL acquires a snapshot, but SET TRANSACTION does not work anymore if a snapshot is set. So we have to handle this separately. --- .../plpgsql/src/expected/plpgsql_transaction.out | 20 +++++++++ src/pl/plpgsql/src/pl_exec.c | 49 ++++++++++++++++++++++ src/pl/plpgsql/src/pl_funcs.c | 23 ++++++++++ src/pl/plpgsql/src/pl_gram.y | 32 +++++++++++++- src/pl/plpgsql/src/pl_scanner.c | 2 + src/pl/plpgsql/src/plpgsql.h | 13 +++++- src/pl/plpgsql/src/sql/plpgsql_transaction.sql | 19 +++++++++ 7 files changed, 156 insertions(+), 2 deletions(-) diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out index 8ec22c646c..5f569dc64a 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out +++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out @@ -236,6 +236,26 @@ SELECT * FROM test3; 1 (1 row) +-- SET TRANSACTION +DO LANGUAGE plpgsql $$ +BEGIN + PERFORM 1; + RAISE INFO '%', current_setting('transaction_isolation'); + COMMIT; + SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; + PERFORM 1; + RAISE INFO '%', current_setting('transaction_isolation'); + COMMIT; + SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; + RESET TRANSACTION ISOLATION LEVEL; + PERFORM 1; + RAISE INFO '%', current_setting('transaction_isolation'); + COMMIT; +END; +$$; +INFO: read committed +INFO: repeatable read +INFO: read committed DROP TABLE test1; DROP TABLE test2; DROP TABLE test3; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 4ff87e0879..9a25ee9ad9 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -32,6 +32,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" @@ -299,6 +300,8 @@ static int exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt); static int exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt); +static int exec_stmt_set(PLpgSQL_execstate *estate, + PLpgSQL_stmt_set *stmt); static void plpgsql_estate_setup(PLpgSQL_execstate *estate, PLpgSQL_function *func, @@ -1997,6 +2000,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt); break; + case PLPGSQL_STMT_SET: + rc = exec_stmt_set(estate, (PLpgSQL_stmt_set *) stmt); + break; + default: estate->err_stmt = save_estmt; elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); @@ -4566,6 +4573,48 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt) return PLPGSQL_RC_OK; } +/* + * exec_stmt_set + * + * Execute SET/RESET statement. + * + * We just parse and execute the statement normally, but we have to do it + * without going through the usual SPI functions, because we don't want to set + * a snapshot for things like SET TRANSACTION. + * + * XXX It might be nice to use SPI_execute(), but in order to not get a + * snapshot, we have to pass read_only = true, which in turn prevents SET + * commands. + */ +static int +exec_stmt_set(PLpgSQL_execstate *estate, PLpgSQL_stmt_set *stmt) +{ + List *raw_parsetree_list; + RawStmt *parsetree; + List *stmt_list; + PlannedStmt *pstmt; + + raw_parsetree_list = pg_parse_query(stmt->expr->query); + Assert(list_length(raw_parsetree_list) == 1); + parsetree = linitial_node(RawStmt, raw_parsetree_list); + + stmt_list = pg_analyze_and_rewrite(parsetree, stmt->expr->query, NULL, 0, NULL); + stmt_list = pg_plan_queries(stmt_list, 0, NULL); + Assert(list_length(stmt_list) == 1); + pstmt = linitial_node(PlannedStmt, stmt_list); + Assert(pstmt->commandType == CMD_UTILITY); + + ProcessUtility(pstmt, + stmt->expr->query, + PROCESS_UTILITY_QUERY, + NULL, + NULL, + NULL, + NULL); + + return PLPGSQL_RC_OK; +} + /* ---------- * exec_assign_expr Put an expression's result into a variable. * ---------- diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index b986fc39b3..9acee818cc 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -288,6 +288,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt) return "COMMIT"; case PLPGSQL_STMT_ROLLBACK: return "ROLLBACK"; + case PLPGSQL_STMT_SET: + return "SET"; } return "unknown"; @@ -369,6 +371,7 @@ static void free_close(PLpgSQL_stmt_close *stmt); static void free_perform(PLpgSQL_stmt_perform *stmt); static void free_commit(PLpgSQL_stmt_commit *stmt); static void free_rollback(PLpgSQL_stmt_rollback *stmt); +static void free_set(PLpgSQL_stmt_set *stmt); static void free_expr(PLpgSQL_expr *expr); @@ -455,6 +458,9 @@ free_stmt(PLpgSQL_stmt *stmt) case PLPGSQL_STMT_ROLLBACK: free_rollback((PLpgSQL_stmt_rollback *) stmt); break; + case PLPGSQL_STMT_SET: + free_set((PLpgSQL_stmt_set *) stmt); + break; default: elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); break; @@ -612,6 +618,12 @@ free_rollback(PLpgSQL_stmt_rollback *stmt) { } +static void +free_set(PLpgSQL_stmt_set *stmt) +{ + free_expr(stmt->expr); +} + static void free_exit(PLpgSQL_stmt_exit *stmt) { @@ -807,6 +819,7 @@ static void dump_close(PLpgSQL_stmt_close *stmt); static void dump_perform(PLpgSQL_stmt_perform *stmt); static void dump_commit(PLpgSQL_stmt_commit *stmt); static void dump_rollback(PLpgSQL_stmt_rollback *stmt); +static void dump_set(PLpgSQL_stmt_set *stmt); static void dump_expr(PLpgSQL_expr *expr); @@ -903,6 +916,9 @@ dump_stmt(PLpgSQL_stmt *stmt) case PLPGSQL_STMT_ROLLBACK: dump_rollback((PLpgSQL_stmt_rollback *) stmt); break; + case PLPGSQL_STMT_SET: + dump_set((PLpgSQL_stmt_set *) stmt); + break; default: elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type); break; @@ -1289,6 +1305,13 @@ dump_rollback(PLpgSQL_stmt_rollback *stmt) printf("ROLLBACK\n"); } +static void +dump_set(PLpgSQL_stmt_set *stmt) +{ + dump_ind(); + printf("%s\n", stmt->expr->query); +} + static void dump_exit(PLpgSQL_stmt_exit *stmt) { diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 688fbd6531..4734c80766 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -198,7 +198,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %type stmt_return stmt_raise stmt_assert stmt_execsql %type stmt_dynexecute stmt_for stmt_perform stmt_getdiag %type stmt_open stmt_fetch stmt_move stmt_close stmt_null -%type stmt_commit stmt_rollback +%type stmt_commit stmt_rollback stmt_set %type stmt_case stmt_foreach_a %type proc_exceptions @@ -323,6 +323,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token K_QUERY %token K_RAISE %token K_RELATIVE +%token K_RESET %token K_RESULT_OID %token K_RETURN %token K_RETURNED_SQLSTATE @@ -333,6 +334,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token K_SCHEMA %token K_SCHEMA_NAME %token K_SCROLL +%token K_SET %token K_SLICE %token K_SQLSTATE %token K_STACKED @@ -887,6 +889,8 @@ proc_stmt : pl_block ';' { $$ = $1; } | stmt_rollback { $$ = $1; } + | stmt_set + { $$ = $1; } ; stmt_perform : K_PERFORM expr_until_semi @@ -2167,6 +2171,30 @@ stmt_rollback : K_ROLLBACK ';' } ; +stmt_set : K_SET + { + PLpgSQL_stmt_set *new; + + new = palloc0(sizeof(PLpgSQL_stmt_set)); + new->cmd_type = PLPGSQL_STMT_SET; + new->lineno = plpgsql_location_to_lineno(@1); + new->expr = read_sql_stmt("SET "); + + $$ = (PLpgSQL_stmt *)new; + } + | K_RESET + { + PLpgSQL_stmt_set *new; + + new = palloc0(sizeof(PLpgSQL_stmt_set)); + new->cmd_type = PLPGSQL_STMT_SET; + new->lineno = plpgsql_location_to_lineno(@1); + new->expr = read_sql_stmt("RESET "); + + $$ = (PLpgSQL_stmt *)new; + } + ; + cursor_variable : T_DATUM { @@ -2452,6 +2480,7 @@ unreserved_keyword : | K_QUERY | K_RAISE | K_RELATIVE + | K_RESET | K_RESULT_OID | K_RETURN | K_RETURNED_SQLSTATE @@ -2462,6 +2491,7 @@ unreserved_keyword : | K_SCHEMA | K_SCHEMA_NAME | K_SCROLL + | K_SET | K_SLICE | K_SQLSTATE | K_STACKED diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c index 12a3e6b818..ed8933f69a 100644 --- a/src/pl/plpgsql/src/pl_scanner.c +++ b/src/pl/plpgsql/src/pl_scanner.c @@ -155,6 +155,7 @@ static const ScanKeyword unreserved_keywords[] = { PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD) PG_KEYWORD("raise", K_RAISE, UNRESERVED_KEYWORD) PG_KEYWORD("relative", K_RELATIVE, UNRESERVED_KEYWORD) + PG_KEYWORD("reset", K_RESET, UNRESERVED_KEYWORD) PG_KEYWORD("result_oid", K_RESULT_OID, UNRESERVED_KEYWORD) PG_KEYWORD("return", K_RETURN, UNRESERVED_KEYWORD) PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE, UNRESERVED_KEYWORD) @@ -165,6 +166,7 @@ static const ScanKeyword unreserved_keywords[] = { PG_KEYWORD("schema", K_SCHEMA, UNRESERVED_KEYWORD) PG_KEYWORD("schema_name", K_SCHEMA_NAME, UNRESERVED_KEYWORD) PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD) + PG_KEYWORD("set", K_SET, UNRESERVED_KEYWORD) PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD) PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD) PG_KEYWORD("stacked", K_STACKED, UNRESERVED_KEYWORD) diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 26a7344e9a..bece089529 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -126,7 +126,8 @@ typedef enum PLpgSQL_stmt_type PLPGSQL_STMT_CLOSE, PLPGSQL_STMT_PERFORM, PLPGSQL_STMT_COMMIT, - PLPGSQL_STMT_ROLLBACK + PLPGSQL_STMT_ROLLBACK, + PLPGSQL_STMT_SET } PLpgSQL_stmt_type; /* @@ -526,6 +527,16 @@ typedef struct PLpgSQL_stmt_rollback int lineno; } PLpgSQL_stmt_rollback; +/* + * SET statement + */ +typedef struct PLpgSQL_stmt_set +{ + PLpgSQL_stmt_type cmd_type; + int lineno; + PLpgSQL_expr *expr; +} PLpgSQL_stmt_set; + /* * GET DIAGNOSTICS item */ diff --git a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql index 02ee735079..d2a2efd661 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql @@ -210,6 +210,25 @@ CREATE TABLE test2 (x int); SELECT * FROM test3; +-- SET TRANSACTION +DO LANGUAGE plpgsql $$ +BEGIN + PERFORM 1; + RAISE INFO '%', current_setting('transaction_isolation'); + COMMIT; + SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; + PERFORM 1; + RAISE INFO '%', current_setting('transaction_isolation'); + COMMIT; + SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; + RESET TRANSACTION ISOLATION LEVEL; + PERFORM 1; + RAISE INFO '%', current_setting('transaction_isolation'); + COMMIT; +END; +$$; + + DROP TABLE test1; DROP TABLE test2; DROP TABLE test3; base-commit: 51057feaa6bd24b51e6a4715c2090491ef037534 -- 2.16.2