v20220911-1-0003-LET-command.patch
text/x-patch
Filename: v20220911-1-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 v20220911-0003
Subject: LET command
| File | + | − |
|---|---|---|
| src/backend/commands/explain.c | 16 | 0 |
| src/backend/commands/session_variable.c | 83 | 0 |
| src/backend/executor/execMain.c | 1 | 0 |
| src/backend/executor/Makefile | 1 | 0 |
| src/backend/executor/svariableReceiver.c | 154 | 0 |
| src/backend/nodes/nodeFuncs.c | 10 | 0 |
| src/backend/parser/analyze.c | 281 | 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 | 12 | 0 |
| src/include/commands/session_variable.h | 3 | 0 |
| src/include/executor/svariableReceiver.h | 25 | 0 |
| src/include/nodes/parsenodes.h | 16 | 0 |
| src/include/nodes/pathnodes.h | 1 | 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 80849f44514411dc166d3568ef1dd7e478892450 Mon Sep 17 00:00:00 2001
From: "okbob@github.com" <okbob@github.com>
Date: Fri, 9 Sep 2022 14:03:15 +0200
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 | 83 +++++++
src/backend/executor/Makefile | 1 +
src/backend/executor/execMain.c | 1 +
src/backend/executor/svariableReceiver.c | 154 +++++++++++++
src/backend/nodes/nodeFuncs.c | 10 +
src/backend/parser/analyze.c | 281 +++++++++++++++++++++++
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 | 12 +
src/include/commands/session_variable.h | 3 +
src/include/executor/svariableReceiver.h | 25 ++
src/include/nodes/parsenodes.h | 16 ++
src/include/nodes/pathnodes.h | 1 +
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 +
27 files changed, 737 insertions(+), 10 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 053d2ca5ae..5d127d8d2c 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 ed0e40618a..b47ee68cb5 100644
--- a/src/backend/commands/session_variable.c
+++ b/src/backend/commands/session_variable.c
@@ -20,19 +20,23 @@
#include "catalog/namespace.h"
#include "catalog/pg_variable.h"
#include "commands/session_variable.h"
+#include "executor/svariableReceiver.h"
#include "funcapi.h"
#include "optimizer/optimizer.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_type.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"
/*
@@ -1060,6 +1064,85 @@ ResetSessionVariables(void)
xact_recheck_varids = NIL;
}
+/*
+ * 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_UPDATE);
+ 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 e260d20007..6a8c166806 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..ed029d1600
--- /dev/null
+++ b/src/backend/executor/svariableReceiver.c
@@ -0,0 +1,154 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+
+ /* Receiver should be initialized by SetVariableDestReceiverParams */
+ Assert(OidIsValid(myState->varid));
+
+ 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;
+ }
+
+ if (outcols == 0)
+ elog(ERROR, "svariable DestReceiver requires one attribute");
+
+ 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);
+
+ 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/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 3bac350bf5..28f8c7a46d 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3822,6 +3822,16 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_LetStmt:
+ {
+ LetStmt *stmt = (LetStmt *) node;
+
+ if (walker(stmt->target, context))
+ return true;
+ if (walker(stmt->query, context))
+ return true;
+ }
+ break;
case T_PLAssignStmt:
{
PLAssignStmt *stmt = (PLAssignStmt *) node;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 702d875d0d..f643966de8 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,6 +1647,274 @@ 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;
+ 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, ¬_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_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 */
+ 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;
+
+ /*
+ * 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 27e145d173..4f6b608712 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -306,7 +306,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
@@ -454,6 +454,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
@@ -717,7 +718,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
@@ -1043,6 +1044,7 @@ stmt:
| ImportForeignSchemaStmt
| IndexStmt
| InsertStmt
+ | LetStmt
| ListenStmt
| RefreshMatViewStmt
| LoadStmt
@@ -11918,6 +11920,7 @@ ExplainableStmt:
| CreateMatViewStmt
| RefreshMatViewStmt
| ExecuteStmt /* by default all are $$=$1 */
+ | LetStmt
;
/*****************************************************************************
@@ -11948,6 +11951,7 @@ PreparableStmt:
| UpdateStmt
| DeleteStmt
| MergeStmt /* by default all are $$=$1 */
+ | LetStmt
;
/*****************************************************************************
@@ -12535,6 +12539,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:
@@ -16905,6 +16952,7 @@ unreserved_keyword:
| LARGE_P
| LAST_P
| LEAKPROOF
+ | LET
| LEVEL
| LISTEN
| LOAD
@@ -17474,6 +17522,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 58d27ca965..31e3c8fe6c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -537,6 +537,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;
@@ -1951,6 +1952,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:
@@ -3326,6 +3328,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 005bc0400f..21fbf939f2 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 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 4e1593d900..a0fef7e6d4 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..4a9fd3691c 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 b2a7237430..36c6b2d63a 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 58f5c2b69c..724b74acb5 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 fc71d27adb..bab0eb73a2 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);
}
/*
@@ -1875,6 +1876,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 b810cb6d57..ca1125004f 100644
--- a/src/include/commands/session_variable.h
+++ b/src/include/commands/session_variable.h
@@ -38,4 +38,7 @@ extern void AtPreEOXact_SessionVariable_on_xact_actions(bool isCommit);
extern void AtEOSubXact_SessionVariable_on_xact_actions(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..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/parsenodes.h b/src/include/nodes/parsenodes.h
index 69639be467..323354ea16 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -136,6 +136,7 @@ typedef struct Query
int resultRelation; /* rtable index of target relation for
* INSERT/UPDATE/DELETE/MERGE; 0 for SELECT */
+ Oid resultVariable; /* target variable of LET statement */
bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasWindowFuncs; /* has window functions in tlist */
bool hasTargetSRFs; /* has set-returning functions in tlist */
@@ -1698,6 +1699,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/pathnodes.h b/src/include/nodes/pathnodes.h
index 5a79a4d591..567f9402ac 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -463,6 +463,7 @@ struct PlannerInfo
/* true if session variables were used */
bool hasSessionVariables;
+
/*
* Information about aggregates. Filled by preprocess_aggrefs().
*/
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e03727e19f..7108203d2b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -49,7 +49,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 dfe3cfc437..84266519b3 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 d07f5b0584..43fd9ca887 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 6c1be5e9d4..357989b52f 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 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;
/* ----------------
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f452d7e6d4..d58d6473cb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1391,6 +1391,7 @@ LargeObjectDesc
LastAttnumInfo
Latch
LerpFunc
+LetStmt
LexDescr
LexemeEntry
LexemeHashKey
@@ -2638,6 +2639,7 @@ SupportRequestRows
SupportRequestSelectivity
SupportRequestSimplify
SupportRequestWFuncMonotonic
+svariableState
SVariable
SVariableData
SVariableXActAction
--
2.37.3