From e0e414fc253e2ad63231380a01707ba31417b480 Mon Sep 17 00:00:00 2001 From: Laurenz Albe Date: Wed, 13 Nov 2024 15:00:28 +0100 Subject: [PATCH 12/22] implementation of temporary session variables The temporary variables are created inside temp schema and they are dropped by dropping this schema. For consistency with temp tables, the CREATE TEMP VARIABLE command supports ON COMMIT DROP clause. The temporary variables with this clause are collected in xact_drop_items list. This list should be carefully maintained and can contain only valid entries (not yet dropped). From this reasons now the subcommit and subaborting have to be handled. --- doc/src/sgml/catalogs.sgml | 10 + doc/src/sgml/ddl.sgml | 6 +- doc/src/sgml/ref/create_variable.sgml | 15 +- src/backend/access/transam/xact.c | 9 + src/backend/catalog/pg_variable.c | 27 ++- src/backend/commands/session_variable.c | 218 +++++++++++++++++- src/backend/commands/view.c | 2 +- src/backend/parser/analyze.c | 4 +- src/backend/parser/gram.y | 30 ++- src/backend/parser/parse_relation.c | 22 +- src/bin/psql/describe.c | 10 +- src/bin/psql/tab-complete.in.c | 5 +- src/include/catalog/pg_variable.h | 9 + src/include/commands/session_variable.h | 6 +- src/include/nodes/parsenodes.h | 1 + src/include/nodes/primnodes.h | 4 +- src/include/parser/parse_relation.h | 2 +- .../isolation/expected/session-variable.out | 3 +- .../isolation/specs/session-variable.spec | 3 +- src/test/regress/expected/psql.out | 36 +-- .../regress/expected/session_variables.out | 120 ++++++++++ src/test/regress/sql/session_variables.sql | 66 ++++++ src/tools/pgindent/typedefs.list | 1 + 23 files changed, 544 insertions(+), 65 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 67e64592506..f651ad90b10 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9839,6 +9839,16 @@ SCRAM-SHA-256$<iteration count>:&l + + + varxactendaction char + + + Action performed at end of transaction: + n = no action, d = drop the variable. + + + varcollation oid diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 8eaf41e0740..5916e7e8f9c 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -5385,9 +5385,9 @@ SELECT current_user_id; LET command. Session variables are not transactional: any changes made to the value of a session variable in a transaction won't be undone if the transaction is rolled back (just like variables in - procedural languages). Session variables themselves are persistent, but - their values are neither persistent nor shared (like the content of - temporary tables). + procedural languages). Session variables themselves can be persistent + or temporary, but their values are neither persistent nor shared (like the + content of temporary tables). diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml index d681404055d..81ffbcc3c62 100644 --- a/doc/src/sgml/ref/create_variable.sgml +++ b/doc/src/sgml/ref/create_variable.sgml @@ -26,7 +26,8 @@ PostgreSQL documentation -CREATE VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type [ COLLATE collation ] +CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type [ COLLATE collation ] + [ ON COMMIT DROP ] @@ -104,6 +105,18 @@ CREATE VARIABLE [ IF NOT EXISTS ] name + + ON COMMIT DROP + + + The ON COMMIT DROP clause specifies the behaviour of a + temporary session variable at transaction commit. With this clause, the + session variable is dropped at commit time. The clause is only allowed + for temporary variables. + + + + diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index d331ab90d78..6168415f8c3 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -36,6 +36,7 @@ #include "catalog/pg_enum.h" #include "catalog/storage.h" #include "commands/async.h" +#include "commands/session_variable.h" #include "commands/tablecmds.h" #include "commands/trigger.h" #include "common/pg_prng.h" @@ -2316,6 +2317,9 @@ CommitTransaction(void) */ smgrDoPendingSyncs(true, is_parallel_worker); + /* remove values of dropped session variables from memory */ + AtPreEOXact_SessionVariables(true); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2912,6 +2916,7 @@ AbortTransaction(void) AtAbort_Portals(); smgrDoPendingSyncs(false, is_parallel_worker); AtEOXact_LargeObject(false); + AtPreEOXact_SessionVariables(false); AtAbort_Notify(); AtEOXact_RelationMap(false, is_parallel_worker); AtAbort_Twophase(); @@ -5177,6 +5182,8 @@ CommitSubTransaction(void) AtEOSubXact_SPI(true, s->subTransactionId); AtEOSubXact_on_commit_actions(true, s->subTransactionId, s->parent->subTransactionId); + AtEOSubXact_SessionVariables(true, s->subTransactionId, + s->parent->subTransactionId); AtEOSubXact_Namespace(true, s->subTransactionId, s->parent->subTransactionId); AtEOSubXact_Files(true, s->subTransactionId, @@ -5342,6 +5349,8 @@ AbortSubTransaction(void) AtEOSubXact_SPI(false, s->subTransactionId); AtEOSubXact_on_commit_actions(false, s->subTransactionId, s->parent->subTransactionId); + AtEOSubXact_SessionVariables(false, s->subTransactionId, + s->parent->subTransactionId); AtEOSubXact_Namespace(false, s->subTransactionId, s->parent->subTransactionId); AtEOSubXact_Files(false, s->subTransactionId, diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c index d672d2a9cb3..00c90ea6fd8 100644 --- a/src/backend/catalog/pg_variable.c +++ b/src/backend/catalog/pg_variable.c @@ -36,7 +36,8 @@ static ObjectAddress create_variable(const char *varName, int32 varTypmod, Oid varOwner, Oid varCollation, - bool if_not_exists); + bool if_not_exists, + VariableXactEndAction varXactEndAction); /* @@ -49,7 +50,8 @@ create_variable(const char *varName, int32 varTypmod, Oid varOwner, Oid varCollation, - bool if_not_exists) + bool if_not_exists, + VariableXactEndAction varXactEndAction) { Acl *varacl; NameData varname; @@ -110,6 +112,7 @@ create_variable(const char *varName, values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod); values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner); values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation); + values[Anum_pg_variable_varxactendaction - 1] = CharGetDatum(varXactEndAction); varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner, varNamespace); @@ -183,6 +186,13 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt) Oid typcollation; ObjectAddress variable; + /* check consistency of arguments */ + if (stmt->XactEndAction == VARIABLE_XACTEND_DROP + && stmt->variable->relpersistence != RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("ON COMMIT DROP can only be used on temporary variables"))); + namespaceid = RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL); @@ -222,11 +232,17 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt) typmod, varowner, collation, - stmt->if_not_exists); + stmt->if_not_exists, + stmt->XactEndAction); elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable", stmt->variable->relname, variable.objectId); + /* we want SessionVariableCreatePostprocess to see the catalog changes. */ + CommandCounterIncrement(); + + SessionVariableCreatePostprocess(variable.objectId, stmt->XactEndAction); + return variable; } @@ -239,6 +255,7 @@ DropVariableById(Oid varid) { Relation rel; HeapTuple tup; + char XactEndAction; rel = table_open(VariableRelationId, RowExclusiveLock); @@ -247,6 +264,8 @@ DropVariableById(Oid varid) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for variable %u", varid); + XactEndAction = ((Form_pg_variable) GETSTRUCT(tup))->varxactendaction; + CatalogTupleDelete(rel, &tup->t_self); ReleaseSysCache(tup); @@ -254,5 +273,5 @@ DropVariableById(Oid varid) table_close(rel, RowExclusiveLock); /* do the necessary cleanup in local memory, if needed */ - SessionVariableDropPostprocess(varid); + SessionVariableDropPostprocess(varid, XactEndAction); } diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index 2e36f9da800..5ea20fb0cf8 100644 --- a/src/backend/commands/session_variable.c +++ b/src/backend/commands/session_variable.c @@ -16,6 +16,8 @@ #include "access/xact.h" #include "catalog/pg_variable.h" +#include "catalog/dependency.h" +#include "catalog/namespace.h" #include "commands/session_variable.h" #include "executor/spi.h" #include "executor/svariableReceiver.h" @@ -32,6 +34,19 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" +typedef struct SVariableXActDropItem +{ + Oid varid; /* varid of session variable */ + + /* + * creating_subid is the ID of the creating subxact. If the action was + * unregistered during the current transaction, deleting_subid is the ID + * of the deleting subxact, otherwise InvalidSubTransactionId. + */ + SubTransactionId creating_subid; + SubTransactionId deleting_subid; +} SVariableXActDropItem; + /* * The session variables use fence context allow to specify * the contexts where using session variable fences are required. @@ -104,13 +119,21 @@ static MemoryContext SVariableMemoryContext = NULL; static bool needs_validation = false; /* - * The content of dropped session variables is not removed immediately. We do - * that in the next transaction that reads or writes a session variable. - * "validated_lxid" stores the transaction that performed said validation, so - * that we can avoid repeating the effort. + * The content of dropped session variables is not removed immediately. If + * possible, we do that at the end of the transaction. But we cannot do that + * if the transaction aborted, because we lost access to the system catalog. + * In that case, we clean up in the next transaction that reads or writes a + * session variable. "validated_lxid" stores the transaction that performed + * said validation, so that we can avoid repeating the effort. */ static LocalTransactionId validated_lxid = InvalidLocalTransactionId; +/* list holds fields of SVariableXActDropItem type */ +static List *xact_drop_items = NIL; + +static void register_session_variable_xact_drop(Oid varid); +static void unregister_session_variable_xact_drop(Oid varid); + /* * Callback function for session variable invalidation. */ @@ -147,16 +170,45 @@ pg_variable_cache_callback(Datum arg, int cacheid, uint32 hashvalue) } } +/* + * Do the necessary work to setup local memory management of a new + * variable. + * + * Caller should already have created the necessary entry in catalog + * and made them visible. + */ +void +SessionVariableCreatePostprocess(Oid varid, char XactEndAction) +{ + /* + * For temporary variables, we need to create a new end of xact action to + * ensure deletion from catalog. + */ + if (XactEndAction == VARIABLE_XACTEND_DROP) + { + Assert(isTempNamespace(get_session_variable_namespace(varid))); + + register_session_variable_xact_drop(varid); + } +} + /* * Handle the local memory cleanup for a DROP VARIABLE command. * * Caller should take care of removing the pg_variable entry first. */ void -SessionVariableDropPostprocess(Oid varid) +SessionVariableDropPostprocess(Oid varid, char XactEndAction) { Assert(LocalTransactionIdIsValid(MyProc->vxid.lxid)); + if (XactEndAction == VARIABLE_XACTEND_DROP) + { + Assert(isTempNamespace(get_session_variable_namespace(varid))); + + unregister_session_variable_xact_drop(varid); + } + if (sessionvars) { bool found; @@ -178,6 +230,57 @@ SessionVariableDropPostprocess(Oid varid) } } +/* + * Registration of actions to be executed on session variables at transaction + * end time. We want to drop temporary session variables with clause ON COMMIT + * DROP. + */ + +/* + * Register a session variable xact action. + */ +static void +register_session_variable_xact_drop(Oid varid) +{ + SVariableXActDropItem *xact_ai; + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + xact_ai = (SVariableXActDropItem *) + palloc(sizeof(SVariableXActDropItem)); + + xact_ai->varid = varid; + + xact_ai->creating_subid = GetCurrentSubTransactionId(); + xact_ai->deleting_subid = InvalidSubTransactionId; + + xact_drop_items = lcons(xact_ai, xact_drop_items); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Unregister an id of a given session variable from drop list. In this + * moment, the action is just marked as deleted by setting deleting_subid. The + * calling even might be rollbacked, in which case we should not lose this + * action. + */ +static void +unregister_session_variable_xact_drop(Oid varid) +{ + ListCell *l; + + foreach(l, xact_drop_items) + { + SVariableXActDropItem *xact_ai = + (SVariableXActDropItem *) lfirst(l); + + if (xact_ai->varid == varid) + xact_ai->deleting_subid = GetCurrentSubTransactionId(); + } +} + /* * Release stored value, free memory */ @@ -230,9 +333,11 @@ is_session_variable_valid(SVariable svar) /* * Check all potentially invalid session variable data in local memory and free * the memory for all invalid ones. This function is called before any read or - * write of a session variable. Freeing of a variable's memory is postponed if - * the variable has been dropped by the current transaction, since that - * operation could still be rolled back. + * write of a session variable or when the transaction ends. At the end of a + * transaction (atEOX is true) we can discard all invalid variables. Inside a + * transaction (atEOX is false) we postpone freeing a variable's memory if the + * variable has been dropped by the current transaction, since that operation + * could still be rolled back. * * It is possible that we receive a cache invalidation message while * remove_invalid_session_variables() is executing, so we cannot guarantee that @@ -240,7 +345,7 @@ is_session_variable_valid(SVariable svar) * done. However, we can guarantee that all entries get checked once. */ static void -remove_invalid_session_variables(void) +remove_invalid_session_variables(bool atEOX) { HASH_SEQ_STATUS status; SVariable svar; @@ -267,7 +372,7 @@ remove_invalid_session_variables(void) { if (!svar->is_valid) { - if (svar->drop_lxid == MyProc->vxid.lxid) + if (!atEOX && svar->drop_lxid == MyProc->vxid.lxid) { /* try again in the next transaction */ needs_validation = true; @@ -288,6 +393,95 @@ remove_invalid_session_variables(void) } } +/* + * Perform ON COMMIT DROP for temporary session variables, + * and remove all dropped variables from memory. + */ +void +AtPreEOXact_SessionVariables(bool isCommit) +{ + if (isCommit) + { + if (xact_drop_items) + { + ListCell *l; + + foreach(l, xact_drop_items) + { + SVariableXActDropItem *xact_ai = + (SVariableXActDropItem *) lfirst(l); + + /* iterate only over entries that are still pending */ + if (xact_ai->deleting_subid == InvalidSubTransactionId) + { + ObjectAddress object; + + object.classId = VariableRelationId; + object.objectId = xact_ai->varid; + object.objectSubId = 0; + + /* + * Since this is an automatic drop, rather than one + * directly initiated by the user, we pass the + * PERFORM_DELETION_INTERNAL flag. + */ + elog(DEBUG1, "session variable (oid:%u) will be deleted (forced by ON COMMIT DROP clause)", + xact_ai->varid); + + performDeletion(&object, DROP_CASCADE, + PERFORM_DELETION_INTERNAL | + PERFORM_DELETION_QUIETLY); + } + } + } + + remove_invalid_session_variables(true); + } + + /* + * We have to clean xact_drop_items. All related variables are dropped + * now, or lost inside aborted transaction. + */ + list_free_deep(xact_drop_items); + xact_drop_items = NULL; +} + +/* + * Post-subcommit or post-subabort cleanup of xact drop list. + * + * During subabort, we can immediately remove entries created during this + * subtransaction. During subcommit, just transfer entries marked during + * this subtransaction as being the parent's responsibility. + */ +void +AtEOSubXact_SessionVariables(bool isCommit, + SubTransactionId mySubid, + SubTransactionId parentSubid) +{ + ListCell *cur_item; + + foreach(cur_item, xact_drop_items) + { + SVariableXActDropItem *xact_ai = + (SVariableXActDropItem *) lfirst(cur_item); + + if (!isCommit && xact_ai->creating_subid == mySubid) + { + /* cur_item must be removed */ + xact_drop_items = foreach_delete_current(xact_drop_items, cur_item); + pfree(xact_ai); + } + else + { + /* cur_item must be preserved */ + if (xact_ai->creating_subid == mySubid) + xact_ai->creating_subid = parentSubid; + if (xact_ai->deleting_subid == mySubid) + xact_ai->deleting_subid = isCommit ? parentSubid : InvalidSubTransactionId; + } + } +} + /* * Initialize attributes cached in "svar" */ @@ -446,7 +640,7 @@ get_session_variable(Oid varid) validated_lxid != MyProc->vxid.lxid) { /* free the memory from dropped session variables */ - remove_invalid_session_variables(); + remove_invalid_session_variables(false); /* don't repeat the above step in the same transaction */ validated_lxid = MyProc->vxid.lxid; @@ -542,7 +736,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull) validated_lxid != MyProc->vxid.lxid) { /* free the memory from dropped session variables */ - remove_invalid_session_variables(); + remove_invalid_session_variables(false); /* don't repeat the above step in the same transaction */ validated_lxid = MyProc->vxid.lxid; diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 6f0301555e0..6bef9a2612a 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -484,7 +484,7 @@ DefineView(ViewStmt *stmt, const char *queryString, */ view = copyObject(stmt->view); /* don't corrupt original command */ if (view->relpersistence == RELPERSISTENCE_PERMANENT - && isQueryUsingTempRelation(viewParse)) + && isQueryUsingTempObject(viewParse)) { view->relpersistence = RELPERSISTENCE_TEMP; ereport(NOTICE, diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index de8c5bfbd16..47df024cdf9 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -3389,10 +3389,10 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) * creation query. It would be hard to refresh data or incrementally * maintain it if a source disappeared. */ - if (isQueryUsingTempRelation(query)) + if (isQueryUsingTempObject(query)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialized views must not use temporary tables or views"))); + errmsg("materialized views must not use temporary tables, views or session variables"))); /* * A materialized view would either need to save parameters for use in diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 6bbad154cbe..7c089549b4b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -465,6 +465,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type OptTemp %type OptNoLog %type OnCommitOption +%type XactEndActionOption %type for_locking_strength %type for_locking_item @@ -5228,26 +5229,39 @@ create_extension_opt_item: *****************************************************************************/ CreateSessionVarStmt: - CREATE VARIABLE qualified_name opt_as Typename opt_collate_clause + CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause XactEndActionOption { CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt); - n->variable = $3; - n->typeName = $5; - n->collClause = (CollateClause *) $6; + $4->relpersistence = $2; + n->variable = $4; + n->typeName = $6; + n->collClause = (CollateClause *) $7; + n->XactEndAction = $8; n->if_not_exists = false; $$ = (Node *) n; } - | CREATE VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause + | CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause XactEndActionOption { CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt); - n->variable = $6; - n->typeName = $8; - n->collClause = (CollateClause *) $9; + $7->relpersistence = $2; + n->variable = $7; + n->typeName = $9; + n->collClause = (CollateClause *) $10; + n->XactEndAction = $11; n->if_not_exists = true; $$ = (Node *) n; } ; +/* + * Temporary session variables can be dropped on successful + * transaction end like tables. + */ +XactEndActionOption: ON COMMIT DROP { $$ = VARIABLE_XACTEND_DROP; } + | /*EMPTY*/ { $$ = VARIABLE_XACTEND_NOOP; } + ; + + /***************************************************************************** * * ALTER EXTENSION name UPDATE [ TO version ] diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 92a04e35dff..865a12d8b71 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -101,7 +101,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, static int specialAttNum(const char *attname); static bool rte_visible_if_lateral(ParseState *pstate, RangeTblEntry *rte); static bool rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte); -static bool isQueryUsingTempRelation_walker(Node *node, void *context); +static bool isQueryUsingTempObject_walker(Node *node, void *context); /* @@ -3896,13 +3896,13 @@ rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte) * the query is a temporary relation (table, view, or materialized view). */ bool -isQueryUsingTempRelation(Query *query) +isQueryUsingTempObject(Query *query) { - return isQueryUsingTempRelation_walker((Node *) query, NULL); + return isQueryUsingTempObject_walker((Node *) query, NULL); } static bool -isQueryUsingTempRelation_walker(Node *node, void *context) +isQueryUsingTempObject_walker(Node *node, void *context) { if (node == NULL) return false; @@ -3928,13 +3928,23 @@ isQueryUsingTempRelation_walker(Node *node, void *context) } return query_tree_walker(query, - isQueryUsingTempRelation_walker, + isQueryUsingTempObject_walker, context, QTW_IGNORE_JOINALIASES); } + else if (IsA(node, Param)) + { + Param *p = (Param *) node; + + if (p->paramkind == PARAM_VARIABLE) + { + if (isAnyTempNamespace(get_session_variable_namespace(p->paramvarid))) + return true; + } + } return expression_tree_walker(node, - isQueryUsingTempRelation_walker, + isQueryUsingTempObject_walker, context); } diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 5546bf91e2e..d6d30e73ee8 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5250,7 +5250,7 @@ listVariables(const char *pattern, bool verbose) PQExpBufferData buf; PGresult *res; printQueryOpt myopt = pset.popt; - static const bool translate_columns[] = {false, false, false, false, false, false, false}; + static const bool translate_columns[] = {false, false, false, false, false, false, false, false}; if (pset.sversion < 180000) { @@ -5270,12 +5270,16 @@ listVariables(const char *pattern, bool verbose) " pg_catalog.format_type(v.vartype, v.vartypmod) as \"%s\",\n" " (SELECT c.collname FROM pg_catalog.pg_collation c, pg_catalog.pg_type bt\n" " WHERE c.oid = v.varcollation AND bt.oid = v.vartype AND v.varcollation <> bt.typcollation) as \"%s\",\n" - " pg_catalog.pg_get_userbyid(v.varowner) as \"%s\"\n", + " pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n" + " CASE v.varxactendaction\n" + " WHEN 'd' THEN 'ON COMMIT DROP'\n" + " END as \"%s\"\n", gettext_noop("Schema"), gettext_noop("Name"), gettext_noop("Type"), gettext_noop("Collation"), - gettext_noop("Owner")); + gettext_noop("Owner"), + gettext_noop("Transactional end action")); if (verbose) { diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index e3c830527ea..249f61878d1 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -3608,7 +3608,7 @@ match_previous_words(int pattern_id, /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ else if (TailMatches("CREATE", "TEMP|TEMPORARY")) - COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW"); + COMPLETE_WITH("SEQUENCE", "TABLE", "VARIABLE", "VIEW"); /* Complete "CREATE UNLOGGED" with TABLE or SEQUENCE */ else if (TailMatches("CREATE", "UNLOGGED")) COMPLETE_WITH("TABLE", "SEQUENCE"); @@ -3959,7 +3959,8 @@ match_previous_words(int pattern_id, } /* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* Complete CREATE VARIABLE with AS */ - else if (TailMatches("CREATE", "VARIABLE", MatchAny)) + else if (TailMatches("CREATE", "VARIABLE", MatchAny) || + TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny)) COMPLETE_WITH("AS"); else if (TailMatches("VARIABLE", MatchAny, "AS")) /* Complete CREATE VARIABLE with AS types */ diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h index 5b45be5afaf..0fc672bc2ba 100644 --- a/src/include/catalog/pg_variable.h +++ b/src/include/catalog/pg_variable.h @@ -56,6 +56,9 @@ CATALOG(pg_variable,9222,VariableRelationId) /* typmod for variable's type */ int32 vartypmod BKI_DEFAULT(-1); + /* action on transaction end */ + char varxactendaction BKI_DEFAULT(n); + /* variable collation */ Oid varcollation BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_collation); @@ -68,6 +71,12 @@ CATALOG(pg_variable,9222,VariableRelationId) #endif } FormData_pg_variable; +typedef enum VariableXactEndAction +{ + VARIABLE_XACTEND_NOOP = 'n', /* NOOP */ + VARIABLE_XACTEND_DROP = 'd', /* ON COMMIT DROP */ +} VariableXactEndAction; + /* ---------------- * Form_pg_variable corresponds to a pointer to a tuple with * the format of the pg_variable relation. diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h index 06ce9640085..23841ab373b 100644 --- a/src/include/commands/session_variable.h +++ b/src/include/commands/session_variable.h @@ -21,7 +21,11 @@ #include "tcop/cmdtag.h" #include "utils/queryenvironment.h" -extern void SessionVariableDropPostprocess(Oid varid); +extern void SessionVariableCreatePostprocess(Oid varid, char XactEndAction); +extern void SessionVariableDropPostprocess(Oid varid, char XactEndAction); +extern void AtPreEOXact_SessionVariables(bool isCommit); +extern void AtEOSubXact_SessionVariables(bool isCommit, SubTransactionId mySubid, + SubTransactionId parentSubid); extern void SetSessionVariable(Oid varid, Datum value, bool isNull); extern Datum GetSessionVariable(Oid varid, bool *isNull, Oid *typid); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index f88edc0d418..ec4a16aea24 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3487,6 +3487,7 @@ typedef struct CreateSessionVarStmt TypeName *typeName; /* the type of variable */ CollateClause *collClause; bool if_not_exists; /* do nothing if it already exists */ + char XactEndAction; /* on transaction end action */ } CreateSessionVarStmt; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 1fa21c4e179..95f8785d8af 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -51,7 +51,9 @@ typedef struct Alias List *colnames; /* optional list of column aliases */ } Alias; -/* What to do at commit time for temporary relations */ +/* + * What to do at commit time for temporary relations or session variables. + */ typedef enum OnCommitAction { ONCOMMIT_NOOP, /* No ON COMMIT clause (do nothing) */ diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index df6fd5550d7..be0d0abffe1 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -126,6 +126,6 @@ extern int attnameAttNum(Relation rd, const char *attname, bool sysColOK); extern const NameData *attnumAttName(Relation rd, int attid); extern Oid attnumTypeId(Relation rd, int attid); extern Oid attnumCollationId(Relation rd, int attid); -extern bool isQueryUsingTempRelation(Query *query); +extern bool isQueryUsingTempObject(Query *query); #endif /* PARSE_RELATION_H */ diff --git a/src/test/isolation/expected/session-variable.out b/src/test/isolation/expected/session-variable.out index 0a5579dc7ce..a609797dc5d 100644 --- a/src/test/isolation/expected/session-variable.out +++ b/src/test/isolation/expected/session-variable.out @@ -86,10 +86,11 @@ myvar step sr1: ROLLBACK; -starting permutation: create3 let3 s3 create4 let4 drop4 drop3 inval3 discard sc3 state +starting permutation: create3 let3 s3 o_c_d create4 let4 drop4 drop3 inval3 discard sc3 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 create4: CREATE VARIABLE myvar4 AS text; step let4: LET myvar4 = 'test'; step drop4: DROP VARIABLE myvar4; diff --git a/src/test/isolation/specs/session-variable.spec b/src/test/isolation/specs/session-variable.spec index c864fee4006..45e65d4085d 100644 --- a/src/test/isolation/specs/session-variable.spec +++ b/src/test/isolation/specs/session-variable.spec @@ -24,6 +24,7 @@ step create { CREATE VARIABLE myvar AS text; } 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 create4 { CREATE VARIABLE myvar4 AS text; } step let4 { LET myvar4 = 'test'; } step drop4 { DROP VARIABLE myvar4; } @@ -47,4 +48,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 create4 let4 drop4 drop3 inval3 discard sc3 state +permutation create3 let3 s3 o_c_d create4 let4 drop4 drop3 inval3 discard sc3 state diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 88e21194711..61077a52b16 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5935,20 +5935,20 @@ CREATE ROLE regress_variable_owner; SET ROLE TO regress_variable_owner; CREATE VARIABLE var1 AS varchar COLLATE "C"; \dV+ var1 - List of variables - Schema | Name | Type | Collation | Owner | Access privileges | Description ---------+------+-------------------+-----------+------------------------+-------------------+------------- - public | var1 | character varying | C | regress_variable_owner | | + List of variables + Schema | Name | Type | Collation | Owner | Transactional end action | Access privileges | Description +--------+------+-------------------+-----------+------------------------+--------------------------+-------------------+------------- + public | var1 | character varying | C | regress_variable_owner | | | (1 row) GRANT SELECT ON VARIABLE var1 TO PUBLIC; COMMENT ON VARIABLE var1 IS 'some description'; \dV+ var1 - List of variables - Schema | Name | Type | Collation | Owner | Access privileges | Description ---------+------+-------------------+-----------+------------------------+--------------------------------------------------+------------------ - public | var1 | character varying | C | regress_variable_owner | regress_variable_owner=rw/regress_variable_owner+| some description - | | | | | =r/regress_variable_owner | + List of variables + Schema | Name | Type | Collation | Owner | Transactional end action | Access privileges | Description +--------+------+-------------------+-----------+------------------------+--------------------------+--------------------------------------------------+------------------ + public | var1 | character varying | C | regress_variable_owner | | regress_variable_owner=rw/regress_variable_owner+| some description + | | | | | | =r/regress_variable_owner | (1 row) DROP VARIABLE var1; @@ -6416,9 +6416,9 @@ List of schemas (0 rows) \dV "no.such.variable" - List of variables - Schema | Name | Type | Collation | Owner ---------+------+------+-----------+------- + List of variables + Schema | Name | Type | Collation | Owner | Transactional end action +--------+------+------+-----------+-------+-------------------------- (0 rows) -- again, but with dotted schema qualifications. @@ -6591,9 +6591,9 @@ improper qualified name (too many dotted names): "no.such.schema"."no.such.insta \dy "no.such.schema"."no.such.event.trigger" improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger" \dV "no.such.schema"."no.such.variable" - List of variables - Schema | Name | Type | Collation | Owner ---------+------+------+-----------+------- + List of variables + Schema | Name | Type | Collation | Owner | Transactional end action +--------+------+------+-----------+-------+-------------------------- (0 rows) -- again, but with current database and dotted schema qualifications. @@ -6730,9 +6730,9 @@ List of text search templates (0 rows) \dV regression."no.such.schema"."no.such.variable" - List of variables - Schema | Name | Type | Collation | Owner ---------+------+------+-----------+------- + List of variables + Schema | Name | Type | Collation | Owner | Transactional end action +--------+------+------+-----------+-------+-------------------------- (0 rows) -- again, but with dotted database and dotted schema qualifications. diff --git a/src/test/regress/expected/session_variables.out b/src/test/regress/expected/session_variables.out index 7fc22083b15..10961a6416f 100644 --- a/src/test/regress/expected/session_variables.out +++ b/src/test/regress/expected/session_variables.out @@ -2109,3 +2109,123 @@ SELECT var1; DEALLOCATE p1; DROP VARIABLE var1; +-- temporary variables +CREATE TEMP VARIABLE var1 AS int; +-- this view should be temporary +CREATE VIEW var_test_view AS SELECT var1; +NOTICE: view "var_test_view" will be a temporary view +DROP VARIABLE var1 CASCADE; +NOTICE: drop cascades to view var_test_view +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + SELECT var1; + var1 +------ + 100 +(1 row) + +COMMIT; +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; + count +------- + 0 +(1 row) + +-- should be zero +SELECT count(*) FROM pg_session_variables(); + count +------- + 0 +(1 row) + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + SELECT var1; + var1 +------ + 100 +(1 row) + +ROLLBACK; +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; + count +------- + 0 +(1 row) + +-- should be zero +SELECT count(*) FROM pg_session_variables(); + count +------- + 0 +(1 row) + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + DROP VARIABLE var1; +COMMIT; +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; + count +------- + 0 +(1 row) + +-- should be zero +SELECT count(*) FROM pg_session_variables(); + count +------- + 0 +(1 row) + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + DROP VARIABLE var1; +ROLLBACK; +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; + count +------- + 0 +(1 row) + +-- should be zero +SELECT count(*) FROM pg_session_variables(); + count +------- + 0 +(1 row) + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + SAVEPOINT s1; + DROP VARIABLE var1; + ROLLBACK TO s1; + SELECT var1; + var1 +------ + 100 +(1 row) + +COMMIT; +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; + count +------- + 0 +(1 row) + +-- should be zero +SELECT count(*) FROM pg_session_variables(); + count +------- + 0 +(1 row) + diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql index e2c040372c3..8031489ff14 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -1196,6 +1196,7 @@ BEGIN; DROP VARIABLE var1; SELECT var2; ROLLBACK; + -- should be ok SELECT var1; @@ -1436,3 +1437,68 @@ SELECT var1; DEALLOCATE p1; DROP VARIABLE var1; + +-- temporary variables +CREATE TEMP VARIABLE var1 AS int; +-- this view should be temporary +CREATE VIEW var_test_view AS SELECT var1; + +DROP VARIABLE var1 CASCADE; + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + SELECT var1; +COMMIT; + +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; +-- should be zero +SELECT count(*) FROM pg_session_variables(); + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + SELECT var1; +ROLLBACK; + +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; +-- should be zero +SELECT count(*) FROM pg_session_variables(); + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + DROP VARIABLE var1; +COMMIT; + +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; +-- should be zero +SELECT count(*) FROM pg_session_variables(); + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + DROP VARIABLE var1; +ROLLBACK; + +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; +-- should be zero +SELECT count(*) FROM pg_session_variables(); + +BEGIN; + CREATE TEMP VARIABLE var1 AS int ON COMMIT DROP; + LET var1 = 100; + SAVEPOINT s1; + DROP VARIABLE var1; + ROLLBACK TO s1; + SELECT var1; +COMMIT; + +-- should be zero +SELECT count(*) FROM pg_variable WHERE varname = 'var1'; +-- should be zero +SELECT count(*) FROM pg_session_variables(); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index a05ce209dff..5c875ae6846 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2806,6 +2806,7 @@ SupportRequestWFuncMonotonic SVariable SVariableData SVariableState +SVariableXActDropItem Syn SyncOps SyncRepConfigData -- 2.47.1