v20230123-2-0003-LET-command.patch
text/x-patch
Filename: v20230123-2-0003-LET-command.patch
Type: text/x-patch
Part: 7
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 v20230123-0003
Subject: LET command
| File | + | − |
|---|---|---|
| src/backend/commands/explain.c | 16 | 0 |
| src/backend/commands/session_variable.c | 86 | 0 |
| src/backend/executor/execMain.c | 1 | 0 |
| src/backend/executor/Makefile | 1 | 0 |
| src/backend/executor/meson.build | 1 | 0 |
| src/backend/executor/svariableReceiver.c | 214 | 0 |
| src/backend/nodes/nodeFuncs.c | 10 | 0 |
| src/backend/optimizer/plan/setrefs.c | 25 | 13 |
| src/backend/parser/analyze.c | 292 | 0 |
| src/backend/parser/gram.y | 53 | 4 |
| src/backend/parser/parse_agg.c | 4 | 0 |
| src/backend/parser/parse_cte.c | 8 | 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 | 12 | 0 |
| src/include/commands/session_variable.h | 3 | 0 |
| src/include/executor/svariableReceiver.h | 25 | 0 |
| src/include/nodes/parsenodes.h | 18 | 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 |
| src/tools/pgindent/typedefs.list | 2 | 0 |
From 9765262acc2080bad6c9932571a75b20abdd0ddb Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Thu, 10 Nov 2022 22:03:04 +0100
Subject: [PATCH 03/10] LET command
Set session variable value to result of query or result of default expression
---
src/backend/commands/explain.c | 16 ++
src/backend/commands/session_variable.c | 86 +++++++
src/backend/executor/Makefile | 1 +
src/backend/executor/execMain.c | 1 +
src/backend/executor/meson.build | 1 +
src/backend/executor/svariableReceiver.c | 214 +++++++++++++++++
src/backend/nodes/nodeFuncs.c | 10 +
src/backend/optimizer/plan/setrefs.c | 38 ++-
src/backend/parser/analyze.c | 292 +++++++++++++++++++++++
src/backend/parser/gram.y | 57 ++++-
src/backend/parser/parse_agg.c | 4 +
src/backend/parser/parse_cte.c | 8 +
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 | 12 +
src/include/commands/session_variable.h | 3 +
src/include/executor/svariableReceiver.h | 25 ++
src/include/nodes/parsenodes.h | 18 ++
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 +-
src/tools/pgindent/typedefs.list | 2 +
29 files changed, 848 insertions(+), 25 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 5212a64b1e..1814517280 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/session_variable.c b/src/backend/commands/session_variable.c
index 1401d849a7..f7691920b9 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -20,16 +20,20 @@
#include "catalog/namespace.h"
#include "catalog/pg_variable.h"
#include "commands/session_variable.h"
+#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"
+#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/snapmgr.h"
#include "utils/syscache.h"
/*
@@ -1241,6 +1245,88 @@ AtEOSubXact_SessionVariable(bool isCommit,
}
}
+/*
+ * 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 = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_session_variable_name(varid));
+
+ /* Create dest receiver for LET */
+ dest = CreateDestReceiver(DestVariable);
+ SetVariableDestReceiverVarid(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. The result should be only one
+ * row. For an check too_many_rows we need to read two rows.
+ */
+ 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();
+}
+
/*
* pg_session_variables - designed for testing
*
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 f82449a65b..eae5b20ca8 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/meson.build b/src/backend/executor/meson.build
index 65f9457c9b..b34b383b07 100644
--- a/src/backend/executor/meson.build
+++ b/src/backend/executor/meson.build
@@ -64,6 +64,7 @@ backend_sources += files(
'nodeWindowAgg.c',
'nodeWorktablescan.c',
'spi.c',
+ 'svariableReceiver.c',
'tqueue.c',
'tstoreReceiver.c',
)
diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c
new file mode 100644
index 0000000000..7c975cbbf9
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,214 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.c
+ * An implementation of DestReceiver that stores the result value in
+ * a session variable.
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/executor/svariableReceiver.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+#include "miscadmin.h"
+
+#include "access/detoast.h"
+#include "executor/svariableReceiver.h"
+#include "commands/session_variable.h"
+#include "storage/lock.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.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;
+
+ /* Receiver should be initialized by SetVariableDestReceiverVarid */
+ Assert(OidIsValid(myState->varid));
+
+ for (i = 0; i < natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
+ Oid typid;
+ Oid collid;
+ int32 typmod;
+
+ if (attr->attisdropped)
+ continue;
+
+ if (++outcols > 1)
+ continue;
+
+ get_session_variable_type_typmod_collid(myState->varid,
+ &typid,
+ &typmod,
+ &collid);
+
+ /*
+ * double check - the type and typmod of target variable should be
+ * same as type and typmod of assignment expression. It should be, the
+ * expression is wrapped by cast to target type and typmod.
+ */
+ if (attr->atttypid != typid ||
+ (attr->atttypmod >= 0 &&
+ attr->atttypmod != typmod))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("target session variable is of type %s"
+ " but expression is of type %s",
+ format_type_with_typemod(typid, typmod),
+ format_type_with_typemod(attr->atttypid,
+ attr->atttypmod))));
+
+ myState->typid = attr->atttypid;
+ myState->typmod = attr->atttypmod;
+ myState->typlen = attr->attlen;
+ myState->slot_offset = i;
+ }
+
+ if (outcols != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("assignment expression returned %d column",
+ "assignment expression returned %d columns",
+ outcols,
+ outcols)));
+
+
+
+ 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;
+ }
+
+ myState->rows += 1;
+
+ if (myState->rows > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ROWS),
+ errmsg("expression returned more than one row")));
+
+ SetSessionVariable(myState->varid, value, isnull);
+
+ if (freeval)
+ pfree(DatumGetPointer(value));
+
+ return true;
+}
+
+/*
+ * Clean up at end of an executor run
+ */
+static void
+svariableShutdownReceiver(DestReceiver *self)
+{
+ if (((SVariableState *) self)->rows == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("expression returned no rows")));
+}
+
+/*
+ * 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 SetVariableDestReceiverVarid and
+ * svariableStartupReceiver.
+ */
+ return (DestReceiver *) self;
+}
+
+/*
+ * Set parameters for a VariableDestReceiver.
+ * Should be called right after creating the DestReceiver.
+ */
+void
+SetVariableDestReceiverVarid(DestReceiver *self, Oid varid)
+{
+ SVariableState *myState = (SVariableState *) self;
+ LOCKTAG locktag PG_USED_FOR_ASSERTS_ONLY;
+
+ Assert(myState->pub.mydest == DestVariable);
+ Assert(OidIsValid(varid));
+ Assert(SearchSysCacheExists1(VARIABLEOID, varid));
+
+#ifdef USE_ASSERT_CHECKING
+
+ SET_LOCKTAG_OBJECT(locktag,
+ MyDatabaseId,
+ VariableRelationId,
+ varid,
+ 0);
+
+ Assert(LockHeldByMe(&locktag, AccessShareLock));
+
+#endif
+
+ myState->varid = varid;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 8fc24c882b..893dbf2edc 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3791,6 +3791,16 @@ raw_expression_tree_walker_impl(Node *node,
return true;
}
break;
+ case T_LetStmt:
+ {
+ LetStmt *stmt = (LetStmt *) node;
+
+ if (WALK(stmt->target))
+ return true;
+ if (WALK(stmt->query))
+ return true;
+ }
+ break;
case T_PLAssignStmt:
{
PLAssignStmt *stmt = (PLAssignStmt *) node;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 965cf6ec22..b8e8fe95a7 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -192,7 +192,7 @@ static List *set_windowagg_runcondition_references(PlannerInfo *root,
Plan *plan);
static bool pull_up_has_session_variables_walker(Node *node,
PlannerInfo *root);
-
+static void record_plan_variable_dependency(PlannerInfo *root, Oid varid);
/*****************************************************************************
*
@@ -2029,18 +2029,7 @@ fix_expr_common(PlannerInfo *root, Node *node)
Param *p = (Param *) node;
if (p->paramkind == PARAM_VARIABLE)
- {
- PlanInvalItem *inval_item = makeNode(PlanInvalItem);
-
- /* paramid is still session variable id */
- inval_item->cacheId = VARIABLEOID;
- inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
- ObjectIdGetDatum(p->paramvarid));
-
- /* Append this variable to global, register dependency */
- root->glob->invalItems = lappend(root->glob->invalItems,
- inval_item);
- }
+ record_plan_variable_dependency(root, p->paramvarid);
}
}
@@ -3434,6 +3423,25 @@ record_plan_type_dependency(PlannerInfo *root, Oid typid)
}
}
+/*
+ * Record dependency on a session variable. The variable can be used as a
+ * session variable in expression list, or target of LET statement.
+ */
+static void
+record_plan_variable_dependency(PlannerInfo *root, Oid varid)
+{
+ PlanInvalItem *inval_item = makeNode(PlanInvalItem);
+
+ /* paramid is still session variable id */
+ inval_item->cacheId = VARIABLEOID;
+ inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID,
+ ObjectIdGetDatum(varid));
+
+ /* Append this variable to global, register dependency */
+ root->glob->invalItems = lappend(root->glob->invalItems,
+ inval_item);
+}
+
/*
* extract_query_dependencies
* Given a rewritten, but not yet planned, query or queries
@@ -3507,6 +3515,10 @@ extract_query_dependencies_walker(Node *node, PlannerInfo *context)
query = UtilityContainsQuery(query->utilityStmt);
if (query == NULL)
return false;
+
+ /* Record the session variable used as target of LET statement. */
+ if (OidIsValid(query->resultVariable))
+ record_plan_variable_dependency(context, query->resultVariable);
}
/* Remember if any Query has RLS quals applied by rewriter */
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c57abee02e..111e12ca5b 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/backend_status.h"
#include "utils/builtins.h"
#include "utils/guc.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;
@@ -1640,6 +1653,285 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
return qry;
}
+/*
+ * transformLetStmt -
+ * transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+{
+ Query *query;
+ Query *result;
+ List *exprList = NIL;
+ List *exprListCoer = NIL;
+ ListCell *lc;
+ ListCell *indirection_head = NULL;
+ Query *selectQuery;
+ 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;
+ int i = 0;
+
+ /* There can't be any outer WITH to worry about */
+ Assert(pstate->p_ctenamespace == NIL);
+
+ names = NamesFromList(stmt->target);
+
+ /*
+ * The AccessShareLock is created on related session variable. The lock
+ * will be kept for the whole transaction.
+ */
+ varid = IdentifyVariable(names, &attrname, ¬_unique, false);
+ 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)));
+
+ /*
+ * Calculate start of possible position of an indirection in list,
+ * and when it is inside the list, store pointer on first node
+ * of indirection.
+ */
+ indirection_start = list_length(names) - (attrname ? 1 : 0);
+ if (list_length(stmt->target) > indirection_start)
+ indirection_head = list_nth_cell(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 = object_aclcheck(VariableRelationId, varid, GetUserId(), ACL_UPDATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, NameListToString(names));
+
+ pstate->p_expr_kind = EXPR_KIND_LET_TARGET;
+
+ /*
+ * The LET statements suppports two syntaxes: LET var = expr and LET var =
+ * DEFAULT. In first case, the expression is of SelectStmt node and it is
+ * transformated to query (SELECT) by usual way. Second syntax should be
+ * transformed differently. It is more cleaner do this transformation here
+ * (like special case), because we don't need to enhance SelectStmt about
+ * fields necessary for this transformation somwehere in SelectStmt
+ * transformation. Isn't to necessary to uglify the SelectStmt
+ * transformation. This is special case of LET statement, not SelectStmt.
+ */
+ if (stmt->set_default)
+ {
+ selectQuery = makeNode(Query);
+ selectQuery->commandType = CMD_SELECT;
+
+ /*
+ * ResTarget(SetToDefault) -> TargetEntry(expr(SetToDefault)) The
+ * SetToDefault is replaced by defexpr by rewriter in RewriteQuery
+ * function.
+ */
+ 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 */
+ Assert(IsA(selectQuery, Query) && selectQuery->commandType == CMD_SELECT);
+
+ /*----------
+ * 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_plural("assignment expression returned %d column",
+ "assignment expression returned %d columns",
+ list_length(exprList),
+ list_length(exprList)),
+ 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_head)
+ {
+ 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,
+ stmt->target,
+ indirection_head,
+ (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;
+
+ /*
+ * Save target session variable id. This value is used multiple times: by
+ * query rewriter (for getting related defexpr), by planner (for acquiring
+ * AccessShareLock on target variable), and by executor (we need to know
+ * target variable id).
+ */
+ 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 because we don't want to 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 4b86bafcc9..40a47f4866 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -304,7 +304,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
@@ -452,6 +452,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
@@ -715,7 +716,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
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
@@ -1042,6 +1043,7 @@ stmt:
| ImportForeignSchemaStmt
| IndexStmt
| InsertStmt
+ | LetStmt
| ListenStmt
| RefreshMatViewStmt
| LoadStmt
@@ -11938,7 +11940,8 @@ ExplainableStmt:
| CreateAsStmt
| CreateMatViewStmt
| RefreshMatViewStmt
- | ExecuteStmt /* by default all are $$=$1 */
+ | ExecuteStmt
+ | LetStmt /* by default all are $$=$1 */
;
/*****************************************************************************
@@ -11968,7 +11971,8 @@ PreparableStmt:
| InsertStmt
| UpdateStmt
| DeleteStmt
- | MergeStmt /* by default all are $$=$1 */
+ | MergeStmt
+ | LetStmt /* by default all are $$=$1 */
;
/*****************************************************************************
@@ -12556,6 +12560,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:
@@ -16978,6 +17025,7 @@ unreserved_keyword:
| LARGE_P
| LAST_P
| LEAKPROOF
+ | LET
| LEVEL
| LISTEN
| LOAD
@@ -17548,6 +17596,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 d7e33988bd..cce979ad68 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -349,6 +349,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
@@ -956,6 +957,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_cte.c b/src/backend/parser/parse_cte.c
index c5b1a49725..35aa070571 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -133,6 +133,14 @@ transformWithClause(ParseState *pstate, WithClause *withClause)
errmsg("MERGE not supported in WITH query"),
parser_errposition(pstate, cte->location));
+ /* LET is allowed by parser, but not supported. Reject for now */
+ if (IsA(cte->ctequery, LetStmt))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("LET not supported in WITH query"),
+ parser_errposition(pstate, cte->location));
+
+
for_each_cell(rest, withClause->ctes, lnext(withClause->ctes, lc))
{
CommonTableExpr *cte2 = (CommonTableExpr *) lfirst(rest);
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 2743e45e2d..1506fb4cea 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -558,6 +558,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;
@@ -1993,6 +1994,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:
@@ -3315,6 +3317,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 64b5857750..e9d7bf404b 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2660,6 +2660,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 LET assignment expression");
+ 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 0ca6beccb8..854d611e8b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -88,7 +88,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 c74bac20b1..972452b0f0 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"
@@ -3672,6 +3673,39 @@ RewriteQuery(Query *parsetree, List *rewrite_events, int orig_rt_length)
}
}
+ /*
+ * 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 569c1c9467..4a1a401288 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -220,10 +220,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 c0406e2ee5..86dbf370ac 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 */
@@ -187,6 +191,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestVariable:
break;
}
}
@@ -232,6 +237,7 @@ NullCommand(CommandDest dest)
case DestSQLFunction:
case DestTransientRel:
case DestTupleQueue:
+ case DestVariable:
break;
}
}
@@ -275,6 +281,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 bd0041159b..685b2fd854 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,
@@ -2197,6 +2203,10 @@ UtilityContainsQuery(Node *parsetree)
return UtilityContainsQuery(qry->utilityStmt);
return qry;
+ case T_LetStmt:
+ qry = castNode(Query, ((LetStmt *) parsetree)->query);
+ return qry;
+
default:
return NULL;
}
@@ -2395,6 +2405,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:
{
@@ -3280,6 +3294,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 39a07446ed..a618a45abc 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);
}
/*
@@ -1878,6 +1879,17 @@ ScanQueryForLocks(Query *parsetree, bool acquire)
(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 f442c5d3b8..ceb0d357da 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -39,4 +39,7 @@ extern void AtEOSubXact_SessionVariable(bool isCommit,
SubTransactionId mySubid,
SubTransactionId parentSubid);
+extern void ExecuteLetStmt(ParseState *pstate, LetStmt *stmt, ParamListInfo params,
+ QueryEnvironment *queryEnv, QueryCompletion *qc);
+
#endif
diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h
new file mode 100644
index 0000000000..63f6ee9b7f
--- /dev/null
+++ b/src/include/executor/svariableReceiver.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * svariableReceiver.h
+ * prototypes for svariableReceiver.c
+ *
+ *
+ * Portions Copyright (c) 1996-2023, 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 SetVariableDestReceiverVarid(DestReceiver *self, Oid varid);
+
+#endif /* SVARIABLE_RECEIVER_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index af03dfd8a8..438c5e3341 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -143,6 +143,9 @@ typedef struct Query
*/
int resultRelation;
+ /* target variable of LET statement */
+ Oid resultVariable;
+
/* has aggregates in tlist or havingQual */
bool hasAggs;
/* has window functions in tlist */
@@ -1780,6 +1783,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 68c03a0eb1..37f3d46e25 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -50,7 +50,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 602a41b06c..192cc41f62 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -237,6 +237,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 6b38b72654..d1c209eeb0 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 259bdc994e..648a4af305 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -186,6 +186,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 a7d86e7abd..e7dd749949 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -95,7 +95,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;
/* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index dd15890713..936b420698 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1395,6 +1395,7 @@ LargeObjectDesc
LastAttnumInfo
Latch
LerpFunc
+LetStmt
LexDescr
LexemeEntry
LexemeHashKey
@@ -2646,6 +2647,7 @@ SupportRequestRows
SupportRequestSelectivity
SupportRequestSimplify
SupportRequestWFuncMonotonic
+SVariableState
SVariable
SVariableData
SVariableXActAction
--
2.39.1