From b671b77ed595e79f4e774304f423d95636f42b8a Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Sun, 28 Jan 2024 21:36:54 +0100 Subject: [PATCH 10/19] 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 | 5 +- 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 | 3 +- 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, 520 insertions(+), 64 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 3af597d5ab..c45317a8ba 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9834,6 +9834,16 @@ SCRAM-SHA-256$<iteration count>:&l + + + vareoxaction 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 163dc74e06..e01a392962 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -5338,8 +5338,9 @@ SELECT current_user_id; been set to something else in the current session using the LET command. The content of a variable is not transactional. This is the same as regular variables in PL languages. - The session variables are persistent, but the content of session variables is - temporary and not shared (like the content of temporary tables). + The session variables can be persistent or can be temporary. In both cases, + the content of session variables is temporary and not 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 70a1a09595..cc276b78e7 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 ] @@ -110,6 +111,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 a8f5efad5d..b682effa31 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -2269,7 +2269,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); @@ -2861,6 +2861,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(); @@ -5132,6 +5133,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, @@ -5296,6 +5299,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 0b21204bc4..e9a6f755fd 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, + VariableEOXAction eoxaction); /* @@ -49,7 +50,8 @@ create_variable(const char *varName, int32 varTypmod, Oid varOwner, Oid varCollation, - bool if_not_exists) + bool if_not_exists, + VariableEOXAction eoxaction) { 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_vareoxaction - 1] = CharGetDatum(eoxaction); 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->eoxaction == VARIABLE_EOX_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->eoxaction); 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->eoxaction); + return variable; } @@ -242,6 +255,7 @@ DropVariableById(Oid varid) { Relation rel; HeapTuple tup; + char eoxaction; rel = table_open(VariableRelationId, RowExclusiveLock); @@ -250,6 +264,8 @@ DropVariableById(Oid varid) if (!HeapTupleIsValid(tup)) elog(ERROR, "cache lookup failed for variable %u", varid); + eoxaction = ((Form_pg_variable) GETSTRUCT(tup))->vareoxaction; + 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, eoxaction); } diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index d9169820a7..548a993026 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; + /* * Values of session variables are stored in the backend local memory * inside sessionvars hash table in binary format inside a dedicated memory @@ -113,6 +128,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. */ @@ -151,16 +172,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 eoxaction) +{ + /* + * For temporary variables, we need to create a new end of xact action to + * ensure deletion from catalog. + */ + if (eoxaction == VARIABLE_EOX_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 eoxaction) { Assert(LocalTransactionIdIsValid(MyProc->vxid.lxid)); + if (eoxaction == VARIABLE_EOX_DROP) + { + Assert(isTempNamespace(get_session_variable_namespace(varid))); + + unregister_session_variable_xact_drop(varid); + } + if (sessionvars) { bool found; @@ -183,6 +233,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 */ @@ -298,15 +399,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 fdad833832..6f74f14f6a 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 99cd0591f8..1f877b330f 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -3306,10 +3306,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 8f974e8500..5ffc573e53 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 OnEOXActionOption %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 OnEOXActionOption { 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->eoxaction = $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 OnEOXActionOption { 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->eoxaction = $11; n->if_not_exists = true; $$ = (Node *) n; } ; +/* + * Temporary session variables can be dropped on successful + * transaction end like tables. + */ +OnEOXActionOption: ON COMMIT DROP { $$ = VARIABLE_EOX_DROP; } + | /*EMPTY*/ { $$ = VARIABLE_EOX_NOOP; } + ; + + /***************************************************************************** * * ALTER EXTENSION name UPDATE [ TO version ] diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 2f64eaf0e3..bde5b1118c 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 5fd01b2b12..95eff5e947 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5149,7 +5149,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}; initPQExpBuffer(&buf); @@ -5159,12 +5159,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.vareoxaction\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 619e40a6e7..2ffc27d022 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3599,7 +3599,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 1f4112790a..afadd6873a 100644 --- a/src/include/catalog/pg_variable.h +++ b/src/include/catalog/pg_variable.h @@ -54,6 +54,9 @@ CATALOG(pg_variable,9222,VariableRelationId) /* typmod for variable's type */ int32 vartypmod BKI_DEFAULT(-1); + /* action on transaction end */ + char vareoxaction BKI_DEFAULT(n); + /* variable collation */ Oid varcollation BKI_DEFAULT(0) BKI_LOOKUP_OPT(pg_collation); @@ -66,6 +69,12 @@ CATALOG(pg_variable,9222,VariableRelationId) #endif } FormData_pg_variable; +typedef enum VariableEOXAction +{ + VARIABLE_EOX_NOOP = 'n', /* NOOP */ + VARIABLE_EOX_DROP = 'd', /* ON COMMIT DROP */ +} VariableEOXAction; + /* ---------------- * Form_pg_variable corresponds to a pointer to a tuple with * the format of pg_variable relation. diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h index 0d4c635ae7..7c8ceea20c 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 eoxaction); +extern void SessionVariableDropPostprocess(Oid varid, char eoxaction); +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 fbab338a93..44b5acd7cf 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 eoxaction; /* on commit action */ } CreateSessionVarStmt; diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 51525a2a56..1e229678bb 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 bea2da5496..585eadf652 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 2968cce089..9fbbf9057a 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 c864fee400..45e65d4085 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 c89067490f..ad7cd1ca39 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 da5ab4de8f..0c1cf710b3 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; @@ -1362,3 +1362,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 0182b62ab5..c911d1b2cb 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -792,6 +792,7 @@ BEGIN; DROP VARIABLE var1; SELECT var2; ROLLBACK; + -- should be ok SELECT var1; @@ -918,3 +919,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 50ac6c74f4..cd8a9f13fa 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2784,6 +2784,7 @@ SupportRequestWFuncMonotonic SVariable SVariableData SVariableState +SVariableXActDropItem Syn SyncOps SyncRepConfigData -- 2.45.2