From 0d9b06c963f8d01e01728a017e22bd226c2dd5af Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Thu, 6 Jul 2023 14:40:37 +0200 Subject: [PATCH 13/19] Implementation of NOT NULL and IMMUTABLE clauses Almost trivial patch - psql, pg_dump support --- doc/src/sgml/catalogs.sgml | 19 ++ doc/src/sgml/plpgsql.sgml | 6 +- doc/src/sgml/ref/create_variable.sgml | 34 +++- src/backend/catalog/pg_variable.c | 8 + src/backend/commands/session_variable.c | 69 +++++++- src/backend/parser/gram.y | 41 +++-- src/bin/pg_dump/pg_dump.c | 19 +- src/bin/pg_dump/pg_dump.h | 2 + src/bin/pg_dump/t/002_pg_dump.pl | 54 ++++++ src/bin/psql/describe.c | 6 +- src/bin/psql/tab-complete.c | 11 +- src/include/catalog/pg_variable.h | 6 + src/include/nodes/parsenodes.h | 2 + src/test/regress/expected/psql.out | 36 ++-- .../regress/expected/session_variables.out | 164 +++++++++++++++++- src/test/regress/sql/session_variables.sql | 129 ++++++++++++++ 16 files changed, 558 insertions(+), 48 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 270aa718d98..3b73ad43cad 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9832,6 +9832,25 @@ SCRAM-SHA-256$<iteration count>:&l + + + varisnotnull boolean + + + True if the session variable doesn't allow null value. The default value is false. + + + + + + varisimmutable boolean + + + True if the variable is immutable (cannot be modified). + The default value is false. + + + vareoxaction char diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index d4cd0fb7204..0ced6501e78 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -6024,9 +6024,9 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE; The PL/pgSQL language has no packages, and therefore no package variables or package constants. - PostgreSQL has session variables. - Session variables can be created by CREATE VARIABLE, - as described in . + PostgreSQL has session variables and immutable + session variables. Session variables can be created by CREATE + VARIABLE, as described in . diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml index 6d6e29ac4b2..ef695d66378 100644 --- a/doc/src/sgml/ref/create_variable.sgml +++ b/doc/src/sgml/ref/create_variable.sgml @@ -26,8 +26,8 @@ PostgreSQL documentation -CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ] [ COLLATE collation ] - [ DEFAULT default_expr ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ] +CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ] [ COLLATE collation ] + [ NOT NULL ] [ DEFAULT default_expr ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ] @@ -71,6 +71,22 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] + IMMUTABLE + + + The assigned value of the session variable can not be changed. + Only if the session variable doesn't have a default value, a single + initialization is allowed using the LET command. Once + done, no further change is allowed until end of transaction + if the session variable was created with clause ON TRANSACTION + END RESET, or until reset of all session variables by + DISCARD VARIABLES, or until reset of all session + objects by command DISCARD ALL. + + + + IF NOT EXISTS @@ -111,6 +127,20 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] + NOT NULL + + + The NOT NULL clause forbids setting the session + variable to a null value. A session variable created as NOT NULL and + without an explicitly declared default value cannot be read until it is + initialized by a LET command. This requires the user to explicitly + initialize the session variable content before reading it, otherwise an + error will be thrown. + + + + DEFAULT default_expr diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c index 6a46461f037..898c2c745e9 100644 --- a/src/backend/catalog/pg_variable.c +++ b/src/backend/catalog/pg_variable.c @@ -40,6 +40,8 @@ static ObjectAddress create_variable(const char *varName, Oid varOwner, Oid varCollation, bool if_not_exists, + bool is_not_null, + bool is_immutable, Node *varDefexpr, VariableEOXAction eoxaction); @@ -55,6 +57,8 @@ create_variable(const char *varName, Oid varOwner, Oid varCollation, bool if_not_exists, + bool is_not_null, + bool is_immutable, Node *varDefexpr, VariableEOXAction eoxaction) { @@ -117,6 +121,8 @@ 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_varisnotnull - 1] = BoolGetDatum(is_not_null); + values[Anum_pg_variable_varisimmutable - 1] = BoolGetDatum(is_immutable); values[Anum_pg_variable_vareoxaction - 1] = CharGetDatum(eoxaction); if (varDefexpr) @@ -259,6 +265,8 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt) varowner, collation, stmt->if_not_exists, + stmt->is_not_null, + stmt->is_immutable, cooked_default, stmt->eoxaction); diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index a8f63e78a61..21560695dc2 100644 --- a/src/backend/commands/session_variable.c +++ b/src/backend/commands/session_variable.c @@ -92,6 +92,9 @@ typedef struct SVariableData void *domain_check_extra; LocalTransactionId domain_check_extra_lxid; + bool is_not_null; + bool is_immutable; + bool reset_at_eox; /* @@ -110,6 +113,9 @@ typedef struct SVariableData bool is_valid; uint32 hashvalue; /* used for pairing sinval message */ + + /* true, when the value is already set, and cannot be changed more */ + bool protect_value; } SVariableData; typedef SVariableData *SVariable; @@ -576,6 +582,23 @@ eval_assign_defexpr(SVariable svar, HeapTuple tup) MemoryContextSwitchTo(oldcxt); } + else + { + /* + * Raise an error if this is a NOT NULL variable but the result of + * DEFAULT expression is NULL. + */ + if (svar->is_not_null) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"", + get_namespace_name(get_session_variable_namespace(svar->varid)), + get_session_variable_name(svar->varid)), + errdetail("The result of DEFAULT expression is NULL."))); + } + + if (svar->is_immutable) + svar->protect_value = true; FreeExecutorState(estate); } @@ -606,6 +629,9 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write) get_typlenbyval(svar->typid, &svar->typlen, &svar->typbyval); + svar->is_not_null = varform->varisnotnull; + svar->is_immutable = varform->varisimmutable; + svar->is_domain = (get_typtype(varform->vartype) == TYPTYPE_DOMAIN); svar->domain_check_extra = NULL; svar->domain_check_extra_lxid = InvalidLocalTransactionId; @@ -635,9 +661,31 @@ setup_session_variable(SVariable svar, Oid varid, bool is_write) svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID, ObjectIdGetDatum(varid)); - if (!is_write) + svar->protect_value = false; + + /* + * When the variable is marked as IMMUTABLE, we prefer to evaluate + * possible DEFAULT before write op. In this case we want to protect + * default value against any overwrite. + */ + if (!is_write || + svar->is_immutable) + { eval_assign_defexpr(svar, tup); + /* + * Raise an error if this is a NOT NULL variable without default + * expression. + */ + if (svar->isnull && svar->is_not_null) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"", + get_namespace_name(get_session_variable_namespace(varid)), + get_session_variable_name(varid)), + errdetail("The session variable was not initialized yet."))); + } + ReleaseSysCache(tup); } @@ -657,6 +705,21 @@ set_session_variable(SVariable svar, Datum value, bool isnull) Assert(svar); Assert(!isnull || value == (Datum) 0); + if (svar->protect_value) + ereport(ERROR, + (errcode(ERRCODE_ERROR_IN_ASSIGNMENT), + errmsg("session variable \"%s.%s\" is declared IMMUTABLE", + get_namespace_name(get_session_variable_namespace(svar->varid)), + get_session_variable_name(svar->varid)))); + + /* don't allow assignment of null to NOT NULL variable */ + if (isnull && svar->is_not_null) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value is not allowed for NOT NULL session variable \"%s.%s\"", + get_namespace_name(get_session_variable_namespace(svar->varid)), + get_session_variable_name(svar->varid)))); + /* * Use typbyval, typbylen from session variable only when they are * trustable (the invalidation message was not accepted for this variable). @@ -693,6 +756,10 @@ set_session_variable(SVariable svar, Datum value, bool isnull) svar->value = newval; svar->isnull = isnull; + /* don't allow more changes of value when variable is IMMUTABLE */ + if (svar->is_immutable) + svar->protect_value = true; + /* * XXX While unlikely, an error here is possible. It wouldn't leak memory * as the allocated chunk has already been correctly assigned to the diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 74627974dea..0a8e553a427 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -683,6 +683,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); json_object_constructor_null_clause_opt json_array_constructor_null_clause_opt +%type OptNotNull OptImmutable /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -5223,27 +5224,31 @@ create_extension_opt_item: *****************************************************************************/ CreateSessionVarStmt: - CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr OnEOXActionOption + CREATE OptTemp OptImmutable VARIABLE qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr OnEOXActionOption { CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt); - $4->relpersistence = $2; - n->variable = $4; - n->typeName = $6; - n->collClause = (CollateClause *) $7; - n->defexpr = $8; - n->eoxaction = $9; + $5->relpersistence = $2; + n->is_immutable = $3; + n->variable = $5; + n->typeName = $7; + n->collClause = (CollateClause *) $8; + n->is_not_null = $9; + n->defexpr = $10; + n->eoxaction = $11; n->if_not_exists = false; $$ = (Node *) n; } - | CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr OnEOXActionOption + | CREATE OptTemp OptImmutable VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptNotNull OptSessionVarDefExpr OnEOXActionOption { CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt); - $7->relpersistence = $2; - n->variable = $7; - n->typeName = $9; - n->collClause = (CollateClause *) $10; - n->defexpr = $11; - n->eoxaction = $12; + $8->relpersistence = $2; + n->is_immutable = $3; + n->variable = $8; + n->typeName = $10; + n->collClause = (CollateClause *) $11; + n->is_not_null = $12; + n->defexpr = $13; + n->eoxaction = $14; n->if_not_exists = true; $$ = (Node *) n; } @@ -5263,6 +5268,14 @@ OnEOXActionOption: ON COMMIT DROP { $$ = VARIABLE_EOX_DROP; } ; +OptNotNull: NOT NULL_P { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; + +OptImmutable: IMMUTABLE { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; + /***************************************************************************** * * ALTER EXTENSION name UPDATE [ TO version ] diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 8e54a7dc8cb..9348fbed788 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -5334,6 +5334,8 @@ getVariables(Archive *fout) int i_vareoxaction; int i_varowner; int i_varcollation; + int i_varisnotnull; + int i_varisimmutable; int i_varacl; int i_acldefault; int i, @@ -5355,6 +5357,8 @@ getVariables(Archive *fout) "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname,\n" "CASE WHEN v.varcollation <> t.typcollation " "THEN v.varcollation ELSE 0 END AS varcollation,\n" + "v.varisnotnull,\n" + "v.varisimmutable,\n" "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n" "v.varowner,\n" "v.varacl,\n" @@ -5376,6 +5380,8 @@ getVariables(Archive *fout) i_vardefexpr = PQfnumber(res, "vardefexpr"); i_vareoxaction = PQfnumber(res, "vareoxaction"); i_varcollation = PQfnumber(res, "varcollation"); + i_varisnotnull = PQfnumber(res, "varisnotnull"); + i_varisimmutable = PQfnumber(res, "varisimmutable"); i_varowner = PQfnumber(res, "varowner"); i_varacl = PQfnumber(res, "varacl"); @@ -5400,6 +5406,8 @@ getVariables(Archive *fout) varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname)); varinfo[i].vareoxaction = pg_strdup(PQgetvalue(res, i, i_vareoxaction)); varinfo[i].varcollation = atooid(PQgetvalue(res, i, i_varcollation)); + varinfo[i].varisnotnull = *(PQgetvalue(res, i, i_varisnotnull)) == 't'; + varinfo[i].varisimmutable = *(PQgetvalue(res, i, i_varisimmutable)) == 't'; varinfo[i].dacl.acl = pg_strdup(PQgetvalue(res, i, i_varacl)); varinfo[i].dacl.acldefault = pg_strdup(PQgetvalue(res, i, i_acldefault)); @@ -5449,7 +5457,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) const char *vartypname; const char *vardefexpr; const char *vareoxaction; + const char *varisimmutable; Oid varcollation; + bool varisnotnull; /* Skip if not to be dumped */ if (!varinfo->dobj.dump || dopt->dataOnly) @@ -5463,12 +5473,14 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) vardefexpr = varinfo->vardefexpr; vareoxaction = varinfo->vareoxaction; varcollation = varinfo->varcollation; + varisnotnull = varinfo->varisnotnull; + varisimmutable = varinfo->varisimmutable ? "IMMUTABLE " : ""; appendPQExpBuffer(delq, "DROP VARIABLE %s;\n", qualvarname); - appendPQExpBuffer(query, "CREATE VARIABLE %s AS %s", - qualvarname, vartypname); + appendPQExpBuffer(query, "CREATE %sVARIABLE %s AS %s", + varisimmutable, qualvarname, vartypname); if (OidIsValid(varcollation)) { @@ -5480,6 +5492,9 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) fmtQualifiedDumpable(coll)); } + if (varisnotnull) + appendPQExpBuffer(query, " NOT NULL"); + if (vardefexpr) appendPQExpBuffer(query, " DEFAULT %s", vardefexpr); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index ee3124bac05..6fa396ece7d 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -710,6 +710,8 @@ typedef struct _VariableInfo char *initrvaracl; Oid varcollation; const char *rolname; /* name of owner, or empty string */ + bool varisnotnull; + bool varisimmutable; } VariableInfo; /* diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index ff5c01020e4..ae310961ab4 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3995,6 +3995,60 @@ my %tests = ( }, }, + 'CREATE IMMUTABLE VARIABLE test_variable' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE IMMUTABLE VARIABLE dump_test.variable5 AS integer', + regexp => qr/^ + \QCREATE IMMUTABLE VARIABLE dump_test.variable5 AS integer;\E/xm, + like => { + %full_runs, + %dump_test_schema_runs, + section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + }, + }, + + 'CREATE VARIABLE test_variable NOT NULL' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE VARIABLE dump_test.variable6 AS integer NOT NULL', + regexp => qr/^ + \QCREATE VARIABLE dump_test.variable6 AS integer NOT NULL;\E/xm, + like => { + %full_runs, + %dump_test_schema_runs, + section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + }, + }, + + 'CREATE IMMUTABLE VARIABLE test_variable NOT NULL DEFAULT' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE IMMUTABLE VARIABLE dump_test.variable7 AS integer NOT NULL DEFAULT 10', + regexp => qr/^ + \QCREATE IMMUTABLE VARIABLE dump_test.variable7 AS integer NOT NULL DEFAULT 10;\E/xm, + like => { + %full_runs, + %dump_test_schema_runs, + section_pre_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE VIEW test_view' => { create_order => 61, create_sql => 'CREATE VIEW dump_test.test_view diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 011383af042..787f9c48b51 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5159,7 +5159,7 @@ listVariables(const char *pattern, bool verbose) PQExpBufferData buf; PGresult *res; printQueryOpt myopt = pset.popt; - static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false}; + static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false, false, false}; initPQExpBuffer(&buf); @@ -5170,6 +5170,8 @@ listVariables(const char *pattern, bool verbose) " (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" + " NOT v.varisnotnull as \"%s\",\n" + " NOT v.varisimmutable as \"%s\",\n" " pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n" " CASE v.vareoxaction\n" " WHEN 'd' THEN 'ON COMMIT DROP'\n" @@ -5180,6 +5182,8 @@ listVariables(const char *pattern, bool verbose) gettext_noop("Type"), gettext_noop("Collation"), gettext_noop("Owner"), + gettext_noop("Nullable"), + gettext_noop("Mutable"), gettext_noop("Default"), gettext_noop("Transactional end action")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 2ffc27d022e..e503b22707d 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1252,6 +1252,8 @@ static const pgsql_thing_t words_after_create[] = { {"FOREIGN TABLE", NULL, NULL, NULL}, {"FUNCTION", NULL, NULL, Query_for_list_of_functions}, {"GROUP", Query_for_list_of_roles}, + {"IMMUTABLE VARIABLE", NULL, NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE IMMUTABLE + * VARIABLE ... */ {"INDEX", NULL, NULL, &Query_for_list_of_indexes}, {"LANGUAGE", Query_for_list_of_languages}, {"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, @@ -3277,7 +3279,7 @@ psql_completion(const char *text, int start, int end) /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ else if (TailMatches("CREATE", "TEMP|TEMPORARY")) - COMPLETE_WITH("SEQUENCE", "TABLE", "VARIABLE", "VIEW"); + COMPLETE_WITH("IMMUTABLE VARIABLE", "SEQUENCE", "TABLE", "VARIABLE", "VIEW"); /* Complete "CREATE UNLOGGED" with TABLE, SEQUENCE or MATVIEW */ else if (TailMatches("CREATE", "UNLOGGED")) { @@ -3600,7 +3602,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) || - TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny)) + TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny) || + TailMatches("IMMUTABLE", "VARIABLE", MatchAny)) COMPLETE_WITH("AS"); else if (TailMatches("VARIABLE", MatchAny, "AS")) /* Complete CREATE VARIABLE with AS types */ @@ -4248,6 +4251,10 @@ psql_completion(const char *text, int start, int end) else if (TailMatches("FROM", "SERVER", MatchAny, "INTO", MatchAny)) COMPLETE_WITH("OPTIONS ("); +/* IMMUTABLE -- can be expend to IMMUTABLE VARIABLE */ + else if (TailMatches("CREATE", "IMMUTABLE")) + COMPLETE_WITH("VARIABLE"); + /* INSERT --- can be inside EXPLAIN, RULE, etc */ /* Complete NOT MATCHED THEN INSERT */ else if (TailMatches("NOT", "MATCHED", "THEN", "INSERT")) diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h index 3d8cbff50d4..43c8bc96583 100644 --- a/src/include/catalog/pg_variable.h +++ b/src/include/catalog/pg_variable.h @@ -54,6 +54,12 @@ CATALOG(pg_variable,9222,VariableRelationId) /* typmod for variable's type */ int32 vartypmod BKI_DEFAULT(-1); + /* don't allow NULL */ + bool varisnotnull BKI_DEFAULT(f); + + /* don't allow changes */ + bool varisimmutable BKI_DEFAULT(f); + /* action on transaction end */ char vareoxaction BKI_DEFAULT(n); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index dda87c33735..cd09291926f 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3455,6 +3455,8 @@ typedef struct CreateSessionVarStmt TypeName *typeName; /* the type of variable */ CollateClause *collClause; bool if_not_exists; /* do nothing if it already exists */ + bool is_not_null; /* disallow nulls */ + bool is_immutable; /* don't allow changes */ Node *defexpr; /* default expression */ char eoxaction; /* on commit action */ } CreateSessionVarStmt; diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index ca131e568eb..f7082639605 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 | Default | Transactional end action | Access privileges | Description ---------+------+-------------------+-----------+------------------------+---------+--------------------------+-------------------+------------- - public | var1 | character varying | C | regress_variable_owner | | | | + List of variables + Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action | Access privileges | Description +--------+------+-------------------+-----------+------------------------+----------+---------+---------+--------------------------+-------------------+------------- + public | var1 | character varying | C | regress_variable_owner | t | t | | | | (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 | Default | 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 | + List of variables + Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action | Access privileges | Description +--------+------+-------------------+-----------+------------------------+----------+---------+---------+--------------------------+--------------------------------------------------+------------------ + public | var1 | character varying | C | regress_variable_owner | t | t | | | 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 | Default | Transactional end action ---------+------+------+-----------+-------+---------+-------------------------- + List of variables + Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | 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 | Default | Transactional end action ---------+------+------+-----------+-------+---------+-------------------------- + List of variables + Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | 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 | Default | Transactional end action ---------+------+------+-----------+-------+---------+-------------------------- + List of variables + Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | 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 b05afa8b38a..eb2411b9019 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 | Default | 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 | + List of variables + Schema | Name | Type | Collation | Owner | Nullable | Mutable | Default | Transactional end action | Access privileges | Description +----------+------+---------+-----------+------------------------+----------+---------+---------+--------------------------+--------------------------------------------------+------------- + svartest | var1 | integer | | regress_variable_owner | t | t | | | regress_variable_owner=rw/regress_variable_owner+| + | | | | | | | | | regress_variable_reader=r/regress_variable_owner | (1 row) DROP VARIABLE svartest.var1; @@ -1579,3 +1579,157 @@ SELECT var1; DROP VARIABLE var1; DROP FUNCTION vartest_fx(); +-- test NOT NULL +-- should be ok +CREATE VARIABLE var1 AS int NOT NULL; +-- should be ok +LET var1 = 10; +SELECT var1; + var1 +------ + 10 +(1 row) + +DISCARD VARIABLES; +-- should fail +SELECT var1; +ERROR: null value is not allowed for NOT NULL session variable "public.var1" +DETAIL: The session variable was not initialized yet. +-- should be ok +LET var1 = 10; +SELECT var1; + var1 +------ + 10 +(1 row) + +DROP VARIABLE var1; +-- should be ok +CREATE VARIABLE var1 AS int NOT NULL DEFAULT 0; +--should be ok +SELECT var1; + var1 +------ + 0 +(1 row) + +-- should be ok +LET var1 = 10; +SELECT var1; + var1 +------ + 10 +(1 row) + +DISCARD VARIABLES; +-- should to fail +LET var1 = NULL; +ERROR: null value is not allowed for NOT NULL session variable "public.var1" +DROP VARIABLE var1; +-- test NOT NULL +CREATE OR REPLACE FUNCTION vartest_fx() +RETURNS int AS $$ +BEGIN + RETURN NULL; +END; +$$ LANGUAGE plpgsql; +CREATE VARIABLE var1 AS int NOT NULL DEFAULT vartest_fx(); +-- should to fail +SELECT var1; +ERROR: null value is not allowed for NOT NULL session variable "public.var1" +DETAIL: The result of DEFAULT expression is NULL. +DISCARD VARIABLES; +-- should be ok +LET var1 = 10; +SELECT var1; + var1 +------ + 10 +(1 row) + +CREATE OR REPLACE FUNCTION vartest_fx() +RETURNS int AS $$ +BEGIN + RETURN 0; +END; +$$ LANGUAGE plpgsql; +DISCARD VARIABLES; +-- should be ok +SELECT var1; + var1 +------ + 0 +(1 row) + +DROP VARIABLE var1; +DROP FUNCTION vartest_fx(); +-- test IMMUTBLE +CREATE IMMUTABLE VARIABLE var1 AS int; +-- should be ok +SELECT var1; + var1 +------ + +(1 row) + +-- first write should ok +-- should be ok +LET var1 = 10; +-- should fail +LET var1 = 20; +ERROR: session variable "public.var1" is declared IMMUTABLE +DISCARD VARIABLES; +-- should be ok +LET var1 = 10; +-- should fail +LET var1 = 20; +ERROR: session variable "public.var1" is declared IMMUTABLE +DISCARD VARIABLES; +-- should be ok +SELECT var1; + var1 +------ + +(1 row) + +-- should be ok +LET var1 = NULL; +-- should fail +LET var1 = 20; +ERROR: session variable "public.var1" is declared IMMUTABLE +DROP VARIABLE var1; +CREATE IMMUTABLE VARIABLE var1 AS int DEFAULT 10; +-- don't allow change when variable has DEFAULT value +-- should to fail +LET var1 = 20; +ERROR: session variable "public.var1" is declared IMMUTABLE +DISCARD VARIABLES; +-- should be ok +SELECT var1; + var1 +------ + 10 +(1 row) + +-- should fail +LET var1 = 20; +ERROR: session variable "public.var1" is declared IMMUTABLE +DROP VARIABLE var1; +-- should be ok +CREATE IMMUTABLE VARIABLE var1 AS INT NOT NULL DEFAULT 10; +-- should to fail +LET var1 = 10; +ERROR: session variable "public.var1" is declared IMMUTABLE +LET var1 = 20; +ERROR: session variable "public.var1" is declared IMMUTABLE +-- should be ok +SELECT var1; + var1 +------ + 10 +(1 row) + +-- should to fail +LET var1 = 30; +ERROR: session variable "public.var1" is declared IMMUTABLE +DROP VARIABLE var1; diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql index 3e1ba235cec..dae2b4ddfa6 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -1052,3 +1052,132 @@ SELECT var1; DROP VARIABLE var1; DROP FUNCTION vartest_fx(); + +-- test NOT NULL +-- should be ok +CREATE VARIABLE var1 AS int NOT NULL; + +-- should be ok +LET var1 = 10; +SELECT var1; + +DISCARD VARIABLES; + +-- should fail +SELECT var1; + +-- should be ok +LET var1 = 10; +SELECT var1; + +DROP VARIABLE var1; + +-- should be ok +CREATE VARIABLE var1 AS int NOT NULL DEFAULT 0; + +--should be ok +SELECT var1; + +-- should be ok +LET var1 = 10; +SELECT var1; + +DISCARD VARIABLES; + +-- should to fail +LET var1 = NULL; + +DROP VARIABLE var1; + +-- test NOT NULL +CREATE OR REPLACE FUNCTION vartest_fx() +RETURNS int AS $$ +BEGIN + RETURN NULL; +END; +$$ LANGUAGE plpgsql; + +CREATE VARIABLE var1 AS int NOT NULL DEFAULT vartest_fx(); + +-- should to fail +SELECT var1; + +DISCARD VARIABLES; + +-- should be ok +LET var1 = 10; +SELECT var1; + +CREATE OR REPLACE FUNCTION vartest_fx() +RETURNS int AS $$ +BEGIN + RETURN 0; +END; +$$ LANGUAGE plpgsql; + +DISCARD VARIABLES; + +-- should be ok +SELECT var1; + +DROP VARIABLE var1; +DROP FUNCTION vartest_fx(); + +-- test IMMUTBLE +CREATE IMMUTABLE VARIABLE var1 AS int; + +-- should be ok +SELECT var1; +-- first write should ok +-- should be ok +LET var1 = 10; +-- should fail +LET var1 = 20; + +DISCARD VARIABLES; + +-- should be ok +LET var1 = 10; +-- should fail +LET var1 = 20; + +DISCARD VARIABLES; + +-- should be ok +SELECT var1; +-- should be ok +LET var1 = NULL; +-- should fail +LET var1 = 20; + +DROP VARIABLE var1; + +CREATE IMMUTABLE VARIABLE var1 AS int DEFAULT 10; + +-- don't allow change when variable has DEFAULT value +-- should to fail +LET var1 = 20; + +DISCARD VARIABLES; + +-- should be ok +SELECT var1; +-- should fail +LET var1 = 20; + +DROP VARIABLE var1; + +-- should be ok +CREATE IMMUTABLE VARIABLE var1 AS INT NOT NULL DEFAULT 10; + +-- should to fail +LET var1 = 10; +LET var1 = 20; + +-- should be ok +SELECT var1; + +-- should to fail +LET var1 = 30; + +DROP VARIABLE var1; -- 2.45.2