From ce9e3abe3326b07c8c1da3378cd55e01ea9b4f1b Mon Sep 17 00:00:00 2001 From: "okbob@github.com" Date: Thu, 6 Jul 2023 10:44:20 +0200 Subject: [PATCH 12/19] Implementation of DEFAULT clause - default expressions for session variables When the evaluation of default expression fails, we remove related entry from sessionvars hash table. Then sessionvars can contain only sucessfully initialized values. Then we don't need special flag for badly initialized session variables. --- doc/src/sgml/catalogs.sgml | 8 ++ doc/src/sgml/ddl.sgml | 13 ++- doc/src/sgml/ref/create_variable.sgml | 21 +++-- doc/src/sgml/ref/discard.sgml | 3 +- src/backend/catalog/pg_variable.c | 27 ++++++ src/backend/commands/session_variable.c | 87 ++++++++++++++++++- src/backend/parser/gram.y | 15 +++- src/backend/parser/parse_agg.c | 2 + src/backend/parser/parse_expr.c | 4 + src/backend/parser/parse_func.c | 1 + src/bin/pg_dump/pg_dump.c | 14 +++ src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/t/002_pg_dump.pl | 36 ++++++++ src/bin/psql/describe.c | 4 +- src/include/catalog/pg_variable.h | 3 + src/include/nodes/parsenodes.h | 1 + src/include/parser/parse_node.h | 1 + src/test/regress/expected/psql.out | 36 ++++---- .../regress/expected/session_variables.out | 73 ++++++++++++++-- src/test/regress/sql/session_variables.sql | 48 ++++++++++ 20 files changed, 353 insertions(+), 45 deletions(-) diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index d2995a78ec1..270aa718d98 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -9866,6 +9866,14 @@ SCRAM-SHA-256$<iteration count>:&l + + + vardefexpr pg_node_tree + + + The internal representation of the variable default value + + diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 28f031b5726..eb78fdc528c 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -5341,13 +5341,12 @@ SELECT current_user_id; The value of a session variable is local to the current session. Retrieving - a variable's value returns a NULL, unless its value has - been set to something else in the current session using the - LET command. The content of a variable is not - transactional. This is the same as regular variables in PL languages. - The session variables can be persistent or can be temporary. In both cases, - the content of session variables is temporary and not shared (like the - content of temporary tables). + a variable's value returns either a NULL or a default + value, unless its value has been set to something else in the current + session using the LET command. The content of a variable + is not transactional. This is the same as regular variables in PL languages. + The session variables are persistent, but the content of session variables + is temporary and not shared (like the content of temporary tables). diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml index 50cf316cdf1..6d6e29ac4b2 100644 --- a/doc/src/sgml/ref/create_variable.sgml +++ b/doc/src/sgml/ref/create_variable.sgml @@ -27,7 +27,7 @@ PostgreSQL documentation CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ] [ COLLATE collation ] - [ { ON COMMIT DROP | ON TRANSACTION END RESET } ] + [ DEFAULT default_expr ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ] @@ -42,10 +42,10 @@ CREATE [ { TEMPORARY | TEMP } ] VARIABLE [ IF NOT EXISTS ] + + DEFAULT default_expr + + + The DEFAULT clause can be used to assign a default + value to a session variable. This expression is evaluated when the session + variable is first accessed for reading and had not yet been assigned a value. + + + + ON COMMIT DROP diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml index 61b967f9c9b..6b0cb95034f 100644 --- a/doc/src/sgml/ref/discard.sgml +++ b/doc/src/sgml/ref/discard.sgml @@ -71,7 +71,8 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP | VARIABLES } Resets the value of all session variables. If a variable - is later reused, it is re-initialized to NULL. + is later reused, it is re-initialized to either + NULL or its default value. diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c index e9a6f755fdc..6a46461f037 100644 --- a/src/backend/catalog/pg_variable.c +++ b/src/backend/catalog/pg_variable.c @@ -24,6 +24,9 @@ #include "catalog/pg_variable.h" #include "commands/session_variable.h" #include "miscadmin.h" +#include "parser/parse_coerce.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" #include "parser/parse_type.h" #include "utils/builtins.h" #include "utils/lsyscache.h" @@ -37,6 +40,7 @@ static ObjectAddress create_variable(const char *varName, Oid varOwner, Oid varCollation, bool if_not_exists, + Node *varDefexpr, VariableEOXAction eoxaction); @@ -51,6 +55,7 @@ create_variable(const char *varName, Oid varOwner, Oid varCollation, bool if_not_exists, + Node *varDefexpr, VariableEOXAction eoxaction) { Acl *varacl; @@ -114,6 +119,11 @@ create_variable(const char *varName, values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation); values[Anum_pg_variable_vareoxaction - 1] = CharGetDatum(eoxaction); + if (varDefexpr) + values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr)); + else + nulls[Anum_pg_variable_vardefexpr - 1] = true; + varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner, varNamespace); if (varacl != NULL) @@ -150,6 +160,11 @@ create_variable(const char *varName, record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); free_object_addresses(addrs); + /* dependency on default expr */ + if (varDefexpr) + recordDependencyOnExpr(&myself, (Node *) varDefexpr, + NIL, DEPENDENCY_NORMAL); + /* dependency on owner */ recordDependencyOnOwner(VariableRelationId, varid, varOwner); @@ -185,6 +200,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt) Oid collation; Oid typcollation; ObjectAddress variable; + Node *cooked_default = NULL; /* Check consistency of arguments */ if (stmt->eoxaction == VARIABLE_EOX_DROP @@ -226,6 +242,16 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt) format_type_be(typid)), parser_errposition(pstate, stmt->collClause->location))); + if (stmt->defexpr) + { + cooked_default = transformExpr(pstate, stmt->defexpr, + EXPR_KIND_VARIABLE_DEFAULT); + + cooked_default = coerce_to_specific_type(pstate, + cooked_default, typid, "DEFAULT"); + assign_expr_collations(pstate, cooked_default); + } + variable = create_variable(stmt->variable->relname, namespaceid, typid, @@ -233,6 +259,7 @@ CreateVariable(ParseState *pstate, CreateSessionVarStmt *stmt) varowner, collation, stmt->if_not_exists, + cooked_default, stmt->eoxaction); elog(DEBUG1, "record for session variable \"%s\" (oid:%d) was created in pg_variable", diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c index fcc9f1616f1..a8f63e78a61 100644 --- a/src/backend/commands/session_variable.c +++ b/src/backend/commands/session_variable.c @@ -22,6 +22,7 @@ #include "executor/svariableReceiver.h" #include "funcapi.h" #include "miscadmin.h" +#include "optimizer/optimizer.h" #include "rewrite/rewriteHandler.h" #include "storage/lmgr.h" #include "storage/proc.h" @@ -523,11 +524,68 @@ AtEOSubXact_SessionVariables(bool isCommit, } } +/* + * evaluate an expression + */ +static void +eval_assign_defexpr(SVariable svar, HeapTuple tup) +{ + Datum defexpr_value; + bool isnull; + + Assert(svar); + Assert(svar->is_valid); + Assert(HeapTupleIsValid(tup)); + + defexpr_value = SysCacheGetAttr(VARIABLEOID, + tup, + Anum_pg_variable_vardefexpr, + &isnull); + + if (!isnull) + { + EState *estate; + ExprState *defexprs; + Expr *defexpr; + char *defexpr_str; + Datum value; + MemoryContext oldcxt; + + estate = CreateExecutorState(); + + defexpr_str = TextDatumGetCString(defexpr_value); + defexpr = (Expr *) stringToNode(defexpr_str); + + oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + + defexpr = expression_planner((Expr *) defexpr); + defexprs = ExecInitExpr(defexpr, NULL); + + value = ExecEvalExprSwitchContext(defexprs, + GetPerTupleExprContext(estate), + &isnull); + + MemoryContextSwitchTo(oldcxt); + + if (!isnull) + { + oldcxt = MemoryContextSwitchTo(SVariableMemoryContext); + + svar->value = datumCopy(value, svar->typbyval, svar->typlen); + svar->isnull = false; + + MemoryContextSwitchTo(oldcxt); + } + + FreeExecutorState(estate); + } +} + /* * Update attributes cached in svar */ static void -setup_session_variable(SVariable svar, Oid varid) +setup_session_variable(SVariable svar, Oid varid, bool is_write) { HeapTuple tup; Form_pg_variable varform; @@ -577,6 +635,9 @@ setup_session_variable(SVariable svar, Oid varid) svar->hashvalue = GetSysCacheHashValue1(VARIABLEOID, ObjectIdGetDatum(varid)); + if (!is_write) + eval_assign_defexpr(svar, tup); + ReleaseSysCache(tup); } @@ -606,7 +667,7 @@ set_session_variable(SVariable svar, Datum value, bool isnull) */ if (!svar->is_valid) { - setup_session_variable(&locsvar, svar->varid); + setup_session_variable(&locsvar, svar->varid, false); _svar = &locsvar; } else @@ -748,7 +809,25 @@ get_session_variable(Oid varid) */ if (!svar->is_valid) { - setup_session_variable(svar, varid); + /* in this case we want to use defexp if it is defined */ + PG_TRY(); + { + /* + * In this case, the setup can execute default expression. When + * the execution of default expression fails, then we need to + * remove entry from session vars. + */ + setup_session_variable(svar, varid, false); + } + PG_CATCH(); + { + /* This entry cannot be valid, remove from sessionvars */ + hash_search(sessionvars, &varid, HASH_REMOVE, NULL); + + /* propagate the error */ + PG_RE_THROW(); + } + PG_END_TRY(); elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by READ)", get_namespace_name(get_session_variable_namespace(varid)), @@ -812,7 +891,7 @@ SetSessionVariable(Oid varid, Datum value, bool isNull) if (!found) { - setup_session_variable(svar, varid); + setup_session_variable(svar, varid, true); elog(DEBUG1, "session variable \"%s.%s\" (oid:%u) has assigned entry in memory (emitted by WRITE)", get_namespace_name(get_session_variable_namespace(svar->varid)), diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 6e91daa84da..74627974dea 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -653,6 +653,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type partitions_list %type hash_partbound %type hash_partbound_elem +%type OptSessionVarDefExpr %type json_format_clause json_format_clause_opt @@ -5222,30 +5223,36 @@ create_extension_opt_item: *****************************************************************************/ CreateSessionVarStmt: - CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause OnEOXActionOption + CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr OnEOXActionOption { CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt); $4->relpersistence = $2; n->variable = $4; n->typeName = $6; n->collClause = (CollateClause *) $7; - n->eoxaction = $8; + n->defexpr = $8; + n->eoxaction = $9; n->if_not_exists = false; $$ = (Node *) n; } - | CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OnEOXActionOption + | CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptSessionVarDefExpr OnEOXActionOption { CreateSessionVarStmt *n = makeNode(CreateSessionVarStmt); $7->relpersistence = $2; n->variable = $7; n->typeName = $9; n->collClause = (CollateClause *) $10; - n->eoxaction = $11; + n->defexpr = $11; + n->eoxaction = $12; n->if_not_exists = true; $$ = (Node *) n; } ; +OptSessionVarDefExpr: DEFAULT b_expr { $$ = $2; } + | /* EMPTY */ { $$ = NULL; } + ; + /* * Temporary session variables can be dropped on successful * transaction end like tables. diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index b494c885e9f..cb57203e185 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -485,6 +485,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: if (isAgg) err = _("aggregate functions are not allowed in DEFAULT expressions"); @@ -934,6 +935,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: err = _("window functions are not allowed in DEFAULT expressions"); break; case EXPR_KIND_INDEX_EXPRESSION: diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f0ebbcf5beb..6007655630d 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -583,6 +583,7 @@ expr_kind_allows_session_variables(ParseExprKind p_expr_kind) case EXPR_KIND_JOIN_USING: case EXPR_KIND_CYCLE_MARK: case EXPR_KIND_ASSIGN_TARGET: + case EXPR_KIND_VARIABLE_DEFAULT: result = false; break; } @@ -668,6 +669,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) case EXPR_KIND_CYCLE_MARK: case EXPR_KIND_ASSIGN_TARGET: case EXPR_KIND_LET_TARGET: + case EXPR_KIND_VARIABLE_DEFAULT: /* okay */ break; @@ -2079,6 +2081,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: err = _("cannot use subquery in DEFAULT expression"); break; case EXPR_KIND_INDEX_EXPRESSION: @@ -3429,6 +3432,7 @@ ParseExprKindName(ParseExprKind exprKind) return "CHECK"; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: return "DEFAULT"; case EXPR_KIND_INDEX_EXPRESSION: return "index expression"; diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 9aa4f60768b..fcac694455d 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2620,6 +2620,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: err = _("set-returning functions are not allowed in DEFAULT expressions"); break; case EXPR_KIND_INDEX_EXPRESSION: diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 6774809add2..8e54a7dc8cb 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -5330,6 +5330,7 @@ getVariables(Archive *fout) int i_varnamespace; int i_vartype; int i_vartypname; + int i_vardefexpr; int i_vareoxaction; int i_varowner; int i_varcollation; @@ -5354,6 +5355,7 @@ 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" + "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr,\n" "v.varowner,\n" "v.varacl,\n" "acldefault('V', v.varowner) AS acldefault\n" @@ -5371,6 +5373,7 @@ getVariables(Archive *fout) i_varnamespace = PQfnumber(res, "varnamespace"); i_vartype = PQfnumber(res, "vartype"); i_vartypname = PQfnumber(res, "vartypname"); + i_vardefexpr = PQfnumber(res, "vardefexpr"); i_vareoxaction = PQfnumber(res, "vareoxaction"); i_varcollation = PQfnumber(res, "varcollation"); @@ -5407,6 +5410,11 @@ getVariables(Archive *fout) /* Decide whether we want to dump it */ selectDumpableObject(&(varinfo[i].dobj), fout); + if (PQgetisnull(res, i, i_vardefexpr)) + varinfo[i].vardefexpr = NULL; + else + varinfo[i].vardefexpr = pg_strdup(PQgetvalue(res, i, i_vardefexpr)); + /* Do not try to dump ACL if no ACL exists. */ if (!PQgetisnull(res, i, i_varacl)) varinfo[i].dobj.components |= DUMP_COMPONENT_ACL; @@ -5439,6 +5447,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) PQExpBuffer query; char *qualvarname; const char *vartypname; + const char *vardefexpr; const char *vareoxaction; Oid varcollation; @@ -5451,6 +5460,7 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) qualvarname = pg_strdup(fmtQualifiedDumpable(varinfo)); vartypname = varinfo->vartypname; + vardefexpr = varinfo->vardefexpr; vareoxaction = varinfo->vareoxaction; varcollation = varinfo->varcollation; @@ -5470,6 +5480,10 @@ dumpVariable(Archive *fout, const VariableInfo *varinfo) fmtQualifiedDumpable(coll)); } + if (vardefexpr) + appendPQExpBuffer(query, " DEFAULT %s", + vardefexpr); + if (strcmp(vareoxaction, "r") == 0) appendPQExpBuffer(query, " ON TRANSACTION END RESET"); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index d17d8b096b1..ee3124bac05 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -702,6 +702,7 @@ typedef struct _VariableInfo DumpableAcl dacl; Oid vartype; char *vartypname; + char *vardefexpr; char *vareoxaction; char *varacl; char *rvaracl; diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 71f8c713f68..ff5c01020e4 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3959,6 +3959,42 @@ my %tests = ( }, }, + 'CREATE VARIABLE test_variable DEFAULT' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE VARIABLE dump_test.variable3 AS integer DEFAULT 10;', + regexp => qr/^ + \QCREATE VARIABLE dump_test.variable3 AS integer 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 VARIABLE test_variable DEFAULT ON TRANSACTION END RESET' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE VARIABLE dump_test.variable4 AS integer DEFAULT 10 ON TRANSACTION END RESET', + regexp => qr/^ + \QCREATE VARIABLE dump_test.variable4 AS integer DEFAULT 10 ON TRANSACTION END RESET;\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 50174a28e03..011383af042 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}; + static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false}; initPQExpBuffer(&buf); @@ -5170,6 +5170,7 @@ 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" + " pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n" " CASE v.vareoxaction\n" " WHEN 'd' THEN 'ON COMMIT DROP'\n" " WHEN 'r' THEN 'ON TRANSACTION END RESET'\n" @@ -5179,6 +5180,7 @@ listVariables(const char *pattern, bool verbose) gettext_noop("Type"), gettext_noop("Collation"), gettext_noop("Owner"), + gettext_noop("Default"), gettext_noop("Transactional end action")); if (verbose) diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h index 2aecbcb50e9..3d8cbff50d4 100644 --- a/src/include/catalog/pg_variable.h +++ b/src/include/catalog/pg_variable.h @@ -66,6 +66,9 @@ CATALOG(pg_variable,9222,VariableRelationId) /* access permissions */ aclitem varacl[1] BKI_DEFAULT(_null_); + /* list of expression trees for variable default (NULL if none) */ + pg_node_tree vardefexpr BKI_DEFAULT(_null_); + #endif } FormData_pg_variable; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 44b5acd7cf0..dda87c33735 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3455,6 +3455,7 @@ typedef struct CreateSessionVarStmt TypeName *typeName; /* the type of variable */ CollateClause *collClause; bool if_not_exists; /* do nothing if it already exists */ + Node *defexpr; /* default expression */ char eoxaction; /* on commit action */ } CreateSessionVarStmt; diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index 6d936919659..baba22ed840 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -84,6 +84,7 @@ typedef enum ParseExprKind EXPR_KIND_CYCLE_MARK, /* cycle mark value */ EXPR_KIND_ASSIGN_TARGET, /* PL/pgSQL assignment target */ EXPR_KIND_LET_TARGET, /* LET target */ + EXPR_KIND_VARIABLE_DEFAULT, /* default value for session variable */ } ParseExprKind; diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index ad7cd1ca390..ca131e568eb 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 | Transactional end action | Access privileges | Description ---------+------+-------------------+-----------+------------------------+--------------------------+-------------------+------------- - public | var1 | character varying | C | regress_variable_owner | | | + List of variables + Schema | Name | Type | Collation | Owner | Default | Transactional end action | Access privileges | Description +--------+------+-------------------+-----------+------------------------+---------+--------------------------+-------------------+------------- + public | var1 | character varying | C | regress_variable_owner | | | | (1 row) GRANT SELECT ON VARIABLE var1 TO PUBLIC; COMMENT ON VARIABLE var1 IS 'some description'; \dV+ var1 - List of variables - Schema | Name | Type | Collation | Owner | 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 | 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 | (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 | Transactional end action ---------+------+------+-----------+-------+-------------------------- + List of variables + Schema | Name | Type | Collation | Owner | 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 | Transactional end action ---------+------+------+-----------+-------+-------------------------- + List of variables + Schema | Name | Type | Collation | Owner | 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 | Transactional end action ---------+------+------+-----------+-------+-------------------------- + List of variables + Schema | Name | Type | Collation | Owner | 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 8602fb2250a..b05afa8b38a 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 | 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 | 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 | (1 row) DROP VARIABLE svartest.var1; @@ -1516,3 +1516,66 @@ SELECT var1 IS NULL; (1 row) DROP VARIABLE var1; +CREATE OR REPLACE FUNCTION vartest_fx() +RETURNS int AS $$ +BEGIN + RAISE NOTICE 'vartest_fx executed'; + RETURN 0; +END; +$$ LANGUAGE plpgsql; +CREATE VARIABLE var1 AS int DEFAULT vartest_fx(); +-- vartest_fx should be protected by dep, should fail +DROP FUNCTION vartest_fx(); +ERROR: cannot drop function vartest_fx() because other objects depend on it +DETAIL: session variable var1 depends on function vartest_fx() +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- should be ok +SELECT var1; +NOTICE: vartest_fx executed + var1 +------ + 0 +(1 row) + +-- the defexpr should be evaluated only once +SELECT var1; + var1 +------ + 0 +(1 row) + +DISCARD VARIABLES; +-- in this case, the defexpr should not be evaluated +LET var1 = 100; +SELECT var1; + var1 +------ + 100 +(1 row) + +DISCARD VARIABLES; +CREATE OR REPLACE FUNCTION vartest_fx() +RETURNS int AS $$ +BEGIN + RAISE EXCEPTION 'vartest_fx is executing'; + RETURN 0; +END; +$$ LANGUAGE plpgsql; +-- should to fail, but not to crash +SELECT var1; +ERROR: vartest_fx is executing +CONTEXT: PL/pgSQL function vartest_fx() line 3 at RAISE +-- again +SELECT var1; +ERROR: vartest_fx is executing +CONTEXT: PL/pgSQL function vartest_fx() line 3 at RAISE +-- but we can write +LET var1 = 100; +SELECT var1; + var1 +------ + 100 +(1 row) + +DROP VARIABLE var1; +DROP FUNCTION vartest_fx(); diff --git a/src/test/regress/sql/session_variables.sql b/src/test/regress/sql/session_variables.sql index 6e6001deba9..3e1ba235cec 100644 --- a/src/test/regress/sql/session_variables.sql +++ b/src/test/regress/sql/session_variables.sql @@ -1004,3 +1004,51 @@ ROLLBACK; SELECT var1 IS NULL; DROP VARIABLE var1; + +CREATE OR REPLACE FUNCTION vartest_fx() +RETURNS int AS $$ +BEGIN + RAISE NOTICE 'vartest_fx executed'; + RETURN 0; +END; +$$ LANGUAGE plpgsql; + +CREATE VARIABLE var1 AS int DEFAULT vartest_fx(); + +-- vartest_fx should be protected by dep, should fail +DROP FUNCTION vartest_fx(); + +-- should be ok +SELECT var1; + +-- the defexpr should be evaluated only once +SELECT var1; + +DISCARD VARIABLES; + +-- in this case, the defexpr should not be evaluated +LET var1 = 100; +SELECT var1; + +DISCARD VARIABLES; + +CREATE OR REPLACE FUNCTION vartest_fx() +RETURNS int AS $$ +BEGIN + RAISE EXCEPTION 'vartest_fx is executing'; + RETURN 0; +END; +$$ LANGUAGE plpgsql; + +-- should to fail, but not to crash +SELECT var1; + +-- again +SELECT var1; + +-- but we can write +LET var1 = 100; +SELECT var1; + +DROP VARIABLE var1; +DROP FUNCTION vartest_fx(); -- 2.45.2