From 1ada634eac0eadb7b199c6c73b3bacde723c0f72 Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Wed, 28 May 2025 11:26:17 +0200 Subject: [PATCH 02/15] CREATE, DROP, ALTER VARIABLE Implementation of commands: CREATE VARIABLE varname AS type DROP VARIABLE varname ALTER VARIABLE varname OWNER TO ALTER VARIABLE varname RENAME TO ALTER VARIABLE varname SET SCHEMA ALTER command uses already prepared infrastructure based on ObjectAddress API, so this patch implements ObjectAddress related functionality too. Event triggers for DDL over session variables are supported. --- doc/src/sgml/ddl.sgml | 21 ++ doc/src/sgml/glossary.sgml | 15 ++ doc/src/sgml/plpgsql.sgml | 14 ++ doc/src/sgml/ref/allfiles.sgml | 3 + doc/src/sgml/ref/alter_variable.sgml | 178 +++++++++++++++ doc/src/sgml/ref/comment.sgml | 1 + doc/src/sgml/ref/create_schema.sgml | 12 +- doc/src/sgml/ref/create_variable.sgml | 149 +++++++++++++ doc/src/sgml/ref/drop_variable.sgml | 117 ++++++++++ doc/src/sgml/reference.sgml | 3 + src/backend/catalog/aclchk.c | 4 + src/backend/catalog/dependency.c | 6 + src/backend/catalog/namespace.c | 207 ++++++++++++++++++ src/backend/catalog/objectaddress.c | 99 +++++++++ src/backend/catalog/pg_shdepend.c | 2 + src/backend/commands/Makefile | 1 + src/backend/commands/alter.c | 9 + src/backend/commands/dropcmds.c | 4 + src/backend/commands/event_trigger.c | 4 + src/backend/commands/meson.build | 1 + src/backend/commands/seclabel.c | 1 + src/backend/commands/session_variable.c | 88 ++++++++ src/backend/commands/tablecmds.c | 41 ++++ src/backend/commands/typecmds.c | 15 ++ src/backend/parser/gram.y | 86 +++++++- src/backend/parser/parse_utilcmd.c | 12 + src/backend/tcop/utility.c | 20 ++ src/backend/utils/cache/lsyscache.c | 65 ++++++ src/include/catalog/namespace.h | 6 + src/include/commands/session_variable.h | 24 ++ src/include/nodes/parsenodes.h | 16 ++ src/include/parser/kwlist.h | 1 + src/include/tcop/cmdtaglist.h | 3 + src/include/utils/lsyscache.h | 4 + src/test/regress/expected/dependency.out | 17 ++ .../expected/session_variables_ddl.out | 163 ++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/dependency.sql | 14 ++ .../regress/sql/session_variables_ddl.sql | 150 +++++++++++++ src/tools/pgindent/typedefs.list | 1 + 40 files changed, 1571 insertions(+), 8 deletions(-) create mode 100644 doc/src/sgml/ref/alter_variable.sgml create mode 100644 doc/src/sgml/ref/create_variable.sgml create mode 100644 doc/src/sgml/ref/drop_variable.sgml create mode 100644 src/backend/commands/session_variable.c create mode 100644 src/include/commands/session_variable.h create mode 100644 src/test/regress/expected/session_variables_ddl.out create mode 100644 src/test/regress/sql/session_variables_ddl.sql diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 65bc070d2e5..fa711a09bc4 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -5362,6 +5362,27 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01'; + + Session Variables + + + Session variables + + + + session variable + + + + Session variables are database objects that can hold a value. + + + + The session variable holds value in session memory. This value is private + to each session and is released when the session ends. + + + Other Database Objects diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml index 8651f0cdb91..c37fd5da50b 100644 --- a/doc/src/sgml/glossary.sgml +++ b/doc/src/sgml/glossary.sgml @@ -1711,6 +1711,21 @@ + + Session variable + + + A persistent database object that holds a value in session memory. This + value is private to each session and is released when the session ends. + Read or write access to session variables is controlled by privileges, + similar to other database objects. + + + For more information, see . + + + + Shared memory diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index e937491e6b8..1e4c43b8b61 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -6036,6 +6036,20 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE; + + + <command>Packages and package variables</command> + + + The PL/pgSQL language has no packages, and + therefore no package variables or package constants. + You can consider translating an Oracle package into a schema in + PostgreSQL. Package functions and procedures + would then become functions and procedures in that schema, and package + variables could be translated to session variables in that schema. + (see ). + + diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index f5be638867a..2f67de3e21b 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -47,6 +47,7 @@ Complete list of usable sgml source files in this directory. + @@ -99,6 +100,7 @@ Complete list of usable sgml source files in this directory. + @@ -147,6 +149,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/alter_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml new file mode 100644 index 00000000000..96d2586423e --- /dev/null +++ b/doc/src/sgml/ref/alter_variable.sgml @@ -0,0 +1,178 @@ + + + + + ALTER VARIABLE + + + + session variable + altering + + + + ALTER VARIABLE + 7 + SQL - Language Statements + + + + ALTER VARIABLE + + change the definition of a session variable + + + + + +ALTER VARIABLE name OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER } +ALTER VARIABLE name RENAME TO new_name +ALTER VARIABLE name SET SCHEMA new_schema + + + + + Description + + + The ALTER VARIABLE command changes the definition of an + existing session variable. There are several subforms: + + + + OWNER + + + This form changes the owner of the session variable. + + + + + + RENAME + + + This form changes the name of the session variable. + + + + + + SET SCHEMA + + + This form moves the session variable into another schema. + + + + + + + + + Only the owner or a superuser is allowed to alter a session variable. + In order to move a session variable from one schema to another, the user + must also have the CREATE privilege on the new schema (or + be a superuser). + + In order to move the session variable ownership from one role to another, + the user must also be a direct or indirect member of the new + owning role, and that role must have the CREATE privilege + on the session variable's schema (or be a superuser). These restrictions + enforce that altering the owner doesn't do anything you couldn't do by + dropping and recreating the session variable. + + + + + Parameters + + + + + name + + + The name (possibly schema-qualified) of the existing session variable + to alter. + + + + + + new_owner + + + The user name of the new owner of the session variable. + + + + + + new_name + + + The new name for the session variable. + + + + + + new_schema + + + The new schema for the session variable. + + + + + + + + + Examples + + + To rename a session variable: + +ALTER VARIABLE foo RENAME TO boo; + + + + + To change the owner of the session variable boo to + joe: + +ALTER VARIABLE boo OWNER TO joe; + + + + + To change the schema of the session variable boo to + private: + +ALTER VARIABLE boo SET SCHEMA private; + + + + + + Compatibility + + + Session variables and this command in particular are a PostgreSQL extension. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index 5b43c56b133..21cd80818fb 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -65,6 +65,7 @@ COMMENT ON TRANSFORM FOR type_name LANGUAGE lang_name | TRIGGER trigger_name ON table_name | TYPE object_name | + VARIABLE object_name | VIEW object_name } IS { string_literal | NULL } diff --git a/doc/src/sgml/ref/create_schema.sgml b/doc/src/sgml/ref/create_schema.sgml index ed69298ccc6..d2bb265209b 100644 --- a/doc/src/sgml/ref/create_schema.sgml +++ b/doc/src/sgml/ref/create_schema.sgml @@ -103,9 +103,10 @@ CREATE SCHEMA IF NOT EXISTS AUTHORIZATION role_sp schema. Currently, only CREATE TABLE, CREATE VIEW, CREATE INDEX, CREATE SEQUENCE, CREATE - TRIGGER and GRANT are accepted as clauses - within CREATE SCHEMA. Other kinds of objects may - be created in separate commands after the schema is created. + TRIGGER, GRANT and CREATE + VARIABLE are accepted as clauses within CREATE + SCHEMA. Other kinds of objects may be created in separate + commands after the schema is created. @@ -214,6 +215,11 @@ CREATE VIEW hollywood.winners AS The IF NOT EXISTS option is a PostgreSQL extension. + + + The CREATE VARIABLE command is a + PostgreSQL extension. + diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml new file mode 100644 index 00000000000..6e988f2e472 --- /dev/null +++ b/doc/src/sgml/ref/create_variable.sgml @@ -0,0 +1,149 @@ + + + + + CREATE VARIABLE + + + + session variable + defining + + + + CREATE VARIABLE + 7 + SQL - Language Statements + + + + CREATE VARIABLE + define a session variable + + + + +CREATE VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type [ COLLATE collation ] + + + + Description + + + The CREATE VARIABLE command creates a session variable. + Session variables, like relations, exist within a schema and their access is + controlled via the commands GRANT and REVOKE. + + + + The value of a session variable is local to the current session. Retrieving + a session variable's value returns NULL, unless its value is set to + something else in the current session with a LET command. + The content of a session variable is not transactional. This is the same as + regular variables in procedural languages. + + + + Session variables are retrieved by the SELECT + command. Their value is set with the LET command. + + + + + Session variables can be shadowed by other identifiers. + For details, see . + + + + + + Parameters + + + + + IF NOT EXISTS + + + Do not throw an error if the name already exists. A notice is issued in + this case. + + + + + + name + + + The name, optionally schema-qualified, of the session variable. + + + + + + data_type + + + The name, optionally schema-qualified, of the data type of the session + variable. + + + + + + COLLATE collation + + + The COLLATE clause assigns a collation to the session + variable (which must be of a collatable data type). If not specified, + the data type's default collation is used. + + + + + + + + + Notes + + + Use the DROP VARIABLE command to remove a session + variable. + + + + + Examples + + + Create an date session variable var1: + +CREATE VARIABLE var1 AS date; + + + + + + + Compatibility + + + The CREATE VARIABLE command is a + PostgreSQL extension. + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml new file mode 100644 index 00000000000..5bdb3560f0b --- /dev/null +++ b/doc/src/sgml/ref/drop_variable.sgml @@ -0,0 +1,117 @@ + + + + + DROP VARIABLE + + + + session variable + removing + + + + DROP VARIABLE + 7 + SQL - Language Statements + + + + DROP VARIABLE + remove a session variable + + + + +DROP VARIABLE [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] + + + + + Description + + + DROP VARIABLE removes a session variable. + A session variable can only be removed by its owner or a superuser. + + + + + Parameters + + + + IF EXISTS + + + Do not throw an error if the session variable does not exist. A notice is + issued in this case. + + + + + + name + + + The name, optionally schema-qualified, of a session variable. + + + + + + CASCADE + + + Automatically drop objects that depend on the session variable (such as + views), and in turn all objects that depend on those objects + (see ). + + + + + + RESTRICT + + + Refuse to drop the session variable if any objects depend on it. This is + the default. + + + + + + + + Examples + + + To remove the session variable var1: + + +DROP VARIABLE var1; + + + + + Compatibility + + + The DROP VARIABLE command is a + PostgreSQL extension. + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index ff85ace83fc..25578f3946c 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -75,6 +75,7 @@ &alterType; &alterUser; &alterUserMapping; + &alterVariable; &alterView; &analyze; &begin; @@ -127,6 +128,7 @@ &createType; &createUser; &createUserMapping; + &createVariable; &createView; &deallocate; &declare; @@ -175,6 +177,7 @@ &dropType; &dropUser; &dropUserMapping; + &dropVariable; &dropView; &end; &execute; diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index cd139bd65a6..00e3630e0ec 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -2784,6 +2784,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: case OBJECT_USER_MAPPING: + case OBJECT_VARIABLE: elog(ERROR, "unsupported object type: %d", objtype); } @@ -2891,6 +2892,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_TSDICTIONARY: msg = gettext_noop("must be owner of text search dictionary %s"); break; + case OBJECT_VARIABLE: + msg = gettext_noop("must be owner of session variable %s"); + break; /* * Special cases: For these, the error message talks diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 7dded634eb8..1d62e63d4f7 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -65,12 +65,14 @@ #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" +#include "catalog/pg_variable.h" #include "commands/comment.h" #include "commands/defrem.h" #include "commands/event_trigger.h" #include "commands/extension.h" #include "commands/policy.h" #include "commands/publicationcmds.h" +#include "commands/schemacmds.h" #include "commands/seclabel.h" #include "commands/sequence.h" #include "commands/trigger.h" @@ -1444,6 +1446,10 @@ doDeletion(const ObjectAddress *object, int flags) RemovePublicationById(object->objectId); break; + case VariableRelationId: + DropVariableById(object->objectId); + break; + case CastRelationId: case CollationRelationId: case ConversionRelationId: diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index d23474da4fb..ab837a3cb9e 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -41,6 +41,7 @@ #include "catalog/pg_ts_parser.h" #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "common/hashfn_unstable.h" #include "funcapi.h" #include "mb/pg_wchar.h" @@ -224,6 +225,7 @@ static bool TSParserIsVisibleExt(Oid prsId, bool *is_missing); static bool TSDictionaryIsVisibleExt(Oid dictId, bool *is_missing); static bool TSTemplateIsVisibleExt(Oid tmplId, bool *is_missing); static bool TSConfigIsVisibleExt(Oid cfgid, bool *is_missing); +static bool VariableIsVisibleExt(Oid varid, bool *is_missing); static void recomputeNamespacePath(void); static void AccessTempTableNamespace(bool force); static void InitTempTableNamespace(void); @@ -985,6 +987,84 @@ RelationIsVisibleExt(Oid relid, bool *is_missing) return visible; } +/* + * VariableIsVisible + * Determine whether a variable (identified by OID) is visible in the + * current search path. Visible means "would be found by searching + * for the unqualified variable name". + */ +bool +VariableIsVisible(Oid varid) +{ + return VariableIsVisibleExt(varid, NULL); +} + +/* + * VariableIsVisibleExt + * As above, but if the variable isn't found and is_missing is not NULL, + * then set *is_missing = true and return false, instead of throwing + * an error. (Caller must initialize *is_missing = false.) + */ +static bool +VariableIsVisibleExt(Oid varid, bool *is_missing) +{ + HeapTuple vartup; + Form_pg_variable varform; + Oid varnamespace; + bool visible; + + vartup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + if (!HeapTupleIsValid(vartup)) + { + if (is_missing != NULL) + { + *is_missing = true; + return false; + } + + elog(ERROR, "cache lookup failed for session variable %u", varid); + } + varform = (Form_pg_variable) GETSTRUCT(vartup); + + recomputeNamespacePath(); + + /* + * Quick check: if it ain't in the path at all, it ain't visible. We don't + * expect usage of session variables in the system namespace. + */ + varnamespace = varform->varnamespace; + if (!list_member_oid(activeSearchPath, varnamespace)) + visible = false; + else + { + /* + * If it is in the path, it might still not be visible; it could be + * hidden by another variable of the same name earlier in the path. So + * we must do a slow check for conflicting relations. + */ + char *varname = NameStr(varform->varname); + + visible = false; + foreach_oid(namespaceId, activeSearchPath) + { + if (namespaceId == varnamespace) + { + /* found it first in path */ + visible = true; + break; + } + if (OidIsValid(get_varname_varid(varname, namespaceId))) + { + /* found something else first in path */ + break; + } + } + } + + ReleaseSysCache(vartup); + + return visible; +} /* * TypenameGetTypid @@ -3359,6 +3439,133 @@ TSConfigIsVisibleExt(Oid cfgid, bool *is_missing) return visible; } +/* + * Returns oid of session variable specified by possibly qualified identifier. + * + * If not found, returns InvalidOid if missing_ok, else throws error. + */ +Oid +LookupVariable(const char *nspname, + const char *varname, + bool missing_ok) +{ + Oid varoid = InvalidOid; + + if (nspname) + { + Oid namespaceId = LookupExplicitNamespace(nspname, missing_ok); + + /* if nspname is a known namespace, the variable must be there */ + if (OidIsValid(namespaceId)) + { + varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid, + PointerGetDatum(varname), + ObjectIdGetDatum(namespaceId)); + } + } + else + { + /* iterate over the schemas on the search_path */ + recomputeNamespacePath(); + + foreach_oid(namespaceId, activeSearchPath) + { + varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid, + PointerGetDatum(varname), + ObjectIdGetDatum(namespaceId)); + + if (OidIsValid(varoid)) + break; + } + } + + if (!OidIsValid(varoid) && !missing_ok) + { + if (nspname) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("session variable \"%s.%s\" does not exist", + nspname, varname))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("session variable \"%s\" does not exist", + varname))); + } + + return varoid; +} + +/* + * Returns oid of session variable specified by possibly qualified identifier + * + * If not found, returns InvalidOid if missing_ok, else throws error. + */ +Oid +LookupVariableFromNameList(List *names, + bool missing_ok) +{ + char *catname = NULL; + char *nspname = NULL; + char *varname = NULL; + + switch (list_length(names)) + { + case 1: + varname = strVal(linitial(names)); + break; + case 2: + nspname = strVal(linitial(names)); + varname = strVal(lsecond(names)); + break; + case 3: + catname = strVal(linitial(names)); + nspname = strVal(lsecond(names)); + varname = strVal(lthird(names)); + + /* check catalog name */ + if (strcmp(catname, get_database_name(MyDatabaseId)) != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cross-database references are not implemented: %s", + NameListToString(names)))); + break; + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("improper session variable name (too many dotted names): %s", + NameListToString(names)))); + break; + } + + return LookupVariable(nspname, varname, missing_ok); +} + +/* + * The input list contains names with indirection expressions used as the left + * part of LET statement. The following routine returns a new list with only + * initial strings (names) - without indirection expressions. + */ +List * +NamesFromList(List *names) +{ + ListCell *l; + List *result = NIL; + + foreach(l, names) + { + Node *n = lfirst(l); + + if (IsA(n, String)) + { + result = lappend(result, n); + } + else + break; + } + + return result; +} /* * DeconstructQualifiedName diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index c75b7131ed7..ac1f8a4db3b 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -62,6 +62,7 @@ #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" +#include "catalog/pg_variable.h" #include "commands/defrem.h" #include "commands/event_trigger.h" #include "commands/extension.h" @@ -635,6 +636,20 @@ static const ObjectPropertyType ObjectProperty[] = OBJECT_USER_MAPPING, false }, + { + "session variable", + VariableRelationId, + VariableOidIndexId, + VARIABLEOID, + VARIABLENAMENSP, + Anum_pg_variable_oid, + Anum_pg_variable_varname, + Anum_pg_variable_varnamespace, + Anum_pg_variable_varowner, + Anum_pg_variable_varacl, + OBJECT_VARIABLE, + true + } }; /* @@ -830,6 +845,9 @@ static const struct object_type_map }, { "statistics object", OBJECT_STATISTIC_EXT + }, + { + "session variable", OBJECT_VARIABLE } }; @@ -855,6 +873,7 @@ static ObjectAddress get_object_address_attrdef(ObjectType objtype, bool missing_ok); static ObjectAddress get_object_address_type(ObjectType objtype, TypeName *typename, bool missing_ok); +static ObjectAddress get_object_address_variable(List *object, bool missing_ok); static ObjectAddress get_object_address_opcf(ObjectType objtype, List *object, bool missing_ok); static ObjectAddress get_object_address_opf_member(ObjectType objtype, @@ -1126,6 +1145,9 @@ get_object_address(ObjectType objtype, Node *object, missing_ok); address.objectSubId = 0; break; + case OBJECT_VARIABLE: + address = get_object_address_variable(castNode(List, object), missing_ok); + break; /* no default, to let compiler warn about missing case */ } @@ -2101,6 +2123,24 @@ textarray_to_strvaluelist(ArrayType *arr) return list; } +/* + * Find the ObjectAddress for a session variable + */ +static ObjectAddress +get_object_address_variable(List *object, bool missing_ok) +{ + ObjectAddress address; + char *nspname = NULL; + char *varname = NULL; + + ObjectAddressSet(address, VariableRelationId, InvalidOid); + + DeconstructQualifiedName(object, &nspname, &varname); + address.objectId = LookupVariable(nspname, varname, missing_ok); + + return address; +} + /* * SQL-callable version of get_object_address */ @@ -2295,6 +2335,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_TABCONSTRAINT: case OBJECT_OPCLASS: case OBJECT_OPFAMILY: + case OBJECT_VARIABLE: objnode = (Node *) name; break; case OBJECT_ACCESS_METHOD: @@ -2466,6 +2507,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, case OBJECT_STATISTIC_EXT: case OBJECT_TSDICTIONARY: case OBJECT_TSCONFIGURATION: + case OBJECT_VARIABLE: if (!object_ownercheck(address.classId, address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, objtype, NameListToString(castNode(List, object))); @@ -3495,6 +3537,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) break; } + case VariableRelationId: + { + char *nspname; + HeapTuple tup; + Form_pg_variable varform; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for session variable %u", + object->objectId); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + if (VariableIsVisible(object->objectId)) + nspname = NULL; + else + nspname = get_namespace_name(varform->varnamespace); + + appendStringInfo(&buffer, _("session variable %s"), + quote_qualified_identifier(nspname, + NameStr(varform->varname))); + + ReleaseSysCache(tup); + break; + } + case TSParserRelationId: { HeapTuple tup; @@ -4669,6 +4737,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok) appendStringInfoString(&buffer, "transform"); break; + case VariableRelationId: + appendStringInfoString(&buffer, "session variable"); + break; + default: elog(ERROR, "unsupported object class: %u", object->classId); } @@ -6019,6 +6091,33 @@ getObjectIdentityParts(const ObjectAddress *object, } break; + case VariableRelationId: + { + char *schema; + char *varname; + HeapTuple tup; + Form_pg_variable varform; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for session variable %u", + object->objectId); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + schema = get_namespace_name_or_temp(varform->varnamespace); + varname = NameStr(varform->varname); + + appendStringInfo(&buffer, "%s", + quote_qualified_identifier(schema, varname)); + + if (objname) + *objname = list_make2(schema, pstrdup(varname)); + + ReleaseSysCache(tup); + break; + } + default: elog(ERROR, "unsupported object class: %u", object->classId); } diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index 16e3e5c7457..6e3e8813328 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -46,6 +46,7 @@ #include "catalog/pg_ts_dict.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" +#include "catalog/pg_variable.h" #include "commands/alter.h" #include "commands/defrem.h" #include "commands/event_trigger.h" @@ -1714,6 +1715,7 @@ shdepReassignOwned_Owner(Form_pg_shdepend sdepForm, Oid newrole) case DatabaseRelationId: case TSConfigRelationId: case TSDictionaryRelationId: + case VariableRelationId: AlterObjectOwner_internal(sdepForm->classid, sdepForm->objid, newrole); diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index cb2fbdc7c60..aee40e7bd59 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -53,6 +53,7 @@ OBJS = \ schemacmds.o \ seclabel.o \ sequence.o \ + session_variable.o \ statscmds.o \ subscriptioncmds.o \ tablecmds.o \ diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index cb75e11fced..aaf61a6f61c 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -41,6 +41,7 @@ #include "catalog/pg_ts_dict.h" #include "catalog/pg_ts_parser.h" #include "catalog/pg_ts_template.h" +#include "catalog/pg_variable.h" #include "commands/alter.h" #include "commands/collationcmds.h" #include "commands/dbcommands.h" @@ -140,6 +141,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid) Assert(OidIsValid(nspOid)); msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\""); break; + case VariableRelationId: + Assert(OidIsValid(nspOid)); + msgfmt = gettext_noop("session variable \"%s\" already exists in schema \"%s\""); + break; default: elog(ERROR, "unsupported object class: %u", classId); break; @@ -435,6 +440,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_TSTEMPLATE: case OBJECT_PUBLICATION: case OBJECT_SUBSCRIPTION: + case OBJECT_VARIABLE: { ObjectAddress address; Relation catalog; @@ -575,6 +581,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, case OBJECT_TSDICTIONARY: case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: + case OBJECT_VARIABLE: { Relation catalog; Oid classId; @@ -657,6 +664,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, case TSDictionaryRelationId: case TSTemplateRelationId: case TSConfigRelationId: + case VariableRelationId: { Relation catalog; @@ -887,6 +895,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt) case OBJECT_TABLESPACE: case OBJECT_TSDICTIONARY: case OBJECT_TSCONFIGURATION: + case OBJECT_VARIABLE: { ObjectAddress address; diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c index ceb9a229b63..ebb585dc4a1 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -476,6 +476,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object) msg = gettext_noop("publication \"%s\" does not exist, skipping"); name = strVal(object); break; + case OBJECT_VARIABLE: + msg = gettext_noop("session variable \"%s\" does not exist, skipping"); + name = NameListToString(castNode(List, object)); + break; case OBJECT_COLUMN: case OBJECT_DATABASE: diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index f34868da5ab..6b46aeaef59 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -2281,6 +2281,8 @@ stringify_grant_objtype(ObjectType objtype) return "TABLESPACE"; case OBJECT_TYPE: return "TYPE"; + case OBJECT_VARIABLE: + return "VARIABLE"; /* these currently aren't used */ case OBJECT_ACCESS_METHOD: case OBJECT_AGGREGATE: @@ -2364,6 +2366,8 @@ stringify_adefprivs_objtype(ObjectType objtype) return "TABLESPACES"; case OBJECT_TYPE: return "TYPES"; + case OBJECT_VARIABLE: + return "VARIABLES"; /* these currently aren't used */ case OBJECT_ACCESS_METHOD: case OBJECT_AGGREGATE: diff --git a/src/backend/commands/meson.build b/src/backend/commands/meson.build index dd4cde41d32..101c8d75dd1 100644 --- a/src/backend/commands/meson.build +++ b/src/backend/commands/meson.build @@ -41,6 +41,7 @@ backend_sources += files( 'schemacmds.c', 'seclabel.c', 'sequence.c', + 'session_variable.c', 'statscmds.c', 'subscriptioncmds.c', 'tablecmds.c', diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index cee5d7bbb9c..57b4e6719c2 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -92,6 +92,7 @@ SecLabelSupportsObjectType(ObjectType objtype) case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: case OBJECT_USER_MAPPING: + case OBJECT_VARIABLE: return false; /* diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c new file mode 100644 index 00000000000..f641e00c1ac --- /dev/null +++ b/src/backend/commands/session_variable.c @@ -0,0 +1,88 @@ +/*------------------------------------------------------------------------- + * + * session_variable.c + * session variable creation/manipulation commands + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/session_variable.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/pg_variable.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "commands/session_variable.h" +#include "miscadmin.h" +#include "parser/parse_type.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" + +/* + * Creates a new variable + * + * Used by CREATE VARIABLE command + */ +ObjectAddress +CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt) +{ + Oid namespaceid; + AclResult aclresult; + Oid typid; + int32 typmod; + Oid varowner = GetUserId(); + Oid collation; + Oid typcollation; + ObjectAddress variable; + + namespaceid = + RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL); + + typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod); + + /* disallow pseudotypes */ + if (get_typtype(typid) == TYPTYPE_PSEUDO) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("session variable cannot be pseudo-type %s", + format_type_be(typid)))); + + aclresult = object_aclcheck(TypeRelationId, typid, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, typid); + + typcollation = get_typcollation(typid); + + if (stmt->collClause) + collation = LookupCollation(pstate, + stmt->collClause->collname, + stmt->collClause->location); + else + collation = typcollation; + + /* complain if COLLATE is applied to an uncollatable type */ + if (OidIsValid(collation) && !OidIsValid(typcollation)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("collations are not supported by type %s", + format_type_be(typid)), + parser_errposition(pstate, stmt->collClause->location))); + + variable = create_variable(stmt->variable->relname, + namespaceid, + typid, + typmod, + varowner, + collation, + stmt->if_not_exists); + + elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable", + stmt->variable->relname, variable.objectId); + + return variable; +} diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5fd8b51312c..8a807161a43 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -53,6 +53,7 @@ #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "catalog/toasting.h" @@ -6912,6 +6913,7 @@ ATTypedTableRecursion(List **wqueue, Relation rel, AlterTableCmd *cmd, * (possibly nested several levels deep in composite types, arrays, etc!). * Eventually, we'd like to propagate the check or rewrite operation * into such tables, but for now, just error out if we find any. + * Also, check if "typeOid" is used as type of some session variable. * * Caller should provide either the associated relation of a rowtype, * or a type name (not both) for use in the error message, if any. @@ -6975,6 +6977,45 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation, continue; } + /* check if the type is used as type of some session variable */ + if (pg_depend->classid == VariableRelationId) + { + Oid varid = pg_depend->objid; + + if (origTypeName) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it", + origTypeName, + get_namespace_name(get_session_variable_namespace(varid)), + get_session_variable_name(varid)))); + else if (origRelation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter type \"%s\" because session variable \"%s.%s\" uses it", + RelationGetRelationName(origRelation), + get_namespace_name(get_session_variable_namespace(varid)), + get_session_variable_name(varid)))); + else if (origRelation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter foreign table \"%s\" because session variable \"%s.%s\" uses it", + RelationGetRelationName(origRelation), + get_namespace_name(get_session_variable_namespace(varid)), + get_session_variable_name(varid)))); + else if (origRelation->rd_rel->relkind == RELKIND_RELATION || + origRelation->rd_rel->relkind == RELKIND_MATVIEW || + origRelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter table \"%s\" because session variable \"%s.%s\" uses it", + RelationGetRelationName(origRelation), + get_namespace_name(get_session_variable_namespace(varid)), + get_session_variable_name(varid)))); + + continue; + } + /* Else, ignore dependees that aren't relations */ if (pg_depend->classid != RelationRelationId) continue; diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 5979580139f..08666ff7a8b 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -53,6 +53,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_range.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "commands/defrem.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" @@ -3392,6 +3393,20 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode) } continue; } + else if (pg_depend->classid == VariableRelationId) + { + /* + * We cannot to validate constraint inside session variables from + * other sessions, so better to fail if there are any session + * variable, that use this domain. + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter domain \"%s\" because session variable \"%s.%s\" uses it", + domainTypeName, + get_namespace_name(get_session_variable_namespace(pg_depend->objid)), + get_session_variable_name(pg_depend->objid)))); + } /* Else, ignore dependees that aren't user columns of relations */ /* (we assume system columns are never of domain types) */ diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a4b29c822e8..cee1bb78f7b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -52,6 +52,7 @@ #include "catalog/namespace.h" #include "catalog/pg_am.h" #include "catalog/pg_trigger.h" +#include "catalog/pg_variable.h" #include "commands/defrem.h" #include "commands/trigger.h" #include "gramparse.h" @@ -290,8 +291,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt - CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt - CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt + CreateSchemaStmt CreateSeqStmt CreateSessionVarStmt CreateStmt CreateStatsStmt + CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt @@ -789,8 +790,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); UESCAPE UNBOUNDED UNCONDITIONAL UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED UNTIL UPDATE USER USING - VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING - VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE + VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARIABLE + VARYING VERBOSE VERSION_P VIEW VIEWS VIRTUAL VOLATILE WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE @@ -1056,6 +1057,7 @@ stmt: | CreatePolicyStmt | CreatePLangStmt | CreateSchemaStmt + | CreateSessionVarStmt | CreateSeqStmt | CreateStmt | CreateSubscriptionStmt @@ -1633,6 +1635,7 @@ schema_stmt: | CreateTrigStmt | GrantStmt | ViewStmt + | CreateSessionVarStmt ; @@ -5315,6 +5318,34 @@ create_extension_opt_item: } ; +/***************************************************************************** + * + * QUERY : + * CREATE VARIABLE varname [AS] type + * + *****************************************************************************/ + +CreateSessionVarStmt: + CREATE VARIABLE qualified_name opt_as Typename opt_collate_clause + { + CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt); + n->variable = $3; + n->typeName = $5; + n->collClause = (CollateClause *) $6; + n->if_not_exists = false; + $$ = (Node *) n; + } + | CREATE VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause + { + CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt); + n->variable = $6; + n->typeName = $8; + n->collClause = (CollateClause *) $9; + n->if_not_exists = true; + $$ = (Node *) n; + } + ; + /***************************************************************************** * * ALTER EXTENSION name UPDATE [ TO version ] @@ -7126,6 +7157,7 @@ object_type_any_name: | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; } | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; } | TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; /* @@ -10061,6 +10093,24 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name n->missing_ok = false; $$ = (Node *) n; } + | ALTER VARIABLE any_name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_VARIABLE; + n->object = (Node *) $3; + n->newname = $6; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER VARIABLE IF_P EXISTS any_name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_VARIABLE; + n->object = (Node *) $5; + n->newname = $8; + n->missing_ok = true; + $$ = (Node *)n; + } ; opt_column: COLUMN @@ -10422,6 +10472,24 @@ AlterObjectSchemaStmt: n->missing_ok = false; $$ = (Node *) n; } + | ALTER VARIABLE any_name SET SCHEMA name + { + AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); + n->objectType = OBJECT_VARIABLE; + n->object = (Node *) $3; + n->newschema = $6; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER VARIABLE IF_P EXISTS any_name SET SCHEMA name + { + AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); + n->objectType = OBJECT_VARIABLE; + n->object = (Node *) $5; + n->newschema = $8; + n->missing_ok = true; + $$ = (Node *)n; + } ; /***************************************************************************** @@ -10703,6 +10771,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec n->newowner = $6; $$ = (Node *) n; } + | ALTER VARIABLE any_name OWNER TO RoleSpec + { + AlterOwnerStmt *n = makeNode(AlterOwnerStmt); + n->objectType = OBJECT_VARIABLE; + n->object = (Node *) $3; + n->newowner = $6; + $$ = (Node *)n; + } ; @@ -18113,6 +18189,7 @@ unreserved_keyword: | VALIDATE | VALIDATOR | VALUE_P + | VARIABLE | VARYING | VERSION_P | VIEW @@ -18769,6 +18846,7 @@ bare_label_keyword: | VALUE_P | VALUES | VARCHAR + | VARIABLE | VARIADIC | VERBOSE | VERSION_P diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index e96b38a59d5..059d8f7f180 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -105,6 +105,7 @@ typedef struct List *indexes; /* CREATE INDEX items */ List *triggers; /* CREATE TRIGGER items */ List *grants; /* GRANT items */ + List *variables; /* CREATE VARIABLE items */ } CreateSchemaStmtContext; @@ -4112,6 +4113,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName) cxt.indexes = NIL; cxt.triggers = NIL; cxt.grants = NIL; + cxt.variables = NIL; /* * Run through each schema element in the schema element list. Separate @@ -4180,6 +4182,15 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName) cxt.grants = lappend(cxt.grants, element); break; + case T_CreateSessionVarStmt: + { + CreateSessionVarStmt *elp = (CreateSessionVarStmt *) element; + + setSchemaName(cxt.schemaname, &elp->variable->schemaname); + cxt.variables = lappend(cxt.variables, element); + } + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(element)); @@ -4193,6 +4204,7 @@ transformCreateSchemaStmtElements(List *schemaElts, const char *schemaName) result = list_concat(result, cxt.indexes); result = list_concat(result, cxt.triggers); result = list_concat(result, cxt.grants); + result = list_concat(result, cxt.variables); return result; } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 918db53dd5e..1a0e11ba701 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -48,6 +48,7 @@ #include "commands/schemacmds.h" #include "commands/seclabel.h" #include "commands/sequence.h" +#include "commands/session_variable.h" #include "commands/subscriptioncmds.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" @@ -182,6 +183,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree) case T_CreateRangeStmt: case T_CreateRoleStmt: case T_CreateSchemaStmt: + case T_CreateSessionVarStmt: case T_CreateSeqStmt: case T_CreateStatsStmt: case T_CreateStmt: @@ -1380,6 +1382,10 @@ ProcessUtilitySlow(ParseState *pstate, } break; + case T_CreateSessionVarStmt: + address = CreateVariable(pstate, (CreateSessionVarStmt *) parsetree); + break; + /* * ************* object creation / destruction ************** */ @@ -2333,6 +2339,9 @@ AlterObjectTypeCommandTag(ObjectType objtype) case OBJECT_STATISTIC_EXT: tag = CMDTAG_ALTER_STATISTICS; break; + case OBJECT_VARIABLE: + tag = CMDTAG_ALTER_VARIABLE; + break; default: tag = CMDTAG_UNKNOWN; break; @@ -2641,6 +2650,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_STATISTIC_EXT: tag = CMDTAG_DROP_STATISTICS; break; + case OBJECT_VARIABLE: + tag = CMDTAG_DROP_VARIABLE; + break; default: tag = CMDTAG_UNKNOWN; } @@ -3217,6 +3229,10 @@ CreateCommandTag(Node *parsetree) } break; + case T_CreateSessionVarStmt: + tag = CMDTAG_CREATE_VARIABLE; + break; + default: elog(WARNING, "unrecognized node type: %d", (int) nodeTag(parsetree)); @@ -3751,6 +3767,10 @@ GetCommandLogLevel(Node *parsetree) } break; + case T_CreateSessionVarStmt: + lev = LOGSTMT_DDL; + break; + default: elog(WARNING, "unrecognized node type: %d", (int) nodeTag(parsetree)); diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index fa7cd7e06a7..1c4031eea23 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -40,6 +40,7 @@ #include "catalog/pg_subscription.h" #include "catalog/pg_transform.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "utils/array.h" @@ -3881,3 +3882,67 @@ get_subscription_name(Oid subid, bool missing_ok) return subname; } + +/* ---------- PG_VARIABLE CACHE ---------- */ + +/* + * get_varname_varid + * Given name and namespace of variable, look up the OID. + */ +Oid +get_varname_varid(const char *varname, Oid varnamespace) +{ + return GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid, + PointerGetDatum(varname), + ObjectIdGetDatum(varnamespace)); +} + +/* + * get_session_variable_name + * Returns a palloc'd copy of the name of a given session variable. + */ +char * +get_session_variable_name(Oid varid) +{ + HeapTuple tup; + Form_pg_variable varform; + char *varname; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for session variable %u", varid); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + varname = pstrdup(NameStr(varform->varname)); + + ReleaseSysCache(tup); + + return varname; +} + +/* + * get_session_variable_namespace + * Returns the pg_namespace OID associated with a given session variable. + */ +Oid +get_session_variable_namespace(Oid varid) +{ + HeapTuple tup; + Form_pg_variable varform; + Oid varnamespace; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for variable %u", varid); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + varnamespace = varform->varnamespace; + + ReleaseSysCache(tup); + + return varnamespace; +} diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index f1423f28c32..d12c3b957b7 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -115,6 +115,8 @@ extern Oid TypenameGetTypid(const char *typname); extern Oid TypenameGetTypidExtended(const char *typname, bool temp_ok); extern bool TypeIsVisible(Oid typid); +extern bool VariableIsVisible(Oid varid); + extern FuncCandidateList FuncnameGetCandidates(List *names, int nargs, List *argnames, bool expand_variadic, @@ -189,6 +191,10 @@ extern SearchPathMatcher *GetSearchPathMatcher(MemoryContext context); extern SearchPathMatcher *CopySearchPathMatcher(SearchPathMatcher *path); extern bool SearchPathMatchesCurrentEnvironment(SearchPathMatcher *path); +extern List *NamesFromList(List *names); +extern Oid LookupVariable(const char *nspname, const char *varname, bool missing_ok); +extern Oid LookupVariableFromNameList(List *names, bool missing_ok); + extern Oid get_collation_oid(List *collname, bool missing_ok); extern Oid get_conversion_oid(List *conname, bool missing_ok); extern Oid FindDefaultConversionProc(int32 for_encoding, int32 to_encoding); diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h new file mode 100644 index 00000000000..49f36ac6885 --- /dev/null +++ b/src/include/commands/session_variable.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * sessionvariable.h + * prototypes for sessionvariable.c. + * + * + * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/session_variable.h + * + *------------------------------------------------------------------------- + */ + +#ifndef SESSIONVARIABLE_H +#define SESSIONVARIABLE_H + +#include "catalog/objectaddress.h" +#include "parser/parse_node.h" +#include "nodes/parsenodes.h" + +extern ObjectAddress CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt); + +#endif diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ecbddd12e1b..6dde01f2c7b 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2373,6 +2373,7 @@ typedef enum ObjectType OBJECT_TSTEMPLATE, OBJECT_TYPE, OBJECT_USER_MAPPING, + OBJECT_VARIABLE, OBJECT_VIEW, } ObjectType; @@ -3555,6 +3556,21 @@ typedef struct AlterStatsStmt bool missing_ok; /* skip error if statistics object is missing */ } AlterStatsStmt; + +/* ---------------------- + * {Create|Alter} VARIABLE Statement + * ---------------------- + */ +typedef struct CreateSessionVarStmt +{ + NodeTag type; + RangeVar *variable; /* the variable to create */ + TypeName *typeName; /* the type of variable */ + CollateClause *collClause; + bool if_not_exists; /* do nothing if it already exists */ +} CreateSessionVarStmt; + + /* ---------------------- * Create Function Statement * ---------------------- diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 84182eaaae2..5e6d3aff8af 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -488,6 +488,7 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD, BARE_LABEL) +PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h index d250a714d59..ea86954dded 100644 --- a/src/include/tcop/cmdtaglist.h +++ b/src/include/tcop/cmdtaglist.h @@ -68,6 +68,7 @@ PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false) PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false) PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false) PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false) +PG_CMDTAG(CMDTAG_ALTER_VARIABLE, "ALTER VARIABLE", true, false, false) PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false) PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false) PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false) @@ -123,6 +124,7 @@ PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false) PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false) PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false) PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false) +PG_CMDTAG(CMDTAG_CREATE_VARIABLE, "CREATE VARIABLE", true, false, false) PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false) PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false) PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false) @@ -175,6 +177,7 @@ PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false) PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false) PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false) PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false) +PG_CMDTAG(CMDTAG_DROP_VARIABLE, "DROP VARIABLE", true, false, false) PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false) PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false) PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false) diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index 50fb149e9ac..03c4c58580e 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -139,6 +139,7 @@ extern char get_func_prokind(Oid funcid); extern bool get_func_leakproof(Oid funcid); extern RegProcedure get_func_support(Oid funcid); extern Oid get_relname_relid(const char *relname, Oid relnamespace); +extern Oid get_varname_varid(const char *varname, Oid varnamespace); extern char *get_rel_name(Oid relid); extern Oid get_rel_namespace(Oid relid); extern Oid get_rel_type_id(Oid relid); @@ -211,6 +212,9 @@ extern char *get_publication_name(Oid pubid, bool missing_ok); extern Oid get_subscription_oid(const char *subname, bool missing_ok); extern char *get_subscription_name(Oid subid, bool missing_ok); +extern char *get_session_variable_name(Oid varid); +extern Oid get_session_variable_namespace(Oid varid); + #define type_is_array(typid) (get_element_type(typid) != InvalidOid) /* type_is_array_domain accepts both plain arrays and domains over arrays */ #define type_is_array_domain(typid) (get_base_element_type(typid) != InvalidOid) diff --git a/src/test/regress/expected/dependency.out b/src/test/regress/expected/dependency.out index 75a078ada9e..cd8e4412fa9 100644 --- a/src/test/regress/expected/dependency.out +++ b/src/test/regress/expected/dependency.out @@ -151,3 +151,20 @@ owner of type deptest_t DROP OWNED BY regress_dep_user2, regress_dep_user0; DROP USER regress_dep_user2; DROP USER regress_dep_user0; +-- dependency on type +CREATE DOMAIN vardomain AS int; +CREATE TYPE vartype AS (a int, b int, c vardomain); +CREATE VARIABLE var1 AS vartype; +-- should fail +DROP DOMAIN vardomain; +ERROR: cannot drop type vardomain because other objects depend on it +DETAIL: column c of composite type vartype depends on type vardomain +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TYPE vartype; +ERROR: cannot drop type vartype because other objects depend on it +DETAIL: session variable var1 depends on type vartype +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- clean up +DROP VARIABLE var1; +DROP TYPE vartype; +DROP DOMAIN vardomain; diff --git a/src/test/regress/expected/session_variables_ddl.out b/src/test/regress/expected/session_variables_ddl.out new file mode 100644 index 00000000000..9c7595e9a41 --- /dev/null +++ b/src/test/regress/expected/session_variables_ddl.out @@ -0,0 +1,163 @@ +SET log_statement TO ddl; +CREATE VARIABLE ddltest_sesvar01 AS int; +CREATE VARIABLE public.ddltest_sesvar02 AS int; +CREATE SCHEMA sesvartest_ddl; +CREATE VARIABLE sesvartest_ddl.ddltest_sesvar03 AS int; +SELECT pg_identify_object_as_address(classid, objid, objsubid) + FROM pg_get_object_address('session variable', '{ddltest_sesvar01}', '{}'); + pg_identify_object_as_address +----------------------------------------------------- + ("session variable","{public,ddltest_sesvar01}",{}) +(1 row) + +SELECT pg_identify_object_as_address(classid, objid, objsubid) + FROM pg_get_object_address('session variable', '{public,ddltest_sesvar02}', '{}'); + pg_identify_object_as_address +----------------------------------------------------- + ("session variable","{public,ddltest_sesvar02}",{}) +(1 row) + +SELECT pg_identify_object_as_address(classid, objid, objsubid) + FROM pg_get_object_address('session variable', '{sesvartest_ddl,ddltest_sesvar03}', '{}'); + pg_identify_object_as_address +------------------------------------------------------------- + ("session variable","{sesvartest_ddl,ddltest_sesvar03}",{}) +(1 row) + +DROP VARIABLE ddltest_sesvar01; +DROP VARIABLE public.ddltest_sesvar02; +CREATE TYPE sesvartest_type_ddl AS (a int, b int); +CREATE DOMAIN sesvartest_domain_ddl AS int; +CREATE TABLE sesvartest_table_ddl (a int, b int); +/* prefix ddltest_ should not be used ever in another tests */ +CREATE VARIABLE ddltest_sesvar04 AS sesvartest_type_ddl; +CREATE VARIABLE ddltest_sesvar05 AS sesvartest_domain_ddl; +CREATE VARIABLE ddltest_sesvar06 AS sesvartest_table_ddl; +-- add new field to composite value is supported, +-- change type of field is prohibited +-- should be ok +ALTER TYPE sesvartest_type_ddl ADD ATTRIBUTE c int; +ALTER TABLE sesvartest_table_ddl ADD COLUMN c int; +-- should fail +ALTER TYPE sesvartest_type_ddl ALTER ATTRIBUTE b TYPE numeric; +ERROR: cannot alter type "sesvartest_type_ddl" because session variable "public.ddltest_sesvar04" uses it +ALTER TABLE sesvartest_table_ddl ALTER COLUMN b TYPE numeric; +ERROR: cannot alter table "sesvartest_table_ddl" because session variable "public.ddltest_sesvar06" uses it +ALTER DOMAIN sesvartest_domain_ddl ADD CHECK(value <> 100); +ERROR: cannot alter domain "sesvartest_domain_ddl" because session variable "public.ddltest_sesvar05" uses it +-- should fail +DROP TYPE sesvartest_type_ddl; +ERROR: cannot drop type sesvartest_type_ddl because other objects depend on it +DETAIL: session variable ddltest_sesvar04 depends on type sesvartest_type_ddl +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP DOMAIN sesvartest_domain_ddl; +ERROR: cannot drop type sesvartest_domain_ddl because other objects depend on it +DETAIL: session variable ddltest_sesvar05 depends on type sesvartest_domain_ddl +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE sesvartest_table_ddl; +ERROR: cannot drop table sesvartest_table_ddl because other objects depend on it +DETAIL: session variable ddltest_sesvar06 depends on type sesvartest_table_ddl +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- check event trigger support +CREATE OR REPLACE FUNCTION svar_event_trigger_report_dropped() +RETURNS event_trigger +AS $$ +DECLARE r record; +BEGIN + FOR r IN SELECT * from pg_event_trigger_dropped_objects() + LOOP + IF r.classid = 'pg_variable'::regclass AND + r.address_names[2] like 'ddltest_sesvar%' + THEN + RAISE NOTICE + 'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%', + r.original, r.normal, r.is_temporary, r.object_type, + r.object_identity, r.address_names, r.address_args; + END IF; + END LOOP; +END; +$$ LANGUAGE plpgsql; +CREATE EVENT TRIGGER svar_regress_event_trigger_report_dropped ON sql_drop + WHEN TAG IN ('DROP VARIABLE', 'DROP SCHEMA') + EXECUTE PROCEDURE svar_event_trigger_report_dropped(); +DROP VARIABLE ddltest_sesvar04; +NOTICE: NORMAL: orig=t normal=f istemp=f type=session variable identity=public.ddltest_sesvar04 name={public,ddltest_sesvar04} args={} +DROP VARIABLE ddltest_sesvar05; +NOTICE: NORMAL: orig=t normal=f istemp=f type=session variable identity=public.ddltest_sesvar05 name={public,ddltest_sesvar05} args={} +DROP VARIABLE ddltest_sesvar06; +NOTICE: NORMAL: orig=t normal=f istemp=f type=session variable identity=public.ddltest_sesvar06 name={public,ddltest_sesvar06} args={} +-- should to fail +DROP SCHEMA sesvartest_ddl; +ERROR: cannot drop schema sesvartest_ddl because other objects depend on it +DETAIL: session variable sesvartest_ddl.ddltest_sesvar03 depends on schema sesvartest_ddl +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- should be ok +DROP SCHEMA sesvartest_ddl CASCADE; +NOTICE: drop cascades to session variable sesvartest_ddl.ddltest_sesvar03 +NOTICE: NORMAL: orig=f normal=t istemp=f type=session variable identity=sesvartest_ddl.ddltest_sesvar03 name={sesvartest_ddl,ddltest_sesvar03} args={} +DROP EVENT TRIGGER svar_regress_event_trigger_report_dropped; +DROP FUNCTION svar_event_trigger_report_dropped(); +-- should be ok +DROP TYPE sesvartest_type_ddl; +DROP DOMAIN sesvartest_domain_ddl; +DROP TABLE sesvartest_table_ddl; +-- check comment on variable +CREATE VARIABLE ddltest_sesvar07 AS int; +COMMENT ON VARIABLE ddltest_sesvar07 IS 'some session variable comment'; +SELECT pg_catalog.obj_description(oid, 'pg_variable') FROM pg_variable WHERE varname = 'ddltest_sesvar07'; + obj_description +------------------------------- + some session variable comment +(1 row) + +DROP VARIABLE ddltest_sesvar07; +CREATE VARIABLE ddltest_sesvar08 AS int; +ALTER VARIABLE ddltest_sesvar08 RENAME TO ddltest_sesvar08_renamed; +CREATE SCHEMA sesvartest_ddl; +ALTER VARIABLE ddltest_sesvar08_renamed SET SCHEMA sesvartest_ddl; +CREATE ROLE regress_variable_owner_ddl; +GRANT ALL ON SCHEMA sesvartest_ddl TO regress_variable_owner_ddl; +SET ROLE TO regress_variable_owner_ddl; +-- should fail +DROP VARIABLE sesvartest_ddl.ddltest_sesvar08_renamed; +ERROR: must be owner of session variable sesvartest_ddl.ddltest_sesvar08_renamed +SET ROLE TO DEFAULT; +ALTER VARIABLE sesvartest_ddl.ddltest_sesvar08_renamed OWNER TO regress_variable_owner_ddl; +-- should fail +DROP ROLE regress_variable_owner_ddl; +ERROR: role "regress_variable_owner_ddl" cannot be dropped because some objects depend on it +DETAIL: owner of session variable sesvartest_ddl.ddltest_sesvar08_renamed +privileges for schema sesvartest_ddl +-- should fail - not on search path +DROP VARIABLE ddltest_sesvar08_renamed; +ERROR: session variable "ddltest_sesvar08_renamed" does not exist +SET SEARCH_PATH TO 'sesvartest_ddl'; +-- should be ok +DROP VARIABLE ddltest_sesvar08_renamed; +SET SEARCH_PATH TO DEFAULT; +SET ROLE TO DEFAULT; +DROP SCHEMA sesvartest_ddl; +DROP ROLE regress_variable_owner_ddl; +SET log_statement TO DEFAULT; +CREATE VARIABLE IF NOT EXISTS ddltest_sesvar09 AS int; +CREATE VARIABLE IF NOT EXISTS ddltest_sesvar09 AS int; +NOTICE: session variable "ddltest_sesvar09" already exists, skipping +DROP VARIABLE IF EXISTS ddltest_sesvar09; +DROP VARIABLE IF EXISTS ddltest_sesvar09; +NOTICE: session variable "ddltest_sesvar09" does not exist, skipping +CREATE SCHEMA svartest01_ddl CREATE VARIABLE sesvar10 AS int; +CREATE VARIABLE svartest01_ddl.sesvar11 AS int; +CREATE SCHEMA svartest02_ddl CREATE VARIABLE sesvar10 AS int; +-- should to fail +CREATE VARIABLE svartest01_ddl.sesvar10 AS int; +ERROR: session variable "sesvar10" already exists +ALTER VARIABLE svartest01_ddl.sesvar11 RENAME TO sesvar10; +ERROR: session variable "sesvar10" already exists in schema "svartest01_ddl" +ALTER VARIABLE svartest02_ddl.sesvar10 SET SCHEMA svartest01_ddl; +ERROR: session variable "sesvar10" already exists in schema "svartest01_ddl" +DROP SCHEMA svartest01_ddl CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to session variable svartest01_ddl.sesvar10 +drop cascades to session variable svartest01_ddl.sesvar11 +DROP SCHEMA svartest02_ddl CASCADE; +NOTICE: drop cascades to session variable svartest02_ddl.sesvar10 diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index a0f5fab0f5d..1bcf69031da 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -115,7 +115,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson # NB: temp.sql does reconnects which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- -test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml session_variables_ddl # ---------- # Another group of parallel tests diff --git a/src/test/regress/sql/dependency.sql b/src/test/regress/sql/dependency.sql index 8d74ed7122c..6c18b7f840a 100644 --- a/src/test/regress/sql/dependency.sql +++ b/src/test/regress/sql/dependency.sql @@ -114,3 +114,17 @@ DROP USER regress_dep_user2; DROP OWNED BY regress_dep_user2, regress_dep_user0; DROP USER regress_dep_user2; DROP USER regress_dep_user0; + +-- dependency on type +CREATE DOMAIN vardomain AS int; +CREATE TYPE vartype AS (a int, b int, c vardomain); +CREATE VARIABLE var1 AS vartype; + +-- should fail +DROP DOMAIN vardomain; +DROP TYPE vartype; + +-- clean up +DROP VARIABLE var1; +DROP TYPE vartype; +DROP DOMAIN vardomain; diff --git a/src/test/regress/sql/session_variables_ddl.sql b/src/test/regress/sql/session_variables_ddl.sql new file mode 100644 index 00000000000..f844469ecb1 --- /dev/null +++ b/src/test/regress/sql/session_variables_ddl.sql @@ -0,0 +1,150 @@ +SET log_statement TO ddl; + +CREATE VARIABLE ddltest_sesvar01 AS int; +CREATE VARIABLE public.ddltest_sesvar02 AS int; +CREATE SCHEMA sesvartest_ddl; +CREATE VARIABLE sesvartest_ddl.ddltest_sesvar03 AS int; + +SELECT pg_identify_object_as_address(classid, objid, objsubid) + FROM pg_get_object_address('session variable', '{ddltest_sesvar01}', '{}'); + +SELECT pg_identify_object_as_address(classid, objid, objsubid) + FROM pg_get_object_address('session variable', '{public,ddltest_sesvar02}', '{}'); + +SELECT pg_identify_object_as_address(classid, objid, objsubid) + FROM pg_get_object_address('session variable', '{sesvartest_ddl,ddltest_sesvar03}', '{}'); + +DROP VARIABLE ddltest_sesvar01; +DROP VARIABLE public.ddltest_sesvar02; + +CREATE TYPE sesvartest_type_ddl AS (a int, b int); +CREATE DOMAIN sesvartest_domain_ddl AS int; +CREATE TABLE sesvartest_table_ddl (a int, b int); + +/* prefix ddltest_ should not be used ever in another tests */ +CREATE VARIABLE ddltest_sesvar04 AS sesvartest_type_ddl; +CREATE VARIABLE ddltest_sesvar05 AS sesvartest_domain_ddl; +CREATE VARIABLE ddltest_sesvar06 AS sesvartest_table_ddl; + +-- add new field to composite value is supported, +-- change type of field is prohibited + +-- should be ok +ALTER TYPE sesvartest_type_ddl ADD ATTRIBUTE c int; +ALTER TABLE sesvartest_table_ddl ADD COLUMN c int; + +-- should fail +ALTER TYPE sesvartest_type_ddl ALTER ATTRIBUTE b TYPE numeric; +ALTER TABLE sesvartest_table_ddl ALTER COLUMN b TYPE numeric; +ALTER DOMAIN sesvartest_domain_ddl ADD CHECK(value <> 100); + +-- should fail +DROP TYPE sesvartest_type_ddl; +DROP DOMAIN sesvartest_domain_ddl; +DROP TABLE sesvartest_table_ddl; + +-- check event trigger support +CREATE OR REPLACE FUNCTION svar_event_trigger_report_dropped() +RETURNS event_trigger +AS $$ +DECLARE r record; +BEGIN + FOR r IN SELECT * from pg_event_trigger_dropped_objects() + LOOP + IF r.classid = 'pg_variable'::regclass AND + r.address_names[2] like 'ddltest_sesvar%' + THEN + RAISE NOTICE + 'NORMAL: orig=% normal=% istemp=% type=% identity=% name=% args=%', + r.original, r.normal, r.is_temporary, r.object_type, + r.object_identity, r.address_names, r.address_args; + END IF; + END LOOP; +END; +$$ LANGUAGE plpgsql; + +CREATE EVENT TRIGGER svar_regress_event_trigger_report_dropped ON sql_drop + WHEN TAG IN ('DROP VARIABLE', 'DROP SCHEMA') + EXECUTE PROCEDURE svar_event_trigger_report_dropped(); + +DROP VARIABLE ddltest_sesvar04; +DROP VARIABLE ddltest_sesvar05; +DROP VARIABLE ddltest_sesvar06; + +-- should to fail +DROP SCHEMA sesvartest_ddl; + +-- should be ok +DROP SCHEMA sesvartest_ddl CASCADE; + +DROP EVENT TRIGGER svar_regress_event_trigger_report_dropped; + +DROP FUNCTION svar_event_trigger_report_dropped(); + +-- should be ok +DROP TYPE sesvartest_type_ddl; +DROP DOMAIN sesvartest_domain_ddl; +DROP TABLE sesvartest_table_ddl; + +-- check comment on variable +CREATE VARIABLE ddltest_sesvar07 AS int; +COMMENT ON VARIABLE ddltest_sesvar07 IS 'some session variable comment'; +SELECT pg_catalog.obj_description(oid, 'pg_variable') FROM pg_variable WHERE varname = 'ddltest_sesvar07'; +DROP VARIABLE ddltest_sesvar07; + +CREATE VARIABLE ddltest_sesvar08 AS int; +ALTER VARIABLE ddltest_sesvar08 RENAME TO ddltest_sesvar08_renamed; + +CREATE SCHEMA sesvartest_ddl; +ALTER VARIABLE ddltest_sesvar08_renamed SET SCHEMA sesvartest_ddl; + +CREATE ROLE regress_variable_owner_ddl; + +GRANT ALL ON SCHEMA sesvartest_ddl TO regress_variable_owner_ddl; + +SET ROLE TO regress_variable_owner_ddl; + +-- should fail +DROP VARIABLE sesvartest_ddl.ddltest_sesvar08_renamed; + +SET ROLE TO DEFAULT; + +ALTER VARIABLE sesvartest_ddl.ddltest_sesvar08_renamed OWNER TO regress_variable_owner_ddl; + +-- should fail +DROP ROLE regress_variable_owner_ddl; + +-- should fail - not on search path +DROP VARIABLE ddltest_sesvar08_renamed; + +SET SEARCH_PATH TO 'sesvartest_ddl'; + +-- should be ok +DROP VARIABLE ddltest_sesvar08_renamed; + +SET SEARCH_PATH TO DEFAULT; + +SET ROLE TO DEFAULT; + +DROP SCHEMA sesvartest_ddl; + +DROP ROLE regress_variable_owner_ddl; + +SET log_statement TO DEFAULT; + +CREATE VARIABLE IF NOT EXISTS ddltest_sesvar09 AS int; +CREATE VARIABLE IF NOT EXISTS ddltest_sesvar09 AS int; +DROP VARIABLE IF EXISTS ddltest_sesvar09; +DROP VARIABLE IF EXISTS ddltest_sesvar09; + +CREATE SCHEMA svartest01_ddl CREATE VARIABLE sesvar10 AS int; +CREATE VARIABLE svartest01_ddl.sesvar11 AS int; +CREATE SCHEMA svartest02_ddl CREATE VARIABLE sesvar10 AS int; + +-- should to fail +CREATE VARIABLE svartest01_ddl.sesvar10 AS int; +ALTER VARIABLE svartest01_ddl.sesvar11 RENAME TO sesvar10; +ALTER VARIABLE svartest02_ddl.sesvar10 SET SCHEMA svartest01_ddl; + +DROP SCHEMA svartest01_ddl CASCADE; +DROP SCHEMA svartest02_ddl CASCADE; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index acf090b1ccf..fc5a27c81f7 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -561,6 +561,7 @@ CreateRoleStmt CreateSchemaStmt CreateSchemaStmtContext CreateSeqStmt +CreateSessionVarStmt CreateStatsStmt CreateStmt CreateStmtContext -- 2.51.1