v20220410-0005-LET-command.patch
text/x-patch
Filename: v20220410-0005-LET-command.patch
Type: text/x-patch
Part: 6
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v20220410-0005
Subject: LET command
| File | + | − |
|---|---|---|
| src/backend/commands/explain.c | 16 | 0 |
| src/backend/commands/sessionvariable.c | 80 | 0 |
| src/backend/executor/execMain.c | 1 | 0 |
| src/backend/executor/Makefile | 1 | 0 |
| src/backend/executor/svariableReceiver.c | 148 | 0 |
| src/backend/nodes/copyfuncs.c | 17 | 0 |
| src/backend/nodes/equalfuncs.c | 14 | 0 |
| src/backend/nodes/outfuncs.c | 16 | 0 |
| src/backend/parser/analyze.c | 269 | 0 |
| src/backend/parser/gram.y | 51 | 2 |
| src/backend/parser/parse_agg.c | 4 | 0 |
| src/backend/parser/parse_expr.c | 4 | 0 |
| src/backend/parser/parse_func.c | 3 | 0 |
| src/backend/parser/parse_target.c | 3 | 1 |
| src/backend/rewrite/rewriteHandler.c | 34 | 0 |
| src/backend/rewrite/rowsecurity.c | 4 | 4 |
| src/backend/tcop/dest.c | 7 | 0 |
| src/backend/tcop/utility.c | 15 | 0 |
| src/backend/utils/cache/plancache.c | 14 | 1 |
| src/include/commands/session_variable.h | 3 | 0 |
| src/include/executor/svariableReceiver.h | 25 | 0 |
| src/include/nodes/nodes.h | 1 | 0 |
| src/include/nodes/parsenodes.h | 15 | 0 |
| src/include/nodes/plannodes.h | 1 | 1 |
| src/include/parser/kwlist.h | 1 | 0 |
| src/include/parser/parse_node.h | 2 | 1 |
| src/include/tcop/cmdtaglist.h | 1 | 0 |
| src/include/tcop/dest.h | 2 | 1 |
From 7aae92610f937d400ff6f95c8a05a46e8dd602cd Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <pavel.stehule@gmail.com>
Date: Tue, 5 Apr 2022 00:54:29 +0200
Subject: [PATCH 05/11] LET command
Set session variable value to result of query or result of default expression
---
src/backend/commands/explain.c | 16 ++
src/backend/commands/sessionvariable.c | 80 +++++++
src/backend/executor/Makefile | 1 +
src/backend/executor/execMain.c | 1 +
src/backend/executor/svariableReceiver.c | 148 +++++++++++++
src/backend/nodes/copyfuncs.c | 17 ++
src/backend/nodes/equalfuncs.c | 14 ++
src/backend/nodes/outfuncs.c | 16 ++
src/backend/parser/analyze.c | 269 +++++++++++++++++++++++
src/backend/parser/gram.y | 53 ++++-
src/backend/parser/parse_agg.c | 4 +
src/backend/parser/parse_expr.c | 4 +
src/backend/parser/parse_func.c | 3 +
src/backend/parser/parse_target.c | 4 +-
src/backend/rewrite/rewriteHandler.c | 34 +++
src/backend/rewrite/rowsecurity.c | 8 +-
src/backend/tcop/dest.c | 7 +
src/backend/tcop/utility.c | 15 ++
src/backend/utils/cache/plancache.c | 15 +-
src/include/commands/session_variable.h | 3 +
src/include/executor/svariableReceiver.h | 25 +++
src/include/nodes/nodes.h | 1 +
src/include/nodes/parsenodes.h | 15 ++
src/include/nodes/plannodes.h | 2 +-
src/include/parser/kwlist.h | 1 +
src/include/parser/parse_node.h | 3 +-
src/include/tcop/cmdtaglist.h | 1 +
src/include/tcop/dest.h | 3 +-
28 files changed, 752 insertions(+), 11 deletions(-)
create mode 100644 src/backend/executor/svariableReceiver.c
create mode 100644 src/include/executor/svariableReceiver.h
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index d2a2479822..1ef210692c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -492,6 +492,22 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
else
ExplainDummyGroup("Notify", NULL, es);
}
+ else if (IsA(utilityStmt, LetStmt))
+ {
+ LetStmt *letstmt = (LetStmt *) utilityStmt;
+ List *rewritten;
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ appendStringInfoString(es->str, "SET SESSION VARIABLE\n");
+ else
+ ExplainDummyGroup("Set Session Variable", NULL, es);
+
+ rewritten = QueryRewrite(castNode(Query, copyObject(letstmt->query)));
+ Assert(list_length(rewritten) == 1);
+ ExplainOneQuery(linitial_node(Query, rewritten),
+ CURSOR_OPT_PARALLEL_OK, NULL, es,
+ queryString, params, queryEnv);
+ }
else
{
if (es->format == EXPLAIN_FORMAT_TEXT)
diff --git a/src/backend/commands/sessionvariable.c b/src/backend/commands/sessionvariable.c
index 7e2112a1bc..7b4b71c514 100644
--- a/src/backend/commands/sessionvariable.c
+++ b/src/backend/commands/sessionvariable.c
@@ -23,6 +23,7 @@
#include "catalog/pg_variable.h"
#include "commands/session_variable.h"
#include "executor/executor.h"
+#include "executor/svariableReceiver.h"
#include "optimizer/optimizer.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
@@ -893,6 +894,85 @@ ResetSessionVariables(void)
recheck_sessionvars_is_required = false;
}
+/*
+ * Assign result of evaluated expression to session variable
+ */
+void
+ExecuteLetStmt(ParseState *pstate,
+ LetStmt *stmt,
+ ParamListInfo params,
+ QueryEnvironment *queryEnv,
+ QueryCompletion *qc)
+{
+ Query *query = castNode(Query, stmt->query);
+ List *rewritten;
+ DestReceiver *dest;
+ AclResult aclresult;
+ PlannedStmt *plan;
+ QueryDesc *queryDesc;
+ Oid varid = query->resultVariable;
+
+ Assert(OidIsValid(varid));
+
+ /*
+ * Is it allowed to write to session variable?
+ */
+ aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+ /* Create dest receiver for LET */
+ dest = CreateDestReceiver(DestVariable);
+ SetVariableDestReceiverParams(dest, varid);
+
+ /* run rewriter - can be used for replacement of DEFAULT node */
+ query = copyObject(query);
+
+ rewritten = QueryRewrite(query);
+
+ Assert(list_length(rewritten) == 1);
+
+ query = linitial_node(Query, rewritten);
+ Assert(query->commandType == CMD_SELECT);
+
+ /* plan the query */
+ plan = pg_plan_query(query, pstate->p_sourcetext,
+ CURSOR_OPT_PARALLEL_OK, params);
+
+ /*
+ * Use a snapshot with an updated command ID to ensure this query sees
+ * results of any previously executed queries. (This could only
+ * matter if the planner executed an allegedly-stable function that
+ * changed the database contents, but let's do it anyway to be
+ * parallel to the EXPLAIN code path.)
+ */
+ PushCopiedSnapshot(GetActiveSnapshot());
+ UpdateActiveSnapshotCommandId();
+
+ /* Create a QueryDesc, redirecting output to our tuple receiver */
+ queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
+ GetActiveSnapshot(), InvalidSnapshot,
+ dest, params, queryEnv, 0);
+
+ /* call ExecutorStart to prepare the plan for execution */
+ ExecutorStart(queryDesc, 0);
+
+ /* run the plan to completion */
+ ExecutorRun(queryDesc, ForwardScanDirection, 2L, true);
+
+ /* save the rowcount if we're given a qc to fill */
+ if (qc)
+ SetQueryCompletion(qc, CMDTAG_LET, queryDesc->estate->es_processed);
+
+ /* and clean up */
+ ExecutorFinish(queryDesc);
+ ExecutorEnd(queryDesc);
+
+ FreeQueryDesc(queryDesc);
+
+ PopActiveSnapshot();
+}
+
/*
* Registration of actions to be executed on session variables at transaction
* end time. We want to drop temporary session variables with clause ON COMMIT
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 11118d0ce0..71248a34f2 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -76,6 +76,7 @@ OBJS = \
nodeWindowAgg.o \
nodeWorktablescan.o \
spi.o \
+ svariableReceiver.o \
tqueue.o \
tstoreReceiver.o
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 66d5a622a9..f7ac24997a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -51,6 +51,7 @@
#include "commands/session_variable.h"
#include "executor/execdebug.h"
#include "executor/nodeSubplan.h"
+#include "executor/svariableReceiver.h"
#include "foreign/fdwapi.h"
#include "jit/jit.h"
#include "mb/pg_wchar.h"
diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c
new file mode 100644
index 0000000000..2cede4ebae
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,148 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ * An implementation of DestReceiver that stores the result value in
+ * a session variable.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/executor/svariableReceiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/detoast.h"
+#include "executor/svariableReceiver.h"
+#include "commands/session_variable.h"
+
+typedef struct
+{
+ DestReceiver pub;
+ Oid varid;
+ Oid typid;
+ int32 typmod;
+ int typlen;
+ int slot_offset;
+ int rows;
+} svariableState;
+
+
+/*
+ * Prepare to receive tuples from executor.
+ */
+static void
+svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ svariableState *myState = (svariableState *) self;
+ int natts = typeinfo->natts;
+ int outcols = 0;
+ int i;
+
+ for (i = 0; i < natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+
+ if (attr->attisdropped)
+ continue;
+
+ if (++outcols > 1)
+ elog(ERROR, "svariable DestReceiver can take only one attribute");
+
+ myState->typid = attr->atttypid;
+ myState->typmod = attr->atttypmod;
+ myState->typlen = attr->attlen;
+ myState->slot_offset = i;
+ }
+
+ myState->rows = 0;
+}
+
+/*
+ * Receive a tuple from the executor and store it in session variable.
+ */
+static bool
+svariableReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
+{
+ svariableState *myState = (svariableState *) self;
+ Datum value;
+ bool isnull;
+ bool freeval = false;
+
+ /* Make sure the tuple is fully deconstructed */
+ slot_getallattrs(slot);
+
+ value = slot->tts_values[myState->slot_offset];
+ isnull = slot->tts_isnull[myState->slot_offset];
+
+ if (myState->typlen == -1 && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value)))
+ {
+ value = PointerGetDatum(detoast_external_attr((struct varlena *)
+ DatumGetPointer(value)));
+ freeval = true;
+ }
+
+ SetSessionVariable(myState->varid, value, isnull, myState->typid);
+
+ if (freeval)
+ pfree(DatumGetPointer(value));
+
+ return true;
+}
+
+/*
+ * Clean up at end of an executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+ /* Nothing to do. */
+}
+
+/*
+ * Destroy receiver when done with it
+ */
+static void
+svariableDestroyReceiver(DestReceiver *self)
+{
+ pfree(self);
+}
+
+/*
+ * Initially create a DestReceiver object.
+ */
+DestReceiver *
+CreateVariableDestReceiver(void)
+{
+ svariableState *self = (svariableState *) palloc0(sizeof(svariableState));
+
+ self->pub.receiveSlot = svariableReceiveSlot;
+ self->pub.rStartup = svariableStartupReceiver;
+ self->pub.rShutdown = svariableShutdownReceiver;
+ self->pub.rDestroy = svariableDestroyReceiver;
+ self->pub.mydest = DestVariable;
+
+ /*
+ * Private fields will be set by SetVariableDestReceiverParams and
+ * svariableStartupReceiver.
+ */
+ return (DestReceiver *) self;
+}
+
+/*
+ * Set parameters for a VariableDestReceiver.
+ * Should be called right after creating the DestReceiver.
+ */
+void
+SetVariableDestReceiverParams(DestReceiver *self, Oid varid)
+{
+ svariableState *myState = (svariableState *) self;
+
+ Assert(myState->pub.mydest == DestVariable);
+ Assert(OidIsValid(varid));
+
+ myState->varid = varid;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ae423f202b..966cc4f74b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3829,6 +3829,20 @@ _copySelectStmt(const SelectStmt *from)
return newnode;
}
+static LetStmt *
+_copyLetStmt(const LetStmt * from)
+{
+ LetStmt *newnode = makeNode(LetStmt);
+
+ COPY_NODE_FIELD(target);
+ COPY_NODE_FIELD(query);
+ COPY_SCALAR_FIELD(set_default);
+ COPY_SCALAR_FIELD(plpgsql_mode);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static SetOperationStmt *
_copySetOperationStmt(const SetOperationStmt *from)
{
@@ -6076,6 +6090,9 @@ copyObjectImpl(const void *from)
case T_SelectStmt:
retval = _copySelectStmt(from);
break;
+ case T_LetStmt:
+ retval = _copyLetStmt(from);
+ break;
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 277c925aab..13e3cc2c7b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1450,6 +1450,17 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
return true;
}
+static bool
+_equalLetStmt(const LetStmt * a, const LetStmt * b)
+{
+ COMPARE_NODE_FIELD(target);
+ COMPARE_NODE_FIELD(query);
+ COMPARE_SCALAR_FIELD(set_default);
+ COMPARE_SCALAR_FIELD(plpgsql_mode);
+
+ return true;
+}
+
static bool
_equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
{
@@ -3877,6 +3888,9 @@ equal(const void *a, const void *b)
case T_SelectStmt:
retval = _equalSelectStmt(a, b);
break;
+ case T_LetStmt:
+ retval = _equalLetStmt(a, b);
+ break;
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1ab31537c4..f0d1822cc1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3028,6 +3028,18 @@ _outPLAssignStmt(StringInfo str, const PLAssignStmt *node)
WRITE_LOCATION_FIELD(location);
}
+static void
+_outLetStmt(StringInfo str, const LetStmt * node)
+{
+ WRITE_NODE_TYPE("LET");
+
+ WRITE_NODE_FIELD(target);
+ WRITE_NODE_FIELD(query);
+ WRITE_BOOL_FIELD(set_default);
+ WRITE_BOOL_FIELD(plpgsql_mode);
+ WRITE_LOCATION_FIELD(location);
+}
+
static void
_outFuncCall(StringInfo str, const FuncCall *node)
{
@@ -3208,6 +3220,7 @@ _outQuery(StringInfo str, const Query *node)
case T_IndexStmt:
case T_NotifyStmt:
case T_DeclareCursorStmt:
+ case T_LetStmt:
WRITE_NODE_FIELD(utilityStmt);
break;
default:
@@ -4570,6 +4583,9 @@ outNode(StringInfo str, const void *obj)
case T_PLAssignStmt:
_outPLAssignStmt(str, obj);
break;
+ case T_LetStmt:
+ _outLetStmt(str, obj);
+ break;
case T_ColumnDef:
_outColumnDef(str, obj);
break;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 5e584bc03e..5d23131627 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -25,8 +25,11 @@
#include "postgres.h"
#include "access/sysattr.h"
+#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
+#include "commands/session_variable.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -51,6 +54,7 @@
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/queryjumble.h"
+#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
@@ -84,6 +88,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate,
CreateTableAsStmt *stmt);
static Query *transformCallStmt(ParseState *pstate,
CallStmt *stmt);
+static Query *transformLetStmt(ParseState *pstate,
+ LetStmt * stmt);
static void transformLockingClause(ParseState *pstate, Query *qry,
LockingClause *lc, bool pushedDown);
#ifdef RAW_EXPRESSION_COVERAGE_TEST
@@ -327,6 +333,7 @@ transformStmt(ParseState *pstate, Node *parseTree)
case T_UpdateStmt:
case T_DeleteStmt:
case T_MergeStmt:
+ case T_LetStmt:
(void) test_raw_expression_coverage(parseTree, NULL);
break;
default:
@@ -400,6 +407,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
(CallStmt *) parseTree);
break;
+ case T_LetStmt:
+ result = transformLetStmt(pstate,
+ (LetStmt *) parseTree);
+ break;
+
default:
/*
@@ -442,6 +454,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
case T_MergeStmt:
case T_SelectStmt:
case T_PLAssignStmt:
+ case T_LetStmt:
result = true;
break;
@@ -1634,12 +1647,268 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasSessionVariables = pstate->p_hasSessionVariables;
assign_query_collations(pstate, qry);
return qry;
}
+/*
+ * transformLetStmt -
+ * transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt * stmt)
+{
+ Query *query;
+ Query *result;
+ List *exprList = NIL;
+ List *exprListCoer = NIL;
+ List *indirection = NIL;
+ ListCell *lc;
+ Query *selectQuery;
+ int i = 0;
+ Oid varid;
+ char *attrname = NULL;
+ bool not_unique;
+ bool is_rowtype;
+ Oid typid;
+ int32 typmod;
+ Oid collid;
+ AclResult aclresult;
+ List *names = NULL;
+ int indirection_start;
+
+ /* There can't be any outer WITH to worry about */
+ Assert(pstate->p_ctenamespace == NIL);
+
+ names = NamesFromList(stmt->target);
+
+ varid = IdentifyVariable(names, &attrname, true, ¬_unique);
+ if (not_unique)
+ ereport(ERROR,
+ (errcode(ERRCODE_AMBIGUOUS_PARAMETER),
+ errmsg("target \"%s\" of LET command is ambiguous",
+ NameListToString(names)),
+ parser_errposition(pstate, stmt->location)));
+
+ if (!OidIsValid(varid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("session variable \"%s\" doesn't exist",
+ NameListToString(names)),
+ parser_errposition(pstate, stmt->location)));
+
+ indirection_start = list_length(names) - (attrname ? 1 : 0);
+ indirection = list_copy_tail(stmt->target, indirection_start);
+
+ get_session_variable_type_typmod_collid(varid, &typid, &typmod, &collid);
+
+ is_rowtype = type_is_rowtype(typid);
+
+ if (attrname && !is_rowtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type \"%s\" of target session variable \"%s.%s\" is not a composite type",
+ format_type_be(typid),
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid)),
+ parser_errposition(pstate, stmt->location)));
+
+ if (stmt->set_default && attrname != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("only session variable (without attribute specification) can be set to default"),
+ parser_errposition(pstate, stmt->location)));
+
+ aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, NameListToString(names));
+
+ pstate->p_expr_kind = EXPR_KIND_LET_TARGET;
+
+ /*
+ * stmt->query is a SelectStmt node. We don't tranform this node to a
+ * SetToDefault node. Instead we directly transform the target list here.
+ */
+ if (stmt->set_default)
+ {
+ selectQuery = makeNode(Query);
+ selectQuery->commandType = CMD_SELECT;
+
+ /*
+ * ResTarget(SetToDefault) -> TargetEntry(expr(SetToDefault)) */
+ selectQuery->targetList = transformTargetList(pstate,
+ ((SelectStmt *) stmt->query)->targetList,
+ EXPR_KIND_LET_TARGET);
+ }
+ else
+ selectQuery = transformStmt(pstate, stmt->query);
+
+ /* The grammar should have produced a SELECT */
+ if (!IsA(selectQuery, Query) ||
+ selectQuery->commandType != CMD_SELECT)
+ elog(ERROR, "unexpected non-SELECT command in LET command");
+
+ /*----------
+ * Generate an expression list for the LET that selects all the
+ * non-resjunk columns from the subquery.
+ *----------
+ */
+ exprList = NIL;
+ foreach(lc, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (tle->resjunk)
+ continue;
+
+ exprList = lappend(exprList, tle->expr);
+ }
+
+ /* don't allow multicolumn result */
+ if (list_length(exprList) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("expression is not scalar value"),
+ parser_errposition(pstate,
+ exprLocation((Node *) exprList))));
+
+ exprListCoer = NIL;
+
+ foreach(lc, exprList)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ Expr *coerced_expr;
+ Param *param;
+ Oid exprtypid;
+
+ if (IsA(expr, SetToDefault))
+ {
+ SetToDefault *def = (SetToDefault *) expr;
+
+ def->typeId = typid;
+ def->typeMod = typmod;
+ def->collation = collid;
+ }
+ else if (IsA(expr, Const) && ((Const *) expr)->constisnull)
+ {
+ /* use known type for NULL value */
+ expr = (Expr *) makeNullConst(typid, typmod, collid);
+ }
+
+ /* now we can read type of expression */
+ exprtypid = exprType((Node *) expr);
+
+ param = makeNode(Param);
+ param->paramkind = PARAM_VARIABLE;
+ param->paramvarid = varid;
+ param->paramtype = typid;
+ param->paramtypmod = typmod;
+
+ if (indirection != NULL)
+ {
+ bool targetIsArray;
+ char *targetName;
+
+ targetName = get_session_variable_name(varid);
+ targetIsArray = OidIsValid(get_element_type(typid));
+
+ pstate->p_hasSessionVariables = true;
+
+ coerced_expr = (Expr *)
+ transformAssignmentIndirection(pstate,
+ (Node *) param,
+ targetName,
+ targetIsArray,
+ typid,
+ typmod,
+ InvalidOid,
+ indirection,
+ list_head(indirection),
+ (Node *) expr,
+ COERCION_PLPGSQL,
+ stmt->location);
+ }
+ else
+ coerced_expr = (Expr *)
+ coerce_to_target_type(pstate,
+ (Node *) expr,
+ exprtypid,
+ typid, typmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ stmt->location);
+
+ if (coerced_expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable \"%s.%s\" is of type %s,"
+ " but expression is of type %s",
+ get_namespace_name(get_session_variable_namespace(varid)),
+ get_session_variable_name(varid),
+ format_type_be(typid),
+ format_type_be(exprtypid)),
+ errhint("You will need to rewrite or cast the expression."),
+ parser_errposition(pstate, exprLocation((Node *) expr))));
+
+ exprListCoer = lappend(exprListCoer, coerced_expr);
+ }
+
+ /*
+ * Generate query's target list using the computed list of expressions.
+ */
+ query = makeNode(Query);
+ query->commandType = CMD_SELECT;
+
+ foreach(lc, exprListCoer)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ TargetEntry *tle;
+
+ tle = makeTargetEntry(expr,
+ i + 1,
+ FigureColname((Node *) expr),
+ false);
+ query->targetList = lappend(query->targetList, tle);
+ }
+
+ /* done building the range table and jointree */
+ query->rtable = pstate->p_rtable;
+ query->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ query->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ query->hasSubLinks = pstate->p_hasSubLinks;
+ query->hasSessionVariables = pstate->p_hasSessionVariables;
+
+ /* This is top query */
+ query->canSetTag = true;
+
+ /*
+ * rewrite SetToDefaults needs varid in Query structure
+ */
+ query->resultVariable = varid;
+
+ assign_query_collations(pstate, query);
+
+ stmt->query = (Node *) query;
+
+ /*
+ * When statement is executed as a PlpgSQL LET statement, then we should
+ * return the query and not use a utilityStmt wrapper node.
+ */
+ if (stmt->plpgsql_mode)
+ return query;
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
/*
* transformSetOperationStmt -
* transforms a set-operations tree
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 10d21df0e2..26ebf06722 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -316,7 +316,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DropTransformStmt
DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
- ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
+ LetStmt ListenStmt LoadStmt LockStmt MergeStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
RemoveFuncStmt RemoveOperStmt RenameStmt ReturnStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -457,6 +457,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
TriggerTransitions TriggerReferencing
vacuum_relation_list opt_vacuum_relation_list
drop_option_list pub_obj_list
+ let_target
%type <node> opt_routine_body
%type <groupclause> group_clause
@@ -815,7 +816,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY KEYS KEEP
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LET LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MERGE METHOD
@@ -1150,6 +1151,7 @@ stmt:
| ImportForeignSchemaStmt
| IndexStmt
| InsertStmt
+ | LetStmt
| ListenStmt
| RefreshMatViewStmt
| LoadStmt
@@ -11367,6 +11369,7 @@ ExplainableStmt:
| CreateMatViewStmt
| RefreshMatViewStmt
| ExecuteStmt /* by default all are $$=$1 */
+ | LetStmt
;
/*****************************************************************************
@@ -11396,6 +11399,7 @@ PreparableStmt:
| UpdateStmt
| DeleteStmt
| MergeStmt /* by default all are $$=$1 */
+ | LetStmt
;
/*****************************************************************************
@@ -11973,6 +11977,49 @@ opt_hold: /* EMPTY */ { $$ = 0; }
| WITHOUT HOLD { $$ = 0; }
;
+/*****************************************************************************
+ *
+ * QUERY:
+ * LET STATEMENTS
+ *
+ *****************************************************************************/
+LetStmt: LET let_target '=' a_expr
+ {
+ LetStmt *n = makeNode(LetStmt);
+ SelectStmt *select;
+ ResTarget *res;
+
+ n->target = $2;
+
+ select = makeNode(SelectStmt);
+ res = makeNode(ResTarget);
+
+ /* Create target list for implicit query */
+ res->name = NULL;
+ res->indirection = NIL;
+ res->val = (Node *) $4;
+ res->location = @4;
+
+ select->targetList = list_make1(res);
+ n->query = (Node *) select;
+
+ n->set_default = IsA($4, SetToDefault);
+
+ n->location = @2;
+ $$ = (Node *) n;
+ }
+ ;
+
+let_target:
+ ColId opt_indirection
+ {
+ $$ = list_make1(makeString($1));
+ if ($2)
+ $$ = list_concat($$,
+ check_indirection($2, yyscanner));
+ }
+ ;
+
/*****************************************************************************
*
* QUERY:
@@ -17163,6 +17210,7 @@ unreserved_keyword:
| LARGE_P
| LAST_P
| LEAKPROOF
+ | LET
| LEVEL
| LISTEN
| LOAD
@@ -17769,6 +17817,7 @@ bare_label_keyword:
| LEAKPROOF
| LEAST
| LEFT
+ | LET
| LEVEL
| LIKE
| LISTEN
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index dbe630a51b..4365692735 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -348,6 +348,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
Assert(false); /* can't happen */
break;
case EXPR_KIND_OTHER:
+ case EXPR_KIND_LET_TARGET:
/*
* Accept aggregate/grouping here; caller must throw error if
@@ -955,6 +956,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
case EXPR_KIND_CYCLE_MARK:
errkind = true;
break;
+ case EXPR_KIND_LET_TARGET:
+ err = _("window functions are not allowed in LET statement");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 9101c7c42d..498531cf41 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -577,6 +577,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
case EXPR_KIND_GENERATED_COLUMN:
case EXPR_KIND_CYCLE_MARK:
case EXPR_KIND_VARIABLE_DEFAULT:
+ case EXPR_KIND_LET_TARGET:
/* okay */
break;
@@ -1987,6 +1988,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_VALUES:
case EXPR_KIND_VALUES_SINGLE:
case EXPR_KIND_CYCLE_MARK:
+ case EXPR_KIND_LET_TARGET:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
@@ -3354,6 +3356,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "GENERATED AS";
case EXPR_KIND_CYCLE_MARK:
return "CYCLE";
+ case EXPR_KIND_LET_TARGET:
+ return "LET";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index c189bd1276..c7b99e4991 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2659,6 +2659,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
case EXPR_KIND_CYCLE_MARK:
errkind = true;
break;
+ case EXPR_KIND_LET_TARGET:
+ err = _("set-returning functions are not allowed in CALL arguments");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..9add612d7e 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -89,7 +89,9 @@ transformTargetEntry(ParseState *pstate,
* through unmodified. (transformExpr will throw the appropriate
* error if we're disallowing it.)
*/
- if (exprKind == EXPR_KIND_UPDATE_SOURCE && IsA(node, SetToDefault))
+ if ((exprKind == EXPR_KIND_UPDATE_SOURCE ||
+ exprKind == EXPR_KIND_LET_TARGET)
+ && IsA(node, SetToDefault))
expr = node;
else
expr = transformExpr(pstate, node, exprKind);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 29ae27e5e3..08ed6ed7f9 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -25,6 +25,7 @@
#include "access/table.h"
#include "catalog/dependency.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_variable.h"
#include "commands/trigger.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
@@ -3674,6 +3675,39 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
}
}
+ /*
+ * Rewrite SetToDefault by default expression of Let statement.
+ */
+ if (event == CMD_SELECT && OidIsValid(parsetree->resultVariable))
+ {
+ Oid resultVariable = parsetree->resultVariable;
+
+ if (list_length(parsetree->targetList) == 1 &&
+ IsA(((TargetEntry *) linitial(parsetree->targetList))->expr, SetToDefault))
+ {
+ Variable var;
+ TargetEntry *tle;
+ TargetEntry *newtle;
+ Expr *defexpr;
+
+ /* Read session variable metadata with defexpr */
+ initVariable(&var, resultVariable, false);
+
+ if (var.has_defexpr)
+ defexpr = (Expr *) var.defexpr;
+ else
+ defexpr = (Expr *) makeNullConst(var.typid, var.typmod, var.collation);
+
+ tle = (TargetEntry *) linitial(parsetree->targetList);
+ newtle = makeTargetEntry(defexpr,
+ tle->resno,
+ pstrdup(tle->resname),
+ false);
+
+ parsetree->targetList = list_make1(newtle);
+ }
+ }
+
/*
* If the statement is an insert, update, delete, or merge, adjust its
* targetlist as needed, and then fire INSERT/UPDATE/DELETE rules on it.
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index a233dd4758..a8351c4499 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -213,10 +213,10 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index,
}
/*
- * For SELECT, UPDATE and DELETE, add security quals to enforce the USING
- * policies. These security quals control access to existing table rows.
- * Restrictive policies are combined together using AND, and permissive
- * policies are combined together using OR.
+ * For SELECT, LET, UPDATE and DELETE, add security quals to enforce the
+ * USING policies. These security quals control access to existing table
+ * rows. Restrictive policies are combined together using AND, and
+ * permissive policies are combined together using OR.
*/
get_policies_for_relation(rel, commandType, user_id, &permissive_policies,
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index c952cbea8b..9c5339804b 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -37,6 +37,7 @@
#include "executor/functions.h"
#include "executor/tqueue.h"
#include "executor/tstoreReceiver.h"
+#include "executor/svariableReceiver.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "utils/portal.h"
@@ -152,6 +153,9 @@ CreateDestReceiver(CommandDest dest)
case DestTupleQueue:
return CreateTupleQueueDestReceiver(NULL);
+
+ case DestVariable:
+ return CreateVariableDestReceiver();
}
/* should never get here */
@@ -207,6 +211,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestVariable:
break;
}
}
@@ -252,6 +257,7 @@ NullCommand(CommandDest dest)
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestVariable:
break;
}
}
@@ -295,6 +301,7 @@ ReadyForQuery(CommandDest dest)
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestVariable:
break;
}
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6d88e163d2..14534ea9e2 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -242,6 +242,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
case T_CallStmt:
case T_DoStmt:
+ case T_LetStmt:
{
/*
* Commands inside the DO block or the called procedure might
@@ -1071,6 +1072,11 @@ standard_ProcessUtility(PlannedStmt *pstmt,
break;
}
+ case T_LetStmt:
+ ExecuteLetStmt(pstate, (LetStmt *) parsetree, params,
+ queryEnv, qc);
+ break;
+
default:
/* All other statement types have event trigger support */
ProcessUtilitySlow(pstate, pstmt, queryString,
@@ -2191,6 +2197,10 @@ UtilityContainsQuery(Node *parsetree)
return UtilityContainsQuery(qry->utilityStmt);
return qry;
+ case T_LetStmt:
+ qry = castNode(Query, ((LetStmt *) parsetree)->query);
+ return qry;
+
default:
return NULL;
}
@@ -2389,6 +2399,10 @@ CreateCommandTag(Node *parsetree)
tag = CMDTAG_SELECT;
break;
+ case T_LetStmt:
+ tag = CMDTAG_LET;
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
{
@@ -3277,6 +3291,7 @@ GetCommandLogLevel(Node *parsetree)
break;
case T_PLAssignStmt:
+ case T_LetStmt:
lev = LOGSTMT_ALL;
break;
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index a25aeb47a9..e4a71a5b2b 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -135,6 +135,7 @@ InitPlanCache(void)
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNSERVEROID, PlanCacheSysCallback, (Datum) 0);
CacheRegisterSyscacheCallback(FOREIGNDATAWRAPPEROID, PlanCacheSysCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(VARIABLEOID, PlanCacheObjectCallback, (Datum) 0);
}
/*
@@ -1868,12 +1869,24 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
* Recurse into sublink subqueries, too. But we already did the ones in
* the rtable and cteList.
*/
- if (parsetree->hasSubLinks)
+ if (parsetree->hasSubLinks ||
+ parsetree->hasSessionVariables)
{
query_tree_walker(parsetree, ScanQueryWalker,
(void *) &acquire,
QTW_IGNORE_RC_SUBQUERIES);
}
+
+ /* Process session variables */
+ if (OidIsValid(parsetree->resultVariable))
+ {
+ if (acquire)
+ LockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+ 0, AccessShareLock);
+ else
+ UnlockDatabaseObject(VariableRelationId, parsetree->resultVariable,
+ 0, AccessShareLock);
+ }
}
/*
diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h
index 7442004d97..b38db9374d 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -32,6 +32,9 @@ extern Datum CopySessionVariable(Oid varid, bool *isNull, Oid *typid);
extern void SetSessionVariable(Oid varid, Datum value, bool isNull, Oid typid);
extern void SetSessionVariableWithSecurityCheck(Oid varid, Datum value, bool isNull, Oid typid);
+extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
+ QueryEnvironment *queryEnv, QueryCompletion *qc);
+
extern void RegisterOnCommitDropSessionVariable(Oid varid);
extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 0000000000..1d823a42a8
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ * prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/svariableReceiver.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef SVARIABLE_RECEIVER_H
+#define SVARIABLE_RECEIVER_H
+
+#include "tcop/dest.h"
+
+
+extern DestReceiver *CreateVariableDestReceiver(void);
+
+extern void SetVariableDestReceiverParams(DestReceiver *self, Oid varid);
+
+#endif /* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index cec659aa21..2935712a43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -447,6 +447,7 @@ typedef enum NodeTag
T_AlterCollationStmt,
T_CallStmt,
T_AlterStatsStmt,
+ T_LetStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 53f652d7a6..df5ea75db4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1981,6 +1981,21 @@ typedef struct MergeStmt
WithClause *withClause; /* WITH clause */
} MergeStmt;
+/* ----------------------
+ * Let Statement
+ * ----------------------
+ */
+typedef struct LetStmt
+{
+ NodeTag type;
+ List *target; /* target variable */
+ Node *query; /* source expression */
+ bool set_default; /* true, when set to DEFAULt is wanted */
+ bool plpgsql_mode; /* true, when command will be executed (parsed)
+ * by plpgsql runtime */
+ int location;
+} LetStmt;
+
/* ----------------------
* Select Statement
*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 9d06747cb6..c75b595b50 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -44,7 +44,7 @@ typedef struct PlannedStmt
{
NodeTag type;
- CmdType commandType; /* select|insert|update|delete|merge|utility */
+ CmdType commandType; /* select|let|insert|update|delete|merge|utility */
uint64 queryId; /* query identifier (copied from Query) */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 114a97865d..59d5bf186a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -255,6 +255,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD, AS_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 59da340265..28d52b51a7 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -81,7 +81,8 @@ typedef enum ParseExprKind
EXPR_KIND_COPY_WHERE, /* WHERE condition in COPY FROM */
EXPR_KIND_GENERATED_COLUMN, /* generation expression for a column */
EXPR_KIND_CYCLE_MARK, /* cycle mark value */
- EXPR_KIND_VARIABLE_DEFAULT /* default value for session variable */
+ EXPR_KIND_VARIABLE_DEFAULT, /* default value for session variable */
+ EXPR_KIND_LET_TARGET /* LET assignment (should be same like UPDATE) */
} ParseExprKind;
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index fc54f517d1..037538a3fe 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -187,6 +187,7 @@ PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
+PG_CMDTAG(CMDTAG_LET, "LET", false, false, false)
PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 3c3eabae67..7f18e07915 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -97,7 +97,8 @@ typedef enum
DestCopyOut, /* results sent to COPY TO code */
DestSQLFunction, /* results sent to SQL-language func mgr */
DestTransientRel, /* results sent to transient relation */
- DestTupleQueue /* results sent to tuple queue */
+ DestTupleQueue, /* results sent to tuple queue */
+ DestVariable /* results sents to session variable */
} CommandDest;
/* ----------------
--
2.35.1