From 24a6f3e4835c4c0b247a99f78985a66b600e9ec9 Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Wed, 20 Mar 2024 08:37:15 +0100 Subject: [PATCH 11/20] 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 | 11 ++++ 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, 158 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 67a54aa3e64..30dfe877572 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9838,7 +9838,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 642346186f0..e1c2940d4ca 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 } ] @@ -117,6 +117,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 db55ca4b1c3..43b1eb50034 100644 --- a/src/backend/commands/session_variable.c +++ b/src/backend/commands/session_variable.c @@ -83,6 +83,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 @@ -110,6 +112,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, @@ -389,6 +397,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. @@ -396,6 +430,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) @@ -507,6 +543,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->varxactendaction == VARIABLE_XACTEND_RESET || + varform->varxactendaction == VARIABLE_XACTEND_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 9f7562bc311..257d67af675 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -5251,6 +5251,7 @@ CreateSessionVarStmt: * transaction end like tables. */ XactEndActionOption: ON COMMIT DROP { $$ = VARIABLE_XACTEND_DROP; } + | ON TRANSACTION END_P RESET { $$ = VARIABLE_XACTEND_RESET; } | /*EMPTY*/ { $$ = VARIABLE_XACTEND_NOOP; } ; diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index d8c74f4e2e4..a796b8c557b 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -5330,6 +5330,7 @@ getVariables(Archive *fout) int i_varnamespace; int i_vartype; int i_vartypname; + int i_varxactendaction; int i_varowner; int i_varcollation; int i_varacl; @@ -5347,6 +5348,7 @@ getVariables(Archive *fout) /* Get the variables in current database. */ appendPQExpBuffer(query, "SELECT v.tableoid, v.oid, v.varname,\n" + " v.varxactendaction,\n" " v.varnamespace, v.vartype,\n" " pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n" " CASE WHEN v.varcollation <> t.typcollation " @@ -5369,6 +5371,7 @@ getVariables(Archive *fout) i_varnamespace = PQfnumber(res, "varnamespace"); i_vartype = PQfnumber(res, "vartype"); i_vartypname = PQfnumber(res, "vartypname"); + i_varxactendaction = PQfnumber(res, "varxactendaction"); i_varcollation = PQfnumber(res, "varcollation"); i_varowner = PQfnumber(res, "varowner"); @@ -5392,6 +5395,9 @@ getVariables(Archive *fout) varinfo[i].vartype = atooid(PQgetvalue(res, i, i_vartype)); varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname)); + varinfo[i].varxactendaction = + pg_strdup(PQgetvalue(res, i, i_varxactendaction)); + varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation)); varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl)); @@ -5432,6 +5438,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) PQExpBuffer query; char *qualvarname; const char *vartypname; + const char *varxactendaction; Oid varcollation; /* Skip if not to be dumped */ @@ -5443,6 +5450,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) qualvarname = pg_strdup(fmtQualifiedDumpable(varinfo)); vartypname = varinfo->vartypname; + varxactendaction = varinfo->varxactendaction; varcollation = varinfo->varcollation; appendPQExpBuffer(delq, "DROP VARIABLE %s;\n", @@ -5461,6 +5469,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) fmtQualifiedDumpable(coll)); } + if (strcmp(varxactendaction, "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 36a69a08324..0910507d9ac 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -702,6 +702,7 @@ typedef struct _VariableInfo DumpableAcl dacl; Oid vartype; char *vartypname; + char *varxactendaction; 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 0d3765eed4f..08ea791420a 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3941,6 +3941,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 f6aa8bc5f6c..50bae539113 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5182,6 +5182,7 @@ listVariables(const char *pattern, bool verbose) " pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n" " CASE v.varxactendaction\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 1e6dd98d415..60291ef7ac2 100644 --- a/src/include/catalog/pg_variable.h +++ b/src/include/catalog/pg_variable.h @@ -74,6 +74,7 @@ typedef enum VariableXactEndAction { VARIABLE_XACTEND_NOOP = 'n', /* NOOP */ VARIABLE_XACTEND_DROP = 'd', /* ON COMMIT DROP */ + VARIABLE_XACTEND_RESET = 'r', /* ON TRANSACTION END RESET */ } VariableXactEndAction; /* ---------------- diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out index 9fbbf9057a6..19a2e5a8f64 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 45e65d4085d..5d089c8a4ed 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 b8a1ce6e7ef..a3156254ad0 100644 --- a/src/test/regress/expected/session_variables.out +++ b/src/test/regress/expected/session_variables.out @@ -1507,3 +1507,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 cd44a0b4a0c..540b317cfef 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -1005,3 +1005,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.45.2