From c9fecbc1d00e6b1bb04e87d98f60335978aefb60 Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Sun, 28 Jan 2024 21:36:54 +0100 Subject: [PATCH 10/20] 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 | 7 +- src/backend/catalog/pg_variable.c | 24 ++- src/backend/commands/session_variable.c | 190 +++++++++++++++++- 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.c | 5 +- src/include/catalog/pg_variable.h | 9 + src/include/commands/session_variable.h | 7 +- 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 | 130 +++++++++++- src/test/regress/sql/session_variables.sql | 66 ++++++ src/tools/pgindent/typedefs.list | 1 + 23 files changed, 521 insertions(+), 66 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index ac3b04ff912..67a54aa3e64 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9832,6 +9832,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 6165fa74449..bec8214ea07 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -5344,9 +5344,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 05bf67e3411..642346186f0 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 2d3ed8607ab..d877e18f96e 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2307,7 +2307,7 @@ CommitTransaction(void) smgrDoPendingSyncs(true, is_parallel_worker); /* Remove values of dropped session variables from memory */ - AtPreEOXact_SessionVariables(); + AtPreEOXact_SessionVariables(true); /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2899,6 +2899,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(); @@ -5175,6 +5176,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, @@ -5339,6 +5342,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 0b21204bc47..70b622aa672 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,7 +232,8 @@ 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); @@ -230,6 +241,8 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt) /* We want SessionVariableCreatePostprocess to see the catalog changes. */ CommandCounterIncrement(); + SessionVariableCreatePostprocess(variable.objectId, stmt->XactEndAction); + return variable; } @@ -242,6 +255,7 @@ DropVariableById(Oid varid) { Relation rel; HeapTuple tup; + char XactEndAction; rel = table_open(VariableRelationId, RowExclusiveLock); @@ -250,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); @@ -257,5 +273,5 @@ DropVariableById(Oid varid) table_close(rel, RowExclusiveLock); /* Do the necessary cleanup if needed in local memory */ - SessionVariableDropPostprocess(varid); + SessionVariableDropPostprocess(varid, XactEndAction); } diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index bd639d0b483..db55ca4b1c3 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/svariableReceiver.h" #include "funcapi.h" @@ -31,6 +33,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 values of session variables are stored in the backend's private memory * in the dedicated memory context SVariableMemoryContext in binary format. @@ -105,6 +120,12 @@ static bool needs_validation = false; */ 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. */ @@ -142,16 +163,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; @@ -174,6 +224,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 */ @@ -289,15 +390,92 @@ remove_invalid_session_variables(bool atEOX) } /* - * Remove values of dropped session variables from memory. + * Perform ON COMMIT DROP for temporary session variables, + * and remove all dropped variables from memory. */ void -AtPreEOXact_SessionVariables(void) +AtPreEOXact_SessionVariables(bool isCommit) { - /* We cannot to do it when transaction is aborted */ - Assert(IsTransactionState()); + 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); + 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; + } + } } /* diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index fdad8338324..6f74f14f6ae 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 06305036944..db75c6e2d88 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -3291,10 +3291,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 ea5a65b3ff7..9f7562bc311 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -480,6 +480,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 @@ -5221,26 +5222,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 2f64eaf0e37..bde5b1118c5 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); /* @@ -3821,13 +3821,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; @@ -3853,13 +3853,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 adf83ed1970..f6aa8bc5f6c 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5159,7 +5159,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) { @@ -5179,12 +5179,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.c b/src/bin/psql/tab-complete.c index da764033dba..ec54abb9a73 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3277,7 +3277,7 @@ psql_completion(const char *text, int start, int end) /* 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"); @@ -3593,7 +3593,8 @@ psql_completion(const char *text, int start, int end) } /* 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 db593cd5bed..1e6dd98d415 100644 --- a/src/include/catalog/pg_variable.h +++ b/src/include/catalog/pg_variable.h @@ -55,6 +55,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); @@ -67,6 +70,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 0d4c635ae7b..dc83296b1ba 100644 --- a/src/include/commands/session_variable.h +++ b/src/include/commands/session_variable.h @@ -21,8 +21,11 @@ #include "tcop/cmdtag.h" #include "utils/queryenvironment.h" -extern void SessionVariableDropPostprocess(Oid varid); -extern void AtPreEOXact_SessionVariables(void); +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 void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index fbab338a938..56e284dc09c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3455,6 +3455,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 5f7b83d13fa..dd8ed3965b7 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 bea2da54961..585eadf6524 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -124,6 +124,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 2968cce0895..9fbbf9057a6 100644 --- a/src/test/isolation/expected/session-variable.out +++ b/src/test/isolation/expected/session-variable.out @@ -85,10 +85,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 c89067490f7..ad7cd1ca390 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -5833,20 +5833,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; @@ -6314,9 +6314,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. @@ -6489,9 +6489,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. @@ -6628,9 +6628,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 44cfe7ed2d6..b8a1ce6e7ef 100644 --- a/src/test/regress/expected/session_variables.out +++ b/src/test/regress/expected/session_variables.out @@ -45,11 +45,11 @@ SET ROLE TO regress_variable_owner; CREATE VARIABLE svartest.var1 AS int; SET ROLE TO DEFAULT; \dV+ svartest.var1 - List of variables - Schema | Name | Type | Collation | Owner | Access privileges | Description -----------+------+---------+-----------+------------------------+--------------------------------------------------+------------- - svartest | var1 | integer | | regress_variable_owner | regress_variable_owner=rw/regress_variable_owner+| - | | | | | regress_variable_reader=r/regress_variable_owner | + List of variables + Schema | Name | Type | Collation | Owner | Transactional end action | Access privileges | Description +----------+------+---------+-----------+------------------------+--------------------------+--------------------------------------------------+------------- + svartest | var1 | integer | | regress_variable_owner | | regress_variable_owner=rw/regress_variable_owner+| + | | | | | | regress_variable_reader=r/regress_variable_owner | (1 row) DROP VARIABLE svartest.var1; @@ -1387,3 +1387,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 e7e16c02bc4..cd44a0b4a0c 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -813,6 +813,7 @@ BEGIN; DROP VARIABLE var1; SELECT var2; ROLLBACK; + -- should be ok SELECT var1; @@ -939,3 +940,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 5e2e5bcd806..0ab9ef134d0 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2789,6 +2789,7 @@ SupportRequestWFuncMonotonic SVariable SVariableData SVariableState +SVariableXActDropItem Syn SyncOps SyncRepConfigData -- 2.45.2