From dae7516879069dfbadca5d2168ab7d2efe8ffab1 Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Mon, 19 Jun 2023 07:45:30 +0200 Subject: [PATCH 10/18] Implementation ON TRANSACTION END RESET clause This is simple patch - just add special flag to session variable memory entry. The entries with active this flag are removed from sessionvars hash table at transaction end. The "TRANSACTION END" is synonyms for "COMMIT ROLLBACK" but the "TRANSACTION END" is more illustrative and less confusing then "COMMIT ROLLBACK". --- doc/src/sgml/catalogs.sgml | 3 +- doc/src/sgml/ref/create_variable.sgml | 13 ++++- src/backend/commands/session_variable.c | 51 +++++++++++++++++++ src/backend/parser/gram.y | 1 + src/bin/pg_dump/pg_dump.c | 9 ++++ src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/t/002_pg_dump.pl | 18 +++++++ src/bin/psql/describe.c | 1 + src/include/catalog/pg_variable.h | 1 + .../isolation/expected/session-variable.out | 4 +- .../isolation/specs/session-variable.spec | 4 +- .../regress/expected/session_variables.out | 34 +++++++++++++ src/test/regress/sql/session_variables.sql | 20 ++++++++ 13 files changed, 156 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 9dbc0043e8..d140770f54 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9793,7 +9793,8 @@ SCRAM-SHA-256$<iteration count>:&l Action performed at end of transaction: - n = no action, d = drop the variable. + n = no action, d = drop the variable, + r = reset the variable to its default value. diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml index 1915dea3a9..001a30c85b 100644 --- a/doc/src/sgml/ref/create_variable.sgml +++ b/doc/src/sgml/ref/create_variable.sgml @@ -27,7 +27,7 @@ PostgreSQL documentation CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ] [ COLLATE collation ] - [ ON COMMIT DROP ] + [ { ON COMMIT DROP | ON TRANSACTION END RESET } ] @@ -123,6 +123,17 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] + ON TRANSACTION END RESET + + + The ON TRANSACTION END RESET clause causes the session + variable to be reset to its default value when the transaction is committed + or rolled back. + + + + diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index d3303337e8..c330981511 100644 --- a/src/backend/commands/session_variable.c +++ b/src/backend/commands/session_variable.c @@ -91,6 +91,8 @@ typedef struct SVariableData void *domain_check_extra; LocalTransactionId domain_check_extra_lxid; + bool reset_at_eox; + /* * Top level local transaction id of the last transaction that dropped the * variable if any. We need this information to avoid freeing memory for @@ -118,6 +120,12 @@ static MemoryContext SVariableMemoryContext = NULL; /* true after accepted sinval message */ static bool needs_validation = false; +/* + * true, when some used session variable has ON COMMIT DROP + * or ON TRANSACTION END RESET clauses + */ +static bool has_session_variables_with_reset_at_eox = false; + /* * The content of session variables is not removed immediately. When it * is possible we do this at the transaction end. But when the transaction failed, @@ -398,6 +406,32 @@ remove_invalid_session_variables(bool atEOX) } } +/* + * remove entries marked as "reset_at_eox" + */ +static void +remove_session_variables_with_reset_at_eox(void) +{ + HASH_SEQ_STATUS status; + SVariable svar; + + if (!sessionvars) + return; + + /* leave quckly, when there are not that variables */ + if (!has_session_variables_with_reset_at_eox) + return; + + hash_seq_init(&status, sessionvars); + while ((svar = (SVariable) hash_seq_search(&status)) != NULL) + { + if (svar->reset_at_eox) + hash_search(sessionvars, &svar->varid, HASH_REMOVE, NULL); + } + + has_session_variables_with_reset_at_eox = false; +} + /* * Perform ON COMMIT DROP for temporary session variables, * and remove all dropped variables from memory. @@ -405,6 +439,8 @@ remove_invalid_session_variables(bool atEOX) void AtPreEOXact_SessionVariables(bool isCommit) { + remove_session_variables_with_reset_at_eox(); + if (isCommit) { if (xact_drop_items) @@ -516,6 +552,21 @@ setup_session_variable(SVariable svar, Oid varid) svar->domain_check_extra = NULL; svar->domain_check_extra_lxid = InvalidLocalTransactionId; + /* + * We don't need to explicitly reset variables marked ON COMMIT DROP. It + * can be done by sinval message processing. But this processing can be + * postponed due aborted transaction. On second hand there is not a + * reason, why don't do it at transaction end immediately. + */ + if (varform->vareoxaction == VARIABLE_EOX_RESET || + varform->vareoxaction == VARIABLE_EOX_DROP) + { + svar->reset_at_eox = true; + has_session_variables_with_reset_at_eox = true; + } + else + svar->reset_at_eox = false; + svar->drop_lxid = InvalidTransactionId; svar->isnull = true; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3f21c59da6..3955c54af2 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -5137,6 +5137,7 @@ CreateSessionVarStmt: * transaction end like tables. */ OnEOXActionOption: ON COMMIT DROP { $$ = VARIABLE_EOX_DROP; } + | ON TRANSACTION END_P RESET { $$ = VARIABLE_EOX_RESET; } | /*EMPTY*/ { $$ = VARIABLE_EOX_NOOP; } ; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 0527fb6254..8e3633ebd8 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -5198,6 +5198,7 @@ getVariables(Archive *fout) int i_varnamespace; int i_vartype; int i_vartypname; + int i_vareoxaction; int i_varowner; int i_varcollation; int i_varacl; @@ -5215,6 +5216,7 @@ getVariables(Archive *fout) /* Get the variables in current database. */ appendPQExpBuffer(query, "SELECT v.tableoid, v.oid, v.varname,\n" + "v.vareoxaction,\n" "v.varnamespace,\n" "v.vartype,\n" "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n" @@ -5237,6 +5239,7 @@ getVariables(Archive *fout) i_varnamespace = PQfnumber(res, "varnamespace"); i_vartype = PQfnumber(res, "vartype"); i_vartypname = PQfnumber(res, "vartypname"); + i_vareoxaction = PQfnumber(res, "vareoxaction"); i_varcollation = PQfnumber(res, "varcollation"); i_varowner = PQfnumber(res, "varowner"); @@ -5260,6 +5263,7 @@ getVariables(Archive *fout) varinfo[i].vartype = atooid(PQgetvalue(res, i, i_vartype)); varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname)); + varinfo[i].vareoxaction = pg_strdup(PQgetvalue(res, i, i_vareoxaction)); varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation)); varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl)); @@ -5303,6 +5307,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) PQExpBuffer query; char *qualvarname; const char *vartypname; + const char *vareoxaction; Oid varcollation; /* Skip if not to be dumped */ @@ -5314,6 +5319,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) qualvarname = pg_strdup(fmtQualifiedDumpable(varinfo)); vartypname = varinfo->vartypname; + vareoxaction = varinfo->vareoxaction; varcollation = varinfo->varcollation; appendPQExpBuffer(delq, "DROP VARIABLE %s;\n", @@ -5332,6 +5338,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) fmtQualifiedDumpable(coll)); } + if (strcmp(vareoxaction, "r") == 0) + appendPQExpBuffer(query, " ON TRANSACTION END RESET"); + appendPQExpBuffer(query, ";\n"); if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 54bdc2a207..117c67bfde 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -696,6 +696,7 @@ typedef struct _VariableInfo DumpableAcl dacl; Oid vartype; char *vartypname; + char *vareoxaction; char *varacl; char *rvaracl; char *initvaracl; diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 4b3e2185c3..30fe4b981e 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3954,6 +3954,24 @@ my %tests = ( }, }, + 'CREATE VARIABLE test_variable ON TRANSACTION END RESET' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE VARIABLE dump_test.variable2 AS integer ON TRANSACTION END RESET;', + regexp => qr/^ + \QCREATE VARIABLE dump_test.variable2 AS integer ON TRANSACTION END RESET;\E/xm, + like => { + %full_runs, + %dump_test_schema_runs, + section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE VIEW test_view' => { create_order => 61, create_sql => 'CREATE VIEW dump_test.test_view diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index f2e134ee4a..2671267d37 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5197,6 +5197,7 @@ listVariables(const char *pattern, bool verbose) " pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n" " CASE v.vareoxaction\n" " WHEN 'd' THEN 'ON COMMIT DROP'\n" + " WHEN 'r' THEN 'ON TRANSACTION END RESET'\n" " END as \"%s\"\n", gettext_noop("Schema"), gettext_noop("Name"), diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h index 03bd5a6557..c976130c53 100644 --- a/src/include/catalog/pg_variable.h +++ b/src/include/catalog/pg_variable.h @@ -73,6 +73,7 @@ typedef enum VariableEOXAction { VARIABLE_EOX_NOOP = 'n', /* NOOP */ VARIABLE_EOX_DROP = 'd', /* ON COMMIT DROP */ + VARIABLE_EOX_RESET = 'r', /* ON TRANSACTION END RESET */ } VariableEOXAction; /* ---------------- diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out index 9fbbf9057a..19a2e5a8f6 100644 --- a/src/test/isolation/expected/session-variable.out +++ b/src/test/isolation/expected/session-variable.out @@ -85,11 +85,12 @@ myvar step sr1: ROLLBACK; -starting permutation: create3 let3 s3 o_c_d create4 let4 drop4 drop3 inval3 discard sc3 state +starting permutation: create3 let3 s3 o_c_d o_eox_r create4 let4 drop4 drop3 inval3 discard sc3 clean state step create3: CREATE VARIABLE myvar3 AS text; step let3: LET myvar3 = 'test'; step s3: BEGIN; step o_c_d: CREATE TEMP VARIABLE myvar_o_c_d AS text ON COMMIT DROP; +step o_eox_r: CREATE VARIABLE myvar_o_eox_r AS text ON TRANSACTION END RESET; LET myvar_o_eox_r = 'test'; step create4: CREATE VARIABLE myvar4 AS text; step let4: LET myvar4 = 'test'; step drop4: DROP VARIABLE myvar4; @@ -102,6 +103,7 @@ t step discard: DISCARD VARIABLES; step sc3: COMMIT; +step clean: DROP VARIABLE myvar_o_eox_r; step state: SELECT varname FROM pg_variable; varname ------- diff --git a/src/test/isolation/specs/session-variable.spec b/src/test/isolation/specs/session-variable.spec index 45e65d4085..5d089c8a4e 100644 --- a/src/test/isolation/specs/session-variable.spec +++ b/src/test/isolation/specs/session-variable.spec @@ -25,12 +25,14 @@ session s3 step s3 { BEGIN; } step let3 { LET myvar3 = 'test'; } step o_c_d { CREATE TEMP VARIABLE myvar_o_c_d AS text ON COMMIT DROP; } +step o_eox_r { CREATE VARIABLE myvar_o_eox_r AS text ON TRANSACTION END RESET; LET myvar_o_eox_r = 'test'; } step create4 { CREATE VARIABLE myvar4 AS text; } step let4 { LET myvar4 = 'test'; } step drop4 { DROP VARIABLE myvar4; } step inval3 { SELECT COUNT(*) >= 0 FROM pg_foreign_table; } step discard { DISCARD VARIABLES; } step sc3 { COMMIT; } +step clean { DROP VARIABLE myvar_o_eox_r; } step state { SELECT varname FROM pg_variable; } session s4 @@ -48,4 +50,4 @@ permutation let val dbg drop create dbg val # calling the dbg step after the concurrent drop permutation let val s1 dbg drop create dbg val sr1 # test for DISCARD ALL when all internal queues have actions registered -permutation create3 let3 s3 o_c_d create4 let4 drop4 drop3 inval3 discard sc3 state +permutation create3 let3 s3 o_c_d o_eox_r create4 let4 drop4 drop3 inval3 discard sc3 clean state diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out index 2a38415ce3..d9bc76405b 100644 --- a/src/test/regress/expected/session_variables.out +++ b/src/test/regress/expected/session_variables.out @@ -1460,3 +1460,37 @@ SELECT count(*) FROM pg_session_variables(); 0 (1 row) +CREATE VARIABLE var1 AS int ON TRANSACTION END RESET; +BEGIN; + LET var1 = 100; + SELECT var1; + var1 +------ + 100 +(1 row) + +COMMIT; +-- should be NULL; +SELECT var1 IS NULL; + ?column? +---------- + t +(1 row) + +BEGIN; + LET var1 = 100; + SELECT var1; + var1 +------ + 100 +(1 row) + +ROLLBACK; +-- should be NULL +SELECT var1 IS NULL; + ?column? +---------- + t +(1 row) + +DROP VARIABLE var1; diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql index d27b6daaf0..55b4055781 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -964,3 +964,23 @@ COMMIT; SELECT count(*) FROM pg_variable WHERE varname = 'var1'; -- should be zero SELECT count(*) FROM pg_session_variables(); + +CREATE VARIABLE var1 AS int ON TRANSACTION END RESET; + +BEGIN; + LET var1 = 100; + SELECT var1; +COMMIT; + +-- should be NULL; +SELECT var1 IS NULL; + +BEGIN; + LET var1 = 100; + SELECT var1; +ROLLBACK; + +-- should be NULL +SELECT var1 IS NULL; + +DROP VARIABLE var1; -- 2.43.0