* benefit, and it'd require postgres_fdw to know more than is desirable
* about Param evaluation.)
*/
- *param_exprs = (List *) ExecInitExpr((Expr *) fdw_exprs, node);
+ *param_exprs = ExecInitExprList(fdw_exprs, node);
/* Allocate buffer for text form of query parameters. */
*param_values = (const char **) palloc0(numParams * sizeof(char *));
/* predicate will likely be null, but may as well copy it */
newind->il_info->ii_Predicate = (List *)
copyObject(indexInfo->ii_Predicate);
- newind->il_info->ii_PredicateState = NIL;
+ newind->il_info->ii_PredicateState = NULL;
/* no exclusion constraints at bootstrap time, so no need to copy */
Assert(indexInfo->ii_ExclusionOps == NULL);
Assert(indexInfo->ii_ExclusionProcs == NULL);
/* fetch index predicate if any */
ii->ii_Predicate = RelationGetIndexPredicate(index);
- ii->ii_PredicateState = NIL;
+ ii->ii_PredicateState = NULL;
/* fetch exclusion constraint info if any */
if (indexStruct->indisexclusion)
indexInfo->ii_ExpressionsState == NIL)
{
/* First time through, set up expression evaluation state */
- indexInfo->ii_ExpressionsState = (List *)
- ExecPrepareExpr((Expr *) indexInfo->ii_Expressions,
- estate);
+ indexInfo->ii_ExpressionsState =
+ ExecPrepareExprList(indexInfo->ii_Expressions, estate);
/* Check caller has set up context correctly */
Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
}
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
double reltuples;
- List *predicate;
+ ExprState *predicate;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = (List *)
- ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
- estate);
+ predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
/*
* Prepare for scan of the base relation. In a normal index build, we use
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NIL)
+ if (predicate != NULL)
{
- if (!ExecQual(predicate, econtext, false))
+ if (!ExecQual(predicate, econtext))
continue;
}
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
- indexInfo->ii_PredicateState = NIL;
+ indexInfo->ii_PredicateState = NULL;
return reltuples;
}
HeapTuple heapTuple;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- List *predicate;
+ ExprState *predicate;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = (List *)
- ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
- estate);
+ predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
/*
* Scan all live tuples in the base relation.
/*
* In a partial index, ignore tuples that don't satisfy the predicate.
*/
- if (predicate != NIL)
+ if (predicate != NULL)
{
- if (!ExecQual(predicate, econtext, false))
+ if (!ExecQual(predicate, econtext))
continue;
}
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
- indexInfo->ii_PredicateState = NIL;
+ indexInfo->ii_PredicateState = NULL;
}
HeapTuple heapTuple;
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
- List *predicate;
+ ExprState *predicate;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate, if any. */
- predicate = (List *)
- ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
- estate);
+ predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
/*
* Prepare for scan of the base relation. We need just those tuples
* In a partial index, discard tuples that don't satisfy the
* predicate.
*/
- if (predicate != NIL)
+ if (predicate != NULL)
{
- if (!ExecQual(predicate, econtext, false))
+ if (!ExecQual(predicate, econtext))
continue;
}
/* These may have been pointing to the now-gone estate */
indexInfo->ii_ExpressionsState = NIL;
- indexInfo->ii_PredicateState = NIL;
+ indexInfo->ii_PredicateState = NULL;
}
GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
/* First time through, set up expression evaluation state */
- pd->keystate = (List *) ExecPrepareExpr((Expr *) pd->key->partexprs,
- estate);
+ pd->keystate = ExecPrepareExprList(pd->key->partexprs, estate);
}
partexpr_item = list_head(pd->keystate);
indexInfo->ii_Expressions = NIL;
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_Predicate = NIL;
- indexInfo->ii_PredicateState = NIL;
+ indexInfo->ii_PredicateState = NULL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
TupleTableSlot *slot;
EState *estate;
ExprContext *econtext;
- List *predicate;
+ ExprState *predicate;
Datum *exprvals;
bool *exprnulls;
int numindexrows,
econtext->ecxt_scantuple = slot;
/* Set up execution state for predicate. */
- predicate = castNode(List,
- ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
- estate));
+ predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
/* Compute and save index expression values */
exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum));
ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
/* If index is partial, check predicate */
- if (predicate != NIL)
+ if (predicate != NULL)
{
- if (!ExecQual(predicate, econtext, false))
+ if (!ExecQual(predicate, econtext))
continue;
}
numindexrows++;
foreach(lst, plans)
{
SubPlanState *sps = (SubPlanState *) lfirst(lst);
- SubPlan *sp = (SubPlan *) sps->xprstate.expr;
+ SubPlan *sp = sps->subplan;
/*
* There can be multiple SubPlan nodes referencing the same physical
indexInfo = makeNode(IndexInfo);
indexInfo->ii_Expressions = NIL;
indexInfo->ii_ExpressionsState = NIL;
- indexInfo->ii_PredicateState = NIL;
+ indexInfo->ii_PredicateState = NULL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
indexInfo->ii_Expressions = NIL; /* for now */
indexInfo->ii_ExpressionsState = NIL;
indexInfo->ii_Predicate = make_ands_implicit((Expr *) stmt->whereClause);
- indexInfo->ii_PredicateState = NIL;
+ indexInfo->ii_PredicateState = NULL;
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
}
/* Prepare the expressions for execution */
- exprstates = (List *) ExecPrepareExpr((Expr *) params, estate);
+ exprstates = ExecPrepareExprList(params, estate);
paramLI = (ParamListInfo)
palloc(offsetof(ParamListInfoData, params) +
i = 0;
foreach(l, exprstates)
{
- ExprState *n = lfirst(l);
+ ExprState *n = (ExprState *) lfirst(l);
ParamExternData *prm = ¶mLI->params[i];
prm->ptype = param_types[i];
Oid refindid; /* OID of PK's index, if FOREIGN */
Oid conid; /* OID of pg_constraint entry, if FOREIGN */
Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */
- List *qualstate; /* Execution state for CHECK */
+ ExprState *qualstate; /* Execution state for CHECK expr */
} NewConstraint;
/*
CommandId mycid;
BulkInsertState bistate;
int hi_options;
- List *partqualstate = NIL;
+ ExprState *partqualstate = NULL;
/*
* Open the relation(s). We have surely already locked the existing
{
case CONSTR_CHECK:
needscan = true;
- con->qualstate = (List *)
- ExecPrepareExpr((Expr *) con->qual, estate);
+ con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate);
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
if (tab->partition_constraint)
{
needscan = true;
- partqualstate = (List *)
- ExecPrepareExpr((Expr *) tab->partition_constraint,
- estate);
+ partqualstate = ExecPrepareCheck(tab->partition_constraint, estate);
}
foreach(l, tab->newvals)
switch (con->contype)
{
case CONSTR_CHECK:
- if (!ExecQual(con->qualstate, econtext, true))
+ if (!ExecCheck(con->qualstate, econtext))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
}
}
- if (partqualstate && !ExecQual(partqualstate, econtext, true))
+ if (partqualstate && !ExecCheck(partqualstate, econtext))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("partition constraint is violated by some row")));
newcon = (NewConstraint *) palloc0(sizeof(NewConstraint));
newcon->name = ccon->name;
newcon->contype = ccon->contype;
- /* ExecQual wants implicit-AND format */
- newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr);
+ newcon->qual = ccon->expr;
tab->constraints = lappend(tab->constraints, newcon);
}
Datum val;
char *conbin;
Expr *origexpr;
- List *exprstate;
+ ExprState *exprstate;
TupleDesc tupdesc;
HeapScanDesc scan;
HeapTuple tuple;
HeapTupleGetOid(constrtup));
conbin = TextDatumGetCString(val);
origexpr = (Expr *) stringToNode(conbin);
- exprstate = (List *)
- ExecPrepareExpr((Expr *) make_ands_implicit(origexpr), estate);
+ exprstate = ExecPrepareExpr(origexpr, estate);
econtext = GetPerTupleExprContext(estate);
tupdesc = RelationGetDescr(rel);
{
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
- if (!ExecQual(exprstate, econtext, true))
+ if (!ExecCheck(exprstate, econtext))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
if (trigger->tgqual)
{
TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
- List **predicate;
+ ExprState **predicate;
ExprContext *econtext;
TupleTableSlot *oldslot = NULL;
TupleTableSlot *newslot = NULL;
* nodetrees for it. Keep them in the per-query memory context so
* they'll survive throughout the query.
*/
- if (*predicate == NIL)
+ if (*predicate == NULL)
{
Node *tgqual;
/* Change references to OLD and NEW to INNER_VAR and OUTER_VAR */
ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER_VAR, 0);
ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0);
- /* ExecQual wants implicit-AND form */
+ /* ExecPrepareQual wants implicit-AND form */
tgqual = (Node *) make_ands_implicit((Expr *) tgqual);
- *predicate = (List *) ExecPrepareExpr((Expr *) tgqual, estate);
+ *predicate = ExecPrepareQual((List *) tgqual, estate);
MemoryContextSwitchTo(oldContext);
}
*/
econtext->ecxt_innertuple = oldslot;
econtext->ecxt_outertuple = newslot;
- if (!ExecQual(*predicate, econtext, false))
+ if (!ExecQual(*predicate, econtext))
return false;
}
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \
- execMain.o execParallel.o execProcnode.o execQual.o \
- execReplication.o execScan.o execTuples.o \
+OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
+ execGrouping.o execIndexing.o execJunk.o \
+ execMain.o execParallel.o execProcnode.o \
+ execReplication.o execScan.o execSRF.o execTuples.o \
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
nodeBitmapAnd.o nodeBitmapOr.o \
nodeBitmapHeapscan.o nodeBitmapIndexscan.o \
--------------------------
The plan tree delivered by the planner contains a tree of Plan nodes (struct
-types derived from struct Plan). Each Plan node may have expression trees
-associated with it, to represent its target list, qualification conditions,
-etc. During executor startup we build a parallel tree of identical structure
-containing executor state nodes --- every plan and expression node type has
-a corresponding executor state node type. Each node in the state tree has a
-pointer to its corresponding node in the plan tree, plus executor state data
-as needed to implement that node type. This arrangement allows the plan
-tree to be completely read-only as far as the executor is concerned: all data
-that is modified during execution is in the state tree. Read-only plan trees
-make life much simpler for plan caching and reuse.
+types derived from struct Plan). During executor startup we build a parallel
+tree of identical structure containing executor state nodes --- every plan
+node type has a corresponding executor state node type. Each node in the
+state tree has a pointer to its corresponding node in the plan tree, plus
+executor state data as needed to implement that node type. This arrangement
+allows the plan tree to be completely read-only so far as the executor is
+concerned: all data that is modified during execution is in the state tree.
+Read-only plan trees make life much simpler for plan caching and reuse.
+
+Each Plan node may have expression trees associated with it, to represent
+its target list, qualification conditions, etc. These trees are also
+read-only to the executor, but the executor state for expression evaluation
+does not mirror the Plan expression's tree shape, as explained below.
+Rather, there's just one ExprState node per expression tree, although this
+may have sub-nodes for some complex expression node types.
Altogether there are four classes of nodes used in these trees: Plan nodes,
-their corresponding PlanState nodes, Expr nodes, and their corresponding
-ExprState nodes. (Actually, there are also List nodes, which are used as
-"glue" in all four kinds of tree.)
+their corresponding PlanState nodes, Expr nodes, and ExprState nodes.
+(Actually, there are also List nodes, which are used as "glue" in all
+three tree-based representations.)
+
+
+Expression Trees and ExprState nodes
+------------------------------------
+
+Expression trees, in contrast to Plan trees, are not mirrored into a
+corresponding tree of state nodes. Instead each separately executable
+expression tree (e.g. a Plan's qual or targetlist) is represented by one
+ExprState node. The ExprState node contains the information needed to
+evaluate the expression in a compact, linear form. That compact form is
+stored as a flat array in ExprState->steps[] (an array of ExprEvalStep,
+not ExprEvalStep *).
+
+The reasons for choosing such a representation include:
+- commonly the amount of work needed to evaluate one Expr-type node is
+ small enough that the overhead of having to perform a tree-walk
+ during evaluation is significant.
+- the flat representation can be evaluated non-recursively within a single
+ function, reducing stack depth and function call overhead.
+- such a representation is usable both for fast interpreted execution,
+ and for compiling into native code.
+
+The Plan-tree representation of an expression is compiled into an
+ExprState node by ExecInitExpr(). As much complexity as possible should
+be handled by ExecInitExpr() (and helpers), instead of execution time
+where both interpreted and compiled versions would need to deal with the
+complexity. Besides duplicating effort between execution approaches,
+runtime initialization checks also have a small but noticeable cost every
+time the expression is evaluated. Therefore, we allow ExecInitExpr() to
+precompute information that we do not expect to vary across execution of a
+single query, for example the set of CHECK constraint expressions to be
+applied to a domain type. This could not be done at plan time without
+greatly increasing the number of events that require plan invalidation.
+(Previously, some information of this kind was rechecked on each
+expression evaluation, but that seems like unnecessary overhead.)
+
+
+Expression Initialization
+-------------------------
+
+During ExecInitExpr() and similar routines, Expr trees are converted
+into the flat representation. Each Expr node might be represented by
+zero, one, or more ExprEvalSteps.
+
+Each ExprEvalStep's work is determined by its opcode (of enum ExprEvalOp)
+and it stores the result of its work into the Datum variable and boolean
+null flag variable pointed to by ExprEvalStep->resvalue/resnull.
+Complex expressions are performed by chaining together several steps.
+For example, "a + b" (one OpExpr, with two Var expressions) would be
+represented as two steps to fetch the Var values, and one step for the
+evaluation of the function underlying the + operator. The steps for the
+Vars would have their resvalue/resnull pointing directly to the appropriate
+arg[] and argnull[] array elements in the FunctionCallInfoData struct that
+is used by the function evaluation step, thus avoiding extra work to copy
+the result values around.
+
+The last entry in a completed ExprState->steps array is always an
+EEOP_DONE step; this removes the need to test for end-of-array while
+iterating. Also, if the expression contains any variable references (to
+user columns of the ExprContext's INNER, OUTER, or SCAN tuples), the steps
+array begins with EEOP_*_FETCHSOME steps that ensure that the relevant
+tuples have been deconstructed to make the required columns directly
+available (cf. slot_getsomeattrs()). This allows individual Var-fetching
+steps to be little more than an array lookup.
+
+Most of ExecInitExpr()'s work is done by the recursive function
+ExecInitExprRec() and its subroutines. ExecInitExprRec() maps one Expr
+node into the steps required for execution, recursing as needed for
+sub-expressions.
+
+Each ExecInitExprRec() call has to specify where that subexpression's
+results are to be stored (via the resv/resnull parameters). This allows
+the above scenario of evaluating a (sub-)expression directly into
+fcinfo->arg/argnull, but also requires some care: target Datum/isnull
+variables may not be shared with another ExecInitExprRec() unless the
+results are only needed by steps executing before further usages of those
+target Datum/isnull variables. Due to the non-recursiveness of the
+ExprEvalStep representation that's usually easy to guarantee.
+
+ExecInitExprRec() pushes new operations into the ExprState->steps array
+using ExprEvalPushStep(). To keep the steps as a consecutively laid out
+array, ExprEvalPushStep() has to repalloc the entire array when there's
+not enough space. Because of that it is *not* allowed to point directly
+into any of the steps during expression initialization. Therefore, the
+resv/resnull for a subexpression usually point to some storage that is
+palloc'd separately from the steps array. For instance, the
+FunctionCallInfoData for a function call step is separately allocated
+rather than being part of the ExprEvalStep array. The overall result
+of a complete expression is typically returned into the resvalue/resnull
+fields of the ExprState node itself.
+
+Some steps, e.g. boolean expressions, allow skipping evaluation of
+certain subexpressions. In the flat representation this amounts to
+jumping to some later step rather than just continuing consecutively
+with the next step. The target for such a jump is represented by
+the integer index in the ExprState->steps array of the step to execute
+next. (Compare the EEO_NEXT and EEO_JUMP macros in execExprInterp.c.)
+
+Typically, ExecInitExprRec() has to push a jumping step into the steps
+array, then recursively generate steps for the subexpression that might
+get skipped over, then go back and fix up the jump target index using
+the now-known length of the subexpression's steps. This is handled by
+adjust_jumps lists in execExpr.c.
+
+The last step in constructing an ExprState is to apply ExecReadyExpr(),
+which readies it for execution using whichever execution method has been
+selected.
+
+
+Expression Evaluation
+---------------------
+
+To allow for different methods of expression evaluation, and for
+better branch/jump target prediction, expressions are evaluated by
+calling ExprState->evalfunc (via ExprEvalExpr() and friends).
+
+ExprReadyExpr() can choose the method of interpretation by setting
+evalfunc to an appropriate function. The default execution function,
+ExecInterpExpr, is implemented in execExprInterp.c; see its header
+comment for details. Special-case evalfuncs are used for certain
+especially-simple expressions.
+
+Note that a lot of the more complex expression evaluation steps, which are
+less performance-critical than the simpler ones, are implemented as
+separate functions outside the fast-path of expression execution, allowing
+their implementation to be shared between interpreted and compiled
+expression evaluation. This means that these helper functions are not
+allowed to perform expression step dispatch themselves, as the method of
+dispatch will vary based on the caller. The helpers therefore cannot call
+for the execution of subexpressions; all subexpression results they need
+must be computed by earlier steps. And dispatch to the following
+expression step must be performed after returning from the helper.
+
+
+Targetlist Evaluation
+---------------------
+
+ExecBuildProjectionInfo builds an ExprState that has the effect of
+evaluating a targetlist into ExprState->resultslot. A generic targetlist
+expression is executed by evaluating it as discussed above (storing the
+result into the ExprState's resvalue/resnull fields) and then using an
+EEOP_ASSIGN_TMP step to move the result into the appropriate tts_values[]
+and tts_isnull[] array elements of the result slot. There are special
+fast-path step types (EEOP_ASSIGN_*_VAR) to handle targetlist entries that
+are simple Vars using only one step instead of two.
Memory Management
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * execExpr.c
+ * Expression evaluation infrastructure.
+ *
+ * During executor startup, we compile each expression tree (which has
+ * previously been processed by the parser and planner) into an ExprState,
+ * using ExecInitExpr() et al. This converts the tree into a flat array
+ * of ExprEvalSteps, which may be thought of as instructions in a program.
+ * At runtime, we'll execute steps, starting with the first, until we reach
+ * an EEOP_DONE opcode.
+ *
+ * This file contains the "compilation" logic. It is independent of the
+ * specific execution technology we use (switch statement, computed goto,
+ * JIT compilation, etc).
+ *
+ * See src/backend/executor/README for some background, specifically the
+ * "Expression Trees and ExprState nodes", "Expression Initialization",
+ * and "Expession Evaluation" sections.
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/executor/execExpr.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/nbtree.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_type.h"
+#include "executor/execExpr.h"
+#include "executor/nodeSubplan.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/planner.h"
+#include "pgstat.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/typcache.h"
+
+
+typedef struct LastAttnumInfo
+{
+ AttrNumber last_inner;
+ AttrNumber last_outer;
+ AttrNumber last_scan;
+} LastAttnumInfo;
+
+static void ExecReadyExpr(ExprState *state);
+static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
+ Datum *resv, bool *resnull);
+static void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
+static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
+ Oid funcid, Oid inputcollid, PlanState *parent,
+ ExprState *state);
+static void ExecInitExprSlots(ExprState *state, Node *node);
+static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
+static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
+ PlanState *parent);
+static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref,
+ PlanState *parent, ExprState *state,
+ Datum *resv, bool *resnull);
+static bool isAssignmentIndirectionExpr(Expr *expr);
+static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
+ PlanState *parent, ExprState *state,
+ Datum *resv, bool *resnull);
+
+
+/*
+ * ExecInitExpr: prepare an expression tree for execution
+ *
+ * This function builds and returns an ExprState implementing the given
+ * Expr node tree. The return ExprState can then be handed to ExecEvalExpr
+ * for execution. Because the Expr tree itself is read-only as far as
+ * ExecInitExpr and ExecEvalExpr are concerned, several different executions
+ * of the same plan tree can occur concurrently. (But note that an ExprState
+ * does mutate at runtime, so it can't be re-used concurrently.)
+ *
+ * This must be called in a memory context that will last as long as repeated
+ * executions of the expression are needed. Typically the context will be
+ * the same as the per-query context of the associated ExprContext.
+ *
+ * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to
+ * the lists of such nodes held by the parent PlanState (or more accurately,
+ * the AggrefExprState etc. nodes created for them are added).
+ *
+ * Note: there is no ExecEndExpr function; we assume that any resource
+ * cleanup needed will be handled by just releasing the memory context
+ * in which the state tree is built. Functions that require additional
+ * cleanup work can register a shutdown callback in the ExprContext.
+ *
+ * 'node' is the root of the expression tree to compile.
+ * 'parent' is the PlanState node that owns the expression.
+ *
+ * 'parent' may be NULL if we are preparing an expression that is not
+ * associated with a plan tree. (If so, it can't have aggs or subplans.)
+ * Such cases should usually come through ExecPrepareExpr, not directly here.
+ *
+ * Also, if 'node' is NULL, we just return NULL. This is convenient for some
+ * callers that may or may not have an expression that needs to be compiled.
+ * Note that a NULL ExprState pointer *cannot* be handed to ExecEvalExpr,
+ * although ExecQual and ExecCheck will accept one (and treat it as "true").
+ */
+ExprState *
+ExecInitExpr(Expr *node, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch;
+
+ /* Special case: NULL expression produces a NULL ExprState pointer */
+ if (node == NULL)
+ return NULL;
+
+ /* Initialize ExprState with empty step list */
+ state = makeNode(ExprState);
+ state->expr = node;
+
+ /* Insert EEOP_*_FETCHSOME steps as needed */
+ ExecInitExprSlots(state, (Node *) node);
+
+ /* Compile the expression proper */
+ ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
+
+ /* Finally, append a DONE step */
+ scratch.opcode = EEOP_DONE;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
+/*
+ * ExecInitQual: prepare a qual for execution by ExecQual
+ *
+ * Prepares for the evaluation of a conjunctive boolean expression (qual list
+ * with implicit AND semantics) that returns true if none of the
+ * subexpressions are false.
+ *
+ * We must return true if the list is empty. Since that's a very common case,
+ * we optimize it a bit further by translating to a NULL ExprState pointer
+ * rather than setting up an ExprState that computes constant TRUE. (Some
+ * especially hot-spot callers of ExecQual detect this and avoid calling
+ * ExecQual at all.)
+ *
+ * If any of the subexpressions yield NULL, then the result of the conjunction
+ * is false. This makes ExecQual primarily useful for evaluating WHERE
+ * clauses, since SQL specifies that tuples with null WHERE results do not
+ * get selected.
+ */
+ExprState *
+ExecInitQual(List *qual, PlanState *parent)
+{
+ ExprState *state;
+ ExprEvalStep scratch;
+ List *adjust_jumps = NIL;
+ ListCell *lc;
+
+ /* short-circuit (here and in ExecQual) for empty restriction list */
+ if (qual == NIL)
+ return NULL;
+
+ Assert(IsA(qual, List));
+
+ state = makeNode(ExprState);
+ state->expr = (Expr *) qual;
+ /* mark expression as to be used with ExecQual() */
+ state->flags = EEO_FLAG_IS_QUAL;
+
+ /* Insert EEOP_*_FETCHSOME steps as needed */
+ ExecInitExprSlots(state, (Node *) qual);
+
+ /*
+ * ExecQual() needs to return false for an expression returning NULL. That
+ * allows us to short-circuit the evaluation the first time a NULL is
+ * encountered. As qual evaluation is a hot-path this warrants using a
+ * special opcode for qual evaluation that's simpler than BOOL_AND (which
+ * has more complex NULL handling).
+ */
+ scratch.opcode = EEOP_QUAL;
+
+ /*
+ * We can use ExprState's resvalue/resnull as target for each qual expr.
+ */
+ scratch.resvalue = &state->resvalue;
+ scratch.resnull = &state->resnull;
+
+ foreach(lc, qual)
+ {
+ Expr *node = (Expr *) lfirst(lc);
+
+ /* first evaluate expression */
+ ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull);
+
+ /* then emit EEOP_QUAL to detect if it's false (or null) */
+ scratch.d.qualexpr.jumpdone = -1;
+ ExprEvalPushStep(state, &scratch);
+ adjust_jumps = lappend_int(adjust_jumps,
+ state->steps_len - 1);
+ }
+
+ /* adjust jump targets */
+ foreach(lc, adjust_jumps)
+ {
+ ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+ Assert(as->opcode == EEOP_QUAL);
+ Assert(as->d.qualexpr.jumpdone == -1);
+ as->d.qualexpr.jumpdone = state->steps_len;
+ }
+
+ /*
+ * At the end, we don't need to do anything more. The last qual expr must
+ * have yielded TRUE, and since its result is stored in the desired output
+ * location, we're done.
+ */
+ scratch.opcode = EEOP_DONE;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return state;
+}
+
+/*
+ * ExecInitCheck: prepare a check constraint for execution by ExecCheck
+ *
+ * This is much like ExecInitQual/ExecQual, except that a null result from
+ * the conjunction is treated as TRUE. This behavior is appropriate for
+ * evaluating CHECK constraints, since SQL specifies that NULL constraint
+ * conditions are not failures.
+ *
+ * Note that like ExecInitQual, this expects input in implicit-AND format.
+ * Users of ExecCheck that have expressions in normal explicit-AND format
+ * can just apply ExecInitExpr to produce suitable input for ExecCheck.
+ */
+ExprState *
+ExecInitCheck(List *qual, PlanState *parent)
+{
+ /* short-circuit (here and in ExecCheck) for empty restriction list */
+ if (qual == NIL)
+ return NULL;
+
+ Assert(IsA(qual, List));
+
+ /*
+ * Just convert the implicit-AND list to an explicit AND (if there's more
+ * than one entry), and compile normally. Unlike ExecQual, we can't
+ * short-circuit on NULL results, so the regular AND behavior is needed.
+ */
+ return ExecInitExpr(make_ands_explicit(qual), parent);
+}
+
+/*
+ * Call ExecInitExpr() on a list of expressions, return a list of ExprStates.
+ */
+List *
+ExecInitExprList(List *nodes, PlanState *parent)
+{
+ List *result = NIL;
+ ListCell *lc;
+
+ foreach(lc, nodes)
+ {
+ Expr *e = lfirst(lc);
+
+ result = lappend(result, ExecInitExpr(e, parent));
+ }
+
+ return result;
+}
+
+/*
+ * ExecBuildProjectionInfo
+ *
+ * Build a ProjectionInfo node for evaluating the given tlist in the given
+ * econtext, and storing the result into the tuple slot. (Caller must have
+ * ensured that tuple slot has a descriptor matching the tlist!)
+ *
+ * inputDesc can be NULL, but if it is not, we check to see whether simple
+ * Vars in the tlist match the descriptor. It is important to provide
+ * inputDesc for relation-scan plan nodes, as a cross check that the relation
+ * hasn't been changed since the plan was made. At higher levels of a plan,
+ * there is no need to recheck.
+ *
+ * This is implemented by internally building an ExprState that performs the
+ * whole projection in one go.
+ *
+ * Caution: before PG v10, the targetList was a list of ExprStates; now it
+ * should be the planner-created targetlist, since we do the compilation here.
+ */
+ProjectionInfo *
+ExecBuildProjectionInfo(List *targetList,
+ ExprContext *econtext,
+ TupleTableSlot *slot,
+ PlanState *parent,
+ TupleDesc inputDesc)
+{
+ ProjectionInfo *projInfo = makeNode(ProjectionInfo);
+ ExprState *state;
+ ExprEvalStep scratch;
+ ListCell *lc;
+
+ projInfo->pi_exprContext = econtext;
+ /* We embed ExprState into ProjectionInfo instead of doing extra palloc */
+ projInfo->pi_state.tag.type = T_ExprState;
+ state = &projInfo->pi_state;
+ state->expr = (Expr *) targetList;
+ state->resultslot = slot;
+
+ /* Insert EEOP_*_FETCHSOME steps as needed */
+ ExecInitExprSlots(state, (Node *) targetList);
+
+ /* Now compile each tlist column */
+ foreach(lc, targetList)
+ {
+ TargetEntry *tle = castNode(TargetEntry, lfirst(lc));
+ Var *variable = NULL;
+ AttrNumber attnum = 0;
+ bool isSafeVar = false;
+
+ /*
+ * If tlist expression is a safe non-system Var, use the fast-path
+ * ASSIGN_*_VAR opcodes. "Safe" means that we don't need to apply
+ * CheckVarSlotCompatibility() during plan startup. If a source slot
+ * was provided, we make the equivalent tests here; if a slot was not
+ * provided, we assume that no check is needed because we're dealing
+ * with a non-relation-scan-level expression.
+ */
+ if (tle->expr != NULL &&
+ IsA(tle->expr, Var) &&
+ ((Var *) tle->expr)->varattno > 0)
+ {
+ /* Non-system Var, but how safe is it? */
+ variable = (Var *) tle->expr;
+ attnum = variable->varattno;
+
+ if (inputDesc == NULL)
+ isSafeVar = true; /* can't check, just assume OK */
+ else if (attnum <= inputDesc->natts)
+ {
+ Form_pg_attribute attr = inputDesc->attrs[attnum - 1];
+
+ /*
+ * If user attribute is dropped or has a type mismatch, don't
+ * use ASSIGN_*_VAR. Instead let the normal expression
+ * machinery handle it (which'll possibly error out).
+ */
+ if (!attr->attisdropped && variable->vartype == attr->atttypid)
+ {
+ isSafeVar = true;
+ }
+ }
+ }
+
+ if (isSafeVar)
+ {
+ /* Fast-path: just generate an EEOP_ASSIGN_*_VAR step */
+ switch (variable->varno)
+ {
+ case INNER_VAR:
+ /* get the tuple from the inner node */
+ scratch.opcode = EEOP_ASSIGN_INNER_VAR;
+ break;
+
+ case OUTER_VAR:
+ /* get the tuple from the outer node */
+ scratch.opcode = EEOP_ASSIGN_OUTER_VAR;
+ break;
+
+ /* INDEX_VAR is handled by default case */
+
+ default:
+ /* get the tuple from the relation being scanned */
+ scratch.opcode = EEOP_ASSIGN_SCAN_VAR;
+ break;
+ }
+
+ scratch.d.assign_var.attnum = attnum - 1;
+ scratch.d.assign_var.resultnum = tle->resno - 1;
+ ExprEvalPushStep(state, &scratch);
+ }
+ else
+ {
+ /*
+ * Otherwise, compile the column expression normally.
+ *
+ * We can't tell the expression to evaluate directly into the
+ * result slot, as the result slot (and the exprstate for that
+ * matter) can change between executions. We instead evaluate
+ * into the ExprState's resvalue/resnull and then move.
+ */
+ ExecInitExprRec(tle->expr, parent, state,
+ &state->resvalue, &state->resnull);
+
+ /*
+ * Column might be referenced multiple times in upper nodes, so
+ * force value to R/O - but only if it could be an expanded datum.
+ */
+ if (get_typlen(exprType((Node *) tle->expr)) == -1)
+ scratch.opcode = EEOP_ASSIGN_TMP_MAKE_RO;
+ else
+ scratch.opcode = EEOP_ASSIGN_TMP;
+ scratch.d.assign_tmp.resultnum = tle->resno - 1;
+ ExprEvalPushStep(state, &scratch);
+ }
+ }
+
+ scratch.opcode = EEOP_DONE;
+ ExprEvalPushStep(state, &scratch);
+
+ ExecReadyExpr(state);
+
+ return projInfo;
+}
+
+/*
+ * ExecPrepareExpr --- initialize for expression execution outside a normal
+ * Plan tree context.
+ *
+ * This differs from ExecInitExpr in that we don't assume the caller is
+ * already running in the EState's per-query context. Also, we run the
+ * passed expression tree through expression_planner() to prepare it for
+ * execution. (In ordinary Plan trees the regular planning process will have
+ * made the appropriate transformations on expressions, but for standalone
+ * expressions this won't have happened.)
+ */
+ExprState *
+ExecPrepareExpr(Expr *node, EState *estate)
+{
+ ExprState *result;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ node = expression_planner(node);
+
+ result = ExecInitExpr(node, NULL);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return result;
+}
+
+/*
+ * ExecPrepareQual --- initialize for qual execution outside a normal
+ * Plan tree context.
+ *
+ * This differs from ExecInitQual in that we don't assume the caller is
+ * already running in the EState's per-query context. Also, we run the
+ * passed expression tree through expression_planner() to prepare it for
+ * execution. (In ordinary Plan trees the regular planning process will have
+ * made the appropriate transformations on expressions, but for standalone
+ * expressions this won't have happened.)
+ */
+ExprState *
+ExecPrepareQual(List *qual, EState *estate)
+{
+ ExprState *result;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ qual = (List *) expression_planner((Expr *) qual);
+
+ result = ExecInitQual(qual, NULL);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return result;
+}
+
+/*
+ * ExecPrepareCheck -- initialize check constraint for execution outside a
+ * normal Plan tree context.
+ *
+ * See ExecPrepareExpr() and ExecInitCheck() for details.
+ */
+ExprState *
+ExecPrepareCheck(List *qual, EState *estate)
+{
+ ExprState *result;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ qual = (List *) expression_planner((Expr *) qual);
+
+ result = ExecInitCheck(qual, NULL);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return result;
+}
+
+/*
+ * Call ExecPrepareExpr() on each member of a list of Exprs, and return
+ * a list of ExprStates.
+ *
+ * See ExecPrepareExpr() for details.
+ */
+List *
+ExecPrepareExprList(List *nodes, EState *estate)
+{
+ List *result = NIL;
+ ListCell *lc;
+
+ foreach(lc, nodes)
+ {
+ Expr *e = (Expr *) lfirst(lc);
+
+ result = lappend(result, ExecPrepareExpr(e, estate));
+ }
+
+ return result;
+}
+
+/*
+ * ExecCheck - evaluate a check constraint
+ *
+ * For check constraints, a null result is taken as TRUE, ie the constraint
+ * passes.
+ *
+ * The check constraint may have been prepared with ExecInitCheck
+ * (possibly via ExecPrepareCheck) if the caller had it in implicit-AND
+ * format, but a regular boolean expression prepared with ExecInitExpr or
+ * ExecPrepareExpr works too.
+ */
+bool
+ExecCheck(ExprState *state, ExprContext *econtext)
+{
+ Datum ret;
+ bool isnull;
+
+ /* short-circuit (here and in ExecInitCheck) for empty restriction list */
+ if (state == NULL)
+ return true;
+
+ /* verify that expression was not compiled using ExecInitQual */
+ Assert(!(state->flags & EEO_FLAG_IS_QUAL));
+
+ ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
+
+ if (isnull)
+ return true;
+
+ return DatumGetBool(ret);
+}
+
+/*
+ * Prepare a compiled expression for execution. This has to be called for
+ * every ExprState before it can be executed.
+ *
+ * NB: While this currently only calls ExecReadyInterpretedExpr(),
+ * this will likely get extended to further expression evaluation methods.
+ * Therefore this should be used instead of directly calling
+ * ExecReadyInterpretedExpr().
+ */
+static void
+ExecReadyExpr(ExprState *state)
+{
+ ExecReadyInterpretedExpr(state);
+}
+
+/*
+ * Append the steps necessary for the evaluation of node to ExprState->steps,
+ * possibly recursing into sub-expressions of node.
+ *
+ * node - expression to evaluate
+ * parent - parent executor node (or NULL if a standalone expression)
+ * state - ExprState to whose ->steps to append the necessary operations
+ * resv / resnull - where to store the result of the node into
+ */
+static void
+ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state,
+ Datum *resv, bool *resnull)
+{
+ ExprEvalStep scratch;
+
+ /* Guard against stack overflow due to overly complex expressions */
+ check_stack_depth();
+
+ /* Step's output location is always what the caller gave us */
+ Assert(resv != NULL && resnull != NULL);
+ scratch.resvalue = resv;
+ scratch.resnull = resnull;
+
+ /* cases should be ordered as they are in enum NodeTag */
+ switch (nodeTag(node))
+ {
+ case T_Var:
+ {
+ Var *variable = (Var *) node;
+
+ if (variable->varattno == InvalidAttrNumber)
+ {
+ /* whole-row Var */
+ ExecInitWholeRowVar(&scratch, variable, parent);
+ }
+ else if (variable->varattno <= 0)
+ {
+ /* system column */
+ scratch.d.var.attnum = variable->varattno;
+ scratch.d.var.vartype = variable->vartype;
+ switch (variable->varno)
+ {
+ case INNER_VAR:
+ scratch.opcode = EEOP_INNER_SYSVAR;
+ break;
+ case OUTER_VAR:
+ scratch.opcode = EEOP_OUTER_SYSVAR;
+ break;
+
+ /* INDEX_VAR is handled by default case */
+
+ default:
+ scratch.opcode = EEOP_SCAN_SYSVAR;
+ break;
+ }
+ }
+ else
+ {
+ /* regular user column */
+ scratch.d.var.attnum = variable->varattno - 1;
+ scratch.d.var.vartype = variable->vartype;
+ /* select EEOP_*_FIRST opcode to force one-time checks */
+ switch (variable->varno)
+ {
+ case INNER_VAR:
+ scratch.opcode = EEOP_INNER_VAR_FIRST;
+ break;
+ case OUTER_VAR:
+ scratch.opcode = EEOP_OUTER_VAR_FIRST;
+ break;
+
+ /* INDEX_VAR is handled by default case */
+
+ default:
+ scratch.opcode = EEOP_SCAN_VAR_FIRST;
+ break;
+ }
+ }
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_Const:
+ {
+ Const *con = (Const *) node;
+
+ scratch.opcode = EEOP_CONST;
+ scratch.d.constval.value = con->constvalue;
+ scratch.d.constval.isnull = con->constisnull;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_Param:
+ {
+ Param *param = (Param *) node;
+
+ switch (param->paramkind)
+ {
+ case PARAM_EXEC:
+ scratch.opcode = EEOP_PARAM_EXEC;
+ scratch.d.param.paramid = param->paramid;
+ scratch.d.param.paramtype = param->paramtype;
+ break;
+ case PARAM_EXTERN:
+ scratch.opcode = EEOP_PARAM_EXTERN;
+ scratch.d.param.paramid = param->paramid;
+ scratch.d.param.paramtype = param->paramtype;
+ break;
+ default:
+ elog(ERROR, "unrecognized paramkind: %d",
+ (int) param->paramkind);
+ break;
+ }
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_Aggref:
+ {
+ Aggref *aggref = (Aggref *) node;
+ AggrefExprState *astate = makeNode(AggrefExprState);
+
+ scratch.opcode = EEOP_AGGREF;
+ scratch.d.aggref.astate = astate;
+ astate->aggref = aggref;
+
+ if (parent && IsA(parent, AggState))
+ {
+ AggState *aggstate = (AggState *) parent;
+
+ aggstate->aggs = lcons(astate, aggstate->aggs);
+ aggstate->numaggs++;
+ }
+ else
+ {
+ /* planner messed up */
+ elog(ERROR, "Aggref found in non-Agg plan node");
+ }
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_GroupingFunc:
+ {
+ GroupingFunc *grp_node = (GroupingFunc *) node;
+ Agg *agg;
+
+ if (!parent || !IsA(parent, AggState) ||
+ !IsA(parent->plan, Agg))
+ elog(ERROR, "GroupingFunc found in non-Agg plan node");
+
+ scratch.opcode = EEOP_GROUPING_FUNC;
+ scratch.d.grouping_func.parent = (AggState *) parent;
+
+ agg = (Agg *) (parent->plan);
+
+ if (agg->groupingSets)
+ scratch.d.grouping_func.clauses = grp_node->cols;
+ else
+ scratch.d.grouping_func.clauses = NIL;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_WindowFunc:
+ {
+ WindowFunc *wfunc = (WindowFunc *) node;
+ WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
+
+ wfstate->wfunc = wfunc;
+
+ if (parent && IsA(parent, WindowAggState))
+ {
+ WindowAggState *winstate = (WindowAggState *) parent;
+ int nfuncs;
+
+ winstate->funcs = lcons(wfstate, winstate->funcs);
+ nfuncs = ++winstate->numfuncs;
+ if (wfunc->winagg)
+ winstate->numaggs++;
+
+ /* for now initialize agg using old style expressions */
+ wfstate->args = ExecInitExprList(wfunc->args, parent);
+ wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
+ parent);
+
+ /*
+ * Complain if the windowfunc's arguments contain any
+ * windowfuncs; nested window functions are semantically
+ * nonsensical. (This should have been caught earlier,
+ * but we defend against it here anyway.)
+ */
+ if (nfuncs != winstate->numfuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_WINDOWING_ERROR),
+ errmsg("window function calls cannot be nested")));
+ }
+ else
+ {
+ /* planner messed up */
+ elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
+ }
+
+ scratch.opcode = EEOP_WINDOW_FUNC;
+ scratch.d.window_func.wfstate = wfstate;
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_ArrayRef:
+ {
+ ArrayRef *aref = (ArrayRef *) node;
+
+ ExecInitArrayRef(&scratch, aref, parent, state, resv, resnull);
+ break;
+ }
+
+ case T_FuncExpr:
+ {
+ FuncExpr *func = (FuncExpr *) node;
+
+ ExecInitFunc(&scratch, node,
+ func->args, func->funcid, func->inputcollid,
+ parent, state);
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_OpExpr:
+ {
+ OpExpr *op = (OpExpr *) node;
+
+ ExecInitFunc(&scratch, node,
+ op->args, op->opfuncid, op->inputcollid,
+ parent, state);
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_DistinctExpr:
+ {
+ DistinctExpr *op = (DistinctExpr *) node;
+
+ ExecInitFunc(&scratch, node,
+ op->args, op->opfuncid, op->inputcollid,
+ parent, state);
+
+ /*
+ * Change opcode of call instruction to EEOP_DISTINCT.
+ *
+ * XXX: historically we've not called the function usage
+ * pgstat infrastructure - that seems inconsistent given that
+ * we do so for normal function *and* operator evaluation. If
+ * we decided to do that here, we'd probably want separate
+ * opcodes for FUSAGE or not.
+ */
+ scratch.opcode = EEOP_DISTINCT;
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_NullIfExpr:
+ {
+ NullIfExpr *op = (NullIfExpr *) node;
+
+ ExecInitFunc(&scratch, node,
+ op->args, op->opfuncid, op->inputcollid,
+ parent, state);
+
+ /*
+ * Change opcode of call instruction to EEOP_NULLIF.
+ *
+ * XXX: historically we've not called the function usage
+ * pgstat infrastructure - that seems inconsistent given that
+ * we do so for normal function *and* operator evaluation. If
+ * we decided to do that here, we'd probably want separate
+ * opcodes for FUSAGE or not.
+ */
+ scratch.opcode = EEOP_NULLIF;
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_ScalarArrayOpExpr:
+ {
+ ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
+ Expr *scalararg;
+ Expr *arrayarg;
+ FmgrInfo *finfo;
+ FunctionCallInfo fcinfo;
+ AclResult aclresult;
+
+ Assert(list_length(opexpr->args) == 2);
+ scalararg = (Expr *) linitial(opexpr->args);
+ arrayarg = (Expr *) lsecond(opexpr->args);
+
+ /* Check permission to call function */
+ aclresult = pg_proc_aclcheck(opexpr->opfuncid,
+ GetUserId(),
+ ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ get_func_name(opexpr->opfuncid));
+ InvokeFunctionExecuteHook(opexpr->opfuncid);
+
+ /* Set up the primary fmgr lookup information */
+ finfo = palloc0(sizeof(FmgrInfo));
+ fcinfo = palloc0(sizeof(FunctionCallInfoData));
+ fmgr_info(opexpr->opfuncid, finfo);
+ fmgr_info_set_expr((Node *) node, finfo);
+ InitFunctionCallInfoData(*fcinfo, finfo, 2,
+ opexpr->inputcollid, NULL, NULL);
+
+ /* Evaluate scalar directly into left function argument */
+ ExecInitExprRec(scalararg, parent, state,
+ &fcinfo->arg[0], &fcinfo->argnull[0]);
+
+ /*
+ * Evaluate array argument into our return value. There's no
+ * danger in that, because the return value is guaranteed to
+ * be overwritten by EEOP_SCALARARRAYOP, and will not be
+ * passed to any other expression.
+ */
+ ExecInitExprRec(arrayarg, parent, state, resv, resnull);
+
+ /* And perform the operation */
+ scratch.opcode = EEOP_SCALARARRAYOP;
+ scratch.d.scalararrayop.element_type = InvalidOid;
+ scratch.d.scalararrayop.useOr = opexpr->useOr;
+ scratch.d.scalararrayop.finfo = finfo;
+ scratch.d.scalararrayop.fcinfo_data = fcinfo;
+ scratch.d.scalararrayop.fn_addr = finfo->fn_addr;
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_BoolExpr:
+ {
+ BoolExpr *boolexpr = (BoolExpr *) node;
+ int nargs = list_length(boolexpr->args);
+ List *adjust_jumps = NIL;
+ int off;
+ ListCell *lc;
+
+ /* allocate scratch memory used by all steps of AND/OR */
+ if (boolexpr->boolop != NOT_EXPR)
+ scratch.d.boolexpr.anynull = (bool *) palloc(sizeof(bool));
+
+ /*
+ * For each argument evaluate the argument itself, then
+ * perform the bool operation's appropriate handling.
+ *
+ * We can evaluate each argument into our result area, since
+ * the short-circuiting logic means we only need to remember
+ * previous NULL values.
+ *
+ * AND/OR is split into separate STEP_FIRST (one) / STEP (zero
+ * or more) / STEP_LAST (one) steps, as each of those has to
+ * perform different work. The FIRST/LAST split is valid
+ * because AND/OR have at least two arguments.
+ */
+ off = 0;
+ foreach(lc, boolexpr->args)
+ {
+ Expr *arg = (Expr *) lfirst(lc);
+
+ /* Evaluate argument into our output variable */
+ ExecInitExprRec(arg, parent, state, resv, resnull);
+
+ /* Perform the appropriate step type */
+ switch (boolexpr->boolop)
+ {
+ case AND_EXPR:
+ Assert(nargs >= 2);
+
+ if (off == 0)
+ scratch.opcode = EEOP_BOOL_AND_STEP_FIRST;
+ else if (off + 1 == nargs)
+ scratch.opcode = EEOP_BOOL_AND_STEP_LAST;
+ else
+ scratch.opcode = EEOP_BOOL_AND_STEP;
+ break;
+ case OR_EXPR:
+ Assert(nargs >= 2);
+
+ if (off == 0)
+ scratch.opcode = EEOP_BOOL_OR_STEP_FIRST;
+ else if (off + 1 == nargs)
+ scratch.opcode = EEOP_BOOL_OR_STEP_LAST;
+ else
+ scratch.opcode = EEOP_BOOL_OR_STEP;
+ break;
+ case NOT_EXPR:
+ Assert(nargs == 1);
+
+ scratch.opcode = EEOP_BOOL_NOT_STEP;
+ break;
+ default:
+ elog(ERROR, "unrecognized boolop: %d",
+ (int) boolexpr->boolop);
+ break;
+ }
+
+ scratch.d.boolexpr.jumpdone = -1;
+ ExprEvalPushStep(state, &scratch);
+ adjust_jumps = lappend_int(adjust_jumps,
+ state->steps_len - 1);
+ off++;
+ }
+
+ /* adjust jump targets */
+ foreach(lc, adjust_jumps)
+ {
+ ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+ Assert(as->d.boolexpr.jumpdone == -1);
+ as->d.boolexpr.jumpdone = state->steps_len;
+ }
+
+ break;
+ }
+
+ case T_SubPlan:
+ {
+ SubPlan *subplan = (SubPlan *) node;
+ SubPlanState *sstate;
+
+ if (!parent)
+ elog(ERROR, "SubPlan found with no parent plan");
+
+ sstate = ExecInitSubPlan(subplan, parent);
+
+ /* add SubPlanState nodes to parent->subPlan */
+ parent->subPlan = lappend(parent->subPlan, sstate);
+
+ scratch.opcode = EEOP_SUBPLAN;
+ scratch.d.subplan.sstate = sstate;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_AlternativeSubPlan:
+ {
+ AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
+ AlternativeSubPlanState *asstate;
+
+ if (!parent)
+ elog(ERROR, "AlternativeSubPlan found with no parent plan");
+
+ asstate = ExecInitAlternativeSubPlan(asplan, parent);
+
+ scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN;
+ scratch.d.alternative_subplan.asstate = asstate;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_FieldSelect:
+ {
+ FieldSelect *fselect = (FieldSelect *) node;
+
+ /* evaluate row/record argument into result area */
+ ExecInitExprRec(fselect->arg, parent, state, resv, resnull);
+
+ /* and extract field */
+ scratch.opcode = EEOP_FIELDSELECT;
+ scratch.d.fieldselect.fieldnum = fselect->fieldnum;
+ scratch.d.fieldselect.resulttype = fselect->resulttype;
+ scratch.d.fieldselect.argdesc = NULL;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_FieldStore:
+ {
+ FieldStore *fstore = (FieldStore *) node;
+ TupleDesc tupDesc;
+ TupleDesc *descp;
+ Datum *values;
+ bool *nulls;
+ int ncolumns;
+ ListCell *l1,
+ *l2;
+
+ /* find out the number of columns in the composite type */
+ tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1);
+ ncolumns = tupDesc->natts;
+ DecrTupleDescRefCount(tupDesc);
+
+ /* create workspace for column values */
+ values = (Datum *) palloc(sizeof(Datum) * ncolumns);
+ nulls = (bool *) palloc(sizeof(bool) * ncolumns);
+
+ /* create workspace for runtime tupdesc cache */
+ descp = (TupleDesc *) palloc(sizeof(TupleDesc));
+ *descp = NULL;
+
+ /* emit code to evaluate the composite input value */
+ ExecInitExprRec(fstore->arg, parent, state, resv, resnull);
+
+ /* next, deform the input tuple into our workspace */
+ scratch.opcode = EEOP_FIELDSTORE_DEFORM;
+ scratch.d.fieldstore.fstore = fstore;
+ scratch.d.fieldstore.argdesc = descp;
+ scratch.d.fieldstore.values = values;
+ scratch.d.fieldstore.nulls = nulls;
+ scratch.d.fieldstore.ncolumns = ncolumns;
+ ExprEvalPushStep(state, &scratch);
+
+ /* evaluate new field values, store in workspace columns */
+ forboth(l1, fstore->newvals, l2, fstore->fieldnums)
+ {
+ Expr *e = (Expr *) lfirst(l1);
+ AttrNumber fieldnum = lfirst_int(l2);
+ Datum *save_innermost_caseval;
+ bool *save_innermost_casenull;
+
+ if (fieldnum <= 0 || fieldnum > ncolumns)
+ elog(ERROR, "field number %d is out of range in FieldStore",
+ fieldnum);
+
+ /*
+ * Use the CaseTestExpr mechanism to pass down the old
+ * value of the field being replaced; this is needed in
+ * case the newval is itself a FieldStore or ArrayRef that
+ * has to obtain and modify the old value. It's safe to
+ * reuse the CASE mechanism because there cannot be a CASE
+ * between here and where the value would be needed, and a
+ * field assignment can't be within a CASE either. (So
+ * saving and restoring innermost_caseval is just
+ * paranoia, but let's do it anyway.)
+ */
+ save_innermost_caseval = state->innermost_caseval;
+ save_innermost_casenull = state->innermost_casenull;
+ state->innermost_caseval = &values[fieldnum - 1];
+ state->innermost_casenull = &nulls[fieldnum - 1];
+
+ ExecInitExprRec(e, parent, state,
+ &values[fieldnum - 1],
+ &nulls[fieldnum - 1]);
+
+ state->innermost_caseval = save_innermost_caseval;
+ state->innermost_casenull = save_innermost_casenull;
+ }
+
+ /* finally, form result tuple */
+ scratch.opcode = EEOP_FIELDSTORE_FORM;
+ scratch.d.fieldstore.fstore = fstore;
+ scratch.d.fieldstore.argdesc = descp;
+ scratch.d.fieldstore.values = values;
+ scratch.d.fieldstore.nulls = nulls;
+ scratch.d.fieldstore.ncolumns = ncolumns;
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_RelabelType:
+ {
+ /* relabel doesn't need to do anything at runtime */
+ RelabelType *relabel = (RelabelType *) node;
+
+ ExecInitExprRec(relabel->arg, parent, state, resv, resnull);
+ break;
+ }
+
+ case T_CoerceViaIO:
+ {
+ CoerceViaIO *iocoerce = (CoerceViaIO *) node;
+ Oid iofunc;
+ bool typisvarlena;
+ Oid typioparam;
+ FunctionCallInfo fcinfo_in;
+
+ /* evaluate argument into step's result area */
+ ExecInitExprRec(iocoerce->arg, parent, state, resv, resnull);
+
+ /*
+ * Prepare both output and input function calls, to be
+ * evaluated inside a single evaluation step for speed - this
+ * can be a very common operation.
+ *
+ * We don't check permissions here as a type's input/output
+ * function are assumed to be executable by everyone.
+ */
+ scratch.opcode = EEOP_IOCOERCE;
+
+ /* lookup the source type's output function */
+ scratch.d.iocoerce.finfo_out = palloc0(sizeof(FmgrInfo));
+ scratch.d.iocoerce.fcinfo_data_out = palloc0(sizeof(FunctionCallInfoData));
+
+ getTypeOutputInfo(exprType((Node *) iocoerce->arg),
+ &iofunc, &typisvarlena);
+ fmgr_info(iofunc, scratch.d.iocoerce.finfo_out);
+ fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_out);
+ InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_out,
+ scratch.d.iocoerce.finfo_out,
+ 1, InvalidOid, NULL, NULL);
+
+ /* lookup the result type's input function */
+ scratch.d.iocoerce.finfo_in = palloc0(sizeof(FmgrInfo));
+ scratch.d.iocoerce.fcinfo_data_in = palloc0(sizeof(FunctionCallInfoData));
+
+ getTypeInputInfo(iocoerce->resulttype,
+ &iofunc, &typioparam);
+ fmgr_info(iofunc, scratch.d.iocoerce.finfo_in);
+ fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_in);
+ InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_in,
+ scratch.d.iocoerce.finfo_in,
+ 3, InvalidOid, NULL, NULL);
+
+ /*
+ * We can preload the second and third arguments for the input
+ * function, since they're constants.
+ */
+ fcinfo_in = scratch.d.iocoerce.fcinfo_data_in;
+ fcinfo_in->arg[1] = ObjectIdGetDatum(typioparam);
+ fcinfo_in->argnull[1] = false;
+ fcinfo_in->arg[2] = Int32GetDatum(-1);
+ fcinfo_in->argnull[2] = false;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_ArrayCoerceExpr:
+ {
+ ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
+ Oid resultelemtype;
+
+ /* evaluate argument into step's result area */
+ ExecInitExprRec(acoerce->arg, parent, state, resv, resnull);
+
+ resultelemtype = get_element_type(acoerce->resulttype);
+ if (!OidIsValid(resultelemtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("target type is not an array")));
+ /* Arrays over domains aren't supported yet */
+ Assert(getBaseType(resultelemtype) == resultelemtype);
+
+ scratch.opcode = EEOP_ARRAYCOERCE;
+ scratch.d.arraycoerce.coerceexpr = acoerce;
+ scratch.d.arraycoerce.resultelemtype = resultelemtype;
+
+ if (OidIsValid(acoerce->elemfuncid))
+ {
+ AclResult aclresult;
+
+ /* Check permission to call function */
+ aclresult = pg_proc_aclcheck(acoerce->elemfuncid,
+ GetUserId(),
+ ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ get_func_name(acoerce->elemfuncid));
+ InvokeFunctionExecuteHook(acoerce->elemfuncid);
+
+ /* Set up the primary fmgr lookup information */
+ scratch.d.arraycoerce.elemfunc =
+ (FmgrInfo *) palloc0(sizeof(FmgrInfo));
+ fmgr_info(acoerce->elemfuncid,
+ scratch.d.arraycoerce.elemfunc);
+ fmgr_info_set_expr((Node *) acoerce,
+ scratch.d.arraycoerce.elemfunc);
+
+ /* Set up workspace for array_map */
+ scratch.d.arraycoerce.amstate =
+ (ArrayMapState *) palloc0(sizeof(ArrayMapState));
+ }
+ else
+ {
+ /* Don't need workspace if there's no conversion func */
+ scratch.d.arraycoerce.elemfunc = NULL;
+ scratch.d.arraycoerce.amstate = NULL;
+ }
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_ConvertRowtypeExpr:
+ {
+ ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
+
+ /* evaluate argument into step's result area */
+ ExecInitExprRec(convert->arg, parent, state, resv, resnull);
+
+ /* and push conversion step */
+ scratch.opcode = EEOP_CONVERT_ROWTYPE;
+ scratch.d.convert_rowtype.convert = convert;
+ scratch.d.convert_rowtype.indesc = NULL;
+ scratch.d.convert_rowtype.outdesc = NULL;
+ scratch.d.convert_rowtype.map = NULL;
+ scratch.d.convert_rowtype.initialized = false;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ /* note that CaseWhen expressions are handled within this block */
+ case T_CaseExpr:
+ {
+ CaseExpr *caseExpr = (CaseExpr *) node;
+ List *adjust_jumps = NIL;
+ Datum *caseval = NULL;
+ bool *casenull = NULL;
+ ListCell *lc;
+
+ /*
+ * If there's a test expression, we have to evaluate it and
+ * save the value where the CaseTestExpr placeholders can find
+ * it.
+ */
+ if (caseExpr->arg != NULL)
+ {
+ /* Evaluate testexpr into caseval/casenull workspace */
+ caseval = palloc(sizeof(Datum));
+ casenull = palloc(sizeof(bool));
+
+ ExecInitExprRec(caseExpr->arg, parent, state,
+ caseval, casenull);
+
+ /*
+ * Since value might be read multiple times, force to R/O
+ * - but only if it could be an expanded datum.
+ */
+ if (get_typlen(exprType((Node *) caseExpr->arg)) == -1)
+ {
+ /* change caseval in-place */
+ scratch.opcode = EEOP_MAKE_READONLY;
+ scratch.resvalue = caseval;
+ scratch.resnull = casenull;
+ scratch.d.make_readonly.value = caseval;
+ scratch.d.make_readonly.isnull = casenull;
+ ExprEvalPushStep(state, &scratch);
+ /* restore normal settings of scratch fields */
+ scratch.resvalue = resv;
+ scratch.resnull = resnull;
+ }
+ }
+
+ /*
+ * Prepare to evaluate each of the WHEN clauses in turn; as
+ * soon as one is true we return the value of the
+ * corresponding THEN clause. If none are true then we return
+ * the value of the ELSE clause, or NULL if there is none.
+ */
+ foreach(lc, caseExpr->args)
+ {
+ CaseWhen *when = (CaseWhen *) lfirst(lc);
+ Datum *save_innermost_caseval;
+ bool *save_innermost_casenull;
+ int whenstep;
+
+ /*
+ * Make testexpr result available to CaseTestExpr nodes
+ * within the condition. We must save and restore prior
+ * setting of innermost_caseval fields, in case this node
+ * is itself within a larger CASE.
+ *
+ * If there's no test expression, we don't actually need
+ * to save and restore these fields; but it's less code to
+ * just do so unconditionally.
+ */
+ save_innermost_caseval = state->innermost_caseval;
+ save_innermost_casenull = state->innermost_casenull;
+ state->innermost_caseval = caseval;
+ state->innermost_casenull = casenull;
+
+ /* evaluate condition into CASE's result variables */
+ ExecInitExprRec(when->expr, parent, state, resv, resnull);
+
+ state->innermost_caseval = save_innermost_caseval;
+ state->innermost_casenull = save_innermost_casenull;
+
+ /* If WHEN result isn't true, jump to next CASE arm */
+ scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
+ scratch.d.jump.jumpdone = -1; /* computed later */
+ ExprEvalPushStep(state, &scratch);
+ whenstep = state->steps_len - 1;
+
+ /*
+ * If WHEN result is true, evaluate THEN result, storing
+ * it into the CASE's result variables.
+ */
+ ExecInitExprRec(when->result, parent, state, resv, resnull);
+
+ /* Emit JUMP step to jump to end of CASE's code */
+ scratch.opcode = EEOP_JUMP;
+ scratch.d.jump.jumpdone = -1; /* computed later */
+ ExprEvalPushStep(state, &scratch);
+
+ /*
+ * Don't know address for that jump yet, compute once the
+ * whole CASE expression is built.
+ */
+ adjust_jumps = lappend_int(adjust_jumps,
+ state->steps_len - 1);
+
+ /*
+ * But we can set WHEN test's jump target now, to make it
+ * jump to the next WHEN subexpression or the ELSE.
+ */
+ state->steps[whenstep].d.jump.jumpdone = state->steps_len;
+ }
+
+ if (caseExpr->defresult)
+ {
+ /* evaluate ELSE expr into CASE's result variables */
+ ExecInitExprRec(caseExpr->defresult, parent, state,
+ resv, resnull);
+ }
+ else
+ {
+ /* default ELSE is to return NULL */
+ scratch.opcode = EEOP_CONST;
+ scratch.d.constval.value = (Datum) 0;
+ scratch.d.constval.isnull = true;
+ ExprEvalPushStep(state, &scratch);
+ }
+
+ /* adjust jump targets */
+ foreach(lc, adjust_jumps)
+ {
+ ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+ Assert(as->opcode == EEOP_JUMP);
+ Assert(as->d.jump.jumpdone == -1);
+ as->d.jump.jumpdone = state->steps_len;
+ }
+
+ break;
+ }
+
+ case T_CaseTestExpr:
+ {
+ /*
+ * Read from location identified by innermost_caseval. Note
+ * that innermost_caseval could be NULL, if this node isn't
+ * actually within a CASE structure; some parts of the system
+ * abuse CaseTestExpr to cause a read of a value externally
+ * supplied in econtext->caseValue_datum. We'll take care of
+ * that scenario at runtime.
+ */
+ scratch.opcode = EEOP_CASE_TESTVAL;
+ scratch.d.casetest.value = state->innermost_caseval;
+ scratch.d.casetest.isnull = state->innermost_casenull;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_ArrayExpr:
+ {
+ ArrayExpr *arrayexpr = (ArrayExpr *) node;
+ int nelems = list_length(arrayexpr->elements);
+ ListCell *lc;
+ int elemoff;
+
+ /*
+ * Evaluate by computing each element, and then forming the
+ * array. Elements are computed into scratch arrays
+ * associated with the ARRAYEXPR step.
+ */
+ scratch.opcode = EEOP_ARRAYEXPR;
+ scratch.d.arrayexpr.elemvalues =
+ (Datum *) palloc(sizeof(Datum) * nelems);
+ scratch.d.arrayexpr.elemnulls =
+ (bool *) palloc(sizeof(bool) * nelems);
+ scratch.d.arrayexpr.nelems = nelems;
+
+ /* fill remaining fields of step */
+ scratch.d.arrayexpr.multidims = arrayexpr->multidims;
+ scratch.d.arrayexpr.elemtype = arrayexpr->element_typeid;
+
+ /* do one-time catalog lookup for type info */
+ get_typlenbyvalalign(arrayexpr->element_typeid,
+ &scratch.d.arrayexpr.elemlength,
+ &scratch.d.arrayexpr.elembyval,
+ &scratch.d.arrayexpr.elemalign);
+
+ /* prepare to evaluate all arguments */
+ elemoff = 0;
+ foreach(lc, arrayexpr->elements)
+ {
+ Expr *e = (Expr *) lfirst(lc);
+
+ ExecInitExprRec(e, parent, state,
+ &scratch.d.arrayexpr.elemvalues[elemoff],
+ &scratch.d.arrayexpr.elemnulls[elemoff]);
+ elemoff++;
+ }
+
+ /* and then collect all into an array */
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_RowExpr:
+ {
+ RowExpr *rowexpr = (RowExpr *) node;
+ int nelems = list_length(rowexpr->args);
+ TupleDesc tupdesc;
+ Form_pg_attribute *attrs;
+ int i;
+ ListCell *l;
+
+ /* Build tupdesc to describe result tuples */
+ if (rowexpr->row_typeid == RECORDOID)
+ {
+ /* generic record, use types of given expressions */
+ tupdesc = ExecTypeFromExprList(rowexpr->args);
+ }
+ else
+ {
+ /* it's been cast to a named type, use that */
+ tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
+ }
+ /* In either case, adopt RowExpr's column aliases */
+ ExecTypeSetColNames(tupdesc, rowexpr->colnames);
+ /* Bless the tupdesc in case it's now of type RECORD */
+ BlessTupleDesc(tupdesc);
+
+ /*
+ * In the named-type case, the tupdesc could have more columns
+ * than are in the args list, since the type might have had
+ * columns added since the ROW() was parsed. We want those
+ * extra columns to go to nulls, so we make sure that the
+ * workspace arrays are large enough and then initialize any
+ * extra columns to read as NULLs.
+ */
+ Assert(nelems <= tupdesc->natts);
+ nelems = Max(nelems, tupdesc->natts);
+
+ /*
+ * Evaluate by first building datums for each field, and then
+ * a final step forming the composite datum.
+ */
+ scratch.opcode = EEOP_ROW;
+ scratch.d.row.tupdesc = tupdesc;
+
+ /* space for the individual field datums */
+ scratch.d.row.elemvalues =
+ (Datum *) palloc(sizeof(Datum) * nelems);
+ scratch.d.row.elemnulls =
+ (bool *) palloc(sizeof(bool) * nelems);
+ /* as explained above, make sure any extra columns are null */
+ memset(scratch.d.row.elemnulls, true, sizeof(bool) * nelems);
+
+ /* Set up evaluation, skipping any deleted columns */
+ attrs = tupdesc->attrs;
+ i = 0;
+ foreach(l, rowexpr->args)
+ {
+ Expr *e = (Expr *) lfirst(l);
+
+ if (!attrs[i]->attisdropped)
+ {
+ /*
+ * Guard against ALTER COLUMN TYPE on rowtype since
+ * the RowExpr was created. XXX should we check
+ * typmod too? Not sure we can be sure it'll be the
+ * same.
+ */
+ if (exprType((Node *) e) != attrs[i]->atttypid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("ROW() column has type %s instead of type %s",
+ format_type_be(exprType((Node *) e)),
+ format_type_be(attrs[i]->atttypid))));
+ }
+ else
+ {
+ /*
+ * Ignore original expression and insert a NULL. We
+ * don't really care what type of NULL it is, so
+ * always make an int4 NULL.
+ */
+ e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
+ }
+
+ /* Evaluate column expr into appropriate workspace slot */
+ ExecInitExprRec(e, parent, state,
+ &scratch.d.row.elemvalues[i],
+ &scratch.d.row.elemnulls[i]);
+ i++;
+ }
+
+ /* And finally build the row value */
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_RowCompareExpr:
+ {
+ RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+ int nopers = list_length(rcexpr->opnos);
+ List *adjust_jumps = NIL;
+ ListCell *l_left_expr,
+ *l_right_expr,
+ *l_opno,
+ *l_opfamily,
+ *l_inputcollid;
+ ListCell *lc;
+ int off;
+
+ /*
+ * Iterate over each field, prepare comparisons. To handle
+ * NULL results, prepare jumps to after the expression. If a
+ * comparison yields a != 0 result, jump to the final step.
+ */
+ Assert(list_length(rcexpr->largs) == nopers);
+ Assert(list_length(rcexpr->rargs) == nopers);
+ Assert(list_length(rcexpr->opfamilies) == nopers);
+ Assert(list_length(rcexpr->inputcollids) == nopers);
+
+ off = 0;
+ for (off = 0,
+ l_left_expr = list_head(rcexpr->largs),
+ l_right_expr = list_head(rcexpr->rargs),
+ l_opno = list_head(rcexpr->opnos),
+ l_opfamily = list_head(rcexpr->opfamilies),
+ l_inputcollid = list_head(rcexpr->inputcollids);
+ off < nopers;
+ off++,
+ l_left_expr = lnext(l_left_expr),
+ l_right_expr = lnext(l_right_expr),
+ l_opno = lnext(l_opno),
+ l_opfamily = lnext(l_opfamily),
+ l_inputcollid = lnext(l_inputcollid))
+ {
+ Expr *left_expr = (Expr *) lfirst(l_left_expr);
+ Expr *right_expr = (Expr *) lfirst(l_right_expr);
+ Oid opno = lfirst_oid(l_opno);
+ Oid opfamily = lfirst_oid(l_opfamily);
+ Oid inputcollid = lfirst_oid(l_inputcollid);
+ int strategy;
+ Oid lefttype;
+ Oid righttype;
+ Oid proc;
+ FmgrInfo *finfo;
+ FunctionCallInfo fcinfo;
+
+ get_op_opfamily_properties(opno, opfamily, false,
+ &strategy,
+ &lefttype,
+ &righttype);
+ proc = get_opfamily_proc(opfamily,
+ lefttype,
+ righttype,
+ BTORDER_PROC);
+
+ /* Set up the primary fmgr lookup information */
+ finfo = palloc0(sizeof(FmgrInfo));
+ fcinfo = palloc0(sizeof(FunctionCallInfoData));
+ fmgr_info(proc, finfo);
+ fmgr_info_set_expr((Node *) node, finfo);
+ InitFunctionCallInfoData(*fcinfo, finfo, 2,
+ inputcollid, NULL, NULL);
+
+ /*
+ * If we enforced permissions checks on index support
+ * functions, we'd need to make a check here. But the
+ * index support machinery doesn't do that, and thus
+ * neither does this code.
+ */
+
+ /* evaluate left and right args directly into fcinfo */
+ ExecInitExprRec(left_expr, parent, state,
+ &fcinfo->arg[0], &fcinfo->argnull[0]);
+ ExecInitExprRec(right_expr, parent, state,
+ &fcinfo->arg[1], &fcinfo->argnull[1]);
+
+ scratch.opcode = EEOP_ROWCOMPARE_STEP;
+ scratch.d.rowcompare_step.finfo = finfo;
+ scratch.d.rowcompare_step.fcinfo_data = fcinfo;
+ scratch.d.rowcompare_step.fn_addr = finfo->fn_addr;
+ /* jump targets filled below */
+ scratch.d.rowcompare_step.jumpnull = -1;
+ scratch.d.rowcompare_step.jumpdone = -1;
+
+ ExprEvalPushStep(state, &scratch);
+ adjust_jumps = lappend_int(adjust_jumps,
+ state->steps_len - 1);
+ }
+
+ /*
+ * We could have a zero-column rowtype, in which case the rows
+ * necessarily compare equal.
+ */
+ if (nopers == 0)
+ {
+ scratch.opcode = EEOP_CONST;
+ scratch.d.constval.value = Int32GetDatum(0);
+ scratch.d.constval.isnull = false;
+ ExprEvalPushStep(state, &scratch);
+ }
+
+ /* Finally, examine the last comparison result */
+ scratch.opcode = EEOP_ROWCOMPARE_FINAL;
+ scratch.d.rowcompare_final.rctype = rcexpr->rctype;
+ ExprEvalPushStep(state, &scratch);
+
+ /* adjust jump targetss */
+ foreach(lc, adjust_jumps)
+ {
+ ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+ Assert(as->opcode == EEOP_ROWCOMPARE_STEP);
+ Assert(as->d.rowcompare_step.jumpdone == -1);
+ Assert(as->d.rowcompare_step.jumpnull == -1);
+
+ /* jump to comparison evaluation */
+ as->d.rowcompare_step.jumpdone = state->steps_len - 1;
+ /* jump to the following expression */
+ as->d.rowcompare_step.jumpnull = state->steps_len;
+ }
+
+ break;
+ }
+
+ case T_CoalesceExpr:
+ {
+ CoalesceExpr *coalesce = (CoalesceExpr *) node;
+ List *adjust_jumps = NIL;
+ ListCell *lc;
+
+ /* We assume there's at least one arg */
+ Assert(coalesce->args != NIL);
+
+ /*
+ * Prepare evaluation of all coalesced arguments, after each
+ * one push a step that short-circuits if not null.
+ */
+ foreach(lc, coalesce->args)
+ {
+ Expr *e = (Expr *) lfirst(lc);
+
+ /* evaluate argument, directly into result datum */
+ ExecInitExprRec(e, parent, state, resv, resnull);
+
+ /* if it's not null, skip to end of COALESCE expr */
+ scratch.opcode = EEOP_JUMP_IF_NOT_NULL;
+ scratch.d.jump.jumpdone = -1; /* adjust later */
+ ExprEvalPushStep(state, &scratch);
+
+ adjust_jumps = lappend_int(adjust_jumps,
+ state->steps_len - 1);
+ }
+
+ /*
+ * No need to add a constant NULL return - we only can get to
+ * the end of the expression if a NULL already is being
+ * returned.
+ */
+
+ /* adjust jump targets */
+ foreach(lc, adjust_jumps)
+ {
+ ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+ Assert(as->opcode == EEOP_JUMP_IF_NOT_NULL);
+ Assert(as->d.jump.jumpdone == -1);
+ as->d.jump.jumpdone = state->steps_len;
+ }
+
+ break;
+ }
+
+ case T_MinMaxExpr:
+ {
+ MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
+ int nelems = list_length(minmaxexpr->args);
+ TypeCacheEntry *typentry;
+ FmgrInfo *finfo;
+ FunctionCallInfo fcinfo;
+ ListCell *lc;
+ int off;
+
+ /* Look up the btree comparison function for the datatype */
+ typentry = lookup_type_cache(minmaxexpr->minmaxtype,
+ TYPECACHE_CMP_PROC);
+ if (!OidIsValid(typentry->cmp_proc))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify a comparison function for type %s",
+ format_type_be(minmaxexpr->minmaxtype))));
+
+ /*
+ * If we enforced permissions checks on index support
+ * functions, we'd need to make a check here. But the index
+ * support machinery doesn't do that, and thus neither does
+ * this code.
+ */
+
+ /* Perform function lookup */
+ finfo = palloc0(sizeof(FmgrInfo));
+ fcinfo = palloc0(sizeof(FunctionCallInfoData));
+ fmgr_info(typentry->cmp_proc, finfo);
+ fmgr_info_set_expr((Node *) node, finfo);
+ InitFunctionCallInfoData(*fcinfo, finfo, 2,
+ minmaxexpr->inputcollid, NULL, NULL);
+
+ scratch.opcode = EEOP_MINMAX;
+ /* allocate space to store arguments */
+ scratch.d.minmax.values =
+ (Datum *) palloc(sizeof(Datum) * nelems);
+ scratch.d.minmax.nulls =
+ (bool *) palloc(sizeof(bool) * nelems);
+ scratch.d.minmax.nelems = nelems;
+
+ scratch.d.minmax.op = minmaxexpr->op;
+ scratch.d.minmax.finfo = finfo;
+ scratch.d.minmax.fcinfo_data = fcinfo;
+
+ /* evaluate expressions into minmax->values/nulls */
+ off = 0;
+ foreach(lc, minmaxexpr->args)
+ {
+ Expr *e = (Expr *) lfirst(lc);
+
+ ExecInitExprRec(e, parent, state,
+ &scratch.d.minmax.values[off],
+ &scratch.d.minmax.nulls[off]);
+ off++;
+ }
+
+ /* and push the final comparison */
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_SQLValueFunction:
+ {
+ SQLValueFunction *svf = (SQLValueFunction *) node;
+
+ scratch.opcode = EEOP_SQLVALUEFUNCTION;
+ scratch.d.sqlvaluefunction.svf = svf;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_XmlExpr:
+ {
+ XmlExpr *xexpr = (XmlExpr *) node;
+ int nnamed = list_length(xexpr->named_args);
+ int nargs = list_length(xexpr->args);
+ int off;
+ ListCell *arg;
+
+ scratch.opcode = EEOP_XMLEXPR;
+ scratch.d.xmlexpr.xexpr = xexpr;
+
+ /* allocate space for storing all the arguments */
+ if (nnamed)
+ {
+ scratch.d.xmlexpr.named_argvalue =
+ (Datum *) palloc(sizeof(Datum) * nnamed);
+ scratch.d.xmlexpr.named_argnull =
+ (bool *) palloc(sizeof(bool) * nnamed);
+ }
+ else
+ {
+ scratch.d.xmlexpr.named_argvalue = NULL;
+ scratch.d.xmlexpr.named_argnull = NULL;
+ }
+
+ if (nargs)
+ {
+ scratch.d.xmlexpr.argvalue =
+ (Datum *) palloc(sizeof(Datum) * nargs);
+ scratch.d.xmlexpr.argnull =
+ (bool *) palloc(sizeof(bool) * nargs);
+ }
+ else
+ {
+ scratch.d.xmlexpr.argvalue = NULL;
+ scratch.d.xmlexpr.argnull = NULL;
+ }
+
+ /* prepare argument execution */
+ off = 0;
+ foreach(arg, xexpr->named_args)
+ {
+ Expr *e = (Expr *) lfirst(arg);
+
+ ExecInitExprRec(e, parent, state,
+ &scratch.d.xmlexpr.named_argvalue[off],
+ &scratch.d.xmlexpr.named_argnull[off]);
+ off++;
+ }
+
+ off = 0;
+ foreach(arg, xexpr->args)
+ {
+ Expr *e = (Expr *) lfirst(arg);
+
+ ExecInitExprRec(e, parent, state,
+ &scratch.d.xmlexpr.argvalue[off],
+ &scratch.d.xmlexpr.argnull[off]);
+ off++;
+ }
+
+ /* and evaluate the actual XML expression */
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_NullTest:
+ {
+ NullTest *ntest = (NullTest *) node;
+
+ if (ntest->nulltesttype == IS_NULL)
+ {
+ if (ntest->argisrow)
+ scratch.opcode = EEOP_NULLTEST_ROWISNULL;
+ else
+ scratch.opcode = EEOP_NULLTEST_ISNULL;
+ }
+ else if (ntest->nulltesttype == IS_NOT_NULL)
+ {
+ if (ntest->argisrow)
+ scratch.opcode = EEOP_NULLTEST_ROWISNOTNULL;
+ else
+ scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
+ }
+ else
+ {
+ elog(ERROR, "unrecognized nulltesttype: %d",
+ (int) ntest->nulltesttype);
+ }
+ /* initialize cache in case it's a row test */
+ scratch.d.nulltest_row.argdesc = NULL;
+
+ /* first evaluate argument into result variable */
+ ExecInitExprRec(ntest->arg, parent, state,
+ resv, resnull);
+
+ /* then push the test of that argument */
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_BooleanTest:
+ {
+ BooleanTest *btest = (BooleanTest *) node;
+
+ /*
+ * Evaluate argument, directly into result datum. That's ok,
+ * because resv/resnull is definitely not used anywhere else,
+ * and will get overwritten by the below EEOP_BOOLTEST_IS_*
+ * step.
+ */
+ ExecInitExprRec(btest->arg, parent, state, resv, resnull);
+
+ switch (btest->booltesttype)
+ {
+ case IS_TRUE:
+ scratch.opcode = EEOP_BOOLTEST_IS_TRUE;
+ break;
+ case IS_NOT_TRUE:
+ scratch.opcode = EEOP_BOOLTEST_IS_NOT_TRUE;
+ break;
+ case IS_FALSE:
+ scratch.opcode = EEOP_BOOLTEST_IS_FALSE;
+ break;
+ case IS_NOT_FALSE:
+ scratch.opcode = EEOP_BOOLTEST_IS_NOT_FALSE;
+ break;
+ case IS_UNKNOWN:
+ /* Same as scalar IS NULL test */
+ scratch.opcode = EEOP_NULLTEST_ISNULL;
+ break;
+ case IS_NOT_UNKNOWN:
+ /* Same as scalar IS NOT NULL test */
+ scratch.opcode = EEOP_NULLTEST_ISNOTNULL;
+ break;
+ default:
+ elog(ERROR, "unrecognized booltesttype: %d",
+ (int) btest->booltesttype);
+ }
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_CoerceToDomain:
+ {
+ CoerceToDomain *ctest = (CoerceToDomain *) node;
+
+ ExecInitCoerceToDomain(&scratch, ctest, parent, state,
+ resv, resnull);
+ break;
+ }
+
+ case T_CoerceToDomainValue:
+ {
+ /*
+ * Read from location identified by innermost_domainval. Note
+ * that innermost_domainval could be NULL, if we're compiling
+ * a standalone domain check rather than one embedded in a
+ * larger expression. In that case we must read from
+ * econtext->domainValue_datum. We'll take care of that
+ * scenario at runtime.
+ */
+ scratch.opcode = EEOP_DOMAIN_TESTVAL;
+ /* we share instruction union variant with case testval */
+ scratch.d.casetest.value = state->innermost_domainval;
+ scratch.d.casetest.isnull = state->innermost_domainnull;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ case T_CurrentOfExpr:
+ {
+ scratch.opcode = EEOP_CURRENTOFEXPR;
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(node));
+ break;
+ }
+}
+
+/*
+ * Add another expression evaluation step to ExprState->steps.
+ *
+ * Note that this potentially re-allocates es->steps, therefore no pointer
+ * into that array may be used while the expression is still being built.
+ */
+static void
+ExprEvalPushStep(ExprState *es, const ExprEvalStep *s)
+{
+ if (es->steps_alloc == 0)
+ {
+ es->steps_alloc = 16;
+ es->steps = palloc(sizeof(ExprEvalStep) * es->steps_alloc);
+ }
+ else if (es->steps_alloc == es->steps_len)
+ {
+ es->steps_alloc *= 2;
+ es->steps = repalloc(es->steps,
+ sizeof(ExprEvalStep) * es->steps_alloc);
+ }
+
+ memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep));
+}
+
+/*
+ * Perform setup necessary for the evaluation of a function-like expression,
+ * appending argument evaluation steps to the steps list in *state, and
+ * setting up *scratch so it is ready to be pushed.
+ *
+ * *scratch is not pushed here, so that callers may override the opcode,
+ * which is useful for function-like cases like DISTINCT.
+ */
+static void
+ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid,
+ Oid inputcollid, PlanState *parent, ExprState *state)
+{
+ int nargs = list_length(args);
+ AclResult aclresult;
+ FmgrInfo *flinfo;
+ FunctionCallInfo fcinfo;
+ int argno;
+ ListCell *lc;
+
+ /* Check permission to call function */
+ aclresult = pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(funcid));
+ InvokeFunctionExecuteHook(funcid);
+
+ /*
+ * Safety check on nargs. Under normal circumstances this should never
+ * fail, as parser should check sooner. But possibly it might fail if
+ * server has been compiled with FUNC_MAX_ARGS smaller than some functions
+ * declared in pg_proc?
+ */
+ if (nargs > FUNC_MAX_ARGS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("cannot pass more than %d argument to a function",
+ "cannot pass more than %d arguments to a function",
+ FUNC_MAX_ARGS,
+ FUNC_MAX_ARGS)));
+
+ /* Allocate function lookup data and parameter workspace for this call */
+ scratch->d.func.finfo = palloc0(sizeof(FmgrInfo));
+ scratch->d.func.fcinfo_data = palloc0(sizeof(FunctionCallInfoData));
+ flinfo = scratch->d.func.finfo;
+ fcinfo = scratch->d.func.fcinfo_data;
+
+ /* Set up the primary fmgr lookup information */
+ fmgr_info(funcid, flinfo);
+ fmgr_info_set_expr((Node *) node, flinfo);
+
+ /* Initialize function call parameter structure too */
+ InitFunctionCallInfoData(*fcinfo, flinfo,
+ nargs, inputcollid, NULL, NULL);
+
+ /* Keep extra copies of this info to save an indirection at runtime */
+ scratch->d.func.fn_addr = flinfo->fn_addr;
+ scratch->d.func.nargs = nargs;
+
+ /* We only support non-set functions here */
+ if (flinfo->fn_retset)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ /* Build code to evaluate arguments directly into the fcinfo struct */
+ argno = 0;
+ foreach(lc, args)
+ {
+ Expr *arg = (Expr *) lfirst(lc);
+
+ if (IsA(arg, Const))
+ {
+ /*
+ * Don't evaluate const arguments every round; especially
+ * interesting for constants in comparisons.
+ */
+ Const *con = (Const *) arg;
+
+ fcinfo->arg[argno] = con->constvalue;
+ fcinfo->argnull[argno] = con->constisnull;
+ }
+ else
+ {
+ ExecInitExprRec(arg, parent, state,
+ &fcinfo->arg[argno], &fcinfo->argnull[argno]);
+ }
+ argno++;
+ }
+
+ /* Insert appropriate opcode depending on strictness and stats level */
+ if (pgstat_track_functions <= flinfo->fn_stats)
+ {
+ if (flinfo->fn_strict && nargs > 0)
+ scratch->opcode = EEOP_FUNCEXPR_STRICT;
+ else
+ scratch->opcode = EEOP_FUNCEXPR;
+ }
+ else
+ {
+ if (flinfo->fn_strict && nargs > 0)
+ scratch->opcode = EEOP_FUNCEXPR_STRICT_FUSAGE;
+ else
+ scratch->opcode = EEOP_FUNCEXPR_FUSAGE;
+ }
+}
+
+/*
+ * Add expression steps deforming the ExprState's inner/outer/scan slots
+ * as much as required by the expression.
+ */
+static void
+ExecInitExprSlots(ExprState *state, Node *node)
+{
+ LastAttnumInfo info = {0, 0, 0};
+ ExprEvalStep scratch;
+
+ /*
+ * Figure out which attributes we're going to need.
+ */
+ get_last_attnums_walker(node, &info);
+
+ /* Emit steps as needed */
+ if (info.last_inner > 0)
+ {
+ scratch.opcode = EEOP_INNER_FETCHSOME;
+ scratch.d.fetch.last_var = info.last_inner;
+ ExprEvalPushStep(state, &scratch);
+ }
+ if (info.last_outer > 0)
+ {
+ scratch.opcode = EEOP_OUTER_FETCHSOME;
+ scratch.d.fetch.last_var = info.last_outer;
+ ExprEvalPushStep(state, &scratch);
+ }
+ if (info.last_scan > 0)
+ {
+ scratch.opcode = EEOP_SCAN_FETCHSOME;
+ scratch.d.fetch.last_var = info.last_scan;
+ ExprEvalPushStep(state, &scratch);
+ }
+}
+
+/*
+ * get_last_attnums_walker: expression walker for ExecInitExprSlots
+ */
+static bool
+get_last_attnums_walker(Node *node, LastAttnumInfo *info)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var))
+ {
+ Var *variable = (Var *) node;
+ AttrNumber attnum = variable->varattno;
+
+ switch (variable->varno)
+ {
+ case INNER_VAR:
+ info->last_inner = Max(info->last_inner, attnum);
+ break;
+
+ case OUTER_VAR:
+ info->last_outer = Max(info->last_outer, attnum);
+ break;
+
+ /* INDEX_VAR is handled by default case */
+
+ default:
+ info->last_scan = Max(info->last_scan, attnum);
+ break;
+ }
+ return false;
+ }
+
+ /*
+ * Don't examine the arguments or filters of Aggrefs or WindowFuncs,
+ * because those do not represent expressions to be evaluated within the
+ * calling expression's econtext. GroupingFunc arguments are never
+ * evaluated at all.
+ */
+ if (IsA(node, Aggref))
+ return false;
+ if (IsA(node, WindowFunc))
+ return false;
+ if (IsA(node, GroupingFunc))
+ return false;
+ return expression_tree_walker(node, get_last_attnums_walker,
+ (void *) info);
+}
+
+/*
+ * Prepare step for the evaluation of a whole-row variable.
+ * The caller still has to push the step.
+ */
+static void
+ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent)
+{
+ /* fill in all but the target */
+ scratch->opcode = EEOP_WHOLEROW;
+ scratch->d.wholerow.var = variable;
+ scratch->d.wholerow.first = true;
+ scratch->d.wholerow.slow = false;
+ scratch->d.wholerow.tupdesc = NULL; /* filled at runtime */
+ scratch->d.wholerow.junkFilter = NULL;
+
+ /*
+ * If the input tuple came from a subquery, it might contain "resjunk"
+ * columns (such as GROUP BY or ORDER BY columns), which we don't want to
+ * keep in the whole-row result. We can get rid of such columns by
+ * passing the tuple through a JunkFilter --- but to make one, we have to
+ * lay our hands on the subquery's targetlist. Fortunately, there are not
+ * very many cases where this can happen, and we can identify all of them
+ * by examining our parent PlanState. We assume this is not an issue in
+ * standalone expressions that don't have parent plans. (Whole-row Vars
+ * can occur in such expressions, but they will always be referencing
+ * table rows.)
+ */
+ if (parent)
+ {
+ PlanState *subplan = NULL;
+
+ switch (nodeTag(parent))
+ {
+ case T_SubqueryScanState:
+ subplan = ((SubqueryScanState *) parent)->subplan;
+ break;
+ case T_CteScanState:
+ subplan = ((CteScanState *) parent)->cteplanstate;
+ break;
+ default:
+ break;
+ }
+
+ if (subplan)
+ {
+ bool junk_filter_needed = false;
+ ListCell *tlist;
+
+ /* Detect whether subplan tlist actually has any junk columns */
+ foreach(tlist, subplan->plan->targetlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tlist);
+
+ if (tle->resjunk)
+ {
+ junk_filter_needed = true;
+ break;
+ }
+ }
+
+ /* If so, build the junkfilter now */
+ if (junk_filter_needed)
+ {
+ scratch->d.wholerow.junkFilter =
+ ExecInitJunkFilter(subplan->plan->targetlist,
+ ExecGetResultType(subplan)->tdhasoid,
+ ExecInitExtraTupleSlot(parent->state));
+ }
+ }
+ }
+}
+
+/*
+ * Prepare evaluation of an ArrayRef expression.
+ */
+static void
+ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent,
+ ExprState *state, Datum *resv, bool *resnull)
+{
+ bool isAssignment = (aref->refassgnexpr != NULL);
+ ArrayRefState *arefstate = palloc0(sizeof(ArrayRefState));
+ List *adjust_jumps = NIL;
+ ListCell *lc;
+ int i;
+
+ /* Fill constant fields of ArrayRefState */
+ arefstate->isassignment = isAssignment;
+ arefstate->refelemtype = aref->refelemtype;
+ arefstate->refattrlength = get_typlen(aref->refarraytype);
+ get_typlenbyvalalign(aref->refelemtype,
+ &arefstate->refelemlength,
+ &arefstate->refelembyval,
+ &arefstate->refelemalign);
+
+ /*
+ * Evaluate array input. It's safe to do so into resv/resnull, because we
+ * won't use that as target for any of the other subexpressions, and it'll
+ * be overwritten by the final EEOP_ARRAYREF_FETCH/ASSIGN step, which is
+ * pushed last.
+ */
+ ExecInitExprRec(aref->refexpr, parent, state, resv, resnull);
+
+ /*
+ * If refexpr yields NULL, and it's a fetch, then result is NULL. We can
+ * implement this with just JUMP_IF_NULL, since we evaluated the array
+ * into the desired target location.
+ */
+ if (!isAssignment)
+ {
+ scratch->opcode = EEOP_JUMP_IF_NULL;
+ scratch->d.jump.jumpdone = -1; /* adjust later */
+ ExprEvalPushStep(state, scratch);
+ adjust_jumps = lappend_int(adjust_jumps,
+ state->steps_len - 1);
+ }
+
+ /* Verify subscript list lengths are within limit */
+ if (list_length(aref->refupperindexpr) > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ list_length(aref->refupperindexpr), MAXDIM)));
+
+ if (list_length(aref->reflowerindexpr) > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ list_length(aref->reflowerindexpr), MAXDIM)));
+
+ /* Evaluate upper subscripts */
+ i = 0;
+ foreach(lc, aref->refupperindexpr)
+ {
+ Expr *e = (Expr *) lfirst(lc);
+
+ /* When slicing, individual subscript bounds can be omitted */
+ if (!e)
+ {
+ arefstate->upperprovided[i] = false;
+ i++;
+ continue;
+ }
+
+ arefstate->upperprovided[i] = true;
+
+ /* Each subscript is evaluated into subscriptvalue/subscriptnull */
+ ExecInitExprRec(e, parent, state,
+ &arefstate->subscriptvalue, &arefstate->subscriptnull);
+
+ /* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
+ scratch->opcode = EEOP_ARRAYREF_SUBSCRIPT;
+ scratch->d.arrayref_subscript.state = arefstate;
+ scratch->d.arrayref_subscript.off = i;
+ scratch->d.arrayref_subscript.isupper = true;
+ scratch->d.arrayref_subscript.jumpdone = -1; /* adjust later */
+ ExprEvalPushStep(state, scratch);
+ adjust_jumps = lappend_int(adjust_jumps,
+ state->steps_len - 1);
+ i++;
+ }
+ arefstate->numupper = i;
+
+ /* Evaluate lower subscripts similarly */
+ i = 0;
+ foreach(lc, aref->reflowerindexpr)
+ {
+ Expr *e = (Expr *) lfirst(lc);
+
+ /* When slicing, individual subscript bounds can be omitted */
+ if (!e)
+ {
+ arefstate->lowerprovided[i] = false;
+ i++;
+ continue;
+ }
+
+ arefstate->lowerprovided[i] = true;
+
+ /* Each subscript is evaluated into subscriptvalue/subscriptnull */
+ ExecInitExprRec(e, parent, state,
+ &arefstate->subscriptvalue, &arefstate->subscriptnull);
+
+ /* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */
+ scratch->opcode = EEOP_ARRAYREF_SUBSCRIPT;
+ scratch->d.arrayref_subscript.state = arefstate;
+ scratch->d.arrayref_subscript.off = i;
+ scratch->d.arrayref_subscript.isupper = false;
+ scratch->d.arrayref_subscript.jumpdone = -1; /* adjust later */
+ ExprEvalPushStep(state, scratch);
+ adjust_jumps = lappend_int(adjust_jumps,
+ state->steps_len - 1);
+ i++;
+ }
+ arefstate->numlower = i;
+
+ /* Should be impossible if parser is sane, but check anyway: */
+ if (arefstate->numlower != 0 &&
+ arefstate->numupper != arefstate->numlower)
+ elog(ERROR, "upper and lower index lists are not same length");
+
+ if (isAssignment)
+ {
+ Datum *save_innermost_caseval;
+ bool *save_innermost_casenull;
+
+ /*
+ * We might have a nested-assignment situation, in which the
+ * refassgnexpr is itself a FieldStore or ArrayRef that needs to
+ * obtain and modify the previous value of the array element or slice
+ * being replaced. If so, we have to extract that value from the
+ * array and pass it down via the CaseTextExpr mechanism. It's safe
+ * to reuse the CASE mechanism because there cannot be a CASE between
+ * here and where the value would be needed, and an array assignment
+ * can't be within a CASE either. (So saving and restoring
+ * innermost_caseval is just paranoia, but let's do it anyway.)
+ *
+ * Since fetching the old element might be a nontrivial expense, do it
+ * only if the argument appears to actually need it.
+ */
+ if (isAssignmentIndirectionExpr(aref->refassgnexpr))
+ {
+ scratch->opcode = EEOP_ARRAYREF_OLD;
+ scratch->d.arrayref.state = arefstate;
+ ExprEvalPushStep(state, scratch);
+ }
+
+ /* ARRAYREF_OLD puts extracted value into prevvalue/prevnull */
+ save_innermost_caseval = state->innermost_caseval;
+ save_innermost_casenull = state->innermost_casenull;
+ state->innermost_caseval = &arefstate->prevvalue;
+ state->innermost_casenull = &arefstate->prevnull;
+
+ /* evaluate replacement value into replacevalue/replacenull */
+ ExecInitExprRec(aref->refassgnexpr, parent, state,
+ &arefstate->replacevalue, &arefstate->replacenull);
+
+ state->innermost_caseval = save_innermost_caseval;
+ state->innermost_casenull = save_innermost_casenull;
+
+ /* and perform the assignment */
+ scratch->opcode = EEOP_ARRAYREF_ASSIGN;
+ scratch->d.arrayref.state = arefstate;
+ ExprEvalPushStep(state, scratch);
+ }
+ else
+ {
+ /* array fetch is much simpler */
+ scratch->opcode = EEOP_ARRAYREF_FETCH;
+ scratch->d.arrayref.state = arefstate;
+ ExprEvalPushStep(state, scratch);
+ }
+
+ /* adjust jump targets */
+ foreach(lc, adjust_jumps)
+ {
+ ExprEvalStep *as = &state->steps[lfirst_int(lc)];
+
+ if (as->opcode == EEOP_ARRAYREF_SUBSCRIPT)
+ {
+ Assert(as->d.arrayref_subscript.jumpdone == -1);
+ as->d.arrayref_subscript.jumpdone = state->steps_len;
+ }
+ else
+ {
+ Assert(as->opcode == EEOP_JUMP_IF_NULL);
+ Assert(as->d.jump.jumpdone == -1);
+ as->d.jump.jumpdone = state->steps_len;
+ }
+ }
+}
+
+/*
+ * Helper for preparing ArrayRef expressions for evaluation: is expr a nested
+ * FieldStore or ArrayRef that might need the old element value passed down?
+ *
+ * (We could use this in FieldStore too, but in that case passing the old
+ * value is so cheap there's no need.)
+ */
+static bool
+isAssignmentIndirectionExpr(Expr *expr)
+{
+ if (expr == NULL)
+ return false; /* just paranoia */
+ if (IsA(expr, FieldStore))
+ {
+ FieldStore *fstore = (FieldStore *) expr;
+
+ if (fstore->arg && IsA(fstore->arg, CaseTestExpr))
+ return true;
+ }
+ else if (IsA(expr, ArrayRef))
+ {
+ ArrayRef *arrayRef = (ArrayRef *) expr;
+
+ if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr))
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Prepare evaluation of a CoerceToDomain expression.
+ */
+static void
+ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
+ PlanState *parent, ExprState *state,
+ Datum *resv, bool *resnull)
+{
+ ExprEvalStep scratch2;
+ DomainConstraintRef *constraint_ref;
+ Datum *domainval = NULL;
+ bool *domainnull = NULL;
+ Datum *save_innermost_domainval;
+ bool *save_innermost_domainnull;
+ ListCell *l;
+
+ scratch->d.domaincheck.resulttype = ctest->resulttype;
+ /* we'll allocate workspace only if needed */
+ scratch->d.domaincheck.checkvalue = NULL;
+ scratch->d.domaincheck.checknull = NULL;
+
+ /*
+ * Evaluate argument - it's fine to directly store it into resv/resnull,
+ * if there's constraint failures there'll be errors, otherwise it's what
+ * needs to be returned.
+ */
+ ExecInitExprRec(ctest->arg, parent, state, resv, resnull);
+
+ /*
+ * Note: if the argument is of varlena type, it could be a R/W expanded
+ * object. We want to return the R/W pointer as the final result, but we
+ * have to pass a R/O pointer as the value to be tested by any functions
+ * in check expressions. We don't bother to emit a MAKE_READONLY step
+ * unless there's actually at least one check expression, though. Until
+ * we've tested that, domainval/domainnull are NULL.
+ */
+
+ /*
+ * Collect the constraints associated with the domain.
+ *
+ * Note: before PG v10 we'd recheck the set of constraints during each
+ * evaluation of the expression. Now we bake them into the ExprState
+ * during executor initialization. That means we don't need typcache.c to
+ * provide compiled exprs.
+ */
+ constraint_ref = (DomainConstraintRef *)
+ palloc(sizeof(DomainConstraintRef));
+ InitDomainConstraintRef(ctest->resulttype,
+ constraint_ref,
+ CurrentMemoryContext,
+ false);
+
+ /*
+ * Compile code to check each domain constraint. NOTNULL constraints can
+ * just be applied on the resv/resnull value, but for CHECK constraints we
+ * need more pushups.
+ */
+ foreach(l, constraint_ref->constraints)
+ {
+ DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
+
+ scratch->d.domaincheck.constraintname = con->name;
+
+ switch (con->constrainttype)
+ {
+ case DOM_CONSTRAINT_NOTNULL:
+ scratch->opcode = EEOP_DOMAIN_NOTNULL;
+ ExprEvalPushStep(state, scratch);
+ break;
+ case DOM_CONSTRAINT_CHECK:
+ /* Allocate workspace for CHECK output if we didn't yet */
+ if (scratch->d.domaincheck.checkvalue == NULL)
+ {
+ scratch->d.domaincheck.checkvalue =
+ (Datum *) palloc(sizeof(Datum));
+ scratch->d.domaincheck.checknull =
+ (bool *) palloc(sizeof(bool));
+ }
+
+ /*
+ * If first time through, determine where CoerceToDomainValue
+ * nodes should read from.
+ */
+ if (domainval == NULL)
+ {
+ /*
+ * Since value might be read multiple times, force to R/O
+ * - but only if it could be an expanded datum.
+ */
+ if (get_typlen(ctest->resulttype) == -1)
+ {
+ /* Yes, so make output workspace for MAKE_READONLY */
+ domainval = (Datum *) palloc(sizeof(Datum));
+ domainnull = (bool *) palloc(sizeof(bool));
+
+ /* Emit MAKE_READONLY */
+ scratch2.opcode = EEOP_MAKE_READONLY;
+ scratch2.resvalue = domainval;
+ scratch2.resnull = domainnull;
+ scratch2.d.make_readonly.value = resv;
+ scratch2.d.make_readonly.isnull = resnull;
+ ExprEvalPushStep(state, &scratch2);
+ }
+ else
+ {
+ /* No, so it's fine to read from resv/resnull */
+ domainval = resv;
+ domainnull = resnull;
+ }
+ }
+
+ /*
+ * Set up value to be returned by CoerceToDomainValue nodes.
+ * We must save and restore innermost_domainval/null fields,
+ * in case this node is itself within a check expression for
+ * another domain.
+ */
+ save_innermost_domainval = state->innermost_domainval;
+ save_innermost_domainnull = state->innermost_domainnull;
+ state->innermost_domainval = domainval;
+ state->innermost_domainnull = domainnull;
+
+ /* evaluate check expression value */
+ ExecInitExprRec(con->check_expr, parent, state,
+ scratch->d.domaincheck.checkvalue,
+ scratch->d.domaincheck.checknull);
+
+ state->innermost_domainval = save_innermost_domainval;
+ state->innermost_domainnull = save_innermost_domainnull;
+
+ /* now test result */
+ scratch->opcode = EEOP_DOMAIN_CHECK;
+ ExprEvalPushStep(state, scratch);
+
+ break;
+ default:
+ elog(ERROR, "unrecognized constraint type: %d",
+ (int) con->constrainttype);
+ break;
+ }
+ }
+}
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * execExprInterp.c
+ * Interpreted evaluation of an expression step list.
+ *
+ * This file provides either a "direct threaded" (for gcc, clang and
+ * compatible) or a "switch threaded" (for all compilers) implementation of
+ * expression evaluation. The former is amongst the fastest known methods
+ * of interpreting programs without resorting to assembly level work, or
+ * just-in-time compilation, but it requires support for computed gotos.
+ * The latter is amongst the fastest approaches doable in standard C.
+ *
+ * In either case we use ExprEvalStep->opcode to dispatch to the code block
+ * within ExecInterpExpr() that implements the specific opcode type.
+ *
+ * Switch-threading uses a plain switch() statement to perform the
+ * dispatch. This has the advantages of being plain C and allowing the
+ * compiler to warn if implementation of a specific opcode has been forgotten.
+ * The disadvantage is that dispatches will, as commonly implemented by
+ * compilers, happen from a single location, requiring more jumps and causing
+ * bad branch prediction.
+ *
+ * In direct threading, we use gcc's label-as-values extension - also adopted
+ * by some other compilers - to replace ExprEvalStep->opcode with the address
+ * of the block implementing the instruction. Dispatch to the next instruction
+ * is done by a "computed goto". This allows for better branch prediction
+ * (as the jumps are happening from different locations) and fewer jumps
+ * (as no preparatory jump to a common dispatch location is needed).
+ *
+ * When using direct threading, ExecReadyInterpretedExpr will replace
+ * each step's opcode field with the address of the relevant code block and
+ * ExprState->flags will contain EEO_FLAG_DIRECT_THREADED to remember that
+ * that's been done.
+ *
+ * For very simple instructions the overhead of the full interpreter
+ * "startup", as minimal as it is, is noticeable. Therefore
+ * ExecReadyInterpretedExpr will choose to implement simple scalar Var
+ * and Const expressions using special fast-path routines (ExecJust*).
+ * Benchmarking shows anything more complex than those may as well use the
+ * "full interpreter".
+ *
+ * Complex or uncommon instructions are not implemented in-line in
+ * ExecInterpExpr(), rather we call out to a helper function appearing later
+ * in this file. For one reason, there'd not be a noticeable performance
+ * benefit, but more importantly those complex routines are intended to be
+ * shared between different expression evaluation approaches. For instance
+ * a JIT compiler would generate calls to them. (This is why they are
+ * exported rather than being "static" in this file.)
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/executor/execExprInterp.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "executor/execExpr.h"
+#include "executor/nodeSubplan.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parsetree.h"
+#include "pgstat.h"
+#include "utils/builtins.h"
+#include "utils/date.h"
+#include "utils/lsyscache.h"
+#include "utils/timestamp.h"
+#include "utils/typcache.h"
+#include "utils/xml.h"
+
+
+/*
+ * Use computed-goto-based opcode dispatch when computed gotos are available.
+ * But use a separate symbol so that it's easy to adjust locally in this file
+ * for development and testing.
+ */
+#ifdef HAVE_COMPUTED_GOTO
+#define EEO_USE_COMPUTED_GOTO
+#endif /* HAVE_COMPUTED_GOTO */
+
+/*
+ * Macros for opcode dispatch.
+ *
+ * EEO_SWITCH - just hides the switch if not in use.
+ * EEO_CASE - labels the implementation of named expression step type.
+ * EEO_DISPATCH - jump to the implementation of the step type for 'op'.
+ * EEO_OPCODE - compute opcode required by used expression evaluation method.
+ * EEO_NEXT - increment 'op' and jump to correct next step type.
+ * EEO_JUMP - jump to the specified step number within the current expression.
+ */
+#if defined(EEO_USE_COMPUTED_GOTO)
+
+/* to make dispatch_table accessible outside ExecInterpExpr() */
+static const void **dispatch_table = NULL;
+
+#define EEO_SWITCH()
+#define EEO_CASE(name) CASE_##name:
+#define EEO_DISPATCH() goto *((void *) op->opcode)
+#define EEO_OPCODE(opcode) ((intptr_t) dispatch_table[opcode])
+
+#else /* !EEO_USE_COMPUTED_GOTO */
+
+#define EEO_SWITCH() starteval: switch ((ExprEvalOp) op->opcode)
+#define EEO_CASE(name) case name:
+#define EEO_DISPATCH() goto starteval
+#define EEO_OPCODE(opcode) (opcode)
+
+#endif /* EEO_USE_COMPUTED_GOTO */
+
+#define EEO_NEXT() \
+ do { \
+ op++; \
+ EEO_DISPATCH(); \
+ } while (0)
+
+#define EEO_JUMP(stepno) \
+ do { \
+ op = &state->steps[stepno]; \
+ EEO_DISPATCH(); \
+ } while (0)
+
+
+static Datum ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull);
+static void ExecInitInterpreter(void);
+
+/* support functions */
+static void CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype);
+static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
+ TupleDesc *cache_field, ExprContext *econtext);
+static void ShutdownTupleDescRef(Datum arg);
+static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext, bool checkisnull);
+
+/* fast-path evaluation functions */
+static Datum ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull);
+static Datum ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull);
+
+
+/*
+ * Prepare ExprState for interpreted execution.
+ */
+void
+ExecReadyInterpretedExpr(ExprState *state)
+{
+ /* Ensure one-time interpreter setup has been done */
+ ExecInitInterpreter();
+
+ /* Simple validity checks on expression */
+ Assert(state->steps_len >= 1);
+ Assert(state->steps[state->steps_len - 1].opcode == EEOP_DONE);
+
+ /*
+ * Don't perform redundant initialization. This is unreachable in current
+ * cases, but might be hit if there's additional expression evaluation
+ * methods that rely on interpreted execution to work.
+ */
+ if (state->flags & EEO_FLAG_INTERPRETER_INITIALIZED)
+ return;
+
+ /* DIRECT_THREADED should not already be set */
+ Assert((state->flags & EEO_FLAG_DIRECT_THREADED) == 0);
+
+ /*
+ * There shouldn't be any errors before the expression is fully
+ * initialized, and even if so, it'd lead to the expression being
+ * abandoned. So we can set the flag now and save some code.
+ */
+ state->flags |= EEO_FLAG_INTERPRETER_INITIALIZED;
+
+ /*
+ * Select fast-path evalfuncs for very simple expressions. "Starting up"
+ * the full interpreter is a measurable overhead for these. Plain Vars
+ * and Const seem to be the only ones where the intrinsic cost is small
+ * enough that the overhead of ExecInterpExpr matters. For more complex
+ * expressions it's cheaper to use ExecInterpExpr always.
+ */
+ if (state->steps_len == 3)
+ {
+ ExprEvalOp step0 = state->steps[0].opcode;
+ ExprEvalOp step1 = state->steps[1].opcode;
+
+ if (step0 == EEOP_INNER_FETCHSOME &&
+ step1 == EEOP_INNER_VAR_FIRST)
+ {
+ state->evalfunc = ExecJustInnerVarFirst;
+ return;
+ }
+ else if (step0 == EEOP_OUTER_FETCHSOME &&
+ step1 == EEOP_OUTER_VAR_FIRST)
+ {
+ state->evalfunc = ExecJustOuterVarFirst;
+ return;
+ }
+ else if (step0 == EEOP_SCAN_FETCHSOME &&
+ step1 == EEOP_SCAN_VAR_FIRST)
+ {
+ state->evalfunc = ExecJustScanVarFirst;
+ return;
+ }
+ else if (step0 == EEOP_INNER_FETCHSOME &&
+ step1 == EEOP_ASSIGN_INNER_VAR)
+ {
+ state->evalfunc = ExecJustAssignInnerVar;
+ return;
+ }
+ else if (step0 == EEOP_OUTER_FETCHSOME &&
+ step1 == EEOP_ASSIGN_OUTER_VAR)
+ {
+ state->evalfunc = ExecJustAssignOuterVar;
+ return;
+ }
+ else if (step0 == EEOP_SCAN_FETCHSOME &&
+ step1 == EEOP_ASSIGN_SCAN_VAR)
+ {
+ state->evalfunc = ExecJustAssignScanVar;
+ return;
+ }
+ }
+ else if (state->steps_len == 2 &&
+ state->steps[0].opcode == EEOP_CONST)
+ {
+ state->evalfunc = ExecJustConst;
+ return;
+ }
+
+#if defined(EEO_USE_COMPUTED_GOTO)
+
+ /*
+ * In the direct-threaded implementation, replace each opcode with the
+ * address to jump to. (Use ExecEvalStepOp() to get back the opcode.)
+ */
+ {
+ int off;
+
+ for (off = 0; off < state->steps_len; off++)
+ {
+ ExprEvalStep *op = &state->steps[off];
+
+ op->opcode = EEO_OPCODE(op->opcode);
+ }
+
+ state->flags |= EEO_FLAG_DIRECT_THREADED;
+ }
+#endif /* EEO_USE_COMPUTED_GOTO */
+
+ state->evalfunc = ExecInterpExpr;
+}
+
+
+/*
+ * Evaluate expression identified by "state" in the execution context
+ * given by "econtext". *isnull is set to the is-null flag for the result,
+ * and the Datum value is the function result.
+ *
+ * As a special case, return the dispatch table's address if state is NULL.
+ * This is used by ExecInitInterpreter to set up the dispatch_table global.
+ * (Only applies when EEO_USE_COMPUTED_GOTO is defined.)
+ */
+static Datum
+ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op;
+ TupleTableSlot *resultslot;
+ TupleTableSlot *innerslot;
+ TupleTableSlot *outerslot;
+ TupleTableSlot *scanslot;
+
+ /*
+ * This array has to be in the same order as enum ExprEvalOp.
+ */
+#if defined(EEO_USE_COMPUTED_GOTO)
+ static const void *const dispatch_table[] = {
+ &&CASE_EEOP_DONE,
+ &&CASE_EEOP_INNER_FETCHSOME,
+ &&CASE_EEOP_OUTER_FETCHSOME,
+ &&CASE_EEOP_SCAN_FETCHSOME,
+ &&CASE_EEOP_INNER_VAR_FIRST,
+ &&CASE_EEOP_INNER_VAR,
+ &&CASE_EEOP_OUTER_VAR_FIRST,
+ &&CASE_EEOP_OUTER_VAR,
+ &&CASE_EEOP_SCAN_VAR_FIRST,
+ &&CASE_EEOP_SCAN_VAR,
+ &&CASE_EEOP_INNER_SYSVAR,
+ &&CASE_EEOP_OUTER_SYSVAR,
+ &&CASE_EEOP_SCAN_SYSVAR,
+ &&CASE_EEOP_WHOLEROW,
+ &&CASE_EEOP_ASSIGN_INNER_VAR,
+ &&CASE_EEOP_ASSIGN_OUTER_VAR,
+ &&CASE_EEOP_ASSIGN_SCAN_VAR,
+ &&CASE_EEOP_ASSIGN_TMP,
+ &&CASE_EEOP_ASSIGN_TMP_MAKE_RO,
+ &&CASE_EEOP_CONST,
+ &&CASE_EEOP_FUNCEXPR,
+ &&CASE_EEOP_FUNCEXPR_STRICT,
+ &&CASE_EEOP_FUNCEXPR_FUSAGE,
+ &&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE,
+ &&CASE_EEOP_BOOL_AND_STEP_FIRST,
+ &&CASE_EEOP_BOOL_AND_STEP,
+ &&CASE_EEOP_BOOL_AND_STEP_LAST,
+ &&CASE_EEOP_BOOL_OR_STEP_FIRST,
+ &&CASE_EEOP_BOOL_OR_STEP,
+ &&CASE_EEOP_BOOL_OR_STEP_LAST,
+ &&CASE_EEOP_BOOL_NOT_STEP,
+ &&CASE_EEOP_QUAL,
+ &&CASE_EEOP_JUMP,
+ &&CASE_EEOP_JUMP_IF_NULL,
+ &&CASE_EEOP_JUMP_IF_NOT_NULL,
+ &&CASE_EEOP_JUMP_IF_NOT_TRUE,
+ &&CASE_EEOP_NULLTEST_ISNULL,
+ &&CASE_EEOP_NULLTEST_ISNOTNULL,
+ &&CASE_EEOP_NULLTEST_ROWISNULL,
+ &&CASE_EEOP_NULLTEST_ROWISNOTNULL,
+ &&CASE_EEOP_BOOLTEST_IS_TRUE,
+ &&CASE_EEOP_BOOLTEST_IS_NOT_TRUE,
+ &&CASE_EEOP_BOOLTEST_IS_FALSE,
+ &&CASE_EEOP_BOOLTEST_IS_NOT_FALSE,
+ &&CASE_EEOP_PARAM_EXEC,
+ &&CASE_EEOP_PARAM_EXTERN,
+ &&CASE_EEOP_CASE_TESTVAL,
+ &&CASE_EEOP_MAKE_READONLY,
+ &&CASE_EEOP_IOCOERCE,
+ &&CASE_EEOP_DISTINCT,
+ &&CASE_EEOP_NULLIF,
+ &&CASE_EEOP_SQLVALUEFUNCTION,
+ &&CASE_EEOP_CURRENTOFEXPR,
+ &&CASE_EEOP_ARRAYEXPR,
+ &&CASE_EEOP_ARRAYCOERCE,
+ &&CASE_EEOP_ROW,
+ &&CASE_EEOP_ROWCOMPARE_STEP,
+ &&CASE_EEOP_ROWCOMPARE_FINAL,
+ &&CASE_EEOP_MINMAX,
+ &&CASE_EEOP_FIELDSELECT,
+ &&CASE_EEOP_FIELDSTORE_DEFORM,
+ &&CASE_EEOP_FIELDSTORE_FORM,
+ &&CASE_EEOP_ARRAYREF_SUBSCRIPT,
+ &&CASE_EEOP_ARRAYREF_OLD,
+ &&CASE_EEOP_ARRAYREF_ASSIGN,
+ &&CASE_EEOP_ARRAYREF_FETCH,
+ &&CASE_EEOP_DOMAIN_TESTVAL,
+ &&CASE_EEOP_DOMAIN_NOTNULL,
+ &&CASE_EEOP_DOMAIN_CHECK,
+ &&CASE_EEOP_CONVERT_ROWTYPE,
+ &&CASE_EEOP_SCALARARRAYOP,
+ &&CASE_EEOP_XMLEXPR,
+ &&CASE_EEOP_AGGREF,
+ &&CASE_EEOP_GROUPING_FUNC,
+ &&CASE_EEOP_WINDOW_FUNC,
+ &&CASE_EEOP_SUBPLAN,
+ &&CASE_EEOP_ALTERNATIVE_SUBPLAN,
+ &&CASE_EEOP_LAST
+ };
+
+ StaticAssertStmt(EEOP_LAST + 1 == lengthof(dispatch_table),
+ "dispatch_table out of whack with ExprEvalOp");
+
+ if (unlikely(state == NULL))
+ return PointerGetDatum(dispatch_table);
+#else
+ Assert(state != NULL);
+#endif /* EEO_USE_COMPUTED_GOTO */
+
+ /* setup state */
+ op = state->steps;
+ resultslot = state->resultslot;
+ innerslot = econtext->ecxt_innertuple;
+ outerslot = econtext->ecxt_outertuple;
+ scanslot = econtext->ecxt_scantuple;
+
+#if defined(EEO_USE_COMPUTED_GOTO)
+ EEO_DISPATCH();
+#endif
+
+ EEO_SWITCH()
+ {
+ EEO_CASE(EEOP_DONE)
+ {
+ goto out;
+ }
+
+ EEO_CASE(EEOP_INNER_FETCHSOME)
+ {
+ /* XXX: worthwhile to check tts_nvalid inline first? */
+ slot_getsomeattrs(innerslot, op->d.fetch.last_var);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_OUTER_FETCHSOME)
+ {
+ slot_getsomeattrs(outerslot, op->d.fetch.last_var);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_SCAN_FETCHSOME)
+ {
+ slot_getsomeattrs(scanslot, op->d.fetch.last_var);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_INNER_VAR_FIRST)
+ {
+ int attnum = op->d.var.attnum;
+
+ /*
+ * First time through, check whether attribute matches Var. Might
+ * not be ok anymore, due to schema changes.
+ */
+ CheckVarSlotCompatibility(innerslot, attnum + 1, op->d.var.vartype);
+
+ /* Skip that check on subsequent evaluations */
+ op->opcode = EEO_OPCODE(EEOP_INNER_VAR);
+
+ /* FALL THROUGH to EEOP_INNER_VAR */
+ }
+
+ EEO_CASE(EEOP_INNER_VAR)
+ {
+ int attnum = op->d.var.attnum;
+
+ /*
+ * Since we already extracted all referenced columns from the
+ * tuple with a FETCHSOME step, we can just grab the value
+ * directly out of the slot's decomposed-data arrays. But let's
+ * have an Assert to check that that did happen.
+ */
+ Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
+ *op->resvalue = innerslot->tts_values[attnum];
+ *op->resnull = innerslot->tts_isnull[attnum];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_OUTER_VAR_FIRST)
+ {
+ int attnum = op->d.var.attnum;
+
+ /* See EEOP_INNER_VAR_FIRST comments */
+
+ CheckVarSlotCompatibility(outerslot, attnum + 1, op->d.var.vartype);
+ op->opcode = EEO_OPCODE(EEOP_OUTER_VAR);
+
+ /* FALL THROUGH to EEOP_OUTER_VAR */
+ }
+
+ EEO_CASE(EEOP_OUTER_VAR)
+ {
+ int attnum = op->d.var.attnum;
+
+ /* See EEOP_INNER_VAR comments */
+
+ Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
+ *op->resvalue = outerslot->tts_values[attnum];
+ *op->resnull = outerslot->tts_isnull[attnum];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_SCAN_VAR_FIRST)
+ {
+ int attnum = op->d.var.attnum;
+
+ /* See EEOP_INNER_VAR_FIRST comments */
+
+ CheckVarSlotCompatibility(scanslot, attnum + 1, op->d.var.vartype);
+ op->opcode = EEO_OPCODE(EEOP_SCAN_VAR);
+
+ /* FALL THROUGH to EEOP_SCAN_VAR */
+ }
+
+ EEO_CASE(EEOP_SCAN_VAR)
+ {
+ int attnum = op->d.var.attnum;
+
+ /* See EEOP_INNER_VAR comments */
+
+ Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
+ *op->resvalue = scanslot->tts_values[attnum];
+ *op->resnull = scanslot->tts_isnull[attnum];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_INNER_SYSVAR)
+ {
+ int attnum = op->d.var.attnum;
+
+ /* these asserts must match defenses in slot_getattr */
+ Assert(innerslot->tts_tuple != NULL);
+ Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
+ /* heap_getsysattr has sufficient defenses against bad attnums */
+
+ *op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum,
+ innerslot->tts_tupleDescriptor,
+ op->resnull);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_OUTER_SYSVAR)
+ {
+ int attnum = op->d.var.attnum;
+
+ /* these asserts must match defenses in slot_getattr */
+ Assert(outerslot->tts_tuple != NULL);
+ Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
+
+ /* heap_getsysattr has sufficient defenses against bad attnums */
+ *op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum,
+ outerslot->tts_tupleDescriptor,
+ op->resnull);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_SCAN_SYSVAR)
+ {
+ int attnum = op->d.var.attnum;
+
+ /* these asserts must match defenses in slot_getattr */
+ Assert(scanslot->tts_tuple != NULL);
+ Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
+ /* heap_getsysattr has sufficient defenses against bad attnums */
+
+ *op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum,
+ scanslot->tts_tupleDescriptor,
+ op->resnull);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_WHOLEROW)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalWholeRowVar(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ASSIGN_INNER_VAR)
+ {
+ int resultnum = op->d.assign_var.resultnum;
+ int attnum = op->d.assign_var.attnum;
+
+ /*
+ * We do not need CheckVarSlotCompatibility here; that was taken
+ * care of at compilation time. But see EEOP_INNER_VAR comments.
+ */
+ Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
+ resultslot->tts_values[resultnum] = innerslot->tts_values[attnum];
+ resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ASSIGN_OUTER_VAR)
+ {
+ int resultnum = op->d.assign_var.resultnum;
+ int attnum = op->d.assign_var.attnum;
+
+ /*
+ * We do not need CheckVarSlotCompatibility here; that was taken
+ * care of at compilation time. But see EEOP_INNER_VAR comments.
+ */
+ Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
+ resultslot->tts_values[resultnum] = outerslot->tts_values[attnum];
+ resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ASSIGN_SCAN_VAR)
+ {
+ int resultnum = op->d.assign_var.resultnum;
+ int attnum = op->d.assign_var.attnum;
+
+ /*
+ * We do not need CheckVarSlotCompatibility here; that was taken
+ * care of at compilation time. But see EEOP_INNER_VAR comments.
+ */
+ Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
+ resultslot->tts_values[resultnum] = scanslot->tts_values[attnum];
+ resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ASSIGN_TMP)
+ {
+ int resultnum = op->d.assign_tmp.resultnum;
+
+ resultslot->tts_values[resultnum] = state->resvalue;
+ resultslot->tts_isnull[resultnum] = state->resnull;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ASSIGN_TMP_MAKE_RO)
+ {
+ int resultnum = op->d.assign_tmp.resultnum;
+
+ resultslot->tts_isnull[resultnum] = state->resnull;
+ if (!resultslot->tts_isnull[resultnum])
+ resultslot->tts_values[resultnum] =
+ MakeExpandedObjectReadOnlyInternal(state->resvalue);
+ else
+ resultslot->tts_values[resultnum] = state->resvalue;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_CONST)
+ {
+ *op->resnull = op->d.constval.isnull;
+ *op->resvalue = op->d.constval.value;
+
+ EEO_NEXT();
+ }
+
+ /*
+ * Function-call implementations. Arguments have previously been
+ * evaluated directly into fcinfo->args.
+ *
+ * As both STRICT checks and function-usage are noticeable performance
+ * wise, and function calls are a very hot-path (they also back
+ * operators!), it's worth having so many separate opcodes.
+ */
+ EEO_CASE(EEOP_FUNCEXPR)
+ {
+ FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+ fcinfo->isnull = false;
+ *op->resvalue = (op->d.func.fn_addr) (fcinfo);
+ *op->resnull = fcinfo->isnull;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_FUNCEXPR_STRICT)
+ {
+ FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+ bool *argnull = fcinfo->argnull;
+ int argno;
+
+ /* strict function, so check for NULL args */
+ for (argno = 0; argno < op->d.func.nargs; argno++)
+ {
+ if (argnull[argno])
+ {
+ *op->resnull = true;
+ goto strictfail;
+ }
+ }
+ fcinfo->isnull = false;
+ *op->resvalue = (op->d.func.fn_addr) (fcinfo);
+ *op->resnull = fcinfo->isnull;
+
+ strictfail:
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_FUNCEXPR_FUSAGE)
+ {
+ FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+ PgStat_FunctionCallUsage fcusage;
+
+ pgstat_init_function_usage(fcinfo, &fcusage);
+
+ fcinfo->isnull = false;
+ *op->resvalue = (op->d.func.fn_addr) (fcinfo);
+ *op->resnull = fcinfo->isnull;
+
+ pgstat_end_function_usage(&fcusage, true);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_FUNCEXPR_STRICT_FUSAGE)
+ {
+ FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+ PgStat_FunctionCallUsage fcusage;
+ bool *argnull = fcinfo->argnull;
+ int argno;
+
+ /* strict function, so check for NULL args */
+ for (argno = 0; argno < op->d.func.nargs; argno++)
+ {
+ if (argnull[argno])
+ {
+ *op->resnull = true;
+ goto strictfail_fusage;
+ }
+ }
+
+ pgstat_init_function_usage(fcinfo, &fcusage);
+
+ fcinfo->isnull = false;
+ *op->resvalue = (op->d.func.fn_addr) (fcinfo);
+ *op->resnull = fcinfo->isnull;
+
+ pgstat_end_function_usage(&fcusage, true);
+
+ strictfail_fusage:
+ EEO_NEXT();
+ }
+
+ /*
+ * If any of its clauses is FALSE, an AND's result is FALSE regardless
+ * of the states of the rest of the clauses, so we can stop evaluating
+ * and return FALSE immediately. If none are FALSE and one or more is
+ * NULL, we return NULL; otherwise we return TRUE. This makes sense
+ * when you interpret NULL as "don't know": perhaps one of the "don't
+ * knows" would have been FALSE if we'd known its value. Only when
+ * all the inputs are known to be TRUE can we state confidently that
+ * the AND's result is TRUE.
+ */
+ EEO_CASE(EEOP_BOOL_AND_STEP_FIRST)
+ {
+ *op->d.boolexpr.anynull = false;
+
+ /*
+ * EEOP_BOOL_AND_STEP_FIRST resets anynull, otherwise it's the
+ * same as EEOP_BOOL_AND_STEP - so fall through to that.
+ */
+
+ /* FALL THROUGH */
+ }
+
+ EEO_CASE(EEOP_BOOL_AND_STEP)
+ {
+ if (*op->resnull)
+ {
+ *op->d.boolexpr.anynull = true;
+ }
+ else if (!DatumGetBool(*op->resvalue))
+ {
+ /* result is already set to FALSE, need not change it */
+ /* bail out early */
+ EEO_JUMP(op->d.boolexpr.jumpdone);
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_BOOL_AND_STEP_LAST)
+ {
+ if (*op->resnull)
+ {
+ /* result is already set to NULL, need not change it */
+ }
+ else if (!DatumGetBool(*op->resvalue))
+ {
+ /* result is already set to FALSE, need not change it */
+
+ /*
+ * No point jumping early to jumpdone - would be same target
+ * (as this is the last argument to the AND expression),
+ * except more expensive.
+ */
+ }
+ else if (*op->d.boolexpr.anynull)
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+ }
+ else
+ {
+ /* result is already set to TRUE, need not change it */
+ }
+
+ EEO_NEXT();
+ }
+
+ /*
+ * If any of its clauses is TRUE, an OR's result is TRUE regardless of
+ * the states of the rest of the clauses, so we can stop evaluating
+ * and return TRUE immediately. If none are TRUE and one or more is
+ * NULL, we return NULL; otherwise we return FALSE. This makes sense
+ * when you interpret NULL as "don't know": perhaps one of the "don't
+ * knows" would have been TRUE if we'd known its value. Only when all
+ * the inputs are known to be FALSE can we state confidently that the
+ * OR's result is FALSE.
+ */
+ EEO_CASE(EEOP_BOOL_OR_STEP_FIRST)
+ {
+ *op->d.boolexpr.anynull = false;
+
+ /*
+ * EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same
+ * as EEOP_BOOL_OR_STEP - so fall through to that.
+ */
+
+ /* FALL THROUGH */
+ }
+
+ EEO_CASE(EEOP_BOOL_OR_STEP)
+ {
+ if (*op->resnull)
+ {
+ *op->d.boolexpr.anynull = true;
+ }
+ else if (DatumGetBool(*op->resvalue))
+ {
+ /* result is already set to TRUE, need not change it */
+ /* bail out early */
+ EEO_JUMP(op->d.boolexpr.jumpdone);
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_BOOL_OR_STEP_LAST)
+ {
+ if (*op->resnull)
+ {
+ /* result is already set to NULL, need not change it */
+ }
+ else if (DatumGetBool(*op->resvalue))
+ {
+ /* result is already set to TRUE, need not change it */
+
+ /*
+ * No point jumping to jumpdone - would be same target (as
+ * this is the last argument to the AND expression), except
+ * more expensive.
+ */
+ }
+ else if (*op->d.boolexpr.anynull)
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+ }
+ else
+ {
+ /* result is already set to FALSE, need not change it */
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_BOOL_NOT_STEP)
+ {
+ /*
+ * Evaluation of 'not' is simple... if expr is false, then return
+ * 'true' and vice versa. It's safe to do this even on a
+ * nominally null value, so we ignore resnull; that means that
+ * NULL in produces NULL out, which is what we want.
+ */
+ *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_QUAL)
+ {
+ /* simplified version of BOOL_AND_STEP for use by ExecQual() */
+
+ /* If argument (also result) is false or null ... */
+ if (*op->resnull ||
+ !DatumGetBool(*op->resvalue))
+ {
+ /* ... bail out early, returning FALSE */
+ *op->resnull = false;
+ *op->resvalue = BoolGetDatum(false);
+ EEO_JUMP(op->d.qualexpr.jumpdone);
+ }
+
+ /*
+ * Otherwise, leave the TRUE value in place, in case this is the
+ * last qual. Then, TRUE is the correct answer.
+ */
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_JUMP)
+ {
+ /* Unconditionally jump to target step */
+ EEO_JUMP(op->d.jump.jumpdone);
+ }
+
+ EEO_CASE(EEOP_JUMP_IF_NULL)
+ {
+ /* Transfer control if current result is null */
+ if (*op->resnull)
+ EEO_JUMP(op->d.jump.jumpdone);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_JUMP_IF_NOT_NULL)
+ {
+ /* Transfer control if current result is non-null */
+ if (!*op->resnull)
+ EEO_JUMP(op->d.jump.jumpdone);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_JUMP_IF_NOT_TRUE)
+ {
+ /* Transfer control if current result is null or false */
+ if (*op->resnull || !DatumGetBool(*op->resvalue))
+ EEO_JUMP(op->d.jump.jumpdone);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_NULLTEST_ISNULL)
+ {
+ *op->resvalue = BoolGetDatum(*op->resnull);
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_NULLTEST_ISNOTNULL)
+ {
+ *op->resvalue = BoolGetDatum(!*op->resnull);
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_NULLTEST_ROWISNULL)
+ {
+ /* out of line implementation: too large */
+ ExecEvalRowNull(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_NULLTEST_ROWISNOTNULL)
+ {
+ /* out of line implementation: too large */
+ ExecEvalRowNotNull(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ /* BooleanTest implementations for all booltesttypes */
+
+ EEO_CASE(EEOP_BOOLTEST_IS_TRUE)
+ {
+ if (*op->resnull)
+ *op->resvalue = BoolGetDatum(false);
+ else
+ *op->resvalue = *op->resvalue;
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_BOOLTEST_IS_NOT_TRUE)
+ {
+ if (*op->resnull)
+ *op->resvalue = BoolGetDatum(true);
+ else
+ *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_BOOLTEST_IS_FALSE)
+ {
+ if (*op->resnull)
+ *op->resvalue = BoolGetDatum(false);
+ else
+ *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue));
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_BOOLTEST_IS_NOT_FALSE)
+ {
+ if (*op->resnull)
+ *op->resvalue = BoolGetDatum(true);
+ else
+ *op->resvalue = *op->resvalue;
+ *op->resnull = false;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_PARAM_EXEC)
+ {
+ /* out of line implementation: too large */
+ ExecEvalParamExec(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_PARAM_EXTERN)
+ {
+ /* out of line implementation: too large */
+ ExecEvalParamExtern(state, op, econtext);
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_CASE_TESTVAL)
+ {
+ /*
+ * Normally upper parts of the expression tree have setup the
+ * values to be returned here, but some parts of the system
+ * currently misuse {caseValue,domainValue}_{datum,isNull} to set
+ * run-time data. So if no values have been set-up, use
+ * ExprContext's. This isn't pretty, but also not *that* ugly,
+ * and this is unlikely to be performance sensitive enough to
+ * worry about an extra branch.
+ */
+ if (op->d.casetest.value)
+ {
+ *op->resvalue = *op->d.casetest.value;
+ *op->resnull = *op->d.casetest.isnull;
+ }
+ else
+ {
+ *op->resvalue = econtext->caseValue_datum;
+ *op->resnull = econtext->caseValue_isNull;
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_DOMAIN_TESTVAL)
+ {
+ /*
+ * See EEOP_CASE_TESTVAL comment.
+ */
+ if (op->d.casetest.value)
+ {
+ *op->resvalue = *op->d.casetest.value;
+ *op->resnull = *op->d.casetest.isnull;
+ }
+ else
+ {
+ *op->resvalue = econtext->domainValue_datum;
+ *op->resnull = econtext->domainValue_isNull;
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_MAKE_READONLY)
+ {
+ /*
+ * Force a varlena value that might be read multiple times to R/O
+ */
+ if (!*op->d.make_readonly.isnull)
+ *op->resvalue =
+ MakeExpandedObjectReadOnlyInternal(*op->d.make_readonly.value);
+ *op->resnull = *op->d.make_readonly.isnull;
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_IOCOERCE)
+ {
+ /*
+ * Evaluate a CoerceViaIO node. This can be quite a hot path, so
+ * inline as much work as possible. The source value is in our
+ * result variable.
+ */
+ char *str;
+
+ /* call output function (similar to OutputFunctionCall) */
+ if (*op->resnull)
+ {
+ /* output functions are not called on nulls */
+ str = NULL;
+ }
+ else
+ {
+ FunctionCallInfo fcinfo_out;
+
+ fcinfo_out = op->d.iocoerce.fcinfo_data_out;
+ fcinfo_out->arg[0] = *op->resvalue;
+ fcinfo_out->argnull[0] = false;
+
+ fcinfo_out->isnull = false;
+ str = DatumGetCString(FunctionCallInvoke(fcinfo_out));
+
+ /* OutputFunctionCall assumes result isn't null */
+ Assert(!fcinfo_out->isnull);
+ }
+
+ /* call input function (similar to InputFunctionCall) */
+ if (!op->d.iocoerce.finfo_in->fn_strict || str != NULL)
+ {
+ FunctionCallInfo fcinfo_in;
+
+ fcinfo_in = op->d.iocoerce.fcinfo_data_in;
+ fcinfo_in->arg[0] = PointerGetDatum(str);
+ fcinfo_in->argnull[0] = *op->resnull;
+ /* second and third arguments are already set up */
+
+ fcinfo_in->isnull = false;
+ *op->resvalue = FunctionCallInvoke(fcinfo_in);
+
+ /* Should get null result if and only if str is NULL */
+ if (str == NULL)
+ {
+ Assert(*op->resnull);
+ Assert(fcinfo_in->isnull);
+ }
+ else
+ {
+ Assert(!*op->resnull);
+ Assert(!fcinfo_in->isnull);
+ }
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_DISTINCT)
+ {
+ /*
+ * IS DISTINCT FROM must evaluate arguments (already done into
+ * fcinfo->arg/argnull) to determine whether they are NULL; if
+ * either is NULL then the result is determined. If neither is
+ * NULL, then proceed to evaluate the comparison function, which
+ * is just the type's standard equality operator. We need not
+ * care whether that function is strict. Because the handling of
+ * nulls is different, we can't just reuse EEOP_FUNCEXPR.
+ */
+ FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+ /* check function arguments for NULLness */
+ if (fcinfo->argnull[0] && fcinfo->argnull[1])
+ {
+ /* Both NULL? Then is not distinct... */
+ *op->resvalue = BoolGetDatum(false);
+ *op->resnull = false;
+ }
+ else if (fcinfo->argnull[0] || fcinfo->argnull[1])
+ {
+ /* Only one is NULL? Then is distinct... */
+ *op->resvalue = BoolGetDatum(true);
+ *op->resnull = false;
+ }
+ else
+ {
+ /* Neither null, so apply the equality function */
+ Datum eqresult;
+
+ fcinfo->isnull = false;
+ eqresult = (op->d.func.fn_addr) (fcinfo);
+ /* Must invert result of "="; safe to do even if null */
+ *op->resvalue = BoolGetDatum(!DatumGetBool(eqresult));
+ *op->resnull = fcinfo->isnull;
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_NULLIF)
+ {
+ /*
+ * The arguments are already evaluated into fcinfo->arg/argnull.
+ */
+ FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+
+ /* if either argument is NULL they can't be equal */
+ if (!fcinfo->argnull[0] && !fcinfo->argnull[1])
+ {
+ Datum result;
+
+ fcinfo->isnull = false;
+ result = (op->d.func.fn_addr) (fcinfo);
+
+ /* if the arguments are equal return null */
+ if (!fcinfo->isnull && DatumGetBool(result))
+ {
+ *op->resvalue = (Datum) 0;
+ *op->resnull = true;
+
+ EEO_NEXT();
+ }
+ }
+
+ /* Arguments aren't equal, so return the first one */
+ *op->resvalue = fcinfo->arg[0];
+ *op->resnull = fcinfo->argnull[0];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_SQLVALUEFUNCTION)
+ {
+ /*
+ * Doesn't seem worthwhile to have an inline implementation
+ * efficiency-wise.
+ */
+ ExecEvalSQLValueFunction(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_CURRENTOFEXPR)
+ {
+ /* error invocation uses space, and shouldn't ever occur */
+ ExecEvalCurrentOfExpr(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ARRAYEXPR)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalArrayExpr(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ARRAYCOERCE)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalArrayCoerce(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ROW)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalRow(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ROWCOMPARE_STEP)
+ {
+ FunctionCallInfo fcinfo = op->d.rowcompare_step.fcinfo_data;
+
+ /* force NULL result if strict fn and NULL input */
+ if (op->d.rowcompare_step.finfo->fn_strict &&
+ (fcinfo->argnull[0] || fcinfo->argnull[1]))
+ {
+ *op->resnull = true;
+ EEO_JUMP(op->d.rowcompare_step.jumpnull);
+ }
+
+ /* Apply comparison function */
+ fcinfo->isnull = false;
+ *op->resvalue = (op->d.rowcompare_step.fn_addr) (fcinfo);
+
+ /* force NULL result if NULL function result */
+ if (fcinfo->isnull)
+ {
+ *op->resnull = true;
+ EEO_JUMP(op->d.rowcompare_step.jumpnull);
+ }
+ *op->resnull = false;
+
+ /* If unequal, no need to compare remaining columns */
+ if (DatumGetInt32(*op->resvalue) != 0)
+ {
+ EEO_JUMP(op->d.rowcompare_step.jumpdone);
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ROWCOMPARE_FINAL)
+ {
+ int32 cmpresult = DatumGetInt32(*op->resvalue);
+ RowCompareType rctype = op->d.rowcompare_final.rctype;
+
+ *op->resnull = false;
+ switch (rctype)
+ {
+ /* EQ and NE cases aren't allowed here */
+ case ROWCOMPARE_LT:
+ *op->resvalue = BoolGetDatum(cmpresult < 0);
+ break;
+ case ROWCOMPARE_LE:
+ *op->resvalue = BoolGetDatum(cmpresult <= 0);
+ break;
+ case ROWCOMPARE_GE:
+ *op->resvalue = BoolGetDatum(cmpresult >= 0);
+ break;
+ case ROWCOMPARE_GT:
+ *op->resvalue = BoolGetDatum(cmpresult > 0);
+ break;
+ default:
+ Assert(false);
+ break;
+ }
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_MINMAX)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalMinMax(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_FIELDSELECT)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalFieldSelect(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_FIELDSTORE_DEFORM)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalFieldStoreDeForm(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_FIELDSTORE_FORM)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalFieldStoreForm(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ARRAYREF_SUBSCRIPT)
+ {
+ /* Process an array subscript */
+
+ /* too complex for an inline implementation */
+ if (ExecEvalArrayRefSubscript(state, op))
+ {
+ EEO_NEXT();
+ }
+ else
+ {
+ /* Subscript is null, short-circuit ArrayRef to NULL */
+ EEO_JUMP(op->d.arrayref_subscript.jumpdone);
+ }
+ }
+
+ EEO_CASE(EEOP_ARRAYREF_OLD)
+ {
+ /*
+ * Fetch the old value in an arrayref assignment, in case it's
+ * referenced (via a CaseTestExpr) inside the assignment
+ * expression.
+ */
+
+ /* too complex for an inline implementation */
+ ExecEvalArrayRefOld(state, op);
+
+ EEO_NEXT();
+ }
+
+ /*
+ * Perform ArrayRef assignment
+ */
+ EEO_CASE(EEOP_ARRAYREF_ASSIGN)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalArrayRefAssign(state, op);
+
+ EEO_NEXT();
+ }
+
+ /*
+ * Fetch subset of an array.
+ */
+ EEO_CASE(EEOP_ARRAYREF_FETCH)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalArrayRefFetch(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_CONVERT_ROWTYPE)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalConvertRowtype(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_SCALARARRAYOP)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalScalarArrayOp(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_DOMAIN_NOTNULL)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalConstraintNotNull(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_DOMAIN_CHECK)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalConstraintCheck(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_XMLEXPR)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalXmlExpr(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_AGGREF)
+ {
+ /*
+ * Returns a Datum whose value is the precomputed aggregate value
+ * found in the given expression context.
+ */
+ AggrefExprState *aggref = op->d.aggref.astate;
+
+ Assert(econtext->ecxt_aggvalues != NULL);
+
+ *op->resvalue = econtext->ecxt_aggvalues[aggref->aggno];
+ *op->resnull = econtext->ecxt_aggnulls[aggref->aggno];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_GROUPING_FUNC)
+ {
+ /* too complex/uncommon for an inline implementation */
+ ExecEvalGroupingFunc(state, op);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_WINDOW_FUNC)
+ {
+ /*
+ * Like Aggref, just return a precomputed value from the econtext.
+ */
+ WindowFuncExprState *wfunc = op->d.window_func.wfstate;
+
+ Assert(econtext->ecxt_aggvalues != NULL);
+
+ *op->resvalue = econtext->ecxt_aggvalues[wfunc->wfuncno];
+ *op->resnull = econtext->ecxt_aggnulls[wfunc->wfuncno];
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_SUBPLAN)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalSubPlan(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_ALTERNATIVE_SUBPLAN)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalAlternativeSubPlan(state, op, econtext);
+
+ EEO_NEXT();
+ }
+
+ EEO_CASE(EEOP_LAST)
+ {
+ /* unreachable */
+ Assert(false);
+ goto out;
+ }
+ }
+
+out:
+ *isnull = state->resnull;
+ return state->resvalue;
+}
+
+/*
+ * Check whether a user attribute in a slot can be referenced by a Var
+ * expression. This should succeed unless there have been schema changes
+ * since the expression tree has been created.
+ */
+static void
+CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype)
+{
+ /*
+ * What we have to check for here is the possibility of an attribute
+ * having been changed in type since the plan tree was created. Ideally
+ * the plan will get invalidated and not re-used, but just in case, we
+ * keep these defenses. Fortunately it's sufficient to check once on the
+ * first time through.
+ *
+ * System attributes don't require checking since their types never
+ * change.
+ *
+ * Note: we allow a reference to a dropped attribute. slot_getattr will
+ * force a NULL result in such cases.
+ *
+ * Note: ideally we'd check typmod as well as typid, but that seems
+ * impractical at the moment: in many cases the tupdesc will have been
+ * generated by ExecTypeFromTL(), and that can't guarantee to generate an
+ * accurate typmod in all cases, because some expression node types don't
+ * carry typmod.
+ */
+ if (attnum > 0)
+ {
+ TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
+ Form_pg_attribute attr;
+
+ if (attnum > slot_tupdesc->natts) /* should never happen */
+ elog(ERROR, "attribute number %d exceeds number of columns %d",
+ attnum, slot_tupdesc->natts);
+
+ attr = slot_tupdesc->attrs[attnum - 1];
+
+ /* can't check type if dropped, since atttypid is probably 0 */
+ if (!attr->attisdropped)
+ {
+ if (vartype != attr->atttypid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("attribute %d has wrong type", attnum),
+ errdetail("Table has type %s, but query expects %s.",
+ format_type_be(attr->atttypid),
+ format_type_be(vartype))));
+ }
+ }
+}
+
+/*
+ * get_cached_rowtype: utility function to lookup a rowtype tupdesc
+ *
+ * type_id, typmod: identity of the rowtype
+ * cache_field: where to cache the TupleDesc pointer in expression state node
+ * (field must be initialized to NULL)
+ * econtext: expression context we are executing in
+ *
+ * NOTE: because the shutdown callback will be called during plan rescan,
+ * must be prepared to re-do this during any node execution; cannot call
+ * just once during expression initialization.
+ */
+static TupleDesc
+get_cached_rowtype(Oid type_id, int32 typmod,
+ TupleDesc *cache_field, ExprContext *econtext)
+{
+ TupleDesc tupDesc = *cache_field;
+
+ /* Do lookup if no cached value or if requested type changed */
+ if (tupDesc == NULL ||
+ type_id != tupDesc->tdtypeid ||
+ typmod != tupDesc->tdtypmod)
+ {
+ tupDesc = lookup_rowtype_tupdesc(type_id, typmod);
+
+ if (*cache_field)
+ {
+ /* Release old tupdesc; but callback is already registered */
+ ReleaseTupleDesc(*cache_field);
+ }
+ else
+ {
+ /* Need to register shutdown callback to release tupdesc */
+ RegisterExprContextCallback(econtext,
+ ShutdownTupleDescRef,
+ PointerGetDatum(cache_field));
+ }
+ *cache_field = tupDesc;
+ }
+ return tupDesc;
+}
+
+/*
+ * Callback function to release a tupdesc refcount at econtext shutdown
+ */
+static void
+ShutdownTupleDescRef(Datum arg)
+{
+ TupleDesc *cache_field = (TupleDesc *) DatumGetPointer(arg);
+
+ if (*cache_field)
+ ReleaseTupleDesc(*cache_field);
+ *cache_field = NULL;
+}
+
+/*
+ * Fast-path functions, for very simple expressions
+ */
+
+/* Simple reference to inner Var, first time through */
+static Datum
+ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.var.attnum + 1;
+ TupleTableSlot *slot = econtext->ecxt_innertuple;
+
+ /* See ExecInterpExpr()'s comments for EEOP_INNER_VAR_FIRST */
+
+ CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
+ op->opcode = EEOP_INNER_VAR; /* just for cleanliness */
+ state->evalfunc = ExecJustInnerVar;
+
+ /*
+ * Since we use slot_getattr(), we don't need to implement the FETCHSOME
+ * step explicitly, and we also needn't Assert that the attnum is in range
+ * --- slot_getattr() will take care of any problems.
+ */
+ return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to inner Var */
+static Datum
+ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.var.attnum + 1;
+ TupleTableSlot *slot = econtext->ecxt_innertuple;
+
+ /* See comments in ExecJustInnerVarFirst */
+ return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to outer Var, first time through */
+static Datum
+ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.var.attnum + 1;
+ TupleTableSlot *slot = econtext->ecxt_outertuple;
+
+ CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
+ op->opcode = EEOP_OUTER_VAR; /* just for cleanliness */
+ state->evalfunc = ExecJustOuterVar;
+
+ /* See comments in ExecJustInnerVarFirst */
+ return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to outer Var */
+static Datum
+ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.var.attnum + 1;
+ TupleTableSlot *slot = econtext->ecxt_outertuple;
+
+ /* See comments in ExecJustInnerVarFirst */
+ return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to scan Var, first time through */
+static Datum
+ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.var.attnum + 1;
+ TupleTableSlot *slot = econtext->ecxt_scantuple;
+
+ CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype);
+ op->opcode = EEOP_SCAN_VAR; /* just for cleanliness */
+ state->evalfunc = ExecJustScanVar;
+
+ /* See comments in ExecJustInnerVarFirst */
+ return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple reference to scan Var */
+static Datum
+ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.var.attnum + 1;
+ TupleTableSlot *slot = econtext->ecxt_scantuple;
+
+ /* See comments in ExecJustInnerVarFirst */
+ return slot_getattr(slot, attnum, isnull);
+}
+
+/* Simple Const expression */
+static Datum
+ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[0];
+
+ *isnull = op->d.constval.isnull;
+ return op->d.constval.value;
+}
+
+/* Evaluate inner Var and assign to appropriate column of result tuple */
+static Datum
+ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.assign_var.attnum + 1;
+ int resultnum = op->d.assign_var.resultnum;
+ TupleTableSlot *inslot = econtext->ecxt_innertuple;
+ TupleTableSlot *outslot = state->resultslot;
+
+ /*
+ * We do not need CheckVarSlotCompatibility here; that was taken care of
+ * at compilation time.
+ *
+ * Since we use slot_getattr(), we don't need to implement the FETCHSOME
+ * step explicitly, and we also needn't Assert that the attnum is in range
+ * --- slot_getattr() will take care of any problems.
+ */
+ outslot->tts_values[resultnum] =
+ slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+ return 0;
+}
+
+/* Evaluate outer Var and assign to appropriate column of result tuple */
+static Datum
+ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.assign_var.attnum + 1;
+ int resultnum = op->d.assign_var.resultnum;
+ TupleTableSlot *inslot = econtext->ecxt_outertuple;
+ TupleTableSlot *outslot = state->resultslot;
+
+ /* See comments in ExecJustAssignInnerVar */
+ outslot->tts_values[resultnum] =
+ slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+ return 0;
+}
+
+/* Evaluate scan Var and assign to appropriate column of result tuple */
+static Datum
+ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
+{
+ ExprEvalStep *op = &state->steps[1];
+ int attnum = op->d.assign_var.attnum + 1;
+ int resultnum = op->d.assign_var.resultnum;
+ TupleTableSlot *inslot = econtext->ecxt_scantuple;
+ TupleTableSlot *outslot = state->resultslot;
+
+ /* See comments in ExecJustAssignInnerVar */
+ outslot->tts_values[resultnum] =
+ slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+ return 0;
+}
+
+
+/*
+ * Do one-time initialization of interpretation machinery.
+ */
+static void
+ExecInitInterpreter(void)
+{
+#if defined(EEO_USE_COMPUTED_GOTO)
+ /* Set up externally-visible pointer to dispatch table */
+ if (dispatch_table == NULL)
+ dispatch_table = (const void **)
+ DatumGetPointer(ExecInterpExpr(NULL, NULL, NULL));
+#endif
+}
+
+/*
+ * Function to return the opcode of an expression step.
+ *
+ * When direct-threading is in use, ExprState->opcode isn't easily
+ * decipherable. This function returns the appropriate enum member.
+ *
+ * This currently is only supposed to be used in paths that aren't critical
+ * performance-wise. If that changes, we could add an inverse dispatch_table
+ * that's sorted on the address, so a binary search can be performed.
+ */
+ExprEvalOp
+ExecEvalStepOp(ExprState *state, ExprEvalStep *op)
+{
+#if defined(EEO_USE_COMPUTED_GOTO)
+ if (state->flags & EEO_FLAG_DIRECT_THREADED)
+ {
+ int i;
+
+ for (i = 0; i < EEOP_LAST; i++)
+ {
+ if ((void *) op->opcode == dispatch_table[i])
+ {
+ return (ExprEvalOp) i;
+ }
+ }
+ elog(ERROR, "unknown opcode");
+ }
+#endif
+ return (ExprEvalOp) op->opcode;
+}
+
+
+/*
+ * Out-of-line helper functions for complex instructions.
+ */
+
+/*
+ * Evaluate a PARAM_EXEC parameter.
+ *
+ * PARAM_EXEC params (internal executor parameters) are stored in the
+ * ecxt_param_exec_vals array, and can be accessed by array index.
+ */
+void
+ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ ParamExecData *prm;
+
+ prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]);
+ if (unlikely(prm->execPlan != NULL))
+ {
+ /* Parameter not evaluated yet, so go do it */
+ ExecSetParamPlan(prm->execPlan, econtext);
+ /* ExecSetParamPlan should have processed this param... */
+ Assert(prm->execPlan == NULL);
+ }
+ *op->resvalue = prm->value;
+ *op->resnull = prm->isnull;
+}
+
+/*
+ * Evaluate a PARAM_EXTERN parameter.
+ *
+ * PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
+ */
+void
+ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ ParamListInfo paramInfo = econtext->ecxt_param_list_info;
+ int paramId = op->d.param.paramid;
+
+ if (likely(paramInfo &&
+ paramId > 0 && paramId <= paramInfo->numParams))
+ {
+ ParamExternData *prm = ¶mInfo->params[paramId - 1];
+
+ /* give hook a chance in case parameter is dynamic */
+ if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
+ (*paramInfo->paramFetch) (paramInfo, paramId);
+
+ if (likely(OidIsValid(prm->ptype)))
+ {
+ /* safety check in case hook did something unexpected */
+ if (unlikely(prm->ptype != op->d.param.paramtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
+ paramId,
+ format_type_be(prm->ptype),
+ format_type_be(op->d.param.paramtype))));
+ *op->resvalue = prm->value;
+ *op->resnull = prm->isnull;
+ return;
+ }
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("no value found for parameter %d", paramId)));
+}
+
+/*
+ * Evaluate a SQLValueFunction expression.
+ */
+void
+ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
+{
+ SQLValueFunction *svf = op->d.sqlvaluefunction.svf;
+ FunctionCallInfoData fcinfo;
+
+ *op->resnull = false;
+
+ /*
+ * Note: current_schema() can return NULL. current_user() etc currently
+ * cannot, but might as well code those cases the same way for safety.
+ */
+ switch (svf->op)
+ {
+ case SVFOP_CURRENT_DATE:
+ *op->resvalue = DateADTGetDatum(GetSQLCurrentDate());
+ break;
+ case SVFOP_CURRENT_TIME:
+ case SVFOP_CURRENT_TIME_N:
+ *op->resvalue = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod));
+ break;
+ case SVFOP_CURRENT_TIMESTAMP:
+ case SVFOP_CURRENT_TIMESTAMP_N:
+ *op->resvalue = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod));
+ break;
+ case SVFOP_LOCALTIME:
+ case SVFOP_LOCALTIME_N:
+ *op->resvalue = TimeADTGetDatum(GetSQLLocalTime(svf->typmod));
+ break;
+ case SVFOP_LOCALTIMESTAMP:
+ case SVFOP_LOCALTIMESTAMP_N:
+ *op->resvalue = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod));
+ break;
+ case SVFOP_CURRENT_ROLE:
+ case SVFOP_CURRENT_USER:
+ case SVFOP_USER:
+ InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+ *op->resvalue = current_user(&fcinfo);
+ *op->resnull = fcinfo.isnull;
+ break;
+ case SVFOP_SESSION_USER:
+ InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+ *op->resvalue = session_user(&fcinfo);
+ *op->resnull = fcinfo.isnull;
+ break;
+ case SVFOP_CURRENT_CATALOG:
+ InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+ *op->resvalue = current_database(&fcinfo);
+ *op->resnull = fcinfo.isnull;
+ break;
+ case SVFOP_CURRENT_SCHEMA:
+ InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+ *op->resvalue = current_schema(&fcinfo);
+ *op->resnull = fcinfo.isnull;
+ break;
+ }
+}
+
+/*
+ * Raise error if a CURRENT OF expression is evaluated.
+ *
+ * The planner should convert CURRENT OF into a TidScan qualification, or some
+ * other special handling in a ForeignScan node. So we have to be able to do
+ * ExecInitExpr on a CurrentOfExpr, but we shouldn't ever actually execute it.
+ * If we get here, we suppose we must be dealing with CURRENT OF on a foreign
+ * table whose FDW doesn't handle it, and complain accordingly.
+ */
+void
+ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("WHERE CURRENT OF is not supported for this table type")));
+}
+
+/*
+ * Evaluate NullTest / IS NULL for rows.
+ */
+void
+ExecEvalRowNull(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ ExecEvalRowNullInt(state, op, econtext, true);
+}
+
+/*
+ * Evaluate NullTest / IS NOT NULL for rows.
+ */
+void
+ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ ExecEvalRowNullInt(state, op, econtext, false);
+}
+
+/* Common code for IS [NOT] NULL on a row value */
+static void
+ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext, bool checkisnull)
+{
+ Datum value = *op->resvalue;
+ bool isnull = *op->resnull;
+ HeapTupleHeader tuple;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupDesc;
+ HeapTupleData tmptup;
+ int att;
+
+ *op->resnull = false;
+
+ /* NULL row variables are treated just as NULL scalar columns */
+ if (isnull)
+ {
+ *op->resvalue = BoolGetDatum(checkisnull);
+ return;
+ }
+
+ /*
+ * The SQL standard defines IS [NOT] NULL for a non-null rowtype argument
+ * as:
+ *
+ * "R IS NULL" is true if every field is the null value.
+ *
+ * "R IS NOT NULL" is true if no field is the null value.
+ *
+ * This definition is (apparently intentionally) not recursive; so our
+ * tests on the fields are primitive attisnull tests, not recursive checks
+ * to see if they are all-nulls or no-nulls rowtypes.
+ *
+ * The standard does not consider the possibility of zero-field rows, but
+ * here we consider them to vacuously satisfy both predicates.
+ */
+
+ tuple = DatumGetHeapTupleHeader(value);
+
+ tupType = HeapTupleHeaderGetTypeId(tuple);
+ tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+
+ /* Lookup tupdesc if first time through or if type changes */
+ tupDesc = get_cached_rowtype(tupType, tupTypmod,
+ &op->d.nulltest_row.argdesc,
+ econtext);
+
+ /*
+ * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader.
+ */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+ tmptup.t_data = tuple;
+
+ for (att = 1; att <= tupDesc->natts; att++)
+ {
+ /* ignore dropped columns */
+ if (tupDesc->attrs[att - 1]->attisdropped)
+ continue;
+ if (heap_attisnull(&tmptup, att))
+ {
+ /* null field disproves IS NOT NULL */
+ if (!checkisnull)
+ {
+ *op->resvalue = BoolGetDatum(false);
+ return;
+ }
+ }
+ else
+ {
+ /* non-null field disproves IS NULL */
+ if (checkisnull)
+ {
+ *op->resvalue = BoolGetDatum(false);
+ return;
+ }
+ }
+ }
+
+ *op->resvalue = BoolGetDatum(true);
+}
+
+/*
+ * Evaluate an ARRAY[] expression.
+ *
+ * The individual array elements (or subarrays) have already been evaluated
+ * into op->d.arrayexpr.elemvalues[]/elemnulls[].
+ */
+void
+ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op)
+{
+ ArrayType *result;
+ Oid element_type = op->d.arrayexpr.elemtype;
+ int nelems = op->d.arrayexpr.nelems;
+ int ndims = 0;
+ int dims[MAXDIM];
+ int lbs[MAXDIM];
+
+ /* Set non-null as default */
+ *op->resnull = false;
+
+ if (!op->d.arrayexpr.multidims)
+ {
+ /* Elements are presumably of scalar type */
+ Datum *dvalues = op->d.arrayexpr.elemvalues;
+ bool *dnulls = op->d.arrayexpr.elemnulls;
+
+ /* Shouldn't happen here, but if length is 0, return empty array */
+ if (nelems == 0)
+ {
+ *op->resvalue =
+ PointerGetDatum(construct_empty_array(element_type));
+ return;
+ }
+
+ /* setup for 1-D array of the given length */
+ ndims = 1;
+ dims[0] = nelems;
+ lbs[0] = 1;
+
+ result = construct_md_array(dvalues, dnulls, ndims, dims, lbs,
+ element_type,
+ op->d.arrayexpr.elemlength,
+ op->d.arrayexpr.elembyval,
+ op->d.arrayexpr.elemalign);
+ }
+ else
+ {
+ /* Must be nested array expressions */
+ int nbytes = 0;
+ int nitems = 0;
+ int outer_nelems = 0;
+ int elem_ndims = 0;
+ int *elem_dims = NULL;
+ int *elem_lbs = NULL;
+ bool firstone = true;
+ bool havenulls = false;
+ bool haveempty = false;
+ char **subdata;
+ bits8 **subbitmaps;
+ int *subbytes;
+ int *subnitems;
+ int32 dataoffset;
+ char *dat;
+ int iitem;
+ int elemoff;
+ int i;
+
+ subdata = (char **) palloc(nelems * sizeof(char *));
+ subbitmaps = (bits8 **) palloc(nelems * sizeof(bits8 *));
+ subbytes = (int *) palloc(nelems * sizeof(int));
+ subnitems = (int *) palloc(nelems * sizeof(int));
+
+ /* loop through and get data area from each element */
+ for (elemoff = 0; elemoff < nelems; elemoff++)
+ {
+ Datum arraydatum;
+ bool eisnull;
+ ArrayType *array;
+ int this_ndims;
+
+ arraydatum = op->d.arrayexpr.elemvalues[elemoff];
+ eisnull = op->d.arrayexpr.elemnulls[elemoff];
+
+ /* temporarily ignore null subarrays */
+ if (eisnull)
+ {
+ haveempty = true;
+ continue;
+ }
+
+ array = DatumGetArrayTypeP(arraydatum);
+
+ /* run-time double-check on element type */
+ if (element_type != ARR_ELEMTYPE(array))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot merge incompatible arrays"),
+ errdetail("Array with element type %s cannot be "
+ "included in ARRAY construct with element type %s.",
+ format_type_be(ARR_ELEMTYPE(array)),
+ format_type_be(element_type))));
+
+ this_ndims = ARR_NDIM(array);
+ /* temporarily ignore zero-dimensional subarrays */
+ if (this_ndims <= 0)
+ {
+ haveempty = true;
+ continue;
+ }
+
+ if (firstone)
+ {
+ /* Get sub-array details from first member */
+ elem_ndims = this_ndims;
+ ndims = elem_ndims + 1;
+ if (ndims <= 0 || ndims > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds " \
+ "the maximum allowed (%d)", ndims, MAXDIM)));
+
+ elem_dims = (int *) palloc(elem_ndims * sizeof(int));
+ memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int));
+ elem_lbs = (int *) palloc(elem_ndims * sizeof(int));
+ memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int));
+
+ firstone = false;
+ }
+ else
+ {
+ /* Check other sub-arrays are compatible */
+ if (elem_ndims != this_ndims ||
+ memcmp(elem_dims, ARR_DIMS(array),
+ elem_ndims * sizeof(int)) != 0 ||
+ memcmp(elem_lbs, ARR_LBOUND(array),
+ elem_ndims * sizeof(int)) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("multidimensional arrays must have array "
+ "expressions with matching dimensions")));
+ }
+
+ subdata[outer_nelems] = ARR_DATA_PTR(array);
+ subbitmaps[outer_nelems] = ARR_NULLBITMAP(array);
+ subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array);
+ nbytes += subbytes[outer_nelems];
+ subnitems[outer_nelems] = ArrayGetNItems(this_ndims,
+ ARR_DIMS(array));
+ nitems += subnitems[outer_nelems];
+ havenulls |= ARR_HASNULL(array);
+ outer_nelems++;
+ }
+
+ /*
+ * If all items were null or empty arrays, return an empty array;
+ * otherwise, if some were and some weren't, raise error. (Note: we
+ * must special-case this somehow to avoid trying to generate a 1-D
+ * array formed from empty arrays. It's not ideal...)
+ */
+ if (haveempty)
+ {
+ if (ndims == 0) /* didn't find any nonempty array */
+ {
+ *op->resvalue = PointerGetDatum(construct_empty_array(element_type));
+ return;
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("multidimensional arrays must have array "
+ "expressions with matching dimensions")));
+ }
+
+ /* setup for multi-D array */
+ dims[0] = outer_nelems;
+ lbs[0] = 1;
+ for (i = 1; i < ndims; i++)
+ {
+ dims[i] = elem_dims[i - 1];
+ lbs[i] = elem_lbs[i - 1];
+ }
+
+ if (havenulls)
+ {
+ dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
+ nbytes += dataoffset;
+ }
+ else
+ {
+ dataoffset = 0; /* marker for no null bitmap */
+ nbytes += ARR_OVERHEAD_NONULLS(ndims);
+ }
+
+ result = (ArrayType *) palloc(nbytes);
+ SET_VARSIZE(result, nbytes);
+ result->ndim = ndims;
+ result->dataoffset = dataoffset;
+ result->elemtype = element_type;
+ memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
+ memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
+
+ dat = ARR_DATA_PTR(result);
+ iitem = 0;
+ for (i = 0; i < outer_nelems; i++)
+ {
+ memcpy(dat, subdata[i], subbytes[i]);
+ dat += subbytes[i];
+ if (havenulls)
+ array_bitmap_copy(ARR_NULLBITMAP(result), iitem,
+ subbitmaps[i], 0,
+ subnitems[i]);
+ iitem += subnitems[i];
+ }
+ }
+
+ *op->resvalue = PointerGetDatum(result);
+}
+
+/*
+ * Evaluate an ArrayCoerceExpr expression.
+ *
+ * Source array is in step's result variable.
+ */
+void
+ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op)
+{
+ ArrayCoerceExpr *acoerce = op->d.arraycoerce.coerceexpr;
+ Datum arraydatum;
+ FunctionCallInfoData locfcinfo;
+
+ /* NULL array -> NULL result */
+ if (*op->resnull)
+ return;
+
+ arraydatum = *op->resvalue;
+
+ /*
+ * If it's binary-compatible, modify the element type in the array header,
+ * but otherwise leave the array as we received it.
+ */
+ if (!OidIsValid(acoerce->elemfuncid))
+ {
+ /* Detoast input array if necessary, and copy in any case */
+ ArrayType *array = DatumGetArrayTypePCopy(arraydatum);
+
+ ARR_ELEMTYPE(array) = op->d.arraycoerce.resultelemtype;
+ *op->resvalue = PointerGetDatum(array);
+ return;
+ }
+
+ /*
+ * Use array_map to apply the function to each array element.
+ *
+ * We pass on the desttypmod and isExplicit flags whether or not the
+ * function wants them.
+ *
+ * Note: coercion functions are assumed to not use collation.
+ */
+ InitFunctionCallInfoData(locfcinfo, op->d.arraycoerce.elemfunc, 3,
+ InvalidOid, NULL, NULL);
+ locfcinfo.arg[0] = arraydatum;
+ locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
+ locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit);
+ locfcinfo.argnull[0] = false;
+ locfcinfo.argnull[1] = false;
+ locfcinfo.argnull[2] = false;
+
+ *op->resvalue = array_map(&locfcinfo, op->d.arraycoerce.resultelemtype,
+ op->d.arraycoerce.amstate);
+}
+
+/*
+ * Evaluate a ROW() expression.
+ *
+ * The individual columns have already been evaluated into
+ * op->d.row.elemvalues[]/elemnulls[].
+ */
+void
+ExecEvalRow(ExprState *state, ExprEvalStep *op)
+{
+ HeapTuple tuple;
+
+ /* build tuple from evaluated field values */
+ tuple = heap_form_tuple(op->d.row.tupdesc,
+ op->d.row.elemvalues,
+ op->d.row.elemnulls);
+
+ *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resnull = false;
+}
+
+/*
+ * Evaluate GREATEST() or LEAST() expression (note this is *not* MIN()/MAX()).
+ *
+ * All of the to-be-compared expressions have already been evaluated into
+ * op->d.minmax.values[]/nulls[].
+ */
+void
+ExecEvalMinMax(ExprState *state, ExprEvalStep *op)
+{
+ Datum *values = op->d.minmax.values;
+ bool *nulls = op->d.minmax.nulls;
+ FunctionCallInfo fcinfo = op->d.minmax.fcinfo_data;
+ MinMaxOp operator = op->d.minmax.op;
+ int off;
+
+ /* set at initialization */
+ Assert(fcinfo->argnull[0] == false);
+ Assert(fcinfo->argnull[1] == false);
+
+ /* default to null result */
+ *op->resnull = true;
+
+ for (off = 0; off < op->d.minmax.nelems; off++)
+ {
+ /* ignore NULL inputs */
+ if (nulls[off])
+ continue;
+
+ if (*op->resnull)
+ {
+ /* first nonnull input, adopt value */
+ *op->resvalue = values[off];
+ *op->resnull = false;
+ }
+ else
+ {
+ int cmpresult;
+
+ /* apply comparison function */
+ fcinfo->arg[0] = *op->resvalue;
+ fcinfo->arg[1] = values[off];
+
+ fcinfo->isnull = false;
+ cmpresult = DatumGetInt32(FunctionCallInvoke(fcinfo));
+ if (fcinfo->isnull) /* probably should not happen */
+ continue;
+
+ if (cmpresult > 0 && operator == IS_LEAST)
+ *op->resvalue = values[off];
+ else if (cmpresult < 0 && operator == IS_GREATEST)
+ *op->resvalue = values[off];
+ }
+ }
+}
+
+/*
+ * Evaluate a FieldSelect node.
+ *
+ * Source record is in step's result variable.
+ */
+void
+ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ AttrNumber fieldnum = op->d.fieldselect.fieldnum;
+ Datum tupDatum;
+ HeapTupleHeader tuple;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupDesc;
+ Form_pg_attribute attr;
+ HeapTupleData tmptup;
+
+ /* NULL record -> NULL result */
+ if (*op->resnull)
+ return;
+
+ /* Get the composite datum and extract its type fields */
+ tupDatum = *op->resvalue;
+ tuple = DatumGetHeapTupleHeader(tupDatum);
+
+ tupType = HeapTupleHeaderGetTypeId(tuple);
+ tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+
+ /* Lookup tupdesc if first time through or if type changes */
+ tupDesc = get_cached_rowtype(tupType, tupTypmod,
+ &op->d.fieldselect.argdesc,
+ econtext);
+
+ /*
+ * Find field's attr record. Note we don't support system columns here: a
+ * datum tuple doesn't have valid values for most of the interesting
+ * system columns anyway.
+ */
+ if (fieldnum <= 0) /* should never happen */
+ elog(ERROR, "unsupported reference to system column %d in FieldSelect",
+ fieldnum);
+ if (fieldnum > tupDesc->natts) /* should never happen */
+ elog(ERROR, "attribute number %d exceeds number of columns %d",
+ fieldnum, tupDesc->natts);
+ attr = tupDesc->attrs[fieldnum - 1];
+
+ /* Check for dropped column, and force a NULL result if so */
+ if (attr->attisdropped)
+ {
+ *op->resnull = true;
+ return;
+ }
+
+ /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
+ /* As in CheckVarSlotCompatibility, we should but can't check typmod */
+ if (op->d.fieldselect.resulttype != attr->atttypid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("attribute %d has wrong type", fieldnum),
+ errdetail("Table has type %s, but query expects %s.",
+ format_type_be(attr->atttypid),
+ format_type_be(op->d.fieldselect.resulttype))));
+
+ /* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+ tmptup.t_data = tuple;
+
+ /* extract the field */
+ *op->resvalue = heap_getattr(&tmptup,
+ fieldnum,
+ tupDesc,
+ op->resnull);
+}
+
+/*
+ * Deform source tuple, filling in the step's values/nulls arrays, before
+ * evaluating individual new values as part of a FieldStore expression.
+ * Subsequent steps will overwrite individual elements of the values/nulls
+ * arrays with the new field values, and then FIELDSTORE_FORM will build the
+ * new tuple value.
+ *
+ * Source record is in step's result variable.
+ */
+void
+ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ TupleDesc tupDesc;
+
+ /* Lookup tupdesc if first time through or after rescan */
+ tupDesc = get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1,
+ op->d.fieldstore.argdesc, econtext);
+
+ /* Check that current tupdesc doesn't have more fields than we allocated */
+ if (unlikely(tupDesc->natts > op->d.fieldstore.ncolumns))
+ elog(ERROR, "too many columns in composite type %u",
+ op->d.fieldstore.fstore->resulttype);
+
+ if (*op->resnull)
+ {
+ /* Convert null input tuple into an all-nulls row */
+ memset(op->d.fieldstore.nulls, true,
+ op->d.fieldstore.ncolumns * sizeof(bool));
+ }
+ else
+ {
+ /*
+ * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We
+ * set all the fields in the struct just in case.
+ */
+ Datum tupDatum = *op->resvalue;
+ HeapTupleHeader tuphdr;
+ HeapTupleData tmptup;
+
+ tuphdr = DatumGetHeapTupleHeader(tupDatum);
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
+ ItemPointerSetInvalid(&(tmptup.t_self));
+ tmptup.t_tableOid = InvalidOid;
+ tmptup.t_data = tuphdr;
+
+ heap_deform_tuple(&tmptup, tupDesc,
+ op->d.fieldstore.values,
+ op->d.fieldstore.nulls);
+ }
+}
+
+/*
+ * Compute the new composite datum after each individual field value of a
+ * FieldStore expression has been evaluated.
+ */
+void
+ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ HeapTuple tuple;
+
+ /* argdesc should already be valid from the DeForm step */
+ tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
+ op->d.fieldstore.values,
+ op->d.fieldstore.nulls);
+
+ *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resnull = false;
+}
+
+/*
+ * Process a subscript in an ArrayRef expression.
+ *
+ * If subscript is NULL, throw error in assignment case, or in fetch case
+ * set result to NULL and return false (instructing caller to skip the rest
+ * of the ArrayRef sequence).
+ *
+ * Subscript expression result is in subscriptvalue/subscriptnull.
+ * On success, integer subscript value has been saved in upperindex[] or
+ * lowerindex[] for use later.
+ */
+bool
+ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op)
+{
+ ArrayRefState *arefstate = op->d.arrayref_subscript.state;
+ int *indexes;
+ int off;
+
+ /* If any index expr yields NULL, result is NULL or error */
+ if (arefstate->subscriptnull)
+ {
+ if (arefstate->isassignment)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("array subscript in assignment must not be null")));
+ *op->resnull = true;
+ return false;
+ }
+
+ /* Convert datum to int, save in appropriate place */
+ if (op->d.arrayref_subscript.isupper)
+ indexes = arefstate->upperindex;
+ else
+ indexes = arefstate->lowerindex;
+ off = op->d.arrayref_subscript.off;
+
+ indexes[off] = DatumGetInt32(arefstate->subscriptvalue);
+
+ return true;
+}
+
+/*
+ * Evaluate ArrayRef fetch.
+ *
+ * Source array is in step's result variable.
+ */
+void
+ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op)
+{
+ ArrayRefState *arefstate = op->d.arrayref.state;
+
+ /* Should not get here if source array (or any subscript) is null */
+ Assert(!(*op->resnull));
+
+ if (arefstate->numlower == 0)
+ {
+ /* Scalar case */
+ *op->resvalue = array_get_element(*op->resvalue,
+ arefstate->numupper,
+ arefstate->upperindex,
+ arefstate->refattrlength,
+ arefstate->refelemlength,
+ arefstate->refelembyval,
+ arefstate->refelemalign,
+ op->resnull);
+ }
+ else
+ {
+ /* Slice case */
+ *op->resvalue = array_get_slice(*op->resvalue,
+ arefstate->numupper,
+ arefstate->upperindex,
+ arefstate->lowerindex,
+ arefstate->upperprovided,
+ arefstate->lowerprovided,
+ arefstate->refattrlength,
+ arefstate->refelemlength,
+ arefstate->refelembyval,
+ arefstate->refelemalign);
+ }
+}
+
+/*
+ * Compute old array element/slice value for an ArrayRef assignment
+ * expression. Will only be generated if the new-value subexpression
+ * contains ArrayRef or FieldStore. The value is stored into the
+ * ArrayRefState's prevvalue/prevnull fields.
+ */
+void
+ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op)
+{
+ ArrayRefState *arefstate = op->d.arrayref.state;
+
+ if (*op->resnull)
+ {
+ /* whole array is null, so any element or slice is too */
+ arefstate->prevvalue = (Datum) 0;
+ arefstate->prevnull = true;
+ }
+ else if (arefstate->numlower == 0)
+ {
+ /* Scalar case */
+ arefstate->prevvalue = array_get_element(*op->resvalue,
+ arefstate->numupper,
+ arefstate->upperindex,
+ arefstate->refattrlength,
+ arefstate->refelemlength,
+ arefstate->refelembyval,
+ arefstate->refelemalign,
+ &arefstate->prevnull);
+ }
+ else
+ {
+ /* Slice case */
+ /* this is currently unreachable */
+ arefstate->prevvalue = array_get_slice(*op->resvalue,
+ arefstate->numupper,
+ arefstate->upperindex,
+ arefstate->lowerindex,
+ arefstate->upperprovided,
+ arefstate->lowerprovided,
+ arefstate->refattrlength,
+ arefstate->refelemlength,
+ arefstate->refelembyval,
+ arefstate->refelemalign);
+ arefstate->prevnull = false;
+ }
+}
+
+/*
+ * Evaluate ArrayRef assignment.
+ *
+ * Input array (possibly null) is in result area, replacement value is in
+ * ArrayRefState's replacevalue/replacenull.
+ */
+void
+ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op)
+{
+ ArrayRefState *arefstate = op->d.arrayref.state;
+
+ /*
+ * For an assignment to a fixed-length array type, both the original array
+ * and the value to be assigned into it must be non-NULL, else we punt and
+ * return the original array.
+ */
+ if (arefstate->refattrlength > 0) /* fixed-length array? */
+ {
+ if (*op->resnull || arefstate->replacenull)
+ return;
+ }
+
+ /*
+ * For assignment to varlena arrays, we handle a NULL original array by
+ * substituting an empty (zero-dimensional) array; insertion of the new
+ * element will result in a singleton array value. It does not matter
+ * whether the new element is NULL.
+ */
+ if (*op->resnull)
+ {
+ *op->resvalue = PointerGetDatum(construct_empty_array(arefstate->refelemtype));
+ *op->resnull = false;
+ }
+
+ if (arefstate->numlower == 0)
+ {
+ /* Scalar case */
+ *op->resvalue = array_set_element(*op->resvalue,
+ arefstate->numupper,
+ arefstate->upperindex,
+ arefstate->replacevalue,
+ arefstate->replacenull,
+ arefstate->refattrlength,
+ arefstate->refelemlength,
+ arefstate->refelembyval,
+ arefstate->refelemalign);
+ }
+ else
+ {
+ /* Slice case */
+ *op->resvalue = array_set_slice(*op->resvalue,
+ arefstate->numupper,
+ arefstate->upperindex,
+ arefstate->lowerindex,
+ arefstate->upperprovided,
+ arefstate->lowerprovided,
+ arefstate->replacevalue,
+ arefstate->replacenull,
+ arefstate->refattrlength,
+ arefstate->refelemlength,
+ arefstate->refelembyval,
+ arefstate->refelemalign);
+ }
+}
+
+/*
+ * Evaluate a rowtype coercion operation.
+ * This may require rearranging field positions.
+ *
+ * Source record is in step's result variable.
+ */
+void
+ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ ConvertRowtypeExpr *convert = op->d.convert_rowtype.convert;
+ HeapTuple result;
+ Datum tupDatum;
+ HeapTupleHeader tuple;
+ HeapTupleData tmptup;
+ TupleDesc indesc,
+ outdesc;
+
+ /* NULL in -> NULL out */
+ if (*op->resnull)
+ return;
+
+ tupDatum = *op->resvalue;
+ tuple = DatumGetHeapTupleHeader(tupDatum);
+
+ /* Lookup tupdescs if first time through or after rescan */
+ if (op->d.convert_rowtype.indesc == NULL)
+ {
+ get_cached_rowtype(exprType((Node *) convert->arg), -1,
+ &op->d.convert_rowtype.indesc,
+ econtext);
+ op->d.convert_rowtype.initialized = false;
+ }
+ if (op->d.convert_rowtype.outdesc == NULL)
+ {
+ get_cached_rowtype(convert->resulttype, -1,
+ &op->d.convert_rowtype.outdesc,
+ econtext);
+ op->d.convert_rowtype.initialized = false;
+ }
+
+ indesc = op->d.convert_rowtype.indesc;
+ outdesc = op->d.convert_rowtype.outdesc;
+
+ /*
+ * We used to be able to assert that incoming tuples are marked with
+ * exactly the rowtype of indesc. However, now that ExecEvalWholeRowVar
+ * might change the tuples' marking to plain RECORD due to inserting
+ * aliases, we can only make this weak test:
+ */
+ Assert(HeapTupleHeaderGetTypeId(tuple) == indesc->tdtypeid ||
+ HeapTupleHeaderGetTypeId(tuple) == RECORDOID);
+
+ /* if first time through, initialize conversion map */
+ if (!op->d.convert_rowtype.initialized)
+ {
+ MemoryContext old_cxt;
+
+ /* allocate map in long-lived memory context */
+ old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+
+ /* prepare map from old to new attribute numbers */
+ op->d.convert_rowtype.map =
+ convert_tuples_by_name(indesc, outdesc,
+ gettext_noop("could not convert row type"));
+ op->d.convert_rowtype.initialized = true;
+
+ MemoryContextSwitchTo(old_cxt);
+ }
+
+ /*
+ * No-op if no conversion needed (not clear this can happen here).
+ */
+ if (op->d.convert_rowtype.map == NULL)
+ return;
+
+ /*
+ * do_convert_tuple needs a HeapTuple not a bare HeapTupleHeader.
+ */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+ tmptup.t_data = tuple;
+
+ result = do_convert_tuple(&tmptup, op->d.convert_rowtype.map);
+
+ *op->resvalue = HeapTupleGetDatum(result);
+}
+
+/*
+ * Evaluate "scalar op ANY/ALL (array)".
+ *
+ * Source array is in our result area, scalar arg is already evaluated into
+ * fcinfo->arg[0]/argnull[0].
+ *
+ * The operator always yields boolean, and we combine the results across all
+ * array elements using OR and AND (for ANY and ALL respectively). Of course
+ * we short-circuit as soon as the result is known.
+ */
+void
+ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
+{
+ FunctionCallInfo fcinfo = op->d.scalararrayop.fcinfo_data;
+ bool useOr = op->d.scalararrayop.useOr;
+ bool strictfunc = op->d.scalararrayop.finfo->fn_strict;
+ ArrayType *arr;
+ int nitems;
+ Datum result;
+ bool resultnull;
+ int i;
+ int16 typlen;
+ bool typbyval;
+ char typalign;
+ char *s;
+ bits8 *bitmap;
+ int bitmask;
+
+ /*
+ * If the array is NULL then we return NULL --- it's not very meaningful
+ * to do anything else, even if the operator isn't strict.
+ */
+ if (*op->resnull)
+ return;
+
+ /* Else okay to fetch and detoast the array */
+ arr = DatumGetArrayTypeP(*op->resvalue);
+
+ /*
+ * If the array is empty, we return either FALSE or TRUE per the useOr
+ * flag. This is correct even if the scalar is NULL; since we would
+ * evaluate the operator zero times, it matters not whether it would want
+ * to return NULL.
+ */
+ nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+ if (nitems <= 0)
+ {
+ *op->resvalue = BoolGetDatum(!useOr);
+ *op->resnull = false;
+ return;
+ }
+
+ /*
+ * If the scalar is NULL, and the function is strict, return NULL; no
+ * point in iterating the loop.
+ */
+ if (fcinfo->argnull[0] && strictfunc)
+ {
+ *op->resnull = true;
+ return;
+ }
+
+ /*
+ * We arrange to look up info about the element type only once per series
+ * of calls, assuming the element type doesn't change underneath us.
+ */
+ if (op->d.scalararrayop.element_type != ARR_ELEMTYPE(arr))
+ {
+ get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+ &op->d.scalararrayop.typlen,
+ &op->d.scalararrayop.typbyval,
+ &op->d.scalararrayop.typalign);
+ op->d.scalararrayop.element_type = ARR_ELEMTYPE(arr);
+ }
+
+ typlen = op->d.scalararrayop.typlen;
+ typbyval = op->d.scalararrayop.typbyval;
+ typalign = op->d.scalararrayop.typalign;
+
+ /* Initialize result appropriately depending on useOr */
+ result = BoolGetDatum(!useOr);
+ resultnull = false;
+
+ /* Loop over the array elements */
+ s = (char *) ARR_DATA_PTR(arr);
+ bitmap = ARR_NULLBITMAP(arr);
+ bitmask = 1;
+
+ for (i = 0; i < nitems; i++)
+ {
+ Datum elt;
+ Datum thisresult;
+
+ /* Get array element, checking for NULL */
+ if (bitmap && (*bitmap & bitmask) == 0)
+ {
+ fcinfo->arg[1] = (Datum) 0;
+ fcinfo->argnull[1] = true;
+ }
+ else
+ {
+ elt = fetch_att(s, typbyval, typlen);
+ s = att_addlength_pointer(s, typlen, s);
+ s = (char *) att_align_nominal(s, typalign);
+ fcinfo->arg[1] = elt;
+ fcinfo->argnull[1] = false;
+ }
+
+ /* Call comparison function */
+ if (fcinfo->argnull[1] && strictfunc)
+ {
+ fcinfo->isnull = true;
+ thisresult = (Datum) 0;
+ }
+ else
+ {
+ fcinfo->isnull = false;
+ thisresult = (op->d.scalararrayop.fn_addr) (fcinfo);
+ }
+
+ /* Combine results per OR or AND semantics */
+ if (fcinfo->isnull)
+ resultnull = true;
+ else if (useOr)
+ {
+ if (DatumGetBool(thisresult))
+ {
+ result = BoolGetDatum(true);
+ resultnull = false;
+ break; /* needn't look at any more elements */
+ }
+ }
+ else
+ {
+ if (!DatumGetBool(thisresult))
+ {
+ result = BoolGetDatum(false);
+ resultnull = false;
+ break; /* needn't look at any more elements */
+ }
+ }
+
+ /* advance bitmap pointer if any */
+ if (bitmap)
+ {
+ bitmask <<= 1;
+ if (bitmask == 0x100)
+ {
+ bitmap++;
+ bitmask = 1;
+ }
+ }
+ }
+
+ *op->resvalue = result;
+ *op->resnull = resultnull;
+}
+
+/*
+ * Evaluate a NOT NULL domain constraint.
+ */
+void
+ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op)
+{
+ if (*op->resnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NOT_NULL_VIOLATION),
+ errmsg("domain %s does not allow null values",
+ format_type_be(op->d.domaincheck.resulttype)),
+ errdatatype(op->d.domaincheck.resulttype)));
+}
+
+/*
+ * Evaluate a CHECK domain constraint.
+ */
+void
+ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op)
+{
+ if (!*op->d.domaincheck.checknull &&
+ !DatumGetBool(*op->d.domaincheck.checkvalue))
+ ereport(ERROR,
+ (errcode(ERRCODE_CHECK_VIOLATION),
+ errmsg("value for domain %s violates check constraint \"%s\"",
+ format_type_be(op->d.domaincheck.resulttype),
+ op->d.domaincheck.constraintname),
+ errdomainconstraint(op->d.domaincheck.resulttype,
+ op->d.domaincheck.constraintname)));
+}
+
+/*
+ * Evaluate the various forms of XmlExpr.
+ *
+ * Arguments have been evaluated into named_argvalue/named_argnull
+ * and/or argvalue/argnull arrays.
+ */
+void
+ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
+{
+ XmlExpr *xexpr = op->d.xmlexpr.xexpr;
+ Datum value;
+ int i;
+
+ *op->resnull = true; /* until we get a result */
+ *op->resvalue = (Datum) 0;
+
+ switch (xexpr->op)
+ {
+ case IS_XMLCONCAT:
+ {
+ Datum *argvalue = op->d.xmlexpr.argvalue;
+ bool *argnull = op->d.xmlexpr.argnull;
+ List *values = NIL;
+
+ for (i = 0; i < list_length(xexpr->args); i++)
+ {
+ if (!argnull[i])
+ values = lappend(values, DatumGetPointer(argvalue[i]));
+ }
+
+ if (values != NIL)
+ {
+ *op->resvalue = PointerGetDatum(xmlconcat(values));
+ *op->resnull = false;
+ }
+ }
+ break;
+
+ case IS_XMLFOREST:
+ {
+ Datum *argvalue = op->d.xmlexpr.named_argvalue;
+ bool *argnull = op->d.xmlexpr.named_argnull;
+ StringInfoData buf;
+ ListCell *lc;
+ ListCell *lc2;
+
+ initStringInfo(&buf);
+
+ i = 0;
+ forboth(lc, xexpr->named_args, lc2, xexpr->arg_names)
+ {
+ Expr *e = (Expr *) lfirst(lc);
+ char *argname = strVal(lfirst(lc2));
+
+ if (!argnull[i])
+ {
+ value = argvalue[i];
+ appendStringInfo(&buf, "<%s>%s</%s>",
+ argname,
+ map_sql_value_to_xml_value(value,
+ exprType((Node *) e), true),
+ argname);
+ *op->resnull = false;
+ }
+ i++;
+ }
+
+ if (!*op->resnull)
+ {
+ text *result;
+
+ result = cstring_to_text_with_len(buf.data, buf.len);
+ *op->resvalue = PointerGetDatum(result);
+ }
+
+ pfree(buf.data);
+ }
+ break;
+
+ case IS_XMLELEMENT:
+ *op->resvalue = PointerGetDatum(xmlelement(xexpr,
+ op->d.xmlexpr.named_argvalue,
+ op->d.xmlexpr.named_argnull,
+ op->d.xmlexpr.argvalue,
+ op->d.xmlexpr.argnull));
+ *op->resnull = false;
+ break;
+
+ case IS_XMLPARSE:
+ {
+ Datum *argvalue = op->d.xmlexpr.argvalue;
+ bool *argnull = op->d.xmlexpr.argnull;
+ text *data;
+ bool preserve_whitespace;
+
+ /* arguments are known to be text, bool */
+ Assert(list_length(xexpr->args) == 2);
+
+ if (argnull[0])
+ return;
+ value = argvalue[0];
+ data = DatumGetTextPP(value);
+
+ if (argnull[1]) /* probably can't happen */
+ return;
+ value = argvalue[1];
+ preserve_whitespace = DatumGetBool(value);
+
+ *op->resvalue = PointerGetDatum(xmlparse(data,
+ xexpr->xmloption,
+ preserve_whitespace));
+ *op->resnull = false;
+ }
+ break;
+
+ case IS_XMLPI:
+ {
+ text *arg;
+ bool isnull;
+
+ /* optional argument is known to be text */
+ Assert(list_length(xexpr->args) <= 1);
+
+ if (xexpr->args)
+ {
+ isnull = op->d.xmlexpr.argnull[0];
+ if (isnull)
+ arg = NULL;
+ else
+ arg = DatumGetTextPP(op->d.xmlexpr.argvalue[0]);
+ }
+ else
+ {
+ arg = NULL;
+ isnull = false;
+ }
+
+ *op->resvalue = PointerGetDatum(xmlpi(xexpr->name,
+ arg,
+ isnull,
+ op->resnull));
+ }
+ break;
+
+ case IS_XMLROOT:
+ {
+ Datum *argvalue = op->d.xmlexpr.argvalue;
+ bool *argnull = op->d.xmlexpr.argnull;
+ xmltype *data;
+ text *version;
+ int standalone;
+
+ /* arguments are known to be xml, text, int */
+ Assert(list_length(xexpr->args) == 3);
+
+ if (argnull[0])
+ return;
+ data = DatumGetXmlP(argvalue[0]);
+
+ if (argnull[1])
+ version = NULL;
+ else
+ version = DatumGetTextPP(argvalue[1]);
+
+ Assert(!argnull[2]); /* always present */
+ standalone = DatumGetInt32(argvalue[2]);
+
+ *op->resvalue = PointerGetDatum(xmlroot(data,
+ version,
+ standalone));
+ *op->resnull = false;
+ }
+ break;
+
+ case IS_XMLSERIALIZE:
+ {
+ Datum *argvalue = op->d.xmlexpr.argvalue;
+ bool *argnull = op->d.xmlexpr.argnull;
+
+ /* argument type is known to be xml */
+ Assert(list_length(xexpr->args) == 1);
+
+ if (argnull[0])
+ return;
+ value = argvalue[0];
+
+ *op->resvalue = PointerGetDatum(
+ xmltotext_with_xmloption(DatumGetXmlP(value),
+ xexpr->xmloption));
+ *op->resnull = false;
+ }
+ break;
+
+ case IS_DOCUMENT:
+ {
+ Datum *argvalue = op->d.xmlexpr.argvalue;
+ bool *argnull = op->d.xmlexpr.argnull;
+
+ /* optional argument is known to be xml */
+ Assert(list_length(xexpr->args) == 1);
+
+ if (argnull[0])
+ return;
+ value = argvalue[0];
+
+ *op->resvalue =
+ BoolGetDatum(xml_is_document(DatumGetXmlP(value)));
+ *op->resnull = false;
+ }
+ break;
+
+ default:
+ elog(ERROR, "unrecognized XML operation");
+ break;
+ }
+}
+
+/*
+ * ExecEvalGroupingFunc
+ *
+ * Computes a bitmask with a bit for each (unevaluated) argument expression
+ * (rightmost arg is least significant bit).
+ *
+ * A bit is set if the corresponding expression is NOT part of the set of
+ * grouping expressions in the current grouping set.
+ */
+void
+ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op)
+{
+ int result = 0;
+ Bitmapset *grouped_cols = op->d.grouping_func.parent->grouped_cols;
+ ListCell *lc;
+
+ foreach(lc, op->d.grouping_func.clauses)
+ {
+ int attnum = lfirst_int(lc);
+
+ result <<= 1;
+
+ if (!bms_is_member(attnum, grouped_cols))
+ result |= 1;
+ }
+
+ *op->resvalue = Int32GetDatum(result);
+ *op->resnull = false;
+}
+
+/*
+ * Hand off evaluation of a subplan to nodeSubplan.c
+ */
+void
+ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ SubPlanState *sstate = op->d.subplan.sstate;
+
+ /* could potentially be nested, so make sure there's enough stack */
+ check_stack_depth();
+
+ *op->resvalue = ExecSubPlan(sstate, econtext, op->resnull);
+}
+
+/*
+ * Hand off evaluation of an alternative subplan to nodeSubplan.c
+ */
+void
+ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ AlternativeSubPlanState *asstate = op->d.alternative_subplan.asstate;
+
+ /* could potentially be nested, so make sure there's enough stack */
+ check_stack_depth();
+
+ *op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull);
+}
+
+/*
+ * Evaluate a wholerow Var expression.
+ *
+ * Returns a Datum whose value is the value of a whole-row range variable
+ * with respect to given expression context.
+ */
+void
+ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
+{
+ Var *variable = op->d.wholerow.var;
+ TupleTableSlot *slot;
+ TupleDesc output_tupdesc;
+ MemoryContext oldcontext;
+ HeapTupleHeader dtuple;
+ HeapTuple tuple;
+
+ /* This was checked by ExecInitExpr */
+ Assert(variable->varattno == InvalidAttrNumber);
+
+ /* Get the input slot we want */
+ switch (variable->varno)
+ {
+ case INNER_VAR:
+ /* get the tuple from the inner node */
+ slot = econtext->ecxt_innertuple;
+ break;
+
+ case OUTER_VAR:
+ /* get the tuple from the outer node */
+ slot = econtext->ecxt_outertuple;
+ break;
+
+ /* INDEX_VAR is handled by default case */
+
+ default:
+ /* get the tuple from the relation being scanned */
+ slot = econtext->ecxt_scantuple;
+ break;
+ }
+
+ /* Apply the junkfilter if any */
+ if (op->d.wholerow.junkFilter != NULL)
+ slot = ExecFilterJunk(op->d.wholerow.junkFilter, slot);
+
+ /*
+ * If first time through, obtain tuple descriptor and check compatibility.
+ *
+ * XXX: It'd be great if this could be moved to the expression
+ * initialization phase, but due to using slots that's currently not
+ * feasible.
+ */
+ if (op->d.wholerow.first)
+ {
+ /* optimistically assume we don't need slow path */
+ op->d.wholerow.slow = false;
+
+ /*
+ * If the Var identifies a named composite type, we must check that
+ * the actual tuple type is compatible with it.
+ */
+ if (variable->vartype != RECORDOID)
+ {
+ TupleDesc var_tupdesc;
+ TupleDesc slot_tupdesc;
+ int i;
+
+ /*
+ * We really only care about numbers of attributes and data types.
+ * Also, we can ignore type mismatch on columns that are dropped
+ * in the destination type, so long as (1) the physical storage
+ * matches or (2) the actual column value is NULL. Case (1) is
+ * helpful in some cases involving out-of-date cached plans, while
+ * case (2) is expected behavior in situations such as an INSERT
+ * into a table with dropped columns (the planner typically
+ * generates an INT4 NULL regardless of the dropped column type).
+ * If we find a dropped column and cannot verify that case (1)
+ * holds, we have to use the slow path to check (2) for each row.
+ */
+ var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
+
+ slot_tupdesc = slot->tts_tupleDescriptor;
+
+ if (var_tupdesc->natts != slot_tupdesc->natts)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table row type and query-specified row type do not match"),
+ errdetail_plural("Table row contains %d attribute, but query expects %d.",
+ "Table row contains %d attributes, but query expects %d.",
+ slot_tupdesc->natts,
+ slot_tupdesc->natts,
+ var_tupdesc->natts)));
+
+ for (i = 0; i < var_tupdesc->natts; i++)
+ {
+ Form_pg_attribute vattr = var_tupdesc->attrs[i];
+ Form_pg_attribute sattr = slot_tupdesc->attrs[i];
+
+ if (vattr->atttypid == sattr->atttypid)
+ continue; /* no worries */
+ if (!vattr->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table row type and query-specified row type do not match"),
+ errdetail("Table has type %s at ordinal position %d, but query expects %s.",
+ format_type_be(sattr->atttypid),
+ i + 1,
+ format_type_be(vattr->atttypid))));
+
+ if (vattr->attlen != sattr->attlen ||
+ vattr->attalign != sattr->attalign)
+ op->d.wholerow.slow = true; /* need to check for nulls */
+ }
+
+ /*
+ * Use the variable's declared rowtype as the descriptor for the
+ * output values, modulo possibly assigning new column names
+ * below. In particular, we *must* absorb any attisdropped
+ * markings.
+ */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ output_tupdesc = CreateTupleDescCopy(var_tupdesc);
+ MemoryContextSwitchTo(oldcontext);
+
+ ReleaseTupleDesc(var_tupdesc);
+ }
+ else
+ {
+ /*
+ * In the RECORD case, we use the input slot's rowtype as the
+ * descriptor for the output values, modulo possibly assigning new
+ * column names below.
+ */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /*
+ * Construct a tuple descriptor for the composite values we'll
+ * produce, and make sure its record type is "blessed". The main
+ * reason to do this is to be sure that operations such as
+ * row_to_json() will see the desired column names when they look up
+ * the descriptor from the type information embedded in the composite
+ * values.
+ *
+ * We already got the correct physical datatype info above, but now we
+ * should try to find the source RTE and adopt its column aliases, in
+ * case they are different from the original rowtype's names. For
+ * example, in "SELECT foo(t) FROM tab t(x,y)", the first two columns
+ * in the composite output should be named "x" and "y" regardless of
+ * tab's column names.
+ *
+ * If we can't locate the RTE, assume the column names we've got are
+ * OK. (As of this writing, the only cases where we can't locate the
+ * RTE are in execution of trigger WHEN clauses, and then the Var will
+ * have the trigger's relation's rowtype, so its names are fine.)
+ * Also, if the creator of the RTE didn't bother to fill in an eref
+ * field, assume our column names are OK. (This happens in COPY, and
+ * perhaps other places.)
+ */
+ if (econtext->ecxt_estate &&
+ variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
+ {
+ RangeTblEntry *rte = rt_fetch(variable->varno,
+ econtext->ecxt_estate->es_range_table);
+
+ if (rte->eref)
+ ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
+ }
+
+ /* Bless the tupdesc if needed, and save it in the execution state */
+ op->d.wholerow.tupdesc = BlessTupleDesc(output_tupdesc);
+
+ op->d.wholerow.first = false;
+ }
+
+ /*
+ * Make sure all columns of the slot are accessible in the slot's
+ * Datum/isnull arrays.
+ */
+ slot_getallattrs(slot);
+
+ if (op->d.wholerow.slow)
+ {
+ /* Check to see if any dropped attributes are non-null */
+ TupleDesc tupleDesc = slot->tts_tupleDescriptor;
+ TupleDesc var_tupdesc = op->d.wholerow.tupdesc;
+ int i;
+
+ Assert(var_tupdesc->natts == tupleDesc->natts);
+
+ for (i = 0; i < var_tupdesc->natts; i++)
+ {
+ Form_pg_attribute vattr = var_tupdesc->attrs[i];
+ Form_pg_attribute sattr = tupleDesc->attrs[i];
+
+ if (!vattr->attisdropped)
+ continue; /* already checked non-dropped cols */
+ if (slot->tts_isnull[i])
+ continue; /* null is always okay */
+ if (vattr->attlen != sattr->attlen ||
+ vattr->attalign != sattr->attalign)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("table row type and query-specified row type do not match"),
+ errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+ i + 1)));
+ }
+ }
+
+ /*
+ * Copy the slot tuple and make sure any toasted fields get detoasted.
+ *
+ * (The intermediate copy is a tad annoying here, but we currently have no
+ * primitive that will do the right thing. Note it is critical that we
+ * not change the slot's state, so we can't use ExecFetchSlotTupleDatum.)
+ */
+ tuple = ExecCopySlotTuple(slot);
+ dtuple = (HeapTupleHeader)
+ DatumGetPointer(heap_copy_tuple_as_datum(tuple,
+ slot->tts_tupleDescriptor));
+ heap_freetuple(tuple);
+
+ /*
+ * Label the datum with the composite type info we identified before.
+ */
+ HeapTupleHeaderSetTypeId(dtuple, op->d.wholerow.tupdesc->tdtypeid);
+ HeapTupleHeaderSetTypMod(dtuple, op->d.wholerow.tupdesc->tdtypmod);
+
+ *op->resnull = false;
+ *op->resvalue = PointerGetDatum(dtuple);
+}
/* Check for partial index */
if (indexInfo->ii_Predicate != NIL)
{
- List *predicate;
+ ExprState *predicate;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
- if (predicate == NIL)
+ if (predicate == NULL)
{
- predicate = (List *)
- ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
- estate);
+ predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
indexInfo->ii_PredicateState = predicate;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext, false))
+ if (!ExecQual(predicate, econtext))
continue;
}
/* Check for partial index */
if (indexInfo->ii_Predicate != NIL)
{
- List *predicate;
+ ExprState *predicate;
/*
* If predicate state not set up yet, create it (in the estate's
* per-query context)
*/
predicate = indexInfo->ii_PredicateState;
- if (predicate == NIL)
+ if (predicate == NULL)
{
- predicate = (List *)
- ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
- estate);
+ predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate);
indexInfo->ii_PredicateState = predicate;
}
/* Skip this index-update if the predicate isn't satisfied */
- if (!ExecQual(predicate, econtext, false))
+ if (!ExecQual(predicate, econtext))
continue;
}
/*
* Only plain-relation RTEs need to be checked here. Function RTEs are
- * checked by init_fcache when the function is prepared for execution.
- * Join, subquery, and special RTEs need no checks.
+ * checked when the function is prepared for execution. Join, subquery,
+ * and special RTEs need no checks.
*/
if (rte->rtekind != RTE_RELATION)
return true;
resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
palloc0(n * sizeof(FmgrInfo));
- resultRelInfo->ri_TrigWhenExprs = (List **)
- palloc0(n * sizeof(List *));
+ resultRelInfo->ri_TrigWhenExprs = (ExprState **)
+ palloc0(n * sizeof(ExprState *));
if (instrument_options)
resultRelInfo->ri_TrigInstrument = InstrAlloc(n, instrument_options);
}
ConstrCheck *check = rel->rd_att->constr->check;
ExprContext *econtext;
MemoryContext oldContext;
- List *qual;
int i;
/*
{
oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
resultRelInfo->ri_ConstraintExprs =
- (List **) palloc(ncheck * sizeof(List *));
+ (ExprState **) palloc(ncheck * sizeof(ExprState *));
for (i = 0; i < ncheck; i++)
{
- /* ExecQual wants implicit-AND form */
- qual = make_ands_implicit(stringToNode(check[i].ccbin));
- resultRelInfo->ri_ConstraintExprs[i] = (List *)
- ExecPrepareExpr((Expr *) qual, estate);
+ Expr *checkconstr;
+
+ checkconstr = stringToNode(check[i].ccbin);
+ resultRelInfo->ri_ConstraintExprs[i] =
+ ExecPrepareExpr(checkconstr, estate);
}
MemoryContextSwitchTo(oldContext);
}
/* And evaluate the constraints */
for (i = 0; i < ncheck; i++)
{
- qual = resultRelInfo->ri_ConstraintExprs[i];
+ ExprState *checkconstr = resultRelInfo->ri_ConstraintExprs[i];
/*
* NOTE: SQL specifies that a NULL result from a constraint expression
- * is not to be treated as a failure. Therefore, tell ExecQual to
- * return TRUE for NULL.
+ * is not to be treated as a failure. Therefore, use ExecCheck not
+ * ExecQual.
*/
- if (!ExecQual(qual, econtext, true))
+ if (!ExecCheck(checkconstr, econtext))
return check[i].ccname;
}
{
List *qual = resultRelInfo->ri_PartitionCheck;
- resultRelInfo->ri_PartitionCheckExpr = (List *)
- ExecPrepareExpr((Expr *) qual, estate);
+ resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(qual, estate);
}
/*
* As in case of the catalogued constraints, we treat a NULL result as
* success here, not a failure.
*/
- return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true);
+ return ExecCheck(resultRelInfo->ri_PartitionCheckExpr, econtext);
}
/*
* is visible (in the case of a view) or that it passes the
* 'with-check' policy (in the case of row security). If the qual
* evaluates to NULL or FALSE, then the new tuple won't be included in
- * the view or doesn't pass the 'with-check' policy for the table. We
- * need ExecQual to return FALSE for NULL to handle the view case (the
- * opposite of what we do above for CHECK constraints).
+ * the view or doesn't pass the 'with-check' policy for the table.
*/
- if (!ExecQual((List *) wcoExpr, econtext, false))
+ if (!ExecQual(wcoExpr, econtext))
{
char *val_desc;
Bitmapset *modifiedCols;
-/*-------------------------------------------------------------------------
- *
- * execQual.c
- * Routines to evaluate qualification and targetlist expressions
- *
- * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- * src/backend/executor/execQual.c
- *
- *-------------------------------------------------------------------------
- */
-/*
- * INTERFACE ROUTINES
- * ExecEvalExpr - (now a macro) evaluate an expression, return a datum
- * ExecEvalExprSwitchContext - same, but switch into eval memory context
- * ExecQual - return true/false if qualification is satisfied
- * ExecProject - form a new tuple by projecting the given tuple
- *
- * NOTES
- * The more heavily used ExecEvalExpr routines, such as ExecEvalScalarVar,
- * are hotspots. Making these faster will speed up the entire system.
- *
- * ExecProject() is used to make tuple projections. Rather then
- * trying to speed it up, the execution plan should be pre-processed
- * to facilitate attribute sharing between nodes wherever possible,
- * instead of doing needless copying. -cim 5/31/91
- *
- * During expression evaluation, we check_stack_depth only in
- * ExecMakeFunctionResultSet/ExecMakeFunctionResultNoSets rather than at
- * every single node. This is a compromise that trades off precision of
- * the stack limit setting to gain speed.
- */
-
-#include "postgres.h"
-
-#include "access/htup_details.h"
-#include "access/nbtree.h"
-#include "access/tupconvert.h"
-#include "catalog/objectaccess.h"
-#include "catalog/pg_type.h"
-#include "executor/execdebug.h"
-#include "executor/nodeSubplan.h"
-#include "funcapi.h"
-#include "miscadmin.h"
-#include "nodes/makefuncs.h"
-#include "nodes/nodeFuncs.h"
-#include "optimizer/planner.h"
-#include "parser/parse_coerce.h"
-#include "parser/parsetree.h"
-#include "pgstat.h"
-#include "utils/acl.h"
-#include "utils/builtins.h"
-#include "utils/date.h"
-#include "utils/lsyscache.h"
-#include "utils/memutils.h"
-#include "utils/timestamp.h"
-#include "utils/typcache.h"
-#include "utils/xml.h"
-
-
-/* static function decls */
-static Datum ExecEvalArrayRef(ArrayRefExprState *astate,
- ExprContext *econtext,
- bool *isNull);
-static bool isAssignmentIndirectionExpr(ExprState *exprstate);
-static Datum ExecEvalAggref(AggrefExprState *aggref,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext,
- bool *isNull);
-static void init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
- MemoryContext fcacheCxt, bool allowSRF, bool needDescForSRF);
-static void ShutdownFuncExpr(Datum arg);
-static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod,
- TupleDesc *cache_field, ExprContext *econtext);
-static void ShutdownTupleDescRef(Datum arg);
-static void ExecEvalFuncArgs(FunctionCallInfo fcinfo,
- List *argList, ExprContext *econtext);
-static void ExecPrepareTuplestoreResult(FuncExprState *fcache,
- ExprContext *econtext,
- Tuplestorestate *resultStore,
- TupleDesc resultDesc);
-static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
-static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalFunc(FuncExprState *fcache, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalOper(FuncExprState *fcache, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalCaseTestExpr(ExprState *exprstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalArray(ArrayExprState *astate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalRow(RowExprState *rstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalRowCompare(RowCompareExprState *rstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalSQLValueFunction(ExprState *svfExpr,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalNullIf(FuncExprState *nullIfExpr,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalNullTest(NullTestState *nstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalBooleanTest(GenericExprState *bstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalCoerceToDomain(CoerceToDomainState *cstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalFieldSelect(FieldSelectState *fstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalFieldStore(FieldStoreState *fstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalRelabelType(GenericExprState *exprstate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
- bool *isNull);
-static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
- ExprContext *econtext,
- bool *isNull);
-
-
-/* ----------------------------------------------------------------
- * ExecEvalExpr routines
- *
- * Recursively evaluate a targetlist or qualification expression.
- *
- * Each of the following routines having the signature
- * Datum ExecEvalFoo(ExprState *expression,
- * ExprContext *econtext,
- * bool *isNull);
- * is responsible for evaluating one type or subtype of ExprState node.
- * They are normally called via the ExecEvalExpr macro, which makes use of
- * the function pointer set up when the ExprState node was built by
- * ExecInitExpr. (In some cases, we change this pointer later to avoid
- * re-executing one-time overhead.)
- *
- * Note: for notational simplicity we declare these functions as taking the
- * specific type of ExprState that they work on. This requires casting when
- * assigning the function pointer in ExecInitExpr. Be careful that the
- * function signature is declared correctly, because the cast suppresses
- * automatic checking!
- *
- *
- * All these functions share this calling convention:
- *
- * Inputs:
- * expression: the expression state tree to evaluate
- * econtext: evaluation context information
- *
- * Outputs:
- * return value: Datum value of result
- * *isNull: set to TRUE if result is NULL (actual return value is
- * meaningless if so); set to FALSE if non-null result
- *
- * The caller should already have switched into the temporary memory
- * context econtext->ecxt_per_tuple_memory. The convenience entry point
- * ExecEvalExprSwitchContext() is provided for callers who don't prefer to
- * do the switch in an outer loop. We do not do the switch in these routines
- * because it'd be a waste of cycles during nested expression evaluation.
- * ----------------------------------------------------------------
- */
-
-
-/*----------
- * ExecEvalArrayRef
- *
- * This function takes an ArrayRef and returns the extracted Datum
- * if it's a simple reference, or the modified array value if it's
- * an array assignment (i.e., array element or slice insertion).
- *
- * NOTE: if we get a NULL result from a subscript expression, we return NULL
- * when it's an array reference, or raise an error when it's an assignment.
- *----------
- */
-static Datum
-ExecEvalArrayRef(ArrayRefExprState *astate,
- ExprContext *econtext,
- bool *isNull)
-{
- ArrayRef *arrayRef = (ArrayRef *) astate->xprstate.expr;
- Datum array_source;
- bool isAssignment = (arrayRef->refassgnexpr != NULL);
- bool eisnull;
- ListCell *l;
- int i = 0,
- j = 0;
- IntArray upper,
- lower;
- bool upperProvided[MAXDIM],
- lowerProvided[MAXDIM];
- int *lIndex;
-
- array_source = ExecEvalExpr(astate->refexpr,
- econtext,
- isNull);
-
- /*
- * If refexpr yields NULL, and it's a fetch, then result is NULL. In the
- * assignment case, we'll cons up something below.
- */
- if (*isNull)
- {
- if (!isAssignment)
- return (Datum) NULL;
- }
-
- foreach(l, astate->refupperindexpr)
- {
- ExprState *eltstate = (ExprState *) lfirst(l);
-
- if (i >= MAXDIM)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
- i + 1, MAXDIM)));
-
- if (eltstate == NULL)
- {
- /* Slice bound is omitted, so use array's upper bound */
- Assert(astate->reflowerindexpr != NIL);
- upperProvided[i++] = false;
- continue;
- }
- upperProvided[i] = true;
-
- upper.indx[i++] = DatumGetInt32(ExecEvalExpr(eltstate,
- econtext,
- &eisnull));
- /* If any index expr yields NULL, result is NULL or error */
- if (eisnull)
- {
- if (isAssignment)
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("array subscript in assignment must not be null")));
- *isNull = true;
- return (Datum) NULL;
- }
- }
-
- if (astate->reflowerindexpr != NIL)
- {
- foreach(l, astate->reflowerindexpr)
- {
- ExprState *eltstate = (ExprState *) lfirst(l);
-
- if (j >= MAXDIM)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
- j + 1, MAXDIM)));
-
- if (eltstate == NULL)
- {
- /* Slice bound is omitted, so use array's lower bound */
- lowerProvided[j++] = false;
- continue;
- }
- lowerProvided[j] = true;
-
- lower.indx[j++] = DatumGetInt32(ExecEvalExpr(eltstate,
- econtext,
- &eisnull));
- /* If any index expr yields NULL, result is NULL or error */
- if (eisnull)
- {
- if (isAssignment)
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("array subscript in assignment must not be null")));
- *isNull = true;
- return (Datum) NULL;
- }
- }
- /* this can't happen unless parser messed up */
- if (i != j)
- elog(ERROR, "upper and lower index lists are not same length");
- lIndex = lower.indx;
- }
- else
- lIndex = NULL;
-
- if (isAssignment)
- {
- Datum sourceData;
- Datum save_datum;
- bool save_isNull;
-
- /*
- * We might have a nested-assignment situation, in which the
- * refassgnexpr is itself a FieldStore or ArrayRef that needs to
- * obtain and modify the previous value of the array element or slice
- * being replaced. If so, we have to extract that value from the
- * array and pass it down via the econtext's caseValue. It's safe to
- * reuse the CASE mechanism because there cannot be a CASE between
- * here and where the value would be needed, and an array assignment
- * can't be within a CASE either. (So saving and restoring the
- * caseValue is just paranoia, but let's do it anyway.)
- *
- * Since fetching the old element might be a nontrivial expense, do it
- * only if the argument appears to actually need it.
- */
- save_datum = econtext->caseValue_datum;
- save_isNull = econtext->caseValue_isNull;
-
- if (isAssignmentIndirectionExpr(astate->refassgnexpr))
- {
- if (*isNull)
- {
- /* whole array is null, so any element or slice is too */
- econtext->caseValue_datum = (Datum) 0;
- econtext->caseValue_isNull = true;
- }
- else if (lIndex == NULL)
- {
- econtext->caseValue_datum =
- array_get_element(array_source, i,
- upper.indx,
- astate->refattrlength,
- astate->refelemlength,
- astate->refelembyval,
- astate->refelemalign,
- &econtext->caseValue_isNull);
- }
- else
- {
- /* this is currently unreachable */
- econtext->caseValue_datum =
- array_get_slice(array_source, i,
- upper.indx, lower.indx,
- upperProvided, lowerProvided,
- astate->refattrlength,
- astate->refelemlength,
- astate->refelembyval,
- astate->refelemalign);
- econtext->caseValue_isNull = false;
- }
- }
- else
- {
- /* argument shouldn't need caseValue, but for safety set it null */
- econtext->caseValue_datum = (Datum) 0;
- econtext->caseValue_isNull = true;
- }
-
- /*
- * Evaluate the value to be assigned into the array.
- */
- sourceData = ExecEvalExpr(astate->refassgnexpr,
- econtext,
- &eisnull);
-
- econtext->caseValue_datum = save_datum;
- econtext->caseValue_isNull = save_isNull;
-
- /*
- * For an assignment to a fixed-length array type, both the original
- * array and the value to be assigned into it must be non-NULL, else
- * we punt and return the original array.
- */
- if (astate->refattrlength > 0) /* fixed-length array? */
- if (eisnull || *isNull)
- return array_source;
-
- /*
- * For assignment to varlena arrays, we handle a NULL original array
- * by substituting an empty (zero-dimensional) array; insertion of the
- * new element will result in a singleton array value. It does not
- * matter whether the new element is NULL.
- */
- if (*isNull)
- {
- array_source = PointerGetDatum(construct_empty_array(arrayRef->refelemtype));
- *isNull = false;
- }
-
- if (lIndex == NULL)
- return array_set_element(array_source, i,
- upper.indx,
- sourceData,
- eisnull,
- astate->refattrlength,
- astate->refelemlength,
- astate->refelembyval,
- astate->refelemalign);
- else
- return array_set_slice(array_source, i,
- upper.indx, lower.indx,
- upperProvided, lowerProvided,
- sourceData,
- eisnull,
- astate->refattrlength,
- astate->refelemlength,
- astate->refelembyval,
- astate->refelemalign);
- }
-
- if (lIndex == NULL)
- return array_get_element(array_source, i,
- upper.indx,
- astate->refattrlength,
- astate->refelemlength,
- astate->refelembyval,
- astate->refelemalign,
- isNull);
- else
- return array_get_slice(array_source, i,
- upper.indx, lower.indx,
- upperProvided, lowerProvided,
- astate->refattrlength,
- astate->refelemlength,
- astate->refelembyval,
- astate->refelemalign);
-}
-
-/*
- * Helper for ExecEvalArrayRef: is expr a nested FieldStore or ArrayRef
- * that might need the old element value passed down?
- *
- * (We could use this in ExecEvalFieldStore too, but in that case passing
- * the old value is so cheap there's no need.)
- */
-static bool
-isAssignmentIndirectionExpr(ExprState *exprstate)
-{
- if (exprstate == NULL)
- return false; /* just paranoia */
- if (IsA(exprstate, FieldStoreState))
- {
- FieldStore *fstore = (FieldStore *) exprstate->expr;
-
- if (fstore->arg && IsA(fstore->arg, CaseTestExpr))
- return true;
- }
- else if (IsA(exprstate, ArrayRefExprState))
- {
- ArrayRef *arrayRef = (ArrayRef *) exprstate->expr;
-
- if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr))
- return true;
- }
- return false;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalAggref
- *
- * Returns a Datum whose value is the value of the precomputed
- * aggregate found in the given expression context.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext,
- bool *isNull)
-{
- if (econtext->ecxt_aggvalues == NULL) /* safety check */
- elog(ERROR, "no aggregates in this expression context");
-
- *isNull = econtext->ecxt_aggnulls[aggref->aggno];
- return econtext->ecxt_aggvalues[aggref->aggno];
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalWindowFunc
- *
- * Returns a Datum whose value is the value of the precomputed
- * window function found in the given expression context.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext,
- bool *isNull)
-{
- if (econtext->ecxt_aggvalues == NULL) /* safety check */
- elog(ERROR, "no window functions in this expression context");
-
- *isNull = econtext->ecxt_aggnulls[wfunc->wfuncno];
- return econtext->ecxt_aggvalues[wfunc->wfuncno];
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalScalarVar
- *
- * Returns a Datum whose value is the value of a scalar (not whole-row)
- * range variable with respect to given expression context.
- *
- * Note: ExecEvalScalarVar is executed only the first time through in a given
- * plan; it changes the ExprState's function pointer to pass control directly
- * to ExecEvalScalarVarFast after making one-time checks.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
- bool *isNull)
-{
- Var *variable = (Var *) exprstate->expr;
- TupleTableSlot *slot;
- AttrNumber attnum;
-
- /* Get the input slot and attribute number we want */
- switch (variable->varno)
- {
- case INNER_VAR: /* get the tuple from the inner node */
- slot = econtext->ecxt_innertuple;
- break;
-
- case OUTER_VAR: /* get the tuple from the outer node */
- slot = econtext->ecxt_outertuple;
- break;
-
- /* INDEX_VAR is handled by default case */
-
- default: /* get the tuple from the relation being
- * scanned */
- slot = econtext->ecxt_scantuple;
- break;
- }
-
- attnum = variable->varattno;
-
- /* This was checked by ExecInitExpr */
- Assert(attnum != InvalidAttrNumber);
-
- /*
- * If it's a user attribute, check validity (bogus system attnums will be
- * caught inside slot_getattr). What we have to check for here is the
- * possibility of an attribute having been changed in type since the plan
- * tree was created. Ideally the plan will get invalidated and not
- * re-used, but just in case, we keep these defenses. Fortunately it's
- * sufficient to check once on the first time through.
- *
- * Note: we allow a reference to a dropped attribute. slot_getattr will
- * force a NULL result in such cases.
- *
- * Note: ideally we'd check typmod as well as typid, but that seems
- * impractical at the moment: in many cases the tupdesc will have been
- * generated by ExecTypeFromTL(), and that can't guarantee to generate an
- * accurate typmod in all cases, because some expression node types don't
- * carry typmod.
- */
- if (attnum > 0)
- {
- TupleDesc slot_tupdesc = slot->tts_tupleDescriptor;
- Form_pg_attribute attr;
-
- if (attnum > slot_tupdesc->natts) /* should never happen */
- elog(ERROR, "attribute number %d exceeds number of columns %d",
- attnum, slot_tupdesc->natts);
-
- attr = slot_tupdesc->attrs[attnum - 1];
-
- /* can't check type if dropped, since atttypid is probably 0 */
- if (!attr->attisdropped)
- {
- if (variable->vartype != attr->atttypid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("attribute %d has wrong type", attnum),
- errdetail("Table has type %s, but query expects %s.",
- format_type_be(attr->atttypid),
- format_type_be(variable->vartype))));
- }
- }
-
- /* Skip the checking on future executions of node */
- exprstate->evalfunc = ExecEvalScalarVarFast;
-
- /* Fetch the value from the slot */
- return slot_getattr(slot, attnum, isNull);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalScalarVarFast
- *
- * Returns a Datum for a scalar variable.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
- bool *isNull)
-{
- Var *variable = (Var *) exprstate->expr;
- TupleTableSlot *slot;
- AttrNumber attnum;
-
- /* Get the input slot and attribute number we want */
- switch (variable->varno)
- {
- case INNER_VAR: /* get the tuple from the inner node */
- slot = econtext->ecxt_innertuple;
- break;
-
- case OUTER_VAR: /* get the tuple from the outer node */
- slot = econtext->ecxt_outertuple;
- break;
-
- /* INDEX_VAR is handled by default case */
-
- default: /* get the tuple from the relation being
- * scanned */
- slot = econtext->ecxt_scantuple;
- break;
- }
-
- attnum = variable->varattno;
-
- /* Fetch the value from the slot */
- return slot_getattr(slot, attnum, isNull);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalWholeRowVar
- *
- * Returns a Datum whose value is the value of a whole-row range
- * variable with respect to given expression context.
- *
- * Note: ExecEvalWholeRowVar is executed only the first time through in a
- * given plan; it changes the ExprState's function pointer to pass control
- * directly to ExecEvalWholeRowFast or ExecEvalWholeRowSlow after making
- * one-time checks.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
- bool *isNull)
-{
- Var *variable = (Var *) wrvstate->xprstate.expr;
- TupleTableSlot *slot;
- TupleDesc output_tupdesc;
- MemoryContext oldcontext;
- bool needslow = false;
-
- /* This was checked by ExecInitExpr */
- Assert(variable->varattno == InvalidAttrNumber);
-
- /* Get the input slot we want */
- switch (variable->varno)
- {
- case INNER_VAR: /* get the tuple from the inner node */
- slot = econtext->ecxt_innertuple;
- break;
-
- case OUTER_VAR: /* get the tuple from the outer node */
- slot = econtext->ecxt_outertuple;
- break;
-
- /* INDEX_VAR is handled by default case */
-
- default: /* get the tuple from the relation being
- * scanned */
- slot = econtext->ecxt_scantuple;
- break;
- }
-
- /*
- * If the input tuple came from a subquery, it might contain "resjunk"
- * columns (such as GROUP BY or ORDER BY columns), which we don't want to
- * keep in the whole-row result. We can get rid of such columns by
- * passing the tuple through a JunkFilter --- but to make one, we have to
- * lay our hands on the subquery's targetlist. Fortunately, there are not
- * very many cases where this can happen, and we can identify all of them
- * by examining our parent PlanState. We assume this is not an issue in
- * standalone expressions that don't have parent plans. (Whole-row Vars
- * can occur in such expressions, but they will always be referencing
- * table rows.)
- */
- if (wrvstate->parent)
- {
- PlanState *subplan = NULL;
-
- switch (nodeTag(wrvstate->parent))
- {
- case T_SubqueryScanState:
- subplan = ((SubqueryScanState *) wrvstate->parent)->subplan;
- break;
- case T_CteScanState:
- subplan = ((CteScanState *) wrvstate->parent)->cteplanstate;
- break;
- default:
- break;
- }
-
- if (subplan)
- {
- bool junk_filter_needed = false;
- ListCell *tlist;
-
- /* Detect whether subplan tlist actually has any junk columns */
- foreach(tlist, subplan->plan->targetlist)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(tlist);
-
- if (tle->resjunk)
- {
- junk_filter_needed = true;
- break;
- }
- }
-
- /* If so, build the junkfilter in the query memory context */
- if (junk_filter_needed)
- {
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- wrvstate->wrv_junkFilter =
- ExecInitJunkFilter(subplan->plan->targetlist,
- ExecGetResultType(subplan)->tdhasoid,
- ExecInitExtraTupleSlot(wrvstate->parent->state));
- MemoryContextSwitchTo(oldcontext);
- }
- }
- }
-
- /* Apply the junkfilter if any */
- if (wrvstate->wrv_junkFilter != NULL)
- slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
-
- /*
- * If the Var identifies a named composite type, we must check that the
- * actual tuple type is compatible with it.
- */
- if (variable->vartype != RECORDOID)
- {
- TupleDesc var_tupdesc;
- TupleDesc slot_tupdesc;
- int i;
-
- /*
- * We really only care about numbers of attributes and data types.
- * Also, we can ignore type mismatch on columns that are dropped in
- * the destination type, so long as (1) the physical storage matches
- * or (2) the actual column value is NULL. Case (1) is helpful in
- * some cases involving out-of-date cached plans, while case (2) is
- * expected behavior in situations such as an INSERT into a table with
- * dropped columns (the planner typically generates an INT4 NULL
- * regardless of the dropped column type). If we find a dropped
- * column and cannot verify that case (1) holds, we have to use
- * ExecEvalWholeRowSlow to check (2) for each row.
- */
- var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1);
-
- slot_tupdesc = slot->tts_tupleDescriptor;
-
- if (var_tupdesc->natts != slot_tupdesc->natts)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail_plural("Table row contains %d attribute, but query expects %d.",
- "Table row contains %d attributes, but query expects %d.",
- slot_tupdesc->natts,
- slot_tupdesc->natts,
- var_tupdesc->natts)));
-
- for (i = 0; i < var_tupdesc->natts; i++)
- {
- Form_pg_attribute vattr = var_tupdesc->attrs[i];
- Form_pg_attribute sattr = slot_tupdesc->attrs[i];
-
- if (vattr->atttypid == sattr->atttypid)
- continue; /* no worries */
- if (!vattr->attisdropped)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail("Table has type %s at ordinal position %d, but query expects %s.",
- format_type_be(sattr->atttypid),
- i + 1,
- format_type_be(vattr->atttypid))));
-
- if (vattr->attlen != sattr->attlen ||
- vattr->attalign != sattr->attalign)
- needslow = true; /* need runtime check for null */
- }
-
- /*
- * Use the variable's declared rowtype as the descriptor for the
- * output values, modulo possibly assigning new column names below. In
- * particular, we *must* absorb any attisdropped markings.
- */
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- output_tupdesc = CreateTupleDescCopy(var_tupdesc);
- MemoryContextSwitchTo(oldcontext);
-
- ReleaseTupleDesc(var_tupdesc);
- }
- else
- {
- /*
- * In the RECORD case, we use the input slot's rowtype as the
- * descriptor for the output values, modulo possibly assigning new
- * column names below.
- */
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
- MemoryContextSwitchTo(oldcontext);
- }
-
- /*
- * Construct a tuple descriptor for the composite values we'll produce,
- * and make sure its record type is "blessed". The main reason to do this
- * is to be sure that operations such as row_to_json() will see the
- * desired column names when they look up the descriptor from the type
- * information embedded in the composite values.
- *
- * We already got the correct physical datatype info above, but now we
- * should try to find the source RTE and adopt its column aliases, in case
- * they are different from the original rowtype's names. For example, in
- * "SELECT foo(t) FROM tab t(x,y)", the first two columns in the composite
- * output should be named "x" and "y" regardless of tab's column names.
- *
- * If we can't locate the RTE, assume the column names we've got are OK.
- * (As of this writing, the only cases where we can't locate the RTE are
- * in execution of trigger WHEN clauses, and then the Var will have the
- * trigger's relation's rowtype, so its names are fine.) Also, if the
- * creator of the RTE didn't bother to fill in an eref field, assume our
- * column names are OK. (This happens in COPY, and perhaps other places.)
- */
- if (econtext->ecxt_estate &&
- variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
- {
- RangeTblEntry *rte = rt_fetch(variable->varno,
- econtext->ecxt_estate->es_range_table);
-
- if (rte->eref)
- ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
- }
-
- /* Bless the tupdesc if needed, and save it in the execution state */
- wrvstate->wrv_tupdesc = BlessTupleDesc(output_tupdesc);
-
- /* Skip all the above on future executions of node */
- if (needslow)
- wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow;
- else
- wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowFast;
-
- /* Fetch the value */
- return (*wrvstate->xprstate.evalfunc) ((ExprState *) wrvstate, econtext,
- isNull);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalWholeRowFast
- *
- * Returns a Datum for a whole-row variable.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
- bool *isNull)
-{
- Var *variable = (Var *) wrvstate->xprstate.expr;
- TupleTableSlot *slot;
- HeapTupleHeader dtuple;
-
- *isNull = false;
-
- /* Get the input slot we want */
- switch (variable->varno)
- {
- case INNER_VAR: /* get the tuple from the inner node */
- slot = econtext->ecxt_innertuple;
- break;
-
- case OUTER_VAR: /* get the tuple from the outer node */
- slot = econtext->ecxt_outertuple;
- break;
-
- /* INDEX_VAR is handled by default case */
-
- default: /* get the tuple from the relation being
- * scanned */
- slot = econtext->ecxt_scantuple;
- break;
- }
-
- /* Apply the junkfilter if any */
- if (wrvstate->wrv_junkFilter != NULL)
- slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
-
- /*
- * Copy the slot tuple and make sure any toasted fields get detoasted.
- */
- dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
-
- /*
- * Label the datum with the composite type info we identified before.
- */
- HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
- HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
-
- return PointerGetDatum(dtuple);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalWholeRowSlow
- *
- * Returns a Datum for a whole-row variable, in the "slow" case where
- * we can't just copy the subplan's output.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
- bool *isNull)
-{
- Var *variable = (Var *) wrvstate->xprstate.expr;
- TupleTableSlot *slot;
- HeapTuple tuple;
- TupleDesc tupleDesc;
- TupleDesc var_tupdesc;
- HeapTupleHeader dtuple;
- int i;
-
- *isNull = false;
-
- /* Get the input slot we want */
- switch (variable->varno)
- {
- case INNER_VAR: /* get the tuple from the inner node */
- slot = econtext->ecxt_innertuple;
- break;
-
- case OUTER_VAR: /* get the tuple from the outer node */
- slot = econtext->ecxt_outertuple;
- break;
-
- /* INDEX_VAR is handled by default case */
-
- default: /* get the tuple from the relation being
- * scanned */
- slot = econtext->ecxt_scantuple;
- break;
- }
-
- /* Apply the junkfilter if any */
- if (wrvstate->wrv_junkFilter != NULL)
- slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot);
-
- tuple = ExecFetchSlotTuple(slot);
- tupleDesc = slot->tts_tupleDescriptor;
-
- /* wrv_tupdesc is a good enough representation of the Var's rowtype */
- Assert(variable->vartype != RECORDOID);
- var_tupdesc = wrvstate->wrv_tupdesc;
-
- /* Check to see if any dropped attributes are non-null */
- for (i = 0; i < var_tupdesc->natts; i++)
- {
- Form_pg_attribute vattr = var_tupdesc->attrs[i];
- Form_pg_attribute sattr = tupleDesc->attrs[i];
-
- if (!vattr->attisdropped)
- continue; /* already checked non-dropped cols */
- if (heap_attisnull(tuple, i + 1))
- continue; /* null is always okay */
- if (vattr->attlen != sattr->attlen ||
- vattr->attalign != sattr->attalign)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("table row type and query-specified row type do not match"),
- errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
- i + 1)));
- }
-
- /*
- * Copy the slot tuple and make sure any toasted fields get detoasted.
- */
- dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot));
-
- /*
- * Label the datum with the composite type info we identified before.
- */
- HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid);
- HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod);
-
- return PointerGetDatum(dtuple);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalConst
- *
- * Returns the value of a constant.
- *
- * Note that for pass-by-ref datatypes, we return a pointer to the
- * actual constant node. This is one of the reasons why functions
- * must treat their input arguments as read-only.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalConst(ExprState *exprstate, ExprContext *econtext,
- bool *isNull)
-{
- Const *con = (Const *) exprstate->expr;
-
- *isNull = con->constisnull;
- return con->constvalue;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalParamExec
- *
- * Returns the value of a PARAM_EXEC parameter.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext,
- bool *isNull)
-{
- Param *expression = (Param *) exprstate->expr;
- int thisParamId = expression->paramid;
- ParamExecData *prm;
-
- /*
- * PARAM_EXEC params (internal executor parameters) are stored in the
- * ecxt_param_exec_vals array, and can be accessed by array index.
- */
- prm = &(econtext->ecxt_param_exec_vals[thisParamId]);
- if (prm->execPlan != NULL)
- {
- /* Parameter not evaluated yet, so go do it */
- ExecSetParamPlan(prm->execPlan, econtext);
- /* ExecSetParamPlan should have processed this param... */
- Assert(prm->execPlan == NULL);
- }
- *isNull = prm->isnull;
- return prm->value;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalParamExtern
- *
- * Returns the value of a PARAM_EXTERN parameter.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext,
- bool *isNull)
-{
- Param *expression = (Param *) exprstate->expr;
- int thisParamId = expression->paramid;
- ParamListInfo paramInfo = econtext->ecxt_param_list_info;
-
- /*
- * PARAM_EXTERN parameters must be sought in ecxt_param_list_info.
- */
- if (paramInfo &&
- thisParamId > 0 && thisParamId <= paramInfo->numParams)
- {
- ParamExternData *prm = ¶mInfo->params[thisParamId - 1];
-
- /* give hook a chance in case parameter is dynamic */
- if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
- (*paramInfo->paramFetch) (paramInfo, thisParamId);
-
- if (OidIsValid(prm->ptype))
- {
- /* safety check in case hook did something unexpected */
- if (prm->ptype != expression->paramtype)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
- thisParamId,
- format_type_be(prm->ptype),
- format_type_be(expression->paramtype))));
-
- *isNull = prm->isnull;
- return prm->value;
- }
- }
-
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("no value found for parameter %d", thisParamId)));
- return (Datum) 0; /* keep compiler quiet */
-}
-
-
-/* ----------------------------------------------------------------
- * ExecEvalOper / ExecEvalFunc support routines
- * ----------------------------------------------------------------
- */
-
-/*
- * GetAttributeByName
- * GetAttributeByNum
- *
- * These functions return the value of the requested attribute
- * out of the given tuple Datum.
- * C functions which take a tuple as an argument are expected
- * to use these. Ex: overpaid(EMP) might call GetAttributeByNum().
- * Note: these are actually rather slow because they do a typcache
- * lookup on each call.
- */
-Datum
-GetAttributeByNum(HeapTupleHeader tuple,
- AttrNumber attrno,
- bool *isNull)
-{
- Datum result;
- Oid tupType;
- int32 tupTypmod;
- TupleDesc tupDesc;
- HeapTupleData tmptup;
-
- if (!AttributeNumberIsValid(attrno))
- elog(ERROR, "invalid attribute number %d", attrno);
-
- if (isNull == NULL)
- elog(ERROR, "a NULL isNull pointer was passed");
-
- if (tuple == NULL)
- {
- /* Kinda bogus but compatible with old behavior... */
- *isNull = true;
- return (Datum) 0;
- }
-
- tupType = HeapTupleHeaderGetTypeId(tuple);
- tupTypmod = HeapTupleHeaderGetTypMod(tuple);
- tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
-
- /*
- * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all
- * the fields in the struct just in case user tries to inspect system
- * columns.
- */
- tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
- ItemPointerSetInvalid(&(tmptup.t_self));
- tmptup.t_tableOid = InvalidOid;
- tmptup.t_data = tuple;
-
- result = heap_getattr(&tmptup,
- attrno,
- tupDesc,
- isNull);
-
- ReleaseTupleDesc(tupDesc);
-
- return result;
-}
-
-Datum
-GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
-{
- AttrNumber attrno;
- Datum result;
- Oid tupType;
- int32 tupTypmod;
- TupleDesc tupDesc;
- HeapTupleData tmptup;
- int i;
-
- if (attname == NULL)
- elog(ERROR, "invalid attribute name");
-
- if (isNull == NULL)
- elog(ERROR, "a NULL isNull pointer was passed");
-
- if (tuple == NULL)
- {
- /* Kinda bogus but compatible with old behavior... */
- *isNull = true;
- return (Datum) 0;
- }
-
- tupType = HeapTupleHeaderGetTypeId(tuple);
- tupTypmod = HeapTupleHeaderGetTypMod(tuple);
- tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
-
- attrno = InvalidAttrNumber;
- for (i = 0; i < tupDesc->natts; i++)
- {
- if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0)
- {
- attrno = tupDesc->attrs[i]->attnum;
- break;
- }
- }
-
- if (attrno == InvalidAttrNumber)
- elog(ERROR, "attribute \"%s\" does not exist", attname);
-
- /*
- * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all
- * the fields in the struct just in case user tries to inspect system
- * columns.
- */
- tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
- ItemPointerSetInvalid(&(tmptup.t_self));
- tmptup.t_tableOid = InvalidOid;
- tmptup.t_data = tuple;
-
- result = heap_getattr(&tmptup,
- attrno,
- tupDesc,
- isNull);
-
- ReleaseTupleDesc(tupDesc);
-
- return result;
-}
-
-/*
- * init_fcache - initialize a FuncExprState node during first use
- */
-static void
-init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache,
- MemoryContext fcacheCxt, bool allowSRF, bool needDescForSRF)
-{
- AclResult aclresult;
-
- /* Check permission to call function */
- aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid));
- InvokeFunctionExecuteHook(foid);
-
- /*
- * Safety check on nargs. Under normal circumstances this should never
- * fail, as parser should check sooner. But possibly it might fail if
- * server has been compiled with FUNC_MAX_ARGS smaller than some functions
- * declared in pg_proc?
- */
- if (list_length(fcache->args) > FUNC_MAX_ARGS)
- ereport(ERROR,
- (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
- errmsg_plural("cannot pass more than %d argument to a function",
- "cannot pass more than %d arguments to a function",
- FUNC_MAX_ARGS,
- FUNC_MAX_ARGS)));
-
- /* Set up the primary fmgr lookup information */
- fmgr_info_cxt(foid, &(fcache->func), fcacheCxt);
- fmgr_info_set_expr((Node *) fcache->xprstate.expr, &(fcache->func));
-
- /* Initialize the function call parameter struct as well */
- InitFunctionCallInfoData(fcache->fcinfo_data, &(fcache->func),
- list_length(fcache->args),
- input_collation, NULL, NULL);
-
- /* If function returns set, check if that's allowed by caller */
- if (fcache->func.fn_retset && !allowSRF)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("set-valued function called in context that cannot accept a set")));
-
- /* Otherwise, ExecInitExpr should have marked the fcache correctly */
- Assert(fcache->func.fn_retset == fcache->funcReturnsSet);
-
- /* If function returns set, prepare expected tuple descriptor */
- if (fcache->func.fn_retset && needDescForSRF)
- {
- TypeFuncClass functypclass;
- Oid funcrettype;
- TupleDesc tupdesc;
- MemoryContext oldcontext;
-
- functypclass = get_expr_result_type(fcache->func.fn_expr,
- &funcrettype,
- &tupdesc);
-
- /* Must save tupdesc in fcache's context */
- oldcontext = MemoryContextSwitchTo(fcacheCxt);
-
- if (functypclass == TYPEFUNC_COMPOSITE)
- {
- /* Composite data type, e.g. a table's row type */
- Assert(tupdesc);
- /* Must copy it out of typcache for safety */
- fcache->funcResultDesc = CreateTupleDescCopy(tupdesc);
- fcache->funcReturnsTuple = true;
- }
- else if (functypclass == TYPEFUNC_SCALAR)
- {
- /* Base data type, i.e. scalar */
- tupdesc = CreateTemplateTupleDesc(1, false);
- TupleDescInitEntry(tupdesc,
- (AttrNumber) 1,
- NULL,
- funcrettype,
- -1,
- 0);
- fcache->funcResultDesc = tupdesc;
- fcache->funcReturnsTuple = false;
- }
- else if (functypclass == TYPEFUNC_RECORD)
- {
- /* This will work if function doesn't need an expectedDesc */
- fcache->funcResultDesc = NULL;
- fcache->funcReturnsTuple = true;
- }
- else
- {
- /* Else, we will fail if function needs an expectedDesc */
- fcache->funcResultDesc = NULL;
- }
-
- MemoryContextSwitchTo(oldcontext);
- }
- else
- fcache->funcResultDesc = NULL;
-
- /* Initialize additional state */
- fcache->funcResultStore = NULL;
- fcache->funcResultSlot = NULL;
- fcache->shutdown_reg = false;
-}
-
-/*
- * callback function in case a FuncExpr returning a set needs to be shut down
- * before it has been run to completion
- */
-static void
-ShutdownFuncExpr(Datum arg)
-{
- FuncExprState *fcache = (FuncExprState *) DatumGetPointer(arg);
-
- /* If we have a slot, make sure it's let go of any tuplestore pointer */
- if (fcache->funcResultSlot)
- ExecClearTuple(fcache->funcResultSlot);
-
- /* Release any open tuplestore */
- if (fcache->funcResultStore)
- tuplestore_end(fcache->funcResultStore);
- fcache->funcResultStore = NULL;
-
- /* Clear any active set-argument state */
- fcache->setArgsValid = false;
-
- /* execUtils will deregister the callback... */
- fcache->shutdown_reg = false;
-}
-
-/*
- * get_cached_rowtype: utility function to lookup a rowtype tupdesc
- *
- * type_id, typmod: identity of the rowtype
- * cache_field: where to cache the TupleDesc pointer in expression state node
- * (field must be initialized to NULL)
- * econtext: expression context we are executing in
- *
- * NOTE: because the shutdown callback will be called during plan rescan,
- * must be prepared to re-do this during any node execution; cannot call
- * just once during expression initialization
- */
-static TupleDesc
-get_cached_rowtype(Oid type_id, int32 typmod,
- TupleDesc *cache_field, ExprContext *econtext)
-{
- TupleDesc tupDesc = *cache_field;
-
- /* Do lookup if no cached value or if requested type changed */
- if (tupDesc == NULL ||
- type_id != tupDesc->tdtypeid ||
- typmod != tupDesc->tdtypmod)
- {
- tupDesc = lookup_rowtype_tupdesc(type_id, typmod);
-
- if (*cache_field)
- {
- /* Release old tupdesc; but callback is already registered */
- ReleaseTupleDesc(*cache_field);
- }
- else
- {
- /* Need to register shutdown callback to release tupdesc */
- RegisterExprContextCallback(econtext,
- ShutdownTupleDescRef,
- PointerGetDatum(cache_field));
- }
- *cache_field = tupDesc;
- }
- return tupDesc;
-}
-
-/*
- * Callback function to release a tupdesc refcount at expression tree shutdown
- */
-static void
-ShutdownTupleDescRef(Datum arg)
-{
- TupleDesc *cache_field = (TupleDesc *) DatumGetPointer(arg);
-
- if (*cache_field)
- ReleaseTupleDesc(*cache_field);
- *cache_field = NULL;
-}
-
-/*
- * Evaluate arguments for a function.
- */
-static void
-ExecEvalFuncArgs(FunctionCallInfo fcinfo,
- List *argList,
- ExprContext *econtext)
-{
- int i;
- ListCell *arg;
-
- i = 0;
- foreach(arg, argList)
- {
- ExprState *argstate = (ExprState *) lfirst(arg);
-
- fcinfo->arg[i] = ExecEvalExpr(argstate,
- econtext,
- &fcinfo->argnull[i]);
- i++;
- }
-
- Assert(i == fcinfo->nargs);
-}
-
-/*
- * ExecPrepareTuplestoreResult
- *
- * Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a
- * tuplestore function result. We must set up a funcResultSlot (unless
- * already done in a previous call cycle) and verify that the function
- * returned the expected tuple descriptor.
- */
-static void
-ExecPrepareTuplestoreResult(FuncExprState *fcache,
- ExprContext *econtext,
- Tuplestorestate *resultStore,
- TupleDesc resultDesc)
-{
- fcache->funcResultStore = resultStore;
-
- if (fcache->funcResultSlot == NULL)
- {
- /* Create a slot so we can read data out of the tuplestore */
- TupleDesc slotDesc;
- MemoryContext oldcontext;
-
- oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt);
-
- /*
- * If we were not able to determine the result rowtype from context,
- * and the function didn't return a tupdesc, we have to fail.
- */
- if (fcache->funcResultDesc)
- slotDesc = fcache->funcResultDesc;
- else if (resultDesc)
- {
- /* don't assume resultDesc is long-lived */
- slotDesc = CreateTupleDescCopy(resultDesc);
- }
- else
- {
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("function returning setof record called in "
- "context that cannot accept type record")));
- slotDesc = NULL; /* keep compiler quiet */
- }
-
- fcache->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
- MemoryContextSwitchTo(oldcontext);
- }
-
- /*
- * If function provided a tupdesc, cross-check it. We only really need to
- * do this for functions returning RECORD, but might as well do it always.
- */
- if (resultDesc)
- {
- if (fcache->funcResultDesc)
- tupledesc_match(fcache->funcResultDesc, resultDesc);
-
- /*
- * If it is a dynamically-allocated TupleDesc, free it: it is
- * typically allocated in a per-query context, so we must avoid
- * leaking it across multiple usages.
- */
- if (resultDesc->tdrefcount == -1)
- FreeTupleDesc(resultDesc);
- }
-
- /* Register cleanup callback if we didn't already */
- if (!fcache->shutdown_reg)
- {
- RegisterExprContextCallback(econtext,
- ShutdownFuncExpr,
- PointerGetDatum(fcache));
- fcache->shutdown_reg = true;
- }
-}
-
-/*
- * Check that function result tuple type (src_tupdesc) matches or can
- * be considered to match what the query expects (dst_tupdesc). If
- * they don't match, ereport.
- *
- * We really only care about number of attributes and data type.
- * Also, we can ignore type mismatch on columns that are dropped in the
- * destination type, so long as the physical storage matches. This is
- * helpful in some cases involving out-of-date cached plans.
- */
-static void
-tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
-{
- int i;
-
- if (dst_tupdesc->natts != src_tupdesc->natts)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("function return row and query-specified return row do not match"),
- errdetail_plural("Returned row contains %d attribute, but query expects %d.",
- "Returned row contains %d attributes, but query expects %d.",
- src_tupdesc->natts,
- src_tupdesc->natts, dst_tupdesc->natts)));
-
- for (i = 0; i < dst_tupdesc->natts; i++)
- {
- Form_pg_attribute dattr = dst_tupdesc->attrs[i];
- Form_pg_attribute sattr = src_tupdesc->attrs[i];
-
- if (IsBinaryCoercible(sattr->atttypid, dattr->atttypid))
- continue; /* no worries */
- if (!dattr->attisdropped)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("function return row and query-specified return row do not match"),
- errdetail("Returned type %s at ordinal position %d, but query expects %s.",
- format_type_be(sattr->atttypid),
- i + 1,
- format_type_be(dattr->atttypid))));
-
- if (dattr->attlen != sattr->attlen ||
- dattr->attalign != sattr->attalign)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("function return row and query-specified return row do not match"),
- errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
- i + 1)));
- }
-}
-
-/*
- * ExecMakeFunctionResultSet
- *
- * Evaluate the arguments to a set-returning function and then call the
- * function itself. The argument expressions may not contain set-returning
- * functions (the planner is supposed to have separated evaluation for those).
- */
-Datum
-ExecMakeFunctionResultSet(FuncExprState *fcache,
- ExprContext *econtext,
- bool *isNull,
- ExprDoneCond *isDone)
-{
- List *arguments;
- Datum result;
- FunctionCallInfo fcinfo;
- PgStat_FunctionCallUsage fcusage;
- ReturnSetInfo rsinfo;
- bool callit;
- int i;
-
-restart:
-
- /* Guard against stack overflow due to overly complex expressions */
- check_stack_depth();
-
- /*
- * Initialize function cache if first time through. The expression node
- * could be either a FuncExpr or an OpExpr.
- */
- if (fcache->func.fn_oid == InvalidOid)
- {
- if (IsA(fcache->xprstate.expr, FuncExpr))
- {
- FuncExpr *func = (FuncExpr *) fcache->xprstate.expr;
-
- init_fcache(func->funcid, func->inputcollid, fcache,
- econtext->ecxt_per_query_memory, true, true);
- }
- else if (IsA(fcache->xprstate.expr, OpExpr))
- {
- OpExpr *op = (OpExpr *) fcache->xprstate.expr;
-
- init_fcache(op->opfuncid, op->inputcollid, fcache,
- econtext->ecxt_per_query_memory, true, true);
- }
- else
- elog(ERROR, "unrecognized node type: %d",
- (int) nodeTag(fcache->xprstate.expr));
-
- /* shouldn't get here otherwise */
- Assert(fcache->func.fn_retset);
- }
-
- /*
- * If a previous call of the function returned a set result in the form of
- * a tuplestore, continue reading rows from the tuplestore until it's
- * empty.
- */
- if (fcache->funcResultStore)
- {
- if (tuplestore_gettupleslot(fcache->funcResultStore, true, false,
- fcache->funcResultSlot))
- {
- *isDone = ExprMultipleResult;
- if (fcache->funcReturnsTuple)
- {
- /* We must return the whole tuple as a Datum. */
- *isNull = false;
- return ExecFetchSlotTupleDatum(fcache->funcResultSlot);
- }
- else
- {
- /* Extract the first column and return it as a scalar. */
- return slot_getattr(fcache->funcResultSlot, 1, isNull);
- }
- }
- /* Exhausted the tuplestore, so clean up */
- tuplestore_end(fcache->funcResultStore);
- fcache->funcResultStore = NULL;
- *isDone = ExprEndResult;
- *isNull = true;
- return (Datum) 0;
- }
-
- /*
- * arguments is a list of expressions to evaluate before passing to the
- * function manager. We skip the evaluation if it was already done in the
- * previous call (ie, we are continuing the evaluation of a set-valued
- * function). Otherwise, collect the current argument values into fcinfo.
- */
- fcinfo = &fcache->fcinfo_data;
- arguments = fcache->args;
- if (!fcache->setArgsValid)
- ExecEvalFuncArgs(fcinfo, arguments, econtext);
- else
- {
- /* Reset flag (we may set it again below) */
- fcache->setArgsValid = false;
- }
-
- /*
- * Now call the function, passing the evaluated parameter values.
- */
-
- /* Prepare a resultinfo node for communication. */
- fcinfo->resultinfo = (Node *) &rsinfo;
- rsinfo.type = T_ReturnSetInfo;
- rsinfo.econtext = econtext;
- rsinfo.expectedDesc = fcache->funcResultDesc;
- rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
- /* note we do not set SFRM_Materialize_Random or _Preferred */
- rsinfo.returnMode = SFRM_ValuePerCall;
- /* isDone is filled below */
- rsinfo.setResult = NULL;
- rsinfo.setDesc = NULL;
-
- /*
- * If function is strict, and there are any NULL arguments, skip calling
- * the function.
- */
- callit = true;
- if (fcache->func.fn_strict)
- {
- for (i = 0; i < fcinfo->nargs; i++)
- {
- if (fcinfo->argnull[i])
- {
- callit = false;
- break;
- }
- }
- }
-
- if (callit)
- {
- pgstat_init_function_usage(fcinfo, &fcusage);
-
- fcinfo->isnull = false;
- rsinfo.isDone = ExprSingleResult;
- result = FunctionCallInvoke(fcinfo);
- *isNull = fcinfo->isnull;
- *isDone = rsinfo.isDone;
-
- pgstat_end_function_usage(&fcusage,
- rsinfo.isDone != ExprMultipleResult);
- }
- else
- {
- /* for a strict SRF, result for NULL is an empty set */
- result = (Datum) 0;
- *isNull = true;
- *isDone = ExprEndResult;
- }
-
- /* Which protocol does function want to use? */
- if (rsinfo.returnMode == SFRM_ValuePerCall)
- {
- if (*isDone != ExprEndResult)
- {
- /*
- * Save the current argument values to re-use on the next call.
- */
- if (*isDone == ExprMultipleResult)
- {
- fcache->setArgsValid = true;
- /* Register cleanup callback if we didn't already */
- if (!fcache->shutdown_reg)
- {
- RegisterExprContextCallback(econtext,
- ShutdownFuncExpr,
- PointerGetDatum(fcache));
- fcache->shutdown_reg = true;
- }
- }
- }
- }
- else if (rsinfo.returnMode == SFRM_Materialize)
- {
- /* check we're on the same page as the function author */
- if (rsinfo.isDone != ExprSingleResult)
- ereport(ERROR,
- (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
- errmsg("table-function protocol for materialize mode was not followed")));
- if (rsinfo.setResult != NULL)
- {
- /* prepare to return values from the tuplestore */
- ExecPrepareTuplestoreResult(fcache, econtext,
- rsinfo.setResult,
- rsinfo.setDesc);
- /* loop back to top to start returning from tuplestore */
- goto restart;
- }
- /* if setResult was left null, treat it as empty set */
- *isDone = ExprEndResult;
- *isNull = true;
- result = (Datum) 0;
- }
- else
- ereport(ERROR,
- (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
- errmsg("unrecognized table-function returnMode: %d",
- (int) rsinfo.returnMode)));
-
- return result;
-}
-
-/*
- * ExecMakeFunctionResultNoSets
- *
- * Evaluate a function or operator node with a non-set-returning function.
- * Assumes init_fcache() already done. Hand-tuned for speed.
- */
-static Datum
-ExecMakeFunctionResultNoSets(FuncExprState *fcache,
- ExprContext *econtext,
- bool *isNull)
-{
- ListCell *arg;
- Datum result;
- FunctionCallInfo fcinfo;
- PgStat_FunctionCallUsage fcusage;
- int i;
-
- /* Guard against stack overflow due to overly complex expressions */
- check_stack_depth();
-
- /* inlined, simplified version of ExecEvalFuncArgs */
- fcinfo = &fcache->fcinfo_data;
- i = 0;
- foreach(arg, fcache->args)
- {
- ExprState *argstate = (ExprState *) lfirst(arg);
-
- fcinfo->arg[i] = ExecEvalExpr(argstate,
- econtext,
- &fcinfo->argnull[i]);
- i++;
- }
-
- /*
- * If function is strict, and there are any NULL arguments, skip calling
- * the function and return NULL.
- */
- if (fcache->func.fn_strict)
- {
- while (--i >= 0)
- {
- if (fcinfo->argnull[i])
- {
- *isNull = true;
- return (Datum) 0;
- }
- }
- }
-
- pgstat_init_function_usage(fcinfo, &fcusage);
-
- fcinfo->isnull = false;
- result = FunctionCallInvoke(fcinfo);
- *isNull = fcinfo->isnull;
-
- pgstat_end_function_usage(&fcusage, true);
-
- return result;
-}
-
-
-/*
- * ExecMakeTableFunctionResult
- *
- * Evaluate a table function, producing a materialized result in a Tuplestore
- * object.
- */
-Tuplestorestate *
-ExecMakeTableFunctionResult(ExprState *funcexpr,
- ExprContext *econtext,
- MemoryContext argContext,
- TupleDesc expectedDesc,
- bool randomAccess)
-{
- Tuplestorestate *tupstore = NULL;
- TupleDesc tupdesc = NULL;
- Oid funcrettype;
- bool returnsTuple;
- bool returnsSet = false;
- FunctionCallInfoData fcinfo;
- PgStat_FunctionCallUsage fcusage;
- ReturnSetInfo rsinfo;
- HeapTupleData tmptup;
- MemoryContext callerContext;
- MemoryContext oldcontext;
- bool direct_function_call;
- bool first_time = true;
-
- callerContext = CurrentMemoryContext;
-
- funcrettype = exprType((Node *) funcexpr->expr);
-
- returnsTuple = type_is_rowtype(funcrettype);
-
- /*
- * Prepare a resultinfo node for communication. We always do this even if
- * not expecting a set result, so that we can pass expectedDesc. In the
- * generic-expression case, the expression doesn't actually get to see the
- * resultinfo, but set it up anyway because we use some of the fields as
- * our own state variables.
- */
- rsinfo.type = T_ReturnSetInfo;
- rsinfo.econtext = econtext;
- rsinfo.expectedDesc = expectedDesc;
- rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
- if (randomAccess)
- rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
- rsinfo.returnMode = SFRM_ValuePerCall;
- /* isDone is filled below */
- rsinfo.setResult = NULL;
- rsinfo.setDesc = NULL;
-
- /*
- * Normally the passed expression tree will be a FuncExprState, since the
- * grammar only allows a function call at the top level of a table
- * function reference. However, if the function doesn't return set then
- * the planner might have replaced the function call via constant-folding
- * or inlining. So if we see any other kind of expression node, execute
- * it via the general ExecEvalExpr() code; the only difference is that we
- * don't get a chance to pass a special ReturnSetInfo to any functions
- * buried in the expression.
- */
- if (funcexpr && IsA(funcexpr, FuncExprState) &&
- IsA(funcexpr->expr, FuncExpr))
- {
- FuncExprState *fcache = (FuncExprState *) funcexpr;
-
- /*
- * This path is similar to ExecMakeFunctionResultSet.
- */
- direct_function_call = true;
-
- /*
- * Initialize function cache if first time through
- */
- if (fcache->func.fn_oid == InvalidOid)
- {
- FuncExpr *func = (FuncExpr *) fcache->xprstate.expr;
-
- init_fcache(func->funcid, func->inputcollid, fcache,
- econtext->ecxt_per_query_memory, true, false);
- }
- returnsSet = fcache->func.fn_retset;
- InitFunctionCallInfoData(fcinfo, &(fcache->func),
- list_length(fcache->args),
- fcache->fcinfo_data.fncollation,
- NULL, (Node *) &rsinfo);
-
- /*
- * Evaluate the function's argument list.
- *
- * We can't do this in the per-tuple context: the argument values
- * would disappear when we reset that context in the inner loop. And
- * the caller's CurrentMemoryContext is typically a query-lifespan
- * context, so we don't want to leak memory there. We require the
- * caller to pass a separate memory context that can be used for this,
- * and can be reset each time through to avoid bloat.
- */
- MemoryContextReset(argContext);
- oldcontext = MemoryContextSwitchTo(argContext);
- ExecEvalFuncArgs(&fcinfo, fcache->args, econtext);
- MemoryContextSwitchTo(oldcontext);
-
- /*
- * If function is strict, and there are any NULL arguments, skip
- * calling the function and act like it returned NULL (or an empty
- * set, in the returns-set case).
- */
- if (fcache->func.fn_strict)
- {
- int i;
-
- for (i = 0; i < fcinfo.nargs; i++)
- {
- if (fcinfo.argnull[i])
- goto no_function_result;
- }
- }
- }
- else
- {
- /* Treat funcexpr as a generic expression */
- direct_function_call = false;
- InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
- }
-
- /*
- * Switch to short-lived context for calling the function or expression.
- */
- MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-
- /*
- * Loop to handle the ValuePerCall protocol (which is also the same
- * behavior needed in the generic ExecEvalExpr path).
- */
- for (;;)
- {
- Datum result;
-
- CHECK_FOR_INTERRUPTS();
-
- /*
- * reset per-tuple memory context before each call of the function or
- * expression. This cleans up any local memory the function may leak
- * when called.
- */
- ResetExprContext(econtext);
-
- /* Call the function or expression one time */
- if (direct_function_call)
- {
- pgstat_init_function_usage(&fcinfo, &fcusage);
-
- fcinfo.isnull = false;
- rsinfo.isDone = ExprSingleResult;
- result = FunctionCallInvoke(&fcinfo);
-
- pgstat_end_function_usage(&fcusage,
- rsinfo.isDone != ExprMultipleResult);
- }
- else
- {
- result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull);
- rsinfo.isDone = ExprSingleResult;
- }
-
- /* Which protocol does function want to use? */
- if (rsinfo.returnMode == SFRM_ValuePerCall)
- {
- /*
- * Check for end of result set.
- */
- if (rsinfo.isDone == ExprEndResult)
- break;
-
- /*
- * If first time through, build tuplestore for result. For a
- * scalar function result type, also make a suitable tupdesc.
- */
- if (first_time)
- {
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo.setResult = tupstore;
- if (!returnsTuple)
- {
- tupdesc = CreateTemplateTupleDesc(1, false);
- TupleDescInitEntry(tupdesc,
- (AttrNumber) 1,
- "column",
- funcrettype,
- -1,
- 0);
- rsinfo.setDesc = tupdesc;
- }
- MemoryContextSwitchTo(oldcontext);
- }
-
- /*
- * Store current resultset item.
- */
- if (returnsTuple)
- {
- if (!fcinfo.isnull)
- {
- HeapTupleHeader td = DatumGetHeapTupleHeader(result);
-
- if (tupdesc == NULL)
- {
- /*
- * This is the first non-NULL result from the
- * function. Use the type info embedded in the
- * rowtype Datum to look up the needed tupdesc. Make
- * a copy for the query.
- */
- oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
- HeapTupleHeaderGetTypMod(td));
- rsinfo.setDesc = tupdesc;
- MemoryContextSwitchTo(oldcontext);
- }
- else
- {
- /*
- * Verify all later returned rows have same subtype;
- * necessary in case the type is RECORD.
- */
- if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
- HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("rows returned by function are not all of the same row type")));
- }
-
- /*
- * tuplestore_puttuple needs a HeapTuple not a bare
- * HeapTupleHeader, but it doesn't need all the fields.
- */
- tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
- tmptup.t_data = td;
-
- tuplestore_puttuple(tupstore, &tmptup);
- }
- else
- {
- /*
- * NULL result from a tuple-returning function; expand it
- * to a row of all nulls. We rely on the expectedDesc to
- * form such rows. (Note: this would be problematic if
- * tuplestore_putvalues saved the tdtypeid/tdtypmod from
- * the provided descriptor, since that might not match
- * what we get from the function itself. But it doesn't.)
- */
- int natts = expectedDesc->natts;
- bool *nullflags;
-
- nullflags = (bool *) palloc(natts * sizeof(bool));
- memset(nullflags, true, natts * sizeof(bool));
- tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
- }
- }
- else
- {
- /* Scalar-type case: just store the function result */
- tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
- }
-
- /*
- * Are we done?
- */
- if (rsinfo.isDone != ExprMultipleResult)
- break;
- }
- else if (rsinfo.returnMode == SFRM_Materialize)
- {
- /* check we're on the same page as the function author */
- if (!first_time || rsinfo.isDone != ExprSingleResult)
- ereport(ERROR,
- (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
- errmsg("table-function protocol for materialize mode was not followed")));
- /* Done evaluating the set result */
- break;
- }
- else
- ereport(ERROR,
- (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
- errmsg("unrecognized table-function returnMode: %d",
- (int) rsinfo.returnMode)));
-
- first_time = false;
- }
-
-no_function_result:
-
- /*
- * If we got nothing from the function (ie, an empty-set or NULL result),
- * we have to create the tuplestore to return, and if it's a
- * non-set-returning function then insert a single all-nulls row. As
- * above, we depend on the expectedDesc to manufacture the dummy row.
- */
- if (rsinfo.setResult == NULL)
- {
- MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
- tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
- rsinfo.setResult = tupstore;
- if (!returnsSet)
- {
- int natts = expectedDesc->natts;
- bool *nullflags;
-
- MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
- nullflags = (bool *) palloc(natts * sizeof(bool));
- memset(nullflags, true, natts * sizeof(bool));
- tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
- }
- }
-
- /*
- * If function provided a tupdesc, cross-check it. We only really need to
- * do this for functions returning RECORD, but might as well do it always.
- */
- if (rsinfo.setDesc)
- {
- tupledesc_match(expectedDesc, rsinfo.setDesc);
-
- /*
- * If it is a dynamically-allocated TupleDesc, free it: it is
- * typically allocated in a per-query context, so we must avoid
- * leaking it across multiple usages.
- */
- if (rsinfo.setDesc->tdrefcount == -1)
- FreeTupleDesc(rsinfo.setDesc);
- }
-
- MemoryContextSwitchTo(callerContext);
-
- /* All done, pass back the tuplestore */
- return rsinfo.setResult;
-}
-
-
-/* ----------------------------------------------------------------
- * ExecEvalFunc
- * ExecEvalOper
- *
- * Evaluate the functional result of a list of arguments by calling the
- * function manager.
- * ----------------------------------------------------------------
- */
-
-/* ----------------------------------------------------------------
- * ExecEvalFunc
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalFunc(FuncExprState *fcache,
- ExprContext *econtext,
- bool *isNull)
-{
- /* This is called only the first time through */
- FuncExpr *func = (FuncExpr *) fcache->xprstate.expr;
-
- /* Initialize function lookup info */
- init_fcache(func->funcid, func->inputcollid, fcache,
- econtext->ecxt_per_query_memory, false, false);
-
- /* Change the evalfunc pointer to save a few cycles in additional calls */
- fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets;
- return ExecMakeFunctionResultNoSets(fcache, econtext, isNull);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalOper
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalOper(FuncExprState *fcache,
- ExprContext *econtext,
- bool *isNull)
-{
- /* This is called only the first time through */
- OpExpr *op = (OpExpr *) fcache->xprstate.expr;
-
- /* Initialize function lookup info */
- init_fcache(op->opfuncid, op->inputcollid, fcache,
- econtext->ecxt_per_query_memory, false, false);
-
- /* Change the evalfunc pointer to save a few cycles in additional calls */
- fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets;
- return ExecMakeFunctionResultNoSets(fcache, econtext, isNull);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalDistinct
- *
- * IS DISTINCT FROM must evaluate arguments to determine whether
- * they are NULL; if either is NULL then the result is already
- * known. If neither is NULL, then proceed to evaluate the
- * function. Note that this is *always* derived from the equals
- * operator, but since we need special processing of the arguments
- * we can not simply reuse ExecEvalOper() or ExecEvalFunc().
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalDistinct(FuncExprState *fcache,
- ExprContext *econtext,
- bool *isNull)
-{
- Datum result;
- FunctionCallInfo fcinfo;
-
- /* Set non-null as default */
- *isNull = false;
-
- /*
- * Initialize function cache if first time through
- */
- if (fcache->func.fn_oid == InvalidOid)
- {
- DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr;
-
- init_fcache(op->opfuncid, op->inputcollid, fcache,
- econtext->ecxt_per_query_memory, false, false);
- }
-
- /*
- * Evaluate arguments
- */
- fcinfo = &fcache->fcinfo_data;
- ExecEvalFuncArgs(fcinfo, fcache->args, econtext);
- Assert(fcinfo->nargs == 2);
-
- if (fcinfo->argnull[0] && fcinfo->argnull[1])
- {
- /* Both NULL? Then is not distinct... */
- result = BoolGetDatum(FALSE);
- }
- else if (fcinfo->argnull[0] || fcinfo->argnull[1])
- {
- /* Only one is NULL? Then is distinct... */
- result = BoolGetDatum(TRUE);
- }
- else
- {
- fcinfo->isnull = false;
- result = FunctionCallInvoke(fcinfo);
- *isNull = fcinfo->isnull;
- /* Must invert result of "=" */
- result = BoolGetDatum(!DatumGetBool(result));
- }
-
- return result;
-}
-
-/*
- * ExecEvalScalarArrayOp
- *
- * Evaluate "scalar op ANY/ALL (array)". The operator always yields boolean,
- * and we combine the results across all array elements using OR and AND
- * (for ANY and ALL respectively). Of course we short-circuit as soon as
- * the result is known.
- */
-static Datum
-ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate,
- ExprContext *econtext,
- bool *isNull)
-{
- ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) sstate->fxprstate.xprstate.expr;
- bool useOr = opexpr->useOr;
- ArrayType *arr;
- int nitems;
- Datum result;
- bool resultnull;
- FunctionCallInfo fcinfo;
- int i;
- int16 typlen;
- bool typbyval;
- char typalign;
- char *s;
- bits8 *bitmap;
- int bitmask;
-
- /* Set non-null as default */
- *isNull = false;
-
- /*
- * Initialize function cache if first time through
- */
- if (sstate->fxprstate.func.fn_oid == InvalidOid)
- {
- init_fcache(opexpr->opfuncid, opexpr->inputcollid, &sstate->fxprstate,
- econtext->ecxt_per_query_memory, false, false);
- }
-
- /*
- * Evaluate arguments
- */
- fcinfo = &sstate->fxprstate.fcinfo_data;
- ExecEvalFuncArgs(fcinfo, sstate->fxprstate.args, econtext);
- Assert(fcinfo->nargs == 2);
-
- /*
- * If the array is NULL then we return NULL --- it's not very meaningful
- * to do anything else, even if the operator isn't strict.
- */
- if (fcinfo->argnull[1])
- {
- *isNull = true;
- return (Datum) 0;
- }
- /* Else okay to fetch and detoast the array */
- arr = DatumGetArrayTypeP(fcinfo->arg[1]);
-
- /*
- * If the array is empty, we return either FALSE or TRUE per the useOr
- * flag. This is correct even if the scalar is NULL; since we would
- * evaluate the operator zero times, it matters not whether it would want
- * to return NULL.
- */
- nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
- if (nitems <= 0)
- return BoolGetDatum(!useOr);
-
- /*
- * If the scalar is NULL, and the function is strict, return NULL; no
- * point in iterating the loop.
- */
- if (fcinfo->argnull[0] && sstate->fxprstate.func.fn_strict)
- {
- *isNull = true;
- return (Datum) 0;
- }
-
- /*
- * We arrange to look up info about the element type only once per series
- * of calls, assuming the element type doesn't change underneath us.
- */
- if (sstate->element_type != ARR_ELEMTYPE(arr))
- {
- get_typlenbyvalalign(ARR_ELEMTYPE(arr),
- &sstate->typlen,
- &sstate->typbyval,
- &sstate->typalign);
- sstate->element_type = ARR_ELEMTYPE(arr);
- }
- typlen = sstate->typlen;
- typbyval = sstate->typbyval;
- typalign = sstate->typalign;
-
- result = BoolGetDatum(!useOr);
- resultnull = false;
-
- /* Loop over the array elements */
- s = (char *) ARR_DATA_PTR(arr);
- bitmap = ARR_NULLBITMAP(arr);
- bitmask = 1;
-
- for (i = 0; i < nitems; i++)
- {
- Datum elt;
- Datum thisresult;
-
- /* Get array element, checking for NULL */
- if (bitmap && (*bitmap & bitmask) == 0)
- {
- fcinfo->arg[1] = (Datum) 0;
- fcinfo->argnull[1] = true;
- }
- else
- {
- elt = fetch_att(s, typbyval, typlen);
- s = att_addlength_pointer(s, typlen, s);
- s = (char *) att_align_nominal(s, typalign);
- fcinfo->arg[1] = elt;
- fcinfo->argnull[1] = false;
- }
-
- /* Call comparison function */
- if (fcinfo->argnull[1] && sstate->fxprstate.func.fn_strict)
- {
- fcinfo->isnull = true;
- thisresult = (Datum) 0;
- }
- else
- {
- fcinfo->isnull = false;
- thisresult = FunctionCallInvoke(fcinfo);
- }
-
- /* Combine results per OR or AND semantics */
- if (fcinfo->isnull)
- resultnull = true;
- else if (useOr)
- {
- if (DatumGetBool(thisresult))
- {
- result = BoolGetDatum(true);
- resultnull = false;
- break; /* needn't look at any more elements */
- }
- }
- else
- {
- if (!DatumGetBool(thisresult))
- {
- result = BoolGetDatum(false);
- resultnull = false;
- break; /* needn't look at any more elements */
- }
- }
-
- /* advance bitmap pointer if any */
- if (bitmap)
- {
- bitmask <<= 1;
- if (bitmask == 0x100)
- {
- bitmap++;
- bitmask = 1;
- }
- }
- }
-
- *isNull = resultnull;
- return result;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalNot
- * ExecEvalOr
- * ExecEvalAnd
- *
- * Evaluate boolean expressions, with appropriate short-circuiting.
- *
- * The query planner reformulates clause expressions in the
- * qualification to conjunctive normal form. If we ever get
- * an AND to evaluate, we can be sure that it's not a top-level
- * clause in the qualification, but appears lower (as a function
- * argument, for example), or in the target list. Not that you
- * need to know this, mind you...
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalNot(BoolExprState *notclause, ExprContext *econtext,
- bool *isNull)
-{
- ExprState *clause = linitial(notclause->args);
- Datum expr_value;
-
- expr_value = ExecEvalExpr(clause, econtext, isNull);
-
- /*
- * if the expression evaluates to null, then we just cascade the null back
- * to whoever called us.
- */
- if (*isNull)
- return expr_value;
-
- /*
- * evaluation of 'not' is simple.. expr is false, then return 'true' and
- * vice versa.
- */
- return BoolGetDatum(!DatumGetBool(expr_value));
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalOr
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext,
- bool *isNull)
-{
- List *clauses = orExpr->args;
- ListCell *clause;
- bool AnyNull;
-
- AnyNull = false;
-
- /*
- * If any of the clauses is TRUE, the OR result is TRUE regardless of the
- * states of the rest of the clauses, so we can stop evaluating and return
- * TRUE immediately. If none are TRUE and one or more is NULL, we return
- * NULL; otherwise we return FALSE. This makes sense when you interpret
- * NULL as "don't know": if we have a TRUE then the OR is TRUE even if we
- * aren't sure about some of the other inputs. If all the known inputs are
- * FALSE, but we have one or more "don't knows", then we have to report
- * that we "don't know" what the OR's result should be --- perhaps one of
- * the "don't knows" would have been TRUE if we'd known its value. Only
- * when all the inputs are known to be FALSE can we state confidently that
- * the OR's result is FALSE.
- */
- foreach(clause, clauses)
- {
- ExprState *clausestate = (ExprState *) lfirst(clause);
- Datum clause_value;
-
- clause_value = ExecEvalExpr(clausestate, econtext, isNull);
-
- /*
- * if we have a non-null true result, then return it.
- */
- if (*isNull)
- AnyNull = true; /* remember we got a null */
- else if (DatumGetBool(clause_value))
- return clause_value;
- }
-
- /* AnyNull is true if at least one clause evaluated to NULL */
- *isNull = AnyNull;
- return BoolGetDatum(false);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalAnd
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext,
- bool *isNull)
-{
- List *clauses = andExpr->args;
- ListCell *clause;
- bool AnyNull;
-
- AnyNull = false;
-
- /*
- * If any of the clauses is FALSE, the AND result is FALSE regardless of
- * the states of the rest of the clauses, so we can stop evaluating and
- * return FALSE immediately. If none are FALSE and one or more is NULL,
- * we return NULL; otherwise we return TRUE. This makes sense when you
- * interpret NULL as "don't know", using the same sort of reasoning as for
- * OR, above.
- */
-
- foreach(clause, clauses)
- {
- ExprState *clausestate = (ExprState *) lfirst(clause);
- Datum clause_value;
-
- clause_value = ExecEvalExpr(clausestate, econtext, isNull);
-
- /*
- * if we have a non-null false result, then return it.
- */
- if (*isNull)
- AnyNull = true; /* remember we got a null */
- else if (!DatumGetBool(clause_value))
- return clause_value;
- }
-
- /* AnyNull is true if at least one clause evaluated to NULL */
- *isNull = AnyNull;
- return BoolGetDatum(!AnyNull);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalConvertRowtype
- *
- * Evaluate a rowtype coercion operation. This may require
- * rearranging field positions.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
- ExprContext *econtext,
- bool *isNull)
-{
- ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) cstate->xprstate.expr;
- HeapTuple result;
- Datum tupDatum;
- HeapTupleHeader tuple;
- HeapTupleData tmptup;
-
- tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull);
-
- /* this test covers the isDone exception too: */
- if (*isNull)
- return tupDatum;
-
- tuple = DatumGetHeapTupleHeader(tupDatum);
-
- /* Lookup tupdescs if first time through or after rescan */
- if (cstate->indesc == NULL)
- {
- get_cached_rowtype(exprType((Node *) convert->arg), -1,
- &cstate->indesc, econtext);
- cstate->initialized = false;
- }
- if (cstate->outdesc == NULL)
- {
- get_cached_rowtype(convert->resulttype, -1,
- &cstate->outdesc, econtext);
- cstate->initialized = false;
- }
-
- /*
- * We used to be able to assert that incoming tuples are marked with
- * exactly the rowtype of cstate->indesc. However, now that
- * ExecEvalWholeRowVar might change the tuples' marking to plain RECORD
- * due to inserting aliases, we can only make this weak test:
- */
- Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid ||
- HeapTupleHeaderGetTypeId(tuple) == RECORDOID);
-
- /* if first time through, initialize conversion map */
- if (!cstate->initialized)
- {
- MemoryContext old_cxt;
-
- /* allocate map in long-lived memory context */
- old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-
- /* prepare map from old to new attribute numbers */
- cstate->map = convert_tuples_by_name(cstate->indesc,
- cstate->outdesc,
- gettext_noop("could not convert row type"));
- cstate->initialized = true;
-
- MemoryContextSwitchTo(old_cxt);
- }
-
- /*
- * No-op if no conversion needed (not clear this can happen here).
- */
- if (cstate->map == NULL)
- return tupDatum;
-
- /*
- * do_convert_tuple needs a HeapTuple not a bare HeapTupleHeader.
- */
- tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
- tmptup.t_data = tuple;
-
- result = do_convert_tuple(&tmptup, cstate->map);
-
- return HeapTupleGetDatum(result);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalCase
- *
- * Evaluate a CASE clause. Will have boolean expressions
- * inside the WHEN clauses, and will have expressions
- * for results.
- * - thomas 1998-11-09
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext,
- bool *isNull)
-{
- List *clauses = caseExpr->args;
- ListCell *clause;
- Datum save_datum;
- bool save_isNull;
-
- /*
- * If there's a test expression, we have to evaluate it and save the value
- * where the CaseTestExpr placeholders can find it. We must save and
- * restore prior setting of econtext's caseValue fields, in case this node
- * is itself within a larger CASE. Furthermore, don't assign to the
- * econtext fields until after returning from evaluation of the test
- * expression. We used to pass &econtext->caseValue_isNull to the
- * recursive call, but that leads to aliasing that variable within said
- * call, which can (and did) produce bugs when the test expression itself
- * contains a CASE.
- *
- * If there's no test expression, we don't actually need to save and
- * restore these fields; but it's less code to just do so unconditionally.
- */
- save_datum = econtext->caseValue_datum;
- save_isNull = econtext->caseValue_isNull;
-
- if (caseExpr->arg)
- {
- Datum arg_value;
- bool arg_isNull;
-
- arg_value = ExecEvalExpr(caseExpr->arg,
- econtext,
- &arg_isNull);
- /* Since caseValue_datum may be read multiple times, force to R/O */
- econtext->caseValue_datum =
- MakeExpandedObjectReadOnly(arg_value,
- arg_isNull,
- caseExpr->argtyplen);
- econtext->caseValue_isNull = arg_isNull;
- }
-
- /*
- * we evaluate each of the WHEN clauses in turn, as soon as one is true we
- * return the corresponding result. If none are true then we return the
- * value of the default clause, or NULL if there is none.
- */
- foreach(clause, clauses)
- {
- CaseWhenState *wclause = lfirst(clause);
- Datum clause_value;
- bool clause_isNull;
-
- clause_value = ExecEvalExpr(wclause->expr,
- econtext,
- &clause_isNull);
-
- /*
- * if we have a true test, then we return the result, since the case
- * statement is satisfied. A NULL result from the test is not
- * considered true.
- */
- if (DatumGetBool(clause_value) && !clause_isNull)
- {
- econtext->caseValue_datum = save_datum;
- econtext->caseValue_isNull = save_isNull;
- return ExecEvalExpr(wclause->result,
- econtext,
- isNull);
- }
- }
-
- econtext->caseValue_datum = save_datum;
- econtext->caseValue_isNull = save_isNull;
-
- if (caseExpr->defresult)
- {
- return ExecEvalExpr(caseExpr->defresult,
- econtext,
- isNull);
- }
-
- *isNull = true;
- return (Datum) 0;
-}
-
-/*
- * ExecEvalCaseTestExpr
- *
- * Return the value stored by CASE.
- */
-static Datum
-ExecEvalCaseTestExpr(ExprState *exprstate,
- ExprContext *econtext,
- bool *isNull)
-{
- *isNull = econtext->caseValue_isNull;
- return econtext->caseValue_datum;
-}
-
-/*
- * ExecEvalGroupingFuncExpr
- *
- * Return a bitmask with a bit for each (unevaluated) argument expression
- * (rightmost arg is least significant bit).
- *
- * A bit is set if the corresponding expression is NOT part of the set of
- * grouping expressions in the current grouping set.
- */
-static Datum
-ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate,
- ExprContext *econtext,
- bool *isNull)
-{
- int result = 0;
- int attnum = 0;
- Bitmapset *grouped_cols = gstate->aggstate->grouped_cols;
- ListCell *lc;
-
- *isNull = false;
-
- foreach(lc, (gstate->clauses))
- {
- attnum = lfirst_int(lc);
-
- result = result << 1;
-
- if (!bms_is_member(attnum, grouped_cols))
- result = result | 1;
- }
-
- return (Datum) result;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalArray - ARRAY[] expressions
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalArray(ArrayExprState *astate, ExprContext *econtext,
- bool *isNull)
-{
- ArrayExpr *arrayExpr = (ArrayExpr *) astate->xprstate.expr;
- ArrayType *result;
- ListCell *element;
- Oid element_type = arrayExpr->element_typeid;
- int ndims = 0;
- int dims[MAXDIM];
- int lbs[MAXDIM];
-
- /* Set non-null as default */
- *isNull = false;
-
- if (!arrayExpr->multidims)
- {
- /* Elements are presumably of scalar type */
- int nelems;
- Datum *dvalues;
- bool *dnulls;
- int i = 0;
-
- ndims = 1;
- nelems = list_length(astate->elements);
-
- /* Shouldn't happen here, but if length is 0, return empty array */
- if (nelems == 0)
- return PointerGetDatum(construct_empty_array(element_type));
-
- dvalues = (Datum *) palloc(nelems * sizeof(Datum));
- dnulls = (bool *) palloc(nelems * sizeof(bool));
-
- /* loop through and build array of datums */
- foreach(element, astate->elements)
- {
- ExprState *e = (ExprState *) lfirst(element);
-
- dvalues[i] = ExecEvalExpr(e, econtext, &dnulls[i]);
- i++;
- }
-
- /* setup for 1-D array of the given length */
- dims[0] = nelems;
- lbs[0] = 1;
-
- result = construct_md_array(dvalues, dnulls, ndims, dims, lbs,
- element_type,
- astate->elemlength,
- astate->elembyval,
- astate->elemalign);
- }
- else
- {
- /* Must be nested array expressions */
- int nbytes = 0;
- int nitems = 0;
- int outer_nelems = 0;
- int elem_ndims = 0;
- int *elem_dims = NULL;
- int *elem_lbs = NULL;
- bool firstone = true;
- bool havenulls = false;
- bool haveempty = false;
- char **subdata;
- bits8 **subbitmaps;
- int *subbytes;
- int *subnitems;
- int i;
- int32 dataoffset;
- char *dat;
- int iitem;
-
- i = list_length(astate->elements);
- subdata = (char **) palloc(i * sizeof(char *));
- subbitmaps = (bits8 **) palloc(i * sizeof(bits8 *));
- subbytes = (int *) palloc(i * sizeof(int));
- subnitems = (int *) palloc(i * sizeof(int));
-
- /* loop through and get data area from each element */
- foreach(element, astate->elements)
- {
- ExprState *e = (ExprState *) lfirst(element);
- bool eisnull;
- Datum arraydatum;
- ArrayType *array;
- int this_ndims;
-
- arraydatum = ExecEvalExpr(e, econtext, &eisnull);
- /* temporarily ignore null subarrays */
- if (eisnull)
- {
- haveempty = true;
- continue;
- }
-
- array = DatumGetArrayTypeP(arraydatum);
-
- /* run-time double-check on element type */
- if (element_type != ARR_ELEMTYPE(array))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("cannot merge incompatible arrays"),
- errdetail("Array with element type %s cannot be "
- "included in ARRAY construct with element type %s.",
- format_type_be(ARR_ELEMTYPE(array)),
- format_type_be(element_type))));
-
- this_ndims = ARR_NDIM(array);
- /* temporarily ignore zero-dimensional subarrays */
- if (this_ndims <= 0)
- {
- haveempty = true;
- continue;
- }
-
- if (firstone)
- {
- /* Get sub-array details from first member */
- elem_ndims = this_ndims;
- ndims = elem_ndims + 1;
- if (ndims <= 0 || ndims > MAXDIM)
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("number of array dimensions (%d) exceeds " \
- "the maximum allowed (%d)", ndims, MAXDIM)));
-
- elem_dims = (int *) palloc(elem_ndims * sizeof(int));
- memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int));
- elem_lbs = (int *) palloc(elem_ndims * sizeof(int));
- memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int));
-
- firstone = false;
- }
- else
- {
- /* Check other sub-arrays are compatible */
- if (elem_ndims != this_ndims ||
- memcmp(elem_dims, ARR_DIMS(array),
- elem_ndims * sizeof(int)) != 0 ||
- memcmp(elem_lbs, ARR_LBOUND(array),
- elem_ndims * sizeof(int)) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
- errmsg("multidimensional arrays must have array "
- "expressions with matching dimensions")));
- }
-
- subdata[outer_nelems] = ARR_DATA_PTR(array);
- subbitmaps[outer_nelems] = ARR_NULLBITMAP(array);
- subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array);
- nbytes += subbytes[outer_nelems];
- subnitems[outer_nelems] = ArrayGetNItems(this_ndims,
- ARR_DIMS(array));
- nitems += subnitems[outer_nelems];
- havenulls |= ARR_HASNULL(array);
- outer_nelems++;
- }
-
- /*
- * If all items were null or empty arrays, return an empty array;
- * otherwise, if some were and some weren't, raise error. (Note: we
- * must special-case this somehow to avoid trying to generate a 1-D
- * array formed from empty arrays. It's not ideal...)
- */
- if (haveempty)
- {
- if (ndims == 0) /* didn't find any nonempty array */
- return PointerGetDatum(construct_empty_array(element_type));
- ereport(ERROR,
- (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
- errmsg("multidimensional arrays must have array "
- "expressions with matching dimensions")));
- }
-
- /* setup for multi-D array */
- dims[0] = outer_nelems;
- lbs[0] = 1;
- for (i = 1; i < ndims; i++)
- {
- dims[i] = elem_dims[i - 1];
- lbs[i] = elem_lbs[i - 1];
- }
-
- if (havenulls)
- {
- dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems);
- nbytes += dataoffset;
- }
- else
- {
- dataoffset = 0; /* marker for no null bitmap */
- nbytes += ARR_OVERHEAD_NONULLS(ndims);
- }
-
- result = (ArrayType *) palloc(nbytes);
- SET_VARSIZE(result, nbytes);
- result->ndim = ndims;
- result->dataoffset = dataoffset;
- result->elemtype = element_type;
- memcpy(ARR_DIMS(result), dims, ndims * sizeof(int));
- memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int));
-
- dat = ARR_DATA_PTR(result);
- iitem = 0;
- for (i = 0; i < outer_nelems; i++)
- {
- memcpy(dat, subdata[i], subbytes[i]);
- dat += subbytes[i];
- if (havenulls)
- array_bitmap_copy(ARR_NULLBITMAP(result), iitem,
- subbitmaps[i], 0,
- subnitems[i]);
- iitem += subnitems[i];
- }
- }
-
- return PointerGetDatum(result);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalRow - ROW() expressions
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalRow(RowExprState *rstate,
- ExprContext *econtext,
- bool *isNull)
-{
- HeapTuple tuple;
- Datum *values;
- bool *isnull;
- int natts;
- ListCell *arg;
- int i;
-
- /* Set non-null as default */
- *isNull = false;
-
- /* Allocate workspace */
- natts = rstate->tupdesc->natts;
- values = (Datum *) palloc0(natts * sizeof(Datum));
- isnull = (bool *) palloc(natts * sizeof(bool));
-
- /* preset to nulls in case rowtype has some later-added columns */
- memset(isnull, true, natts * sizeof(bool));
-
- /* Evaluate field values */
- i = 0;
- foreach(arg, rstate->args)
- {
- ExprState *e = (ExprState *) lfirst(arg);
-
- values[i] = ExecEvalExpr(e, econtext, &isnull[i]);
- i++;
- }
-
- tuple = heap_form_tuple(rstate->tupdesc, values, isnull);
-
- pfree(values);
- pfree(isnull);
-
- return HeapTupleGetDatum(tuple);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalRowCompare - ROW() comparison-op ROW()
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalRowCompare(RowCompareExprState *rstate,
- ExprContext *econtext,
- bool *isNull)
-{
- bool result;
- RowCompareType rctype = ((RowCompareExpr *) rstate->xprstate.expr)->rctype;
- int32 cmpresult = 0;
- ListCell *l;
- ListCell *r;
- int i;
-
- *isNull = true; /* until we get a result */
-
- i = 0;
- forboth(l, rstate->largs, r, rstate->rargs)
- {
- ExprState *le = (ExprState *) lfirst(l);
- ExprState *re = (ExprState *) lfirst(r);
- FunctionCallInfoData locfcinfo;
-
- InitFunctionCallInfoData(locfcinfo, &(rstate->funcs[i]), 2,
- rstate->collations[i],
- NULL, NULL);
- locfcinfo.arg[0] = ExecEvalExpr(le, econtext,
- &locfcinfo.argnull[0]);
- locfcinfo.arg[1] = ExecEvalExpr(re, econtext,
- &locfcinfo.argnull[1]);
- if (rstate->funcs[i].fn_strict &&
- (locfcinfo.argnull[0] || locfcinfo.argnull[1]))
- return (Datum) 0; /* force NULL result */
- locfcinfo.isnull = false;
- cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
- if (locfcinfo.isnull)
- return (Datum) 0; /* force NULL result */
- if (cmpresult != 0)
- break; /* no need to compare remaining columns */
- i++;
- }
-
- switch (rctype)
- {
- /* EQ and NE cases aren't allowed here */
- case ROWCOMPARE_LT:
- result = (cmpresult < 0);
- break;
- case ROWCOMPARE_LE:
- result = (cmpresult <= 0);
- break;
- case ROWCOMPARE_GE:
- result = (cmpresult >= 0);
- break;
- case ROWCOMPARE_GT:
- result = (cmpresult > 0);
- break;
- default:
- elog(ERROR, "unrecognized RowCompareType: %d", (int) rctype);
- result = 0; /* keep compiler quiet */
- break;
- }
-
- *isNull = false;
- return BoolGetDatum(result);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalCoalesce
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext,
- bool *isNull)
-{
- ListCell *arg;
-
- /* Simply loop through until something NOT NULL is found */
- foreach(arg, coalesceExpr->args)
- {
- ExprState *e = (ExprState *) lfirst(arg);
- Datum value;
-
- value = ExecEvalExpr(e, econtext, isNull);
- if (!*isNull)
- return value;
- }
-
- /* Else return NULL */
- *isNull = true;
- return (Datum) 0;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalMinMax
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext,
- bool *isNull)
-{
- Datum result = (Datum) 0;
- MinMaxExpr *minmax = (MinMaxExpr *) minmaxExpr->xprstate.expr;
- Oid collation = minmax->inputcollid;
- MinMaxOp op = minmax->op;
- FunctionCallInfoData locfcinfo;
- ListCell *arg;
-
- *isNull = true; /* until we get a result */
-
- InitFunctionCallInfoData(locfcinfo, &minmaxExpr->cfunc, 2,
- collation, NULL, NULL);
- locfcinfo.argnull[0] = false;
- locfcinfo.argnull[1] = false;
-
- foreach(arg, minmaxExpr->args)
- {
- ExprState *e = (ExprState *) lfirst(arg);
- Datum value;
- bool valueIsNull;
- int32 cmpresult;
-
- value = ExecEvalExpr(e, econtext, &valueIsNull);
- if (valueIsNull)
- continue; /* ignore NULL inputs */
-
- if (*isNull)
- {
- /* first nonnull input, adopt value */
- result = value;
- *isNull = false;
- }
- else
- {
- /* apply comparison function */
- locfcinfo.arg[0] = result;
- locfcinfo.arg[1] = value;
- locfcinfo.isnull = false;
- cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo));
- if (locfcinfo.isnull) /* probably should not happen */
- continue;
- if (cmpresult > 0 && op == IS_LEAST)
- result = value;
- else if (cmpresult < 0 && op == IS_GREATEST)
- result = value;
- }
- }
-
- return result;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalSQLValueFunction
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalSQLValueFunction(ExprState *svfExpr,
- ExprContext *econtext,
- bool *isNull)
-{
- Datum result = (Datum) 0;
- SQLValueFunction *svf = (SQLValueFunction *) svfExpr->expr;
- FunctionCallInfoData fcinfo;
-
- *isNull = false;
-
- /*
- * Note: current_schema() can return NULL. current_user() etc currently
- * cannot, but might as well code those cases the same way for safety.
- */
- switch (svf->op)
- {
- case SVFOP_CURRENT_DATE:
- result = DateADTGetDatum(GetSQLCurrentDate());
- break;
- case SVFOP_CURRENT_TIME:
- case SVFOP_CURRENT_TIME_N:
- result = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod));
- break;
- case SVFOP_CURRENT_TIMESTAMP:
- case SVFOP_CURRENT_TIMESTAMP_N:
- result = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod));
- break;
- case SVFOP_LOCALTIME:
- case SVFOP_LOCALTIME_N:
- result = TimeADTGetDatum(GetSQLLocalTime(svf->typmod));
- break;
- case SVFOP_LOCALTIMESTAMP:
- case SVFOP_LOCALTIMESTAMP_N:
- result = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod));
- break;
- case SVFOP_CURRENT_ROLE:
- case SVFOP_CURRENT_USER:
- case SVFOP_USER:
- InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
- result = current_user(&fcinfo);
- *isNull = fcinfo.isnull;
- break;
- case SVFOP_SESSION_USER:
- InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
- result = session_user(&fcinfo);
- *isNull = fcinfo.isnull;
- break;
- case SVFOP_CURRENT_CATALOG:
- InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
- result = current_database(&fcinfo);
- *isNull = fcinfo.isnull;
- break;
- case SVFOP_CURRENT_SCHEMA:
- InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
- result = current_schema(&fcinfo);
- *isNull = fcinfo.isnull;
- break;
- }
-
- return result;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalXml
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext,
- bool *isNull)
-{
- XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr;
- Datum value;
- bool isnull;
- ListCell *arg;
- ListCell *narg;
-
- *isNull = true; /* until we get a result */
-
- switch (xexpr->op)
- {
- case IS_XMLCONCAT:
- {
- List *values = NIL;
-
- foreach(arg, xmlExpr->args)
- {
- ExprState *e = (ExprState *) lfirst(arg);
-
- value = ExecEvalExpr(e, econtext, &isnull);
- if (!isnull)
- values = lappend(values, DatumGetPointer(value));
- }
-
- if (list_length(values) > 0)
- {
- *isNull = false;
- return PointerGetDatum(xmlconcat(values));
- }
- else
- return (Datum) 0;
- }
- break;
-
- case IS_XMLFOREST:
- {
- StringInfoData buf;
-
- initStringInfo(&buf);
- forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names)
- {
- ExprState *e = (ExprState *) lfirst(arg);
- char *argname = strVal(lfirst(narg));
-
- value = ExecEvalExpr(e, econtext, &isnull);
- if (!isnull)
- {
- appendStringInfo(&buf, "<%s>%s</%s>",
- argname,
- map_sql_value_to_xml_value(value, exprType((Node *) e->expr), true),
- argname);
- *isNull = false;
- }
- }
-
- if (*isNull)
- {
- pfree(buf.data);
- return (Datum) 0;
- }
- else
- {
- text *result;
-
- result = cstring_to_text_with_len(buf.data, buf.len);
- pfree(buf.data);
-
- return PointerGetDatum(result);
- }
- }
- break;
-
- case IS_XMLELEMENT:
- *isNull = false;
- return PointerGetDatum(xmlelement(xmlExpr, econtext));
- break;
-
- case IS_XMLPARSE:
- {
- ExprState *e;
- text *data;
- bool preserve_whitespace;
-
- /* arguments are known to be text, bool */
- Assert(list_length(xmlExpr->args) == 2);
-
- e = (ExprState *) linitial(xmlExpr->args);
- value = ExecEvalExpr(e, econtext, &isnull);
- if (isnull)
- return (Datum) 0;
- data = DatumGetTextPP(value);
-
- e = (ExprState *) lsecond(xmlExpr->args);
- value = ExecEvalExpr(e, econtext, &isnull);
- if (isnull) /* probably can't happen */
- return (Datum) 0;
- preserve_whitespace = DatumGetBool(value);
-
- *isNull = false;
-
- return PointerGetDatum(xmlparse(data,
- xexpr->xmloption,
- preserve_whitespace));
- }
- break;
-
- case IS_XMLPI:
- {
- ExprState *e;
- text *arg;
-
- /* optional argument is known to be text */
- Assert(list_length(xmlExpr->args) <= 1);
-
- if (xmlExpr->args)
- {
- e = (ExprState *) linitial(xmlExpr->args);
- value = ExecEvalExpr(e, econtext, &isnull);
- if (isnull)
- arg = NULL;
- else
- arg = DatumGetTextPP(value);
- }
- else
- {
- arg = NULL;
- isnull = false;
- }
-
- return PointerGetDatum(xmlpi(xexpr->name, arg, isnull, isNull));
- }
- break;
-
- case IS_XMLROOT:
- {
- ExprState *e;
- xmltype *data;
- text *version;
- int standalone;
-
- /* arguments are known to be xml, text, int */
- Assert(list_length(xmlExpr->args) == 3);
-
- e = (ExprState *) linitial(xmlExpr->args);
- value = ExecEvalExpr(e, econtext, &isnull);
- if (isnull)
- return (Datum) 0;
- data = DatumGetXmlP(value);
-
- e = (ExprState *) lsecond(xmlExpr->args);
- value = ExecEvalExpr(e, econtext, &isnull);
- if (isnull)
- version = NULL;
- else
- version = DatumGetTextPP(value);
-
- e = (ExprState *) lthird(xmlExpr->args);
- value = ExecEvalExpr(e, econtext, &isnull);
- standalone = DatumGetInt32(value);
-
- *isNull = false;
-
- return PointerGetDatum(xmlroot(data,
- version,
- standalone));
- }
- break;
-
- case IS_XMLSERIALIZE:
- {
- ExprState *e;
-
- /* argument type is known to be xml */
- Assert(list_length(xmlExpr->args) == 1);
-
- e = (ExprState *) linitial(xmlExpr->args);
- value = ExecEvalExpr(e, econtext, &isnull);
- if (isnull)
- return (Datum) 0;
-
- *isNull = false;
-
- return PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value), xexpr->xmloption));
- }
- break;
-
- case IS_DOCUMENT:
- {
- ExprState *e;
-
- /* optional argument is known to be xml */
- Assert(list_length(xmlExpr->args) == 1);
-
- e = (ExprState *) linitial(xmlExpr->args);
- value = ExecEvalExpr(e, econtext, &isnull);
- if (isnull)
- return (Datum) 0;
- else
- {
- *isNull = false;
- return BoolGetDatum(xml_is_document(DatumGetXmlP(value)));
- }
- }
- break;
- }
-
- elog(ERROR, "unrecognized XML operation");
- return (Datum) 0;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalNullIf
- *
- * Note that this is *always* derived from the equals operator,
- * but since we need special processing of the arguments
- * we can not simply reuse ExecEvalOper() or ExecEvalFunc().
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalNullIf(FuncExprState *nullIfExpr,
- ExprContext *econtext,
- bool *isNull)
-{
- Datum result;
- FunctionCallInfo fcinfo;
-
- /*
- * Initialize function cache if first time through
- */
- if (nullIfExpr->func.fn_oid == InvalidOid)
- {
- NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr;
-
- init_fcache(op->opfuncid, op->inputcollid, nullIfExpr,
- econtext->ecxt_per_query_memory, false, false);
- }
-
- /*
- * Evaluate arguments
- */
- fcinfo = &nullIfExpr->fcinfo_data;
- ExecEvalFuncArgs(fcinfo, nullIfExpr->args, econtext);
- Assert(fcinfo->nargs == 2);
-
- /* if either argument is NULL they can't be equal */
- if (!fcinfo->argnull[0] && !fcinfo->argnull[1])
- {
- fcinfo->isnull = false;
- result = FunctionCallInvoke(fcinfo);
- /* if the arguments are equal return null */
- if (!fcinfo->isnull && DatumGetBool(result))
- {
- *isNull = true;
- return (Datum) 0;
- }
- }
-
- /* else return first argument */
- *isNull = fcinfo->argnull[0];
- return fcinfo->arg[0];
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalNullTest
- *
- * Evaluate a NullTest node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalNullTest(NullTestState *nstate,
- ExprContext *econtext,
- bool *isNull)
-{
- NullTest *ntest = (NullTest *) nstate->xprstate.expr;
- Datum result;
-
- result = ExecEvalExpr(nstate->arg, econtext, isNull);
-
- if (ntest->argisrow && !(*isNull))
- {
- /*
- * The SQL standard defines IS [NOT] NULL for a non-null rowtype
- * argument as:
- *
- * "R IS NULL" is true if every field is the null value.
- *
- * "R IS NOT NULL" is true if no field is the null value.
- *
- * This definition is (apparently intentionally) not recursive; so our
- * tests on the fields are primitive attisnull tests, not recursive
- * checks to see if they are all-nulls or no-nulls rowtypes.
- *
- * The standard does not consider the possibility of zero-field rows,
- * but here we consider them to vacuously satisfy both predicates.
- */
- HeapTupleHeader tuple;
- Oid tupType;
- int32 tupTypmod;
- TupleDesc tupDesc;
- HeapTupleData tmptup;
- int att;
-
- tuple = DatumGetHeapTupleHeader(result);
-
- tupType = HeapTupleHeaderGetTypeId(tuple);
- tupTypmod = HeapTupleHeaderGetTypMod(tuple);
-
- /* Lookup tupdesc if first time through or if type changes */
- tupDesc = get_cached_rowtype(tupType, tupTypmod,
- &nstate->argdesc, econtext);
-
- /*
- * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader.
- */
- tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
- tmptup.t_data = tuple;
-
- for (att = 1; att <= tupDesc->natts; att++)
- {
- /* ignore dropped columns */
- if (tupDesc->attrs[att - 1]->attisdropped)
- continue;
- if (heap_attisnull(&tmptup, att))
- {
- /* null field disproves IS NOT NULL */
- if (ntest->nulltesttype == IS_NOT_NULL)
- return BoolGetDatum(false);
- }
- else
- {
- /* non-null field disproves IS NULL */
- if (ntest->nulltesttype == IS_NULL)
- return BoolGetDatum(false);
- }
- }
-
- return BoolGetDatum(true);
- }
- else
- {
- /* Simple scalar-argument case, or a null rowtype datum */
- switch (ntest->nulltesttype)
- {
- case IS_NULL:
- if (*isNull)
- {
- *isNull = false;
- return BoolGetDatum(true);
- }
- else
- return BoolGetDatum(false);
- case IS_NOT_NULL:
- if (*isNull)
- {
- *isNull = false;
- return BoolGetDatum(false);
- }
- else
- return BoolGetDatum(true);
- default:
- elog(ERROR, "unrecognized nulltesttype: %d",
- (int) ntest->nulltesttype);
- return (Datum) 0; /* keep compiler quiet */
- }
- }
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalBooleanTest
- *
- * Evaluate a BooleanTest node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalBooleanTest(GenericExprState *bstate,
- ExprContext *econtext,
- bool *isNull)
-{
- BooleanTest *btest = (BooleanTest *) bstate->xprstate.expr;
- Datum result;
-
- result = ExecEvalExpr(bstate->arg, econtext, isNull);
-
- switch (btest->booltesttype)
- {
- case IS_TRUE:
- if (*isNull)
- {
- *isNull = false;
- return BoolGetDatum(false);
- }
- else if (DatumGetBool(result))
- return BoolGetDatum(true);
- else
- return BoolGetDatum(false);
- case IS_NOT_TRUE:
- if (*isNull)
- {
- *isNull = false;
- return BoolGetDatum(true);
- }
- else if (DatumGetBool(result))
- return BoolGetDatum(false);
- else
- return BoolGetDatum(true);
- case IS_FALSE:
- if (*isNull)
- {
- *isNull = false;
- return BoolGetDatum(false);
- }
- else if (DatumGetBool(result))
- return BoolGetDatum(false);
- else
- return BoolGetDatum(true);
- case IS_NOT_FALSE:
- if (*isNull)
- {
- *isNull = false;
- return BoolGetDatum(true);
- }
- else if (DatumGetBool(result))
- return BoolGetDatum(true);
- else
- return BoolGetDatum(false);
- case IS_UNKNOWN:
- if (*isNull)
- {
- *isNull = false;
- return BoolGetDatum(true);
- }
- else
- return BoolGetDatum(false);
- case IS_NOT_UNKNOWN:
- if (*isNull)
- {
- *isNull = false;
- return BoolGetDatum(false);
- }
- else
- return BoolGetDatum(true);
- default:
- elog(ERROR, "unrecognized booltesttype: %d",
- (int) btest->booltesttype);
- return (Datum) 0; /* keep compiler quiet */
- }
-}
-
-/*
- * ExecEvalCoerceToDomain
- *
- * Test the provided data against the domain constraint(s). If the data
- * passes the constraint specifications, pass it through (return the
- * datum) otherwise throw an error.
- */
-static Datum
-ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext,
- bool *isNull)
-{
- CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr;
- Datum result;
- ListCell *l;
-
- result = ExecEvalExpr(cstate->arg, econtext, isNull);
-
- /* Make sure we have up-to-date constraints */
- UpdateDomainConstraintRef(cstate->constraint_ref);
-
- foreach(l, cstate->constraint_ref->constraints)
- {
- DomainConstraintState *con = (DomainConstraintState *) lfirst(l);
-
- switch (con->constrainttype)
- {
- case DOM_CONSTRAINT_NOTNULL:
- if (*isNull)
- ereport(ERROR,
- (errcode(ERRCODE_NOT_NULL_VIOLATION),
- errmsg("domain %s does not allow null values",
- format_type_be(ctest->resulttype)),
- errdatatype(ctest->resulttype)));
- break;
- case DOM_CONSTRAINT_CHECK:
- {
- Datum conResult;
- bool conIsNull;
- Datum save_datum;
- bool save_isNull;
-
- /*
- * Set up value to be returned by CoerceToDomainValue
- * nodes. We must save and restore prior setting of
- * econtext's domainValue fields, in case this node is
- * itself within a check expression for another domain.
- *
- * Also, if we are working with a read-write expanded
- * datum, be sure that what we pass to CHECK expressions
- * is a read-only pointer; else called functions might
- * modify or even delete the expanded object.
- */
- save_datum = econtext->domainValue_datum;
- save_isNull = econtext->domainValue_isNull;
-
- econtext->domainValue_datum =
- MakeExpandedObjectReadOnly(result, *isNull,
- cstate->constraint_ref->tcache->typlen);
- econtext->domainValue_isNull = *isNull;
-
- conResult = ExecEvalExpr(con->check_expr, econtext,
- &conIsNull);
-
- if (!conIsNull &&
- !DatumGetBool(conResult))
- ereport(ERROR,
- (errcode(ERRCODE_CHECK_VIOLATION),
- errmsg("value for domain %s violates check constraint \"%s\"",
- format_type_be(ctest->resulttype),
- con->name),
- errdomainconstraint(ctest->resulttype,
- con->name)));
- econtext->domainValue_datum = save_datum;
- econtext->domainValue_isNull = save_isNull;
-
- break;
- }
- default:
- elog(ERROR, "unrecognized constraint type: %d",
- (int) con->constrainttype);
- break;
- }
- }
-
- /* If all has gone well (constraints did not fail) return the datum */
- return result;
-}
-
-/*
- * ExecEvalCoerceToDomainValue
- *
- * Return the value stored by CoerceToDomain.
- */
-static Datum
-ExecEvalCoerceToDomainValue(ExprState *exprstate,
- ExprContext *econtext,
- bool *isNull)
-{
- *isNull = econtext->domainValue_isNull;
- return econtext->domainValue_datum;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalFieldSelect
- *
- * Evaluate a FieldSelect node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalFieldSelect(FieldSelectState *fstate,
- ExprContext *econtext,
- bool *isNull)
-{
- FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr;
- AttrNumber fieldnum = fselect->fieldnum;
- Datum result;
- Datum tupDatum;
- HeapTupleHeader tuple;
- Oid tupType;
- int32 tupTypmod;
- TupleDesc tupDesc;
- Form_pg_attribute attr;
- HeapTupleData tmptup;
-
- tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull);
-
- if (*isNull)
- return tupDatum;
-
- tuple = DatumGetHeapTupleHeader(tupDatum);
-
- tupType = HeapTupleHeaderGetTypeId(tuple);
- tupTypmod = HeapTupleHeaderGetTypMod(tuple);
-
- /* Lookup tupdesc if first time through or if type changes */
- tupDesc = get_cached_rowtype(tupType, tupTypmod,
- &fstate->argdesc, econtext);
-
- /*
- * Find field's attr record. Note we don't support system columns here: a
- * datum tuple doesn't have valid values for most of the interesting
- * system columns anyway.
- */
- if (fieldnum <= 0) /* should never happen */
- elog(ERROR, "unsupported reference to system column %d in FieldSelect",
- fieldnum);
- if (fieldnum > tupDesc->natts) /* should never happen */
- elog(ERROR, "attribute number %d exceeds number of columns %d",
- fieldnum, tupDesc->natts);
- attr = tupDesc->attrs[fieldnum - 1];
-
- /* Check for dropped column, and force a NULL result if so */
- if (attr->attisdropped)
- {
- *isNull = true;
- return (Datum) 0;
- }
-
- /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */
- /* As in ExecEvalScalarVar, we should but can't check typmod */
- if (fselect->resulttype != attr->atttypid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("attribute %d has wrong type", fieldnum),
- errdetail("Table has type %s, but query expects %s.",
- format_type_be(attr->atttypid),
- format_type_be(fselect->resulttype))));
-
- /* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */
- tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
- tmptup.t_data = tuple;
-
- result = heap_getattr(&tmptup,
- fieldnum,
- tupDesc,
- isNull);
- return result;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalFieldStore
- *
- * Evaluate a FieldStore node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalFieldStore(FieldStoreState *fstate,
- ExprContext *econtext,
- bool *isNull)
-{
- FieldStore *fstore = (FieldStore *) fstate->xprstate.expr;
- HeapTuple tuple;
- Datum tupDatum;
- TupleDesc tupDesc;
- Datum *values;
- bool *isnull;
- Datum save_datum;
- bool save_isNull;
- ListCell *l1,
- *l2;
-
- tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull);
-
- /* Lookup tupdesc if first time through or after rescan */
- tupDesc = get_cached_rowtype(fstore->resulttype, -1,
- &fstate->argdesc, econtext);
-
- /* Allocate workspace */
- values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
- isnull = (bool *) palloc(tupDesc->natts * sizeof(bool));
-
- if (!*isNull)
- {
- /*
- * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We
- * set all the fields in the struct just in case.
- */
- HeapTupleHeader tuphdr;
- HeapTupleData tmptup;
-
- tuphdr = DatumGetHeapTupleHeader(tupDatum);
- tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr);
- ItemPointerSetInvalid(&(tmptup.t_self));
- tmptup.t_tableOid = InvalidOid;
- tmptup.t_data = tuphdr;
-
- heap_deform_tuple(&tmptup, tupDesc, values, isnull);
- }
- else
- {
- /* Convert null input tuple into an all-nulls row */
- memset(isnull, true, tupDesc->natts * sizeof(bool));
- }
-
- /* Result is never null */
- *isNull = false;
-
- save_datum = econtext->caseValue_datum;
- save_isNull = econtext->caseValue_isNull;
-
- forboth(l1, fstate->newvals, l2, fstore->fieldnums)
- {
- ExprState *newval = (ExprState *) lfirst(l1);
- AttrNumber fieldnum = lfirst_int(l2);
-
- Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
-
- /*
- * Use the CaseTestExpr mechanism to pass down the old value of the
- * field being replaced; this is needed in case the newval is itself a
- * FieldStore or ArrayRef that has to obtain and modify the old value.
- * It's safe to reuse the CASE mechanism because there cannot be a
- * CASE between here and where the value would be needed, and a field
- * assignment can't be within a CASE either. (So saving and restoring
- * the caseValue is just paranoia, but let's do it anyway.)
- */
- econtext->caseValue_datum = values[fieldnum - 1];
- econtext->caseValue_isNull = isnull[fieldnum - 1];
-
- values[fieldnum - 1] = ExecEvalExpr(newval,
- econtext,
- &isnull[fieldnum - 1]);
- }
-
- econtext->caseValue_datum = save_datum;
- econtext->caseValue_isNull = save_isNull;
-
- tuple = heap_form_tuple(tupDesc, values, isnull);
-
- pfree(values);
- pfree(isnull);
-
- return HeapTupleGetDatum(tuple);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalRelabelType
- *
- * Evaluate a RelabelType node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalRelabelType(GenericExprState *exprstate,
- ExprContext *econtext,
- bool *isNull)
-{
- return ExecEvalExpr(exprstate->arg, econtext, isNull);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalCoerceViaIO
- *
- * Evaluate a CoerceViaIO node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCoerceViaIO(CoerceViaIOState *iostate,
- ExprContext *econtext,
- bool *isNull)
-{
- Datum result;
- Datum inputval;
- char *string;
-
- inputval = ExecEvalExpr(iostate->arg, econtext, isNull);
-
- if (*isNull)
- string = NULL; /* output functions are not called on nulls */
- else
- string = OutputFunctionCall(&iostate->outfunc, inputval);
-
- result = InputFunctionCall(&iostate->infunc,
- string,
- iostate->intypioparam,
- -1);
-
- /* The input function cannot change the null/not-null status */
- return result;
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalArrayCoerceExpr
- *
- * Evaluate an ArrayCoerceExpr node.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate,
- ExprContext *econtext,
- bool *isNull)
-{
- ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) astate->xprstate.expr;
- Datum result;
- FunctionCallInfoData locfcinfo;
-
- result = ExecEvalExpr(astate->arg, econtext, isNull);
-
- if (*isNull)
- return result; /* nothing to do */
-
- /*
- * If it's binary-compatible, modify the element type in the array header,
- * but otherwise leave the array as we received it.
- */
- if (!OidIsValid(acoerce->elemfuncid))
- {
- /* Detoast input array if necessary, and copy in any case */
- ArrayType *array = DatumGetArrayTypePCopy(result);
-
- ARR_ELEMTYPE(array) = astate->resultelemtype;
- PG_RETURN_ARRAYTYPE_P(array);
- }
-
- /* Initialize function cache if first time through */
- if (astate->elemfunc.fn_oid == InvalidOid)
- {
- AclResult aclresult;
-
- /* Check permission to call function */
- aclresult = pg_proc_aclcheck(acoerce->elemfuncid, GetUserId(),
- ACL_EXECUTE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_PROC,
- get_func_name(acoerce->elemfuncid));
- InvokeFunctionExecuteHook(acoerce->elemfuncid);
-
- /* Set up the primary fmgr lookup information */
- fmgr_info_cxt(acoerce->elemfuncid, &(astate->elemfunc),
- econtext->ecxt_per_query_memory);
- fmgr_info_set_expr((Node *) acoerce, &(astate->elemfunc));
- }
-
- /*
- * Use array_map to apply the function to each array element.
- *
- * We pass on the desttypmod and isExplicit flags whether or not the
- * function wants them.
- *
- * Note: coercion functions are assumed to not use collation.
- */
- InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3,
- InvalidOid, NULL, NULL);
- locfcinfo.arg[0] = result;
- locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod);
- locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit);
- locfcinfo.argnull[0] = false;
- locfcinfo.argnull[1] = false;
- locfcinfo.argnull[2] = false;
-
- return array_map(&locfcinfo, astate->resultelemtype, astate->amstate);
-}
-
-/* ----------------------------------------------------------------
- * ExecEvalCurrentOfExpr
- *
- * The planner should convert CURRENT OF into a TidScan qualification, or some
- * other special handling in a ForeignScan node. So we have to be able to do
- * ExecInitExpr on a CurrentOfExpr, but we shouldn't ever actually execute it.
- * If we get here, we suppose we must be dealing with CURRENT OF on a foreign
- * table whose FDW doesn't handle it, and complain accordingly.
- * ----------------------------------------------------------------
- */
-static Datum
-ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext,
- bool *isNull)
-{
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("WHERE CURRENT OF is not supported for this table type")));
- return 0; /* keep compiler quiet */
-}
-
-
-/*
- * ExecEvalExprSwitchContext
- *
- * Same as ExecEvalExpr, but get into the right allocation context explicitly.
- */
-Datum
-ExecEvalExprSwitchContext(ExprState *expression,
- ExprContext *econtext,
- bool *isNull)
-{
- Datum retDatum;
- MemoryContext oldContext;
-
- oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
- retDatum = ExecEvalExpr(expression, econtext, isNull);
- MemoryContextSwitchTo(oldContext);
- return retDatum;
-}
-
-
-/*
- * ExecInitExpr: prepare an expression tree for execution
- *
- * This function builds and returns an ExprState tree paralleling the given
- * Expr node tree. The ExprState tree can then be handed to ExecEvalExpr
- * for execution. Because the Expr tree itself is read-only as far as
- * ExecInitExpr and ExecEvalExpr are concerned, several different executions
- * of the same plan tree can occur concurrently.
- *
- * This must be called in a memory context that will last as long as repeated
- * executions of the expression are needed. Typically the context will be
- * the same as the per-query context of the associated ExprContext.
- *
- * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to the
- * lists of such nodes held by the parent PlanState. Otherwise, we do very
- * little initialization here other than building the state-node tree. Any
- * nontrivial work associated with initializing runtime info for a node should
- * happen during the first actual evaluation of that node. (This policy lets
- * us avoid work if the node is never actually evaluated.)
- *
- * Note: there is no ExecEndExpr function; we assume that any resource
- * cleanup needed will be handled by just releasing the memory context
- * in which the state tree is built. Functions that require additional
- * cleanup work can register a shutdown callback in the ExprContext.
- *
- * 'node' is the root of the expression tree to examine
- * 'parent' is the PlanState node that owns the expression.
- *
- * 'parent' may be NULL if we are preparing an expression that is not
- * associated with a plan tree. (If so, it can't have aggs or subplans.)
- * This case should usually come through ExecPrepareExpr, not directly here.
- */
-ExprState *
-ExecInitExpr(Expr *node, PlanState *parent)
-{
- ExprState *state;
-
- if (node == NULL)
- return NULL;
-
- /* Guard against stack overflow due to overly complex expressions */
- check_stack_depth();
-
- switch (nodeTag(node))
- {
- case T_Var:
- /* varattno == InvalidAttrNumber means it's a whole-row Var */
- if (((Var *) node)->varattno == InvalidAttrNumber)
- {
- WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState);
-
- wstate->parent = parent;
- wstate->wrv_tupdesc = NULL;
- wstate->wrv_junkFilter = NULL;
- state = (ExprState *) wstate;
- state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar;
- }
- else
- {
- state = makeNode(ExprState);
- state->evalfunc = ExecEvalScalarVar;
- }
- break;
- case T_Const:
- state = makeNode(ExprState);
- state->evalfunc = ExecEvalConst;
- break;
- case T_Param:
- state = makeNode(ExprState);
- switch (((Param *) node)->paramkind)
- {
- case PARAM_EXEC:
- state->evalfunc = ExecEvalParamExec;
- break;
- case PARAM_EXTERN:
- state->evalfunc = ExecEvalParamExtern;
- break;
- default:
- elog(ERROR, "unrecognized paramkind: %d",
- (int) ((Param *) node)->paramkind);
- break;
- }
- break;
- case T_CoerceToDomainValue:
- state = makeNode(ExprState);
- state->evalfunc = ExecEvalCoerceToDomainValue;
- break;
- case T_CaseTestExpr:
- state = makeNode(ExprState);
- state->evalfunc = ExecEvalCaseTestExpr;
- break;
- case T_Aggref:
- {
- AggrefExprState *astate = makeNode(AggrefExprState);
-
- astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref;
- if (parent && IsA(parent, AggState))
- {
- AggState *aggstate = (AggState *) parent;
-
- aggstate->aggs = lcons(astate, aggstate->aggs);
- aggstate->numaggs++;
- }
- else
- {
- /* planner messed up */
- elog(ERROR, "Aggref found in non-Agg plan node");
- }
- state = (ExprState *) astate;
- }
- break;
- case T_GroupingFunc:
- {
- GroupingFunc *grp_node = (GroupingFunc *) node;
- GroupingFuncExprState *grp_state = makeNode(GroupingFuncExprState);
- Agg *agg = NULL;
-
- if (!parent || !IsA(parent, AggState) ||!IsA(parent->plan, Agg))
- elog(ERROR, "parent of GROUPING is not Agg node");
-
- grp_state->aggstate = (AggState *) parent;
-
- agg = (Agg *) (parent->plan);
-
- if (agg->groupingSets)
- grp_state->clauses = grp_node->cols;
- else
- grp_state->clauses = NIL;
-
- state = (ExprState *) grp_state;
- state->evalfunc = (ExprStateEvalFunc) ExecEvalGroupingFuncExpr;
- }
- break;
- case T_WindowFunc:
- {
- WindowFunc *wfunc = (WindowFunc *) node;
- WindowFuncExprState *wfstate = makeNode(WindowFuncExprState);
-
- wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc;
- if (parent && IsA(parent, WindowAggState))
- {
- WindowAggState *winstate = (WindowAggState *) parent;
- int nfuncs;
-
- winstate->funcs = lcons(wfstate, winstate->funcs);
- nfuncs = ++winstate->numfuncs;
- if (wfunc->winagg)
- winstate->numaggs++;
-
- wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args,
- parent);
- wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter,
- parent);
-
- /*
- * Complain if the windowfunc's arguments contain any
- * windowfuncs; nested window functions are semantically
- * nonsensical. (This should have been caught earlier,
- * but we defend against it here anyway.)
- */
- if (nfuncs != winstate->numfuncs)
- ereport(ERROR,
- (errcode(ERRCODE_WINDOWING_ERROR),
- errmsg("window function calls cannot be nested")));
- }
- else
- {
- /* planner messed up */
- elog(ERROR, "WindowFunc found in non-WindowAgg plan node");
- }
- state = (ExprState *) wfstate;
- }
- break;
- case T_ArrayRef:
- {
- ArrayRef *aref = (ArrayRef *) node;
- ArrayRefExprState *astate = makeNode(ArrayRefExprState);
-
- astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef;
- astate->refupperindexpr = (List *)
- ExecInitExpr((Expr *) aref->refupperindexpr, parent);
- astate->reflowerindexpr = (List *)
- ExecInitExpr((Expr *) aref->reflowerindexpr, parent);
- astate->refexpr = ExecInitExpr(aref->refexpr, parent);
- astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr,
- parent);
- /* do one-time catalog lookups for type info */
- astate->refattrlength = get_typlen(aref->refarraytype);
- get_typlenbyvalalign(aref->refelemtype,
- &astate->refelemlength,
- &astate->refelembyval,
- &astate->refelemalign);
- state = (ExprState *) astate;
- }
- break;
- case T_FuncExpr:
- {
- FuncExpr *funcexpr = (FuncExpr *) node;
- FuncExprState *fstate = makeNode(FuncExprState);
-
- fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc;
- fstate->args = (List *)
- ExecInitExpr((Expr *) funcexpr->args, parent);
- fstate->func.fn_oid = InvalidOid; /* not initialized */
- fstate->funcReturnsSet = funcexpr->funcretset;
- state = (ExprState *) fstate;
- }
- break;
- case T_OpExpr:
- {
- OpExpr *opexpr = (OpExpr *) node;
- FuncExprState *fstate = makeNode(FuncExprState);
-
- fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper;
- fstate->args = (List *)
- ExecInitExpr((Expr *) opexpr->args, parent);
- fstate->func.fn_oid = InvalidOid; /* not initialized */
- fstate->funcReturnsSet = opexpr->opretset;
- state = (ExprState *) fstate;
- }
- break;
- case T_DistinctExpr:
- {
- DistinctExpr *distinctexpr = (DistinctExpr *) node;
- FuncExprState *fstate = makeNode(FuncExprState);
-
- fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct;
- fstate->args = (List *)
- ExecInitExpr((Expr *) distinctexpr->args, parent);
- fstate->func.fn_oid = InvalidOid; /* not initialized */
- fstate->funcReturnsSet = false; /* not supported */
- state = (ExprState *) fstate;
- }
- break;
- case T_NullIfExpr:
- {
- NullIfExpr *nullifexpr = (NullIfExpr *) node;
- FuncExprState *fstate = makeNode(FuncExprState);
-
- fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf;
- fstate->args = (List *)
- ExecInitExpr((Expr *) nullifexpr->args, parent);
- fstate->func.fn_oid = InvalidOid; /* not initialized */
- fstate->funcReturnsSet = false; /* not supported */
- state = (ExprState *) fstate;
- }
- break;
- case T_ScalarArrayOpExpr:
- {
- ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node;
- ScalarArrayOpExprState *sstate = makeNode(ScalarArrayOpExprState);
-
- sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp;
- sstate->fxprstate.args = (List *)
- ExecInitExpr((Expr *) opexpr->args, parent);
- sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */
- sstate->fxprstate.funcReturnsSet = false; /* not supported */
- sstate->element_type = InvalidOid; /* ditto */
- state = (ExprState *) sstate;
- }
- break;
- case T_BoolExpr:
- {
- BoolExpr *boolexpr = (BoolExpr *) node;
- BoolExprState *bstate = makeNode(BoolExprState);
-
- switch (boolexpr->boolop)
- {
- case AND_EXPR:
- bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAnd;
- break;
- case OR_EXPR:
- bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOr;
- break;
- case NOT_EXPR:
- bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNot;
- break;
- default:
- elog(ERROR, "unrecognized boolop: %d",
- (int) boolexpr->boolop);
- break;
- }
- bstate->args = (List *)
- ExecInitExpr((Expr *) boolexpr->args, parent);
- state = (ExprState *) bstate;
- }
- break;
- case T_SubPlan:
- {
- SubPlan *subplan = (SubPlan *) node;
- SubPlanState *sstate;
-
- if (!parent)
- elog(ERROR, "SubPlan found with no parent plan");
-
- sstate = ExecInitSubPlan(subplan, parent);
-
- /* Add SubPlanState nodes to parent->subPlan */
- parent->subPlan = lappend(parent->subPlan, sstate);
-
- state = (ExprState *) sstate;
- }
- break;
- case T_AlternativeSubPlan:
- {
- AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
- AlternativeSubPlanState *asstate;
-
- if (!parent)
- elog(ERROR, "AlternativeSubPlan found with no parent plan");
-
- asstate = ExecInitAlternativeSubPlan(asplan, parent);
-
- state = (ExprState *) asstate;
- }
- break;
- case T_FieldSelect:
- {
- FieldSelect *fselect = (FieldSelect *) node;
- FieldSelectState *fstate = makeNode(FieldSelectState);
-
- fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect;
- fstate->arg = ExecInitExpr(fselect->arg, parent);
- fstate->argdesc = NULL;
- state = (ExprState *) fstate;
- }
- break;
- case T_FieldStore:
- {
- FieldStore *fstore = (FieldStore *) node;
- FieldStoreState *fstate = makeNode(FieldStoreState);
-
- fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore;
- fstate->arg = ExecInitExpr(fstore->arg, parent);
- fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent);
- fstate->argdesc = NULL;
- state = (ExprState *) fstate;
- }
- break;
- case T_RelabelType:
- {
- RelabelType *relabel = (RelabelType *) node;
- GenericExprState *gstate = makeNode(GenericExprState);
-
- gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType;
- gstate->arg = ExecInitExpr(relabel->arg, parent);
- state = (ExprState *) gstate;
- }
- break;
- case T_CoerceViaIO:
- {
- CoerceViaIO *iocoerce = (CoerceViaIO *) node;
- CoerceViaIOState *iostate = makeNode(CoerceViaIOState);
- Oid iofunc;
- bool typisvarlena;
-
- iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO;
- iostate->arg = ExecInitExpr(iocoerce->arg, parent);
- /* lookup the result type's input function */
- getTypeInputInfo(iocoerce->resulttype, &iofunc,
- &iostate->intypioparam);
- fmgr_info(iofunc, &iostate->infunc);
- /* lookup the input type's output function */
- getTypeOutputInfo(exprType((Node *) iocoerce->arg),
- &iofunc, &typisvarlena);
- fmgr_info(iofunc, &iostate->outfunc);
- state = (ExprState *) iostate;
- }
- break;
- case T_ArrayCoerceExpr:
- {
- ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
- ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState);
-
- astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr;
- astate->arg = ExecInitExpr(acoerce->arg, parent);
- astate->resultelemtype = get_element_type(acoerce->resulttype);
- if (astate->resultelemtype == InvalidOid)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("target type is not an array")));
- /* Arrays over domains aren't supported yet */
- Assert(getBaseType(astate->resultelemtype) ==
- astate->resultelemtype);
- astate->elemfunc.fn_oid = InvalidOid; /* not initialized */
- astate->amstate = (ArrayMapState *) palloc0(sizeof(ArrayMapState));
- state = (ExprState *) astate;
- }
- break;
- case T_ConvertRowtypeExpr:
- {
- ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node;
- ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState);
-
- cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype;
- cstate->arg = ExecInitExpr(convert->arg, parent);
- state = (ExprState *) cstate;
- }
- break;
- case T_CaseExpr:
- {
- CaseExpr *caseexpr = (CaseExpr *) node;
- CaseExprState *cstate = makeNode(CaseExprState);
- List *outlist = NIL;
- ListCell *l;
-
- cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase;
- cstate->arg = ExecInitExpr(caseexpr->arg, parent);
- foreach(l, caseexpr->args)
- {
- CaseWhen *when = castNode(CaseWhen, lfirst(l));
- CaseWhenState *wstate = makeNode(CaseWhenState);
-
- wstate->xprstate.evalfunc = NULL; /* not used */
- wstate->xprstate.expr = (Expr *) when;
- wstate->expr = ExecInitExpr(when->expr, parent);
- wstate->result = ExecInitExpr(when->result, parent);
- outlist = lappend(outlist, wstate);
- }
- cstate->args = outlist;
- cstate->defresult = ExecInitExpr(caseexpr->defresult, parent);
- if (caseexpr->arg)
- cstate->argtyplen = get_typlen(exprType((Node *) caseexpr->arg));
- state = (ExprState *) cstate;
- }
- break;
- case T_ArrayExpr:
- {
- ArrayExpr *arrayexpr = (ArrayExpr *) node;
- ArrayExprState *astate = makeNode(ArrayExprState);
- List *outlist = NIL;
- ListCell *l;
-
- astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArray;
- foreach(l, arrayexpr->elements)
- {
- Expr *e = (Expr *) lfirst(l);
- ExprState *estate;
-
- estate = ExecInitExpr(e, parent);
- outlist = lappend(outlist, estate);
- }
- astate->elements = outlist;
- /* do one-time catalog lookup for type info */
- get_typlenbyvalalign(arrayexpr->element_typeid,
- &astate->elemlength,
- &astate->elembyval,
- &astate->elemalign);
- state = (ExprState *) astate;
- }
- break;
- case T_RowExpr:
- {
- RowExpr *rowexpr = (RowExpr *) node;
- RowExprState *rstate = makeNode(RowExprState);
- Form_pg_attribute *attrs;
- List *outlist = NIL;
- ListCell *l;
- int i;
-
- rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRow;
- /* Build tupdesc to describe result tuples */
- if (rowexpr->row_typeid == RECORDOID)
- {
- /* generic record, use types of given expressions */
- rstate->tupdesc = ExecTypeFromExprList(rowexpr->args);
- }
- else
- {
- /* it's been cast to a named type, use that */
- rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
- }
- /* In either case, adopt RowExpr's column aliases */
- ExecTypeSetColNames(rstate->tupdesc, rowexpr->colnames);
- /* Bless the tupdesc in case it's now of type RECORD */
- BlessTupleDesc(rstate->tupdesc);
- /* Set up evaluation, skipping any deleted columns */
- Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts);
- attrs = rstate->tupdesc->attrs;
- i = 0;
- foreach(l, rowexpr->args)
- {
- Expr *e = (Expr *) lfirst(l);
- ExprState *estate;
-
- if (!attrs[i]->attisdropped)
- {
- /*
- * Guard against ALTER COLUMN TYPE on rowtype since
- * the RowExpr was created. XXX should we check
- * typmod too? Not sure we can be sure it'll be the
- * same.
- */
- if (exprType((Node *) e) != attrs[i]->atttypid)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("ROW() column has type %s instead of type %s",
- format_type_be(exprType((Node *) e)),
- format_type_be(attrs[i]->atttypid))));
- }
- else
- {
- /*
- * Ignore original expression and insert a NULL. We
- * don't really care what type of NULL it is, so
- * always make an int4 NULL.
- */
- e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid);
- }
- estate = ExecInitExpr(e, parent);
- outlist = lappend(outlist, estate);
- i++;
- }
- rstate->args = outlist;
- state = (ExprState *) rstate;
- }
- break;
- case T_RowCompareExpr:
- {
- RowCompareExpr *rcexpr = (RowCompareExpr *) node;
- RowCompareExprState *rstate = makeNode(RowCompareExprState);
- int nopers = list_length(rcexpr->opnos);
- List *outlist;
- ListCell *l;
- ListCell *l2;
- ListCell *l3;
- int i;
-
- rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare;
- Assert(list_length(rcexpr->largs) == nopers);
- outlist = NIL;
- foreach(l, rcexpr->largs)
- {
- Expr *e = (Expr *) lfirst(l);
- ExprState *estate;
-
- estate = ExecInitExpr(e, parent);
- outlist = lappend(outlist, estate);
- }
- rstate->largs = outlist;
- Assert(list_length(rcexpr->rargs) == nopers);
- outlist = NIL;
- foreach(l, rcexpr->rargs)
- {
- Expr *e = (Expr *) lfirst(l);
- ExprState *estate;
-
- estate = ExecInitExpr(e, parent);
- outlist = lappend(outlist, estate);
- }
- rstate->rargs = outlist;
- Assert(list_length(rcexpr->opfamilies) == nopers);
- rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo));
- rstate->collations = (Oid *) palloc(nopers * sizeof(Oid));
- i = 0;
- forthree(l, rcexpr->opnos, l2, rcexpr->opfamilies, l3, rcexpr->inputcollids)
- {
- Oid opno = lfirst_oid(l);
- Oid opfamily = lfirst_oid(l2);
- Oid inputcollid = lfirst_oid(l3);
- int strategy;
- Oid lefttype;
- Oid righttype;
- Oid proc;
-
- get_op_opfamily_properties(opno, opfamily, false,
- &strategy,
- &lefttype,
- &righttype);
- proc = get_opfamily_proc(opfamily,
- lefttype,
- righttype,
- BTORDER_PROC);
-
- /*
- * If we enforced permissions checks on index support
- * functions, we'd need to make a check here. But the
- * index support machinery doesn't do that, and neither
- * does this code.
- */
- fmgr_info(proc, &(rstate->funcs[i]));
- rstate->collations[i] = inputcollid;
- i++;
- }
- state = (ExprState *) rstate;
- }
- break;
- case T_CoalesceExpr:
- {
- CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
- CoalesceExprState *cstate = makeNode(CoalesceExprState);
- List *outlist = NIL;
- ListCell *l;
-
- cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoalesce;
- foreach(l, coalesceexpr->args)
- {
- Expr *e = (Expr *) lfirst(l);
- ExprState *estate;
-
- estate = ExecInitExpr(e, parent);
- outlist = lappend(outlist, estate);
- }
- cstate->args = outlist;
- state = (ExprState *) cstate;
- }
- break;
- case T_MinMaxExpr:
- {
- MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
- MinMaxExprState *mstate = makeNode(MinMaxExprState);
- List *outlist = NIL;
- ListCell *l;
- TypeCacheEntry *typentry;
-
- mstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalMinMax;
- foreach(l, minmaxexpr->args)
- {
- Expr *e = (Expr *) lfirst(l);
- ExprState *estate;
-
- estate = ExecInitExpr(e, parent);
- outlist = lappend(outlist, estate);
- }
- mstate->args = outlist;
- /* Look up the btree comparison function for the datatype */
- typentry = lookup_type_cache(minmaxexpr->minmaxtype,
- TYPECACHE_CMP_PROC);
- if (!OidIsValid(typentry->cmp_proc))
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_FUNCTION),
- errmsg("could not identify a comparison function for type %s",
- format_type_be(minmaxexpr->minmaxtype))));
-
- /*
- * If we enforced permissions checks on index support
- * functions, we'd need to make a check here. But the index
- * support machinery doesn't do that, and neither does this
- * code.
- */
- fmgr_info(typentry->cmp_proc, &(mstate->cfunc));
- state = (ExprState *) mstate;
- }
- break;
- case T_SQLValueFunction:
- state = makeNode(ExprState);
- state->evalfunc = ExecEvalSQLValueFunction;
- break;
- case T_XmlExpr:
- {
- XmlExpr *xexpr = (XmlExpr *) node;
- XmlExprState *xstate = makeNode(XmlExprState);
- List *outlist;
- ListCell *arg;
-
- xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml;
- outlist = NIL;
- foreach(arg, xexpr->named_args)
- {
- Expr *e = (Expr *) lfirst(arg);
- ExprState *estate;
-
- estate = ExecInitExpr(e, parent);
- outlist = lappend(outlist, estate);
- }
- xstate->named_args = outlist;
-
- outlist = NIL;
- foreach(arg, xexpr->args)
- {
- Expr *e = (Expr *) lfirst(arg);
- ExprState *estate;
-
- estate = ExecInitExpr(e, parent);
- outlist = lappend(outlist, estate);
- }
- xstate->args = outlist;
-
- state = (ExprState *) xstate;
- }
- break;
- case T_NullTest:
- {
- NullTest *ntest = (NullTest *) node;
- NullTestState *nstate = makeNode(NullTestState);
-
- nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest;
- nstate->arg = ExecInitExpr(ntest->arg, parent);
- nstate->argdesc = NULL;
- state = (ExprState *) nstate;
- }
- break;
- case T_BooleanTest:
- {
- BooleanTest *btest = (BooleanTest *) node;
- GenericExprState *gstate = makeNode(GenericExprState);
-
- gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest;
- gstate->arg = ExecInitExpr(btest->arg, parent);
- state = (ExprState *) gstate;
- }
- break;
- case T_CoerceToDomain:
- {
- CoerceToDomain *ctest = (CoerceToDomain *) node;
- CoerceToDomainState *cstate = makeNode(CoerceToDomainState);
-
- cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain;
- cstate->arg = ExecInitExpr(ctest->arg, parent);
- /* We spend an extra palloc to reduce header inclusions */
- cstate->constraint_ref = (DomainConstraintRef *)
- palloc(sizeof(DomainConstraintRef));
- InitDomainConstraintRef(ctest->resulttype,
- cstate->constraint_ref,
- CurrentMemoryContext);
- state = (ExprState *) cstate;
- }
- break;
- case T_CurrentOfExpr:
- state = makeNode(ExprState);
- state->evalfunc = ExecEvalCurrentOfExpr;
- break;
- case T_TargetEntry:
- {
- TargetEntry *tle = (TargetEntry *) node;
- GenericExprState *gstate = makeNode(GenericExprState);
-
- gstate->xprstate.evalfunc = NULL; /* not used */
- gstate->arg = ExecInitExpr(tle->expr, parent);
- state = (ExprState *) gstate;
- }
- break;
- case T_List:
- {
- List *outlist = NIL;
- ListCell *l;
-
- foreach(l, (List *) node)
- {
- outlist = lappend(outlist,
- ExecInitExpr((Expr *) lfirst(l),
- parent));
- }
- /* Don't fall through to the "common" code below */
- return (ExprState *) outlist;
- }
- default:
- elog(ERROR, "unrecognized node type: %d",
- (int) nodeTag(node));
- state = NULL; /* keep compiler quiet */
- break;
- }
-
- /* Common code for all state-node types */
- state->expr = node;
-
- return state;
-}
-
-/*
- * ExecPrepareExpr --- initialize for expression execution outside a normal
- * Plan tree context.
- *
- * This differs from ExecInitExpr in that we don't assume the caller is
- * already running in the EState's per-query context. Also, we run the
- * passed expression tree through expression_planner() to prepare it for
- * execution. (In ordinary Plan trees the regular planning process will have
- * made the appropriate transformations on expressions, but for standalone
- * expressions this won't have happened.)
- */
-ExprState *
-ExecPrepareExpr(Expr *node, EState *estate)
-{
- ExprState *result;
- MemoryContext oldcontext;
-
- oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
-
- node = expression_planner(node);
-
- result = ExecInitExpr(node, NULL);
-
- MemoryContextSwitchTo(oldcontext);
-
- return result;
-}
-
-
-/* ----------------------------------------------------------------
- * ExecQual / ExecTargetList / ExecProject
- * ----------------------------------------------------------------
- */
-
-/* ----------------------------------------------------------------
- * ExecQual
- *
- * Evaluates a conjunctive boolean expression (qual list) and
- * returns true iff none of the subexpressions are false.
- * (We also return true if the list is empty.)
- *
- * If some of the subexpressions yield NULL but none yield FALSE,
- * then the result of the conjunction is NULL (ie, unknown)
- * according to three-valued boolean logic. In this case,
- * we return the value specified by the "resultForNull" parameter.
- *
- * Callers evaluating WHERE clauses should pass resultForNull=FALSE,
- * since SQL specifies that tuples with null WHERE results do not
- * get selected. On the other hand, callers evaluating constraint
- * conditions should pass resultForNull=TRUE, since SQL also specifies
- * that NULL constraint conditions are not failures.
- *
- * NOTE: it would not be correct to use this routine to evaluate an
- * AND subclause of a boolean expression; for that purpose, a NULL
- * result must be returned as NULL so that it can be properly treated
- * in the next higher operator (cf. ExecEvalAnd and ExecEvalOr).
- * This routine is only used in contexts where a complete expression
- * is being evaluated and we know that NULL can be treated the same
- * as one boolean result or the other.
- *
- * ----------------------------------------------------------------
- */
-bool
-ExecQual(List *qual, ExprContext *econtext, bool resultForNull)
-{
- bool result;
- MemoryContext oldContext;
- ListCell *l;
-
- /*
- * debugging stuff
- */
- EV_printf("ExecQual: qual is ");
- EV_nodeDisplay(qual);
- EV_printf("\n");
-
- /*
- * Run in short-lived per-tuple context while computing expressions.
- */
- oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-
- /*
- * Evaluate the qual conditions one at a time. If we find a FALSE result,
- * we can stop evaluating and return FALSE --- the AND result must be
- * FALSE. Also, if we find a NULL result when resultForNull is FALSE, we
- * can stop and return FALSE --- the AND result must be FALSE or NULL in
- * that case, and the caller doesn't care which.
- *
- * If we get to the end of the list, we can return TRUE. This will happen
- * when the AND result is indeed TRUE, or when the AND result is NULL (one
- * or more NULL subresult, with all the rest TRUE) and the caller has
- * specified resultForNull = TRUE.
- */
- result = true;
-
- foreach(l, qual)
- {
- ExprState *clause = (ExprState *) lfirst(l);
- Datum expr_value;
- bool isNull;
-
- expr_value = ExecEvalExpr(clause, econtext, &isNull);
-
- if (isNull)
- {
- if (resultForNull == false)
- {
- result = false; /* treat NULL as FALSE */
- break;
- }
- }
- else
- {
- if (!DatumGetBool(expr_value))
- {
- result = false; /* definitely FALSE */
- break;
- }
- }
- }
-
- MemoryContextSwitchTo(oldContext);
-
- return result;
-}
-
-/*
- * Number of items in a tlist (including any resjunk items!)
- */
-int
-ExecTargetListLength(List *targetlist)
-{
- /* This used to be more complex, but fjoins are dead */
- return list_length(targetlist);
-}
-
-/*
- * Number of items in a tlist, not including any resjunk items
- */
-int
-ExecCleanTargetListLength(List *targetlist)
-{
- int len = 0;
- ListCell *tl;
-
- foreach(tl, targetlist)
- {
- TargetEntry *curTle = castNode(TargetEntry, lfirst(tl));
-
- if (!curTle->resjunk)
- len++;
- }
- return len;
-}
-
-/*
- * ExecTargetList
- * Evaluates a targetlist with respect to the given
- * expression context.
- *
- * tupdesc must describe the rowtype of the expected result.
- *
- * Results are stored into the passed values and isnull arrays.
- *
- * Since fields of the result tuple might be multiply referenced in higher
- * plan nodes, we have to force any read/write expanded values to read-only
- * status. It's a bit annoying to have to do that for every projected
- * expression; in the future, consider teaching the planner to detect
- * actually-multiply-referenced Vars and insert an expression node that
- * would do that only where really required.
- */
-static void
-ExecTargetList(List *targetlist,
- TupleDesc tupdesc,
- ExprContext *econtext,
- Datum *values,
- bool *isnull)
-{
- Form_pg_attribute *att = tupdesc->attrs;
- MemoryContext oldContext;
- ListCell *tl;
-
- /*
- * Run in short-lived per-tuple context while computing expressions.
- */
- oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-
- /*
- * evaluate all the expressions in the target list
- */
- foreach(tl, targetlist)
- {
- GenericExprState *gstate = (GenericExprState *) lfirst(tl);
- TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
- AttrNumber resind = tle->resno - 1;
-
- values[resind] = ExecEvalExpr(gstate->arg,
- econtext,
- &isnull[resind]);
-
- values[resind] = MakeExpandedObjectReadOnly(values[resind],
- isnull[resind],
- att[resind]->attlen);
- }
-
- MemoryContextSwitchTo(oldContext);
-}
-
-/*
- * ExecProject
- *
- * projects a tuple based on projection info and stores
- * it in the previously specified tuple table slot.
- *
- * Note: the result is always a virtual tuple; therefore it
- * may reference the contents of the exprContext's scan tuples
- * and/or temporary results constructed in the exprContext.
- * If the caller wishes the result to be valid longer than that
- * data will be valid, he must call ExecMaterializeSlot on the
- * result slot.
- */
-TupleTableSlot *
-ExecProject(ProjectionInfo *projInfo)
-{
- TupleTableSlot *slot;
- ExprContext *econtext;
- int numSimpleVars;
-
- /*
- * sanity checks
- */
- Assert(projInfo != NULL);
-
- /*
- * get the projection info we want
- */
- slot = projInfo->pi_slot;
- econtext = projInfo->pi_exprContext;
-
- /*
- * Clear any former contents of the result slot. This makes it safe for
- * us to use the slot's Datum/isnull arrays as workspace.
- */
- ExecClearTuple(slot);
-
- /*
- * Force extraction of all input values that we'll need. The
- * Var-extraction loops below depend on this, and we are also prefetching
- * all attributes that will be referenced in the generic expressions.
- */
- if (projInfo->pi_lastInnerVar > 0)
- slot_getsomeattrs(econtext->ecxt_innertuple,
- projInfo->pi_lastInnerVar);
- if (projInfo->pi_lastOuterVar > 0)
- slot_getsomeattrs(econtext->ecxt_outertuple,
- projInfo->pi_lastOuterVar);
- if (projInfo->pi_lastScanVar > 0)
- slot_getsomeattrs(econtext->ecxt_scantuple,
- projInfo->pi_lastScanVar);
-
- /*
- * Assign simple Vars to result by direct extraction of fields from source
- * slots ... a mite ugly, but fast ...
- */
- numSimpleVars = projInfo->pi_numSimpleVars;
- if (numSimpleVars > 0)
- {
- Datum *values = slot->tts_values;
- bool *isnull = slot->tts_isnull;
- int *varSlotOffsets = projInfo->pi_varSlotOffsets;
- int *varNumbers = projInfo->pi_varNumbers;
- int i;
-
- if (projInfo->pi_directMap)
- {
- /* especially simple case where vars go to output in order */
- for (i = 0; i < numSimpleVars; i++)
- {
- char *slotptr = ((char *) econtext) + varSlotOffsets[i];
- TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr);
- int varNumber = varNumbers[i] - 1;
-
- values[i] = varSlot->tts_values[varNumber];
- isnull[i] = varSlot->tts_isnull[varNumber];
- }
- }
- else
- {
- /* we have to pay attention to varOutputCols[] */
- int *varOutputCols = projInfo->pi_varOutputCols;
-
- for (i = 0; i < numSimpleVars; i++)
- {
- char *slotptr = ((char *) econtext) + varSlotOffsets[i];
- TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr);
- int varNumber = varNumbers[i] - 1;
- int varOutputCol = varOutputCols[i] - 1;
-
- values[varOutputCol] = varSlot->tts_values[varNumber];
- isnull[varOutputCol] = varSlot->tts_isnull[varNumber];
- }
- }
- }
-
- /*
- * If there are any generic expressions, evaluate them.
- */
- if (projInfo->pi_targetlist)
- {
- ExecTargetList(projInfo->pi_targetlist,
- slot->tts_tupleDescriptor,
- econtext,
- slot->tts_values,
- slot->tts_isnull);
- }
-
- /*
- * Mark the result slot as containing a valid virtual tuple.
- */
- return ExecStoreVirtualTuple(slot);
-}
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * execSRF.c
+ * Routines implementing the API for set-returning functions
+ *
+ * This file serves nodeFunctionscan.c and nodeProjectSet.c, providing
+ * common code for calling set-returning functions according to the
+ * ReturnSetInfo API.
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/executor/execSRF.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/objectaccess.h"
+#include "executor/execdebug.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_coerce.h"
+#include "pgstat.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/typcache.h"
+
+
+/* static function decls */
+static void init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr,
+ MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF);
+static void ShutdownSetExpr(Datum arg);
+static void ExecEvalFuncArgs(FunctionCallInfo fcinfo,
+ List *argList, ExprContext *econtext);
+static void ExecPrepareTuplestoreResult(SetExprState *sexpr,
+ ExprContext *econtext,
+ Tuplestorestate *resultStore,
+ TupleDesc resultDesc);
+static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc);
+
+
+/*
+ * Prepare function call in FROM (ROWS FROM) for execution.
+ *
+ * This is used by nodeFunctionscan.c.
+ */
+SetExprState *
+ExecInitTableFunctionResult(Expr *expr,
+ ExprContext *econtext, PlanState *parent)
+{
+ SetExprState *state = makeNode(SetExprState);
+
+ state->funcReturnsSet = false;
+ state->expr = expr;
+ state->func.fn_oid = InvalidOid;
+
+ /*
+ * Normally the passed expression tree will be a FuncExpr, since the
+ * grammar only allows a function call at the top level of a table
+ * function reference. However, if the function doesn't return set then
+ * the planner might have replaced the function call via constant-folding
+ * or inlining. So if we see any other kind of expression node, execute
+ * it via the general ExecEvalExpr() code. That code path will not
+ * support set-returning functions buried in the expression, though.
+ */
+ if (IsA(expr, FuncExpr))
+ {
+ FuncExpr *func = (FuncExpr *) expr;
+
+ state->funcReturnsSet = func->funcretset;
+ state->args = ExecInitExprList(func->args, parent);
+
+ init_sexpr(func->funcid, func->inputcollid, state,
+ econtext->ecxt_per_query_memory, func->funcretset, false);
+ }
+ else
+ {
+ state->elidedFuncState = ExecInitExpr(expr, parent);
+ }
+
+ return state;
+}
+
+/*
+ * ExecMakeTableFunctionResult
+ *
+ * Evaluate a table function, producing a materialized result in a Tuplestore
+ * object.
+ *
+ * This is used by nodeFunctionscan.c.
+ */
+Tuplestorestate *
+ExecMakeTableFunctionResult(SetExprState *setexpr,
+ ExprContext *econtext,
+ MemoryContext argContext,
+ TupleDesc expectedDesc,
+ bool randomAccess)
+{
+ Tuplestorestate *tupstore = NULL;
+ TupleDesc tupdesc = NULL;
+ Oid funcrettype;
+ bool returnsTuple;
+ bool returnsSet = false;
+ FunctionCallInfoData fcinfo;
+ PgStat_FunctionCallUsage fcusage;
+ ReturnSetInfo rsinfo;
+ HeapTupleData tmptup;
+ MemoryContext callerContext;
+ MemoryContext oldcontext;
+ bool first_time = true;
+
+ callerContext = CurrentMemoryContext;
+
+ funcrettype = exprType((Node *) setexpr->expr);
+
+ returnsTuple = type_is_rowtype(funcrettype);
+
+ /*
+ * Prepare a resultinfo node for communication. We always do this even if
+ * not expecting a set result, so that we can pass expectedDesc. In the
+ * generic-expression case, the expression doesn't actually get to see the
+ * resultinfo, but set it up anyway because we use some of the fields as
+ * our own state variables.
+ */
+ rsinfo.type = T_ReturnSetInfo;
+ rsinfo.econtext = econtext;
+ rsinfo.expectedDesc = expectedDesc;
+ rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred);
+ if (randomAccess)
+ rsinfo.allowedModes |= (int) SFRM_Materialize_Random;
+ rsinfo.returnMode = SFRM_ValuePerCall;
+ /* isDone is filled below */
+ rsinfo.setResult = NULL;
+ rsinfo.setDesc = NULL;
+
+ /*
+ * Normally the passed expression tree will be a SetExprState, since the
+ * grammar only allows a function call at the top level of a table
+ * function reference. However, if the function doesn't return set then
+ * the planner might have replaced the function call via constant-folding
+ * or inlining. So if we see any other kind of expression node, execute
+ * it via the general ExecEvalExpr() code; the only difference is that we
+ * don't get a chance to pass a special ReturnSetInfo to any functions
+ * buried in the expression.
+ */
+ if (!setexpr->elidedFuncState)
+ {
+ /*
+ * This path is similar to ExecMakeFunctionResultSet.
+ */
+ returnsSet = setexpr->funcReturnsSet;
+ InitFunctionCallInfoData(fcinfo, &(setexpr->func),
+ list_length(setexpr->args),
+ setexpr->fcinfo_data.fncollation,
+ NULL, (Node *) &rsinfo);
+
+ /*
+ * Evaluate the function's argument list.
+ *
+ * We can't do this in the per-tuple context: the argument values
+ * would disappear when we reset that context in the inner loop. And
+ * the caller's CurrentMemoryContext is typically a query-lifespan
+ * context, so we don't want to leak memory there. We require the
+ * caller to pass a separate memory context that can be used for this,
+ * and can be reset each time through to avoid bloat.
+ */
+ MemoryContextReset(argContext);
+ oldcontext = MemoryContextSwitchTo(argContext);
+ ExecEvalFuncArgs(&fcinfo, setexpr->args, econtext);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * If function is strict, and there are any NULL arguments, skip
+ * calling the function and act like it returned NULL (or an empty
+ * set, in the returns-set case).
+ */
+ if (setexpr->func.fn_strict)
+ {
+ int i;
+
+ for (i = 0; i < fcinfo.nargs; i++)
+ {
+ if (fcinfo.argnull[i])
+ goto no_function_result;
+ }
+ }
+ }
+ else
+ {
+ /* Treat setexpr as a generic expression */
+ InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+ }
+
+ /*
+ * Switch to short-lived context for calling the function or expression.
+ */
+ MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+
+ /*
+ * Loop to handle the ValuePerCall protocol (which is also the same
+ * behavior needed in the generic ExecEvalExpr path).
+ */
+ for (;;)
+ {
+ Datum result;
+
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * reset per-tuple memory context before each call of the function or
+ * expression. This cleans up any local memory the function may leak
+ * when called.
+ */
+ ResetExprContext(econtext);
+
+ /* Call the function or expression one time */
+ if (!setexpr->elidedFuncState)
+ {
+ pgstat_init_function_usage(&fcinfo, &fcusage);
+
+ fcinfo.isnull = false;
+ rsinfo.isDone = ExprSingleResult;
+ result = FunctionCallInvoke(&fcinfo);
+
+ pgstat_end_function_usage(&fcusage,
+ rsinfo.isDone != ExprMultipleResult);
+ }
+ else
+ {
+ result =
+ ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo.isnull);
+ rsinfo.isDone = ExprSingleResult;
+ }
+
+ /* Which protocol does function want to use? */
+ if (rsinfo.returnMode == SFRM_ValuePerCall)
+ {
+ /*
+ * Check for end of result set.
+ */
+ if (rsinfo.isDone == ExprEndResult)
+ break;
+
+ /*
+ * If first time through, build tuplestore for result. For a
+ * scalar function result type, also make a suitable tupdesc.
+ */
+ if (first_time)
+ {
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+ rsinfo.setResult = tupstore;
+ if (!returnsTuple)
+ {
+ tupdesc = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) 1,
+ "column",
+ funcrettype,
+ -1,
+ 0);
+ rsinfo.setDesc = tupdesc;
+ }
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /*
+ * Store current resultset item.
+ */
+ if (returnsTuple)
+ {
+ if (!fcinfo.isnull)
+ {
+ HeapTupleHeader td = DatumGetHeapTupleHeader(result);
+
+ if (tupdesc == NULL)
+ {
+ /*
+ * This is the first non-NULL result from the
+ * function. Use the type info embedded in the
+ * rowtype Datum to look up the needed tupdesc. Make
+ * a copy for the query.
+ */
+ oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td),
+ HeapTupleHeaderGetTypMod(td));
+ rsinfo.setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+ }
+ else
+ {
+ /*
+ * Verify all later returned rows have same subtype;
+ * necessary in case the type is RECORD.
+ */
+ if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid ||
+ HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("rows returned by function are not all of the same row type")));
+ }
+
+ /*
+ * tuplestore_puttuple needs a HeapTuple not a bare
+ * HeapTupleHeader, but it doesn't need all the fields.
+ */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+ tmptup.t_data = td;
+
+ tuplestore_puttuple(tupstore, &tmptup);
+ }
+ else
+ {
+ /*
+ * NULL result from a tuple-returning function; expand it
+ * to a row of all nulls. We rely on the expectedDesc to
+ * form such rows. (Note: this would be problematic if
+ * tuplestore_putvalues saved the tdtypeid/tdtypmod from
+ * the provided descriptor, since that might not match
+ * what we get from the function itself. But it doesn't.)
+ */
+ int natts = expectedDesc->natts;
+ bool *nullflags;
+
+ nullflags = (bool *) palloc(natts * sizeof(bool));
+ memset(nullflags, true, natts * sizeof(bool));
+ tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
+ }
+ }
+ else
+ {
+ /* Scalar-type case: just store the function result */
+ tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
+ }
+
+ /*
+ * Are we done?
+ */
+ if (rsinfo.isDone != ExprMultipleResult)
+ break;
+ }
+ else if (rsinfo.returnMode == SFRM_Materialize)
+ {
+ /* check we're on the same page as the function author */
+ if (!first_time || rsinfo.isDone != ExprSingleResult)
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+ errmsg("table-function protocol for materialize mode was not followed")));
+ /* Done evaluating the set result */
+ break;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+ errmsg("unrecognized table-function returnMode: %d",
+ (int) rsinfo.returnMode)));
+
+ first_time = false;
+ }
+
+no_function_result:
+
+ /*
+ * If we got nothing from the function (ie, an empty-set or NULL result),
+ * we have to create the tuplestore to return, and if it's a
+ * non-set-returning function then insert a single all-nulls row. As
+ * above, we depend on the expectedDesc to manufacture the dummy row.
+ */
+ if (rsinfo.setResult == NULL)
+ {
+ MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
+ rsinfo.setResult = tupstore;
+ if (!returnsSet)
+ {
+ int natts = expectedDesc->natts;
+ bool *nullflags;
+
+ MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ nullflags = (bool *) palloc(natts * sizeof(bool));
+ memset(nullflags, true, natts * sizeof(bool));
+ tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags);
+ }
+ }
+
+ /*
+ * If function provided a tupdesc, cross-check it. We only really need to
+ * do this for functions returning RECORD, but might as well do it always.
+ */
+ if (rsinfo.setDesc)
+ {
+ tupledesc_match(expectedDesc, rsinfo.setDesc);
+
+ /*
+ * If it is a dynamically-allocated TupleDesc, free it: it is
+ * typically allocated in a per-query context, so we must avoid
+ * leaking it across multiple usages.
+ */
+ if (rsinfo.setDesc->tdrefcount == -1)
+ FreeTupleDesc(rsinfo.setDesc);
+ }
+
+ MemoryContextSwitchTo(callerContext);
+
+ /* All done, pass back the tuplestore */
+ return rsinfo.setResult;
+}
+
+
+/*
+ * Prepare targetlist SRF function call for execution.
+ *
+ * This is used by nodeProjectSet.c.
+ */
+SetExprState *
+ExecInitFunctionResultSet(Expr *expr,
+ ExprContext *econtext, PlanState *parent)
+{
+ SetExprState *state = makeNode(SetExprState);
+
+ state->funcReturnsSet = true;
+ state->expr = expr;
+ state->func.fn_oid = InvalidOid;
+
+ /*
+ * Initialize metadata. The expression node could be either a FuncExpr or
+ * an OpExpr.
+ */
+ if (IsA(expr, FuncExpr))
+ {
+ FuncExpr *func = (FuncExpr *) expr;
+
+ state->args = ExecInitExprList(func->args, parent);
+ init_sexpr(func->funcid, func->inputcollid, state,
+ econtext->ecxt_per_query_memory, true, true);
+ }
+ else if (IsA(expr, OpExpr))
+ {
+ OpExpr *op = (OpExpr *) expr;
+
+ state->args = ExecInitExprList(op->args, parent);
+ init_sexpr(op->opfuncid, op->inputcollid, state,
+ econtext->ecxt_per_query_memory, true, true);
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(expr));
+
+ /* shouldn't get here unless the selected function returns set */
+ Assert(state->func.fn_retset);
+
+ return state;
+}
+
+/*
+ * ExecMakeFunctionResultSet
+ *
+ * Evaluate the arguments to a set-returning function and then call the
+ * function itself. The argument expressions may not contain set-returning
+ * functions (the planner is supposed to have separated evaluation for those).
+ *
+ * This is used by nodeProjectSet.c.
+ */
+Datum
+ExecMakeFunctionResultSet(SetExprState *fcache,
+ ExprContext *econtext,
+ bool *isNull,
+ ExprDoneCond *isDone)
+{
+ List *arguments;
+ Datum result;
+ FunctionCallInfo fcinfo;
+ PgStat_FunctionCallUsage fcusage;
+ ReturnSetInfo rsinfo;
+ bool callit;
+ int i;
+
+restart:
+
+ /* Guard against stack overflow due to overly complex expressions */
+ check_stack_depth();
+
+ /*
+ * If a previous call of the function returned a set result in the form of
+ * a tuplestore, continue reading rows from the tuplestore until it's
+ * empty.
+ */
+ if (fcache->funcResultStore)
+ {
+ if (tuplestore_gettupleslot(fcache->funcResultStore, true, false,
+ fcache->funcResultSlot))
+ {
+ *isDone = ExprMultipleResult;
+ if (fcache->funcReturnsTuple)
+ {
+ /* We must return the whole tuple as a Datum. */
+ *isNull = false;
+ return ExecFetchSlotTupleDatum(fcache->funcResultSlot);
+ }
+ else
+ {
+ /* Extract the first column and return it as a scalar. */
+ return slot_getattr(fcache->funcResultSlot, 1, isNull);
+ }
+ }
+ /* Exhausted the tuplestore, so clean up */
+ tuplestore_end(fcache->funcResultStore);
+ fcache->funcResultStore = NULL;
+ *isDone = ExprEndResult;
+ *isNull = true;
+ return (Datum) 0;
+ }
+
+ /*
+ * arguments is a list of expressions to evaluate before passing to the
+ * function manager. We skip the evaluation if it was already done in the
+ * previous call (ie, we are continuing the evaluation of a set-valued
+ * function). Otherwise, collect the current argument values into fcinfo.
+ */
+ fcinfo = &fcache->fcinfo_data;
+ arguments = fcache->args;
+ if (!fcache->setArgsValid)
+ ExecEvalFuncArgs(fcinfo, arguments, econtext);
+ else
+ {
+ /* Reset flag (we may set it again below) */
+ fcache->setArgsValid = false;
+ }
+
+ /*
+ * Now call the function, passing the evaluated parameter values.
+ */
+
+ /* Prepare a resultinfo node for communication. */
+ fcinfo->resultinfo = (Node *) &rsinfo;
+ rsinfo.type = T_ReturnSetInfo;
+ rsinfo.econtext = econtext;
+ rsinfo.expectedDesc = fcache->funcResultDesc;
+ rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize);
+ /* note we do not set SFRM_Materialize_Random or _Preferred */
+ rsinfo.returnMode = SFRM_ValuePerCall;
+ /* isDone is filled below */
+ rsinfo.setResult = NULL;
+ rsinfo.setDesc = NULL;
+
+ /*
+ * If function is strict, and there are any NULL arguments, skip calling
+ * the function.
+ */
+ callit = true;
+ if (fcache->func.fn_strict)
+ {
+ for (i = 0; i < fcinfo->nargs; i++)
+ {
+ if (fcinfo->argnull[i])
+ {
+ callit = false;
+ break;
+ }
+ }
+ }
+
+ if (callit)
+ {
+ pgstat_init_function_usage(fcinfo, &fcusage);
+
+ fcinfo->isnull = false;
+ rsinfo.isDone = ExprSingleResult;
+ result = FunctionCallInvoke(fcinfo);
+ *isNull = fcinfo->isnull;
+ *isDone = rsinfo.isDone;
+
+ pgstat_end_function_usage(&fcusage,
+ rsinfo.isDone != ExprMultipleResult);
+ }
+ else
+ {
+ /* for a strict SRF, result for NULL is an empty set */
+ result = (Datum) 0;
+ *isNull = true;
+ *isDone = ExprEndResult;
+ }
+
+ /* Which protocol does function want to use? */
+ if (rsinfo.returnMode == SFRM_ValuePerCall)
+ {
+ if (*isDone != ExprEndResult)
+ {
+ /*
+ * Save the current argument values to re-use on the next call.
+ */
+ if (*isDone == ExprMultipleResult)
+ {
+ fcache->setArgsValid = true;
+ /* Register cleanup callback if we didn't already */
+ if (!fcache->shutdown_reg)
+ {
+ RegisterExprContextCallback(econtext,
+ ShutdownSetExpr,
+ PointerGetDatum(fcache));
+ fcache->shutdown_reg = true;
+ }
+ }
+ }
+ }
+ else if (rsinfo.returnMode == SFRM_Materialize)
+ {
+ /* check we're on the same page as the function author */
+ if (rsinfo.isDone != ExprSingleResult)
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+ errmsg("table-function protocol for materialize mode was not followed")));
+ if (rsinfo.setResult != NULL)
+ {
+ /* prepare to return values from the tuplestore */
+ ExecPrepareTuplestoreResult(fcache, econtext,
+ rsinfo.setResult,
+ rsinfo.setDesc);
+ /* loop back to top to start returning from tuplestore */
+ goto restart;
+ }
+ /* if setResult was left null, treat it as empty set */
+ *isDone = ExprEndResult;
+ *isNull = true;
+ result = (Datum) 0;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED),
+ errmsg("unrecognized table-function returnMode: %d",
+ (int) rsinfo.returnMode)));
+
+ return result;
+}
+
+
+/*
+ * init_sexpr - initialize a SetExprState node during first use
+ */
+static void
+init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr,
+ MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF)
+{
+ AclResult aclresult;
+
+ /* Check permission to call function */
+ aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid));
+ InvokeFunctionExecuteHook(foid);
+
+ /*
+ * Safety check on nargs. Under normal circumstances this should never
+ * fail, as parser should check sooner. But possibly it might fail if
+ * server has been compiled with FUNC_MAX_ARGS smaller than some functions
+ * declared in pg_proc?
+ */
+ if (list_length(sexpr->args) > FUNC_MAX_ARGS)
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg_plural("cannot pass more than %d argument to a function",
+ "cannot pass more than %d arguments to a function",
+ FUNC_MAX_ARGS,
+ FUNC_MAX_ARGS)));
+
+ /* Set up the primary fmgr lookup information */
+ fmgr_info_cxt(foid, &(sexpr->func), sexprCxt);
+ fmgr_info_set_expr((Node *) sexpr->expr, &(sexpr->func));
+
+ /* Initialize the function call parameter struct as well */
+ InitFunctionCallInfoData(sexpr->fcinfo_data, &(sexpr->func),
+ list_length(sexpr->args),
+ input_collation, NULL, NULL);
+
+ /* If function returns set, check if that's allowed by caller */
+ if (sexpr->func.fn_retset && !allowSRF)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ /* Otherwise, caller should have marked the sexpr correctly */
+ Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet);
+
+ /* If function returns set, prepare expected tuple descriptor */
+ if (sexpr->func.fn_retset && needDescForSRF)
+ {
+ TypeFuncClass functypclass;
+ Oid funcrettype;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+
+ functypclass = get_expr_result_type(sexpr->func.fn_expr,
+ &funcrettype,
+ &tupdesc);
+
+ /* Must save tupdesc in sexpr's context */
+ oldcontext = MemoryContextSwitchTo(sexprCxt);
+
+ if (functypclass == TYPEFUNC_COMPOSITE)
+ {
+ /* Composite data type, e.g. a table's row type */
+ Assert(tupdesc);
+ /* Must copy it out of typcache for safety */
+ sexpr->funcResultDesc = CreateTupleDescCopy(tupdesc);
+ sexpr->funcReturnsTuple = true;
+ }
+ else if (functypclass == TYPEFUNC_SCALAR)
+ {
+ /* Base data type, i.e. scalar */
+ tupdesc = CreateTemplateTupleDesc(1, false);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) 1,
+ NULL,
+ funcrettype,
+ -1,
+ 0);
+ sexpr->funcResultDesc = tupdesc;
+ sexpr->funcReturnsTuple = false;
+ }
+ else if (functypclass == TYPEFUNC_RECORD)
+ {
+ /* This will work if function doesn't need an expectedDesc */
+ sexpr->funcResultDesc = NULL;
+ sexpr->funcReturnsTuple = true;
+ }
+ else
+ {
+ /* Else, we will fail if function needs an expectedDesc */
+ sexpr->funcResultDesc = NULL;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+ else
+ sexpr->funcResultDesc = NULL;
+
+ /* Initialize additional state */
+ sexpr->funcResultStore = NULL;
+ sexpr->funcResultSlot = NULL;
+ sexpr->shutdown_reg = false;
+}
+
+/*
+ * callback function in case a SetExprState needs to be shut down before it
+ * has been run to completion
+ */
+static void
+ShutdownSetExpr(Datum arg)
+{
+ SetExprState *sexpr = castNode(SetExprState, DatumGetPointer(arg));
+
+ /* If we have a slot, make sure it's let go of any tuplestore pointer */
+ if (sexpr->funcResultSlot)
+ ExecClearTuple(sexpr->funcResultSlot);
+
+ /* Release any open tuplestore */
+ if (sexpr->funcResultStore)
+ tuplestore_end(sexpr->funcResultStore);
+ sexpr->funcResultStore = NULL;
+
+ /* Clear any active set-argument state */
+ sexpr->setArgsValid = false;
+
+ /* execUtils will deregister the callback... */
+ sexpr->shutdown_reg = false;
+}
+
+/*
+ * Evaluate arguments for a function.
+ */
+static void
+ExecEvalFuncArgs(FunctionCallInfo fcinfo,
+ List *argList,
+ ExprContext *econtext)
+{
+ int i;
+ ListCell *arg;
+
+ i = 0;
+ foreach(arg, argList)
+ {
+ ExprState *argstate = (ExprState *) lfirst(arg);
+
+ fcinfo->arg[i] = ExecEvalExpr(argstate,
+ econtext,
+ &fcinfo->argnull[i]);
+ i++;
+ }
+
+ Assert(i == fcinfo->nargs);
+}
+
+/*
+ * ExecPrepareTuplestoreResult
+ *
+ * Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a
+ * tuplestore function result. We must set up a funcResultSlot (unless
+ * already done in a previous call cycle) and verify that the function
+ * returned the expected tuple descriptor.
+ */
+static void
+ExecPrepareTuplestoreResult(SetExprState *sexpr,
+ ExprContext *econtext,
+ Tuplestorestate *resultStore,
+ TupleDesc resultDesc)
+{
+ sexpr->funcResultStore = resultStore;
+
+ if (sexpr->funcResultSlot == NULL)
+ {
+ /* Create a slot so we can read data out of the tuplestore */
+ TupleDesc slotDesc;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(sexpr->func.fn_mcxt);
+
+ /*
+ * If we were not able to determine the result rowtype from context,
+ * and the function didn't return a tupdesc, we have to fail.
+ */
+ if (sexpr->funcResultDesc)
+ slotDesc = sexpr->funcResultDesc;
+ else if (resultDesc)
+ {
+ /* don't assume resultDesc is long-lived */
+ slotDesc = CreateTupleDescCopy(resultDesc);
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning setof record called in "
+ "context that cannot accept type record")));
+ slotDesc = NULL; /* keep compiler quiet */
+ }
+
+ sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /*
+ * If function provided a tupdesc, cross-check it. We only really need to
+ * do this for functions returning RECORD, but might as well do it always.
+ */
+ if (resultDesc)
+ {
+ if (sexpr->funcResultDesc)
+ tupledesc_match(sexpr->funcResultDesc, resultDesc);
+
+ /*
+ * If it is a dynamically-allocated TupleDesc, free it: it is
+ * typically allocated in a per-query context, so we must avoid
+ * leaking it across multiple usages.
+ */
+ if (resultDesc->tdrefcount == -1)
+ FreeTupleDesc(resultDesc);
+ }
+
+ /* Register cleanup callback if we didn't already */
+ if (!sexpr->shutdown_reg)
+ {
+ RegisterExprContextCallback(econtext,
+ ShutdownSetExpr,
+ PointerGetDatum(sexpr));
+ sexpr->shutdown_reg = true;
+ }
+}
+
+/*
+ * Check that function result tuple type (src_tupdesc) matches or can
+ * be considered to match what the query expects (dst_tupdesc). If
+ * they don't match, ereport.
+ *
+ * We really only care about number of attributes and data type.
+ * Also, we can ignore type mismatch on columns that are dropped in the
+ * destination type, so long as the physical storage matches. This is
+ * helpful in some cases involving out-of-date cached plans.
+ */
+static void
+tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
+{
+ int i;
+
+ if (dst_tupdesc->natts != src_tupdesc->natts)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail_plural("Returned row contains %d attribute, but query expects %d.",
+ "Returned row contains %d attributes, but query expects %d.",
+ src_tupdesc->natts,
+ src_tupdesc->natts, dst_tupdesc->natts)));
+
+ for (i = 0; i < dst_tupdesc->natts; i++)
+ {
+ Form_pg_attribute dattr = dst_tupdesc->attrs[i];
+ Form_pg_attribute sattr = src_tupdesc->attrs[i];
+
+ if (IsBinaryCoercible(sattr->atttypid, dattr->atttypid))
+ continue; /* no worries */
+ if (!dattr->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail("Returned type %s at ordinal position %d, but query expects %s.",
+ format_type_be(sattr->atttypid),
+ i + 1,
+ format_type_be(dattr->atttypid))));
+
+ if (dattr->attlen != sattr->attlen ||
+ dattr->attalign != sattr->attalign)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("function return row and query-specified return row do not match"),
+ errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.",
+ i + 1)));
+ }
+}
ExecScanRecheckMtd recheckMtd)
{
ExprContext *econtext;
- List *qual;
+ ExprState *qual;
ProjectionInfo *projInfo;
/*
if (TupIsNull(slot))
{
if (projInfo)
- return ExecClearTuple(projInfo->pi_slot);
+ return ExecClearTuple(projInfo->pi_state.resultslot);
else
return slot;
}
/*
* check that the current tuple satisfies the qual-clause
*
- * check for non-nil qual here to avoid a function call to ExecQual()
- * when the qual is nil ... saves only a few cycles, but they add up
+ * check for non-null qual here to avoid a function call to ExecQual()
+ * when the qual is null ... saves only a few cycles, but they add up
* ...
*/
- if (!qual || ExecQual(qual, econtext, false))
+ if (qual == NULL || ExecQual(qual, econtext))
{
/*
* Found a satisfactory scan tuple.
* RegisterExprContextCallback Register function shutdown callback
* UnregisterExprContextCallback Deregister function shutdown callback
*
+ * GetAttributeByName Runtime extraction of columns from tuples.
+ * GetAttributeByNum
+ *
* NOTES
* This file has traditionally been the place to stick misc.
* executor support stuff that doesn't really go anyplace else.
#include "nodes/nodeFuncs.h"
#include "parser/parsetree.h"
#include "storage/lmgr.h"
+#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/rel.h"
+#include "utils/typcache.h"
-static bool get_last_attnums(Node *node, ProjectionInfo *projInfo);
static void ShutdownExprContext(ExprContext *econtext, bool isCommit);
return slot->tts_tupleDescriptor;
}
-/* ----------------
- * ExecBuildProjectionInfo
- *
- * Build a ProjectionInfo node for evaluating the given tlist in the given
- * econtext, and storing the result into the tuple slot. (Caller must have
- * ensured that tuple slot has a descriptor matching the tlist!) Note that
- * the given tlist should be a list of ExprState nodes, not Expr nodes.
- *
- * inputDesc can be NULL, but if it is not, we check to see whether simple
- * Vars in the tlist match the descriptor. It is important to provide
- * inputDesc for relation-scan plan nodes, as a cross check that the relation
- * hasn't been changed since the plan was made. At higher levels of a plan,
- * there is no need to recheck.
- * ----------------
- */
-ProjectionInfo *
-ExecBuildProjectionInfo(List *targetList,
- ExprContext *econtext,
- TupleTableSlot *slot,
- TupleDesc inputDesc)
-{
- ProjectionInfo *projInfo = makeNode(ProjectionInfo);
- int len = ExecTargetListLength(targetList);
- int *workspace;
- int *varSlotOffsets;
- int *varNumbers;
- int *varOutputCols;
- List *exprlist;
- int numSimpleVars;
- bool directMap;
- ListCell *tl;
-
- projInfo->pi_exprContext = econtext;
- projInfo->pi_slot = slot;
- /* since these are all int arrays, we need do just one palloc */
- workspace = (int *) palloc(len * 3 * sizeof(int));
- projInfo->pi_varSlotOffsets = varSlotOffsets = workspace;
- projInfo->pi_varNumbers = varNumbers = workspace + len;
- projInfo->pi_varOutputCols = varOutputCols = workspace + len * 2;
- projInfo->pi_lastInnerVar = 0;
- projInfo->pi_lastOuterVar = 0;
- projInfo->pi_lastScanVar = 0;
-
- /*
- * We separate the target list elements into simple Var references and
- * expressions which require the full ExecTargetList machinery. To be a
- * simple Var, a Var has to be a user attribute and not mismatch the
- * inputDesc. (Note: if there is a type mismatch then ExecEvalScalarVar
- * will probably throw an error at runtime, but we leave that to it.)
- */
- exprlist = NIL;
- numSimpleVars = 0;
- directMap = true;
- foreach(tl, targetList)
- {
- GenericExprState *gstate = (GenericExprState *) lfirst(tl);
- Var *variable = (Var *) gstate->arg->expr;
- bool isSimpleVar = false;
-
- if (variable != NULL &&
- IsA(variable, Var) &&
- variable->varattno > 0)
- {
- if (!inputDesc)
- isSimpleVar = true; /* can't check type, assume OK */
- else if (variable->varattno <= inputDesc->natts)
- {
- Form_pg_attribute attr;
-
- attr = inputDesc->attrs[variable->varattno - 1];
- if (!attr->attisdropped && variable->vartype == attr->atttypid)
- isSimpleVar = true;
- }
- }
-
- if (isSimpleVar)
- {
- TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
- AttrNumber attnum = variable->varattno;
-
- varNumbers[numSimpleVars] = attnum;
- varOutputCols[numSimpleVars] = tle->resno;
- if (tle->resno != numSimpleVars + 1)
- directMap = false;
-
- switch (variable->varno)
- {
- case INNER_VAR:
- varSlotOffsets[numSimpleVars] = offsetof(ExprContext,
- ecxt_innertuple);
- if (projInfo->pi_lastInnerVar < attnum)
- projInfo->pi_lastInnerVar = attnum;
- break;
-
- case OUTER_VAR:
- varSlotOffsets[numSimpleVars] = offsetof(ExprContext,
- ecxt_outertuple);
- if (projInfo->pi_lastOuterVar < attnum)
- projInfo->pi_lastOuterVar = attnum;
- break;
-
- /* INDEX_VAR is handled by default case */
-
- default:
- varSlotOffsets[numSimpleVars] = offsetof(ExprContext,
- ecxt_scantuple);
- if (projInfo->pi_lastScanVar < attnum)
- projInfo->pi_lastScanVar = attnum;
- break;
- }
- numSimpleVars++;
- }
- else
- {
- /* Not a simple variable, add it to generic targetlist */
- exprlist = lappend(exprlist, gstate);
- /* Examine expr to include contained Vars in lastXXXVar counts */
- get_last_attnums((Node *) variable, projInfo);
- }
- }
- projInfo->pi_targetlist = exprlist;
- projInfo->pi_numSimpleVars = numSimpleVars;
- projInfo->pi_directMap = directMap;
-
- return projInfo;
-}
-
-/*
- * get_last_attnums: expression walker for ExecBuildProjectionInfo
- *
- * Update the lastXXXVar counts to be at least as large as the largest
- * attribute numbers found in the expression
- */
-static bool
-get_last_attnums(Node *node, ProjectionInfo *projInfo)
-{
- if (node == NULL)
- return false;
- if (IsA(node, Var))
- {
- Var *variable = (Var *) node;
- AttrNumber attnum = variable->varattno;
-
- switch (variable->varno)
- {
- case INNER_VAR:
- if (projInfo->pi_lastInnerVar < attnum)
- projInfo->pi_lastInnerVar = attnum;
- break;
-
- case OUTER_VAR:
- if (projInfo->pi_lastOuterVar < attnum)
- projInfo->pi_lastOuterVar = attnum;
- break;
-
- /* INDEX_VAR is handled by default case */
-
- default:
- if (projInfo->pi_lastScanVar < attnum)
- projInfo->pi_lastScanVar = attnum;
- break;
- }
- return false;
- }
-
- /*
- * Don't examine the arguments or filters of Aggrefs or WindowFuncs,
- * because those do not represent expressions to be evaluated within the
- * overall targetlist's econtext. GroupingFunc arguments are never
- * evaluated at all.
- */
- if (IsA(node, Aggref))
- return false;
- if (IsA(node, WindowFunc))
- return false;
- if (IsA(node, GroupingFunc))
- return false;
- return expression_tree_walker(node, get_last_attnums,
- (void *) projInfo);
-}
/* ----------------
* ExecAssignProjectionInfo
TupleDesc inputDesc)
{
planstate->ps_ProjInfo =
- ExecBuildProjectionInfo(planstate->targetlist,
+ ExecBuildProjectionInfo(planstate->plan->targetlist,
planstate->ps_ExprContext,
planstate->ps_ResultTupleSlot,
+ planstate,
inputDesc);
}
}
}
}
+
+/*
+ * GetAttributeByName
+ * GetAttributeByNum
+ *
+ * These functions return the value of the requested attribute
+ * out of the given tuple Datum.
+ * C functions which take a tuple as an argument are expected
+ * to use these. Ex: overpaid(EMP) might call GetAttributeByNum().
+ * Note: these are actually rather slow because they do a typcache
+ * lookup on each call.
+ */
+Datum
+GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull)
+{
+ AttrNumber attrno;
+ Datum result;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupDesc;
+ HeapTupleData tmptup;
+ int i;
+
+ if (attname == NULL)
+ elog(ERROR, "invalid attribute name");
+
+ if (isNull == NULL)
+ elog(ERROR, "a NULL isNull pointer was passed");
+
+ if (tuple == NULL)
+ {
+ /* Kinda bogus but compatible with old behavior... */
+ *isNull = true;
+ return (Datum) 0;
+ }
+
+ tupType = HeapTupleHeaderGetTypeId(tuple);
+ tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+ tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+ attrno = InvalidAttrNumber;
+ for (i = 0; i < tupDesc->natts; i++)
+ {
+ if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0)
+ {
+ attrno = tupDesc->attrs[i]->attnum;
+ break;
+ }
+ }
+
+ if (attrno == InvalidAttrNumber)
+ elog(ERROR, "attribute \"%s\" does not exist", attname);
+
+ /*
+ * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all
+ * the fields in the struct just in case user tries to inspect system
+ * columns.
+ */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+ ItemPointerSetInvalid(&(tmptup.t_self));
+ tmptup.t_tableOid = InvalidOid;
+ tmptup.t_data = tuple;
+
+ result = heap_getattr(&tmptup,
+ attrno,
+ tupDesc,
+ isNull);
+
+ ReleaseTupleDesc(tupDesc);
+
+ return result;
+}
+
+Datum
+GetAttributeByNum(HeapTupleHeader tuple,
+ AttrNumber attrno,
+ bool *isNull)
+{
+ Datum result;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupDesc;
+ HeapTupleData tmptup;
+
+ if (!AttributeNumberIsValid(attrno))
+ elog(ERROR, "invalid attribute number %d", attrno);
+
+ if (isNull == NULL)
+ elog(ERROR, "a NULL isNull pointer was passed");
+
+ if (tuple == NULL)
+ {
+ /* Kinda bogus but compatible with old behavior... */
+ *isNull = true;
+ return (Datum) 0;
+ }
+
+ tupType = HeapTupleHeaderGetTypeId(tuple);
+ tupTypmod = HeapTupleHeaderGetTypMod(tuple);
+ tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+ /*
+ * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all
+ * the fields in the struct just in case user tries to inspect system
+ * columns.
+ */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+ ItemPointerSetInvalid(&(tmptup.t_self));
+ tmptup.t_tableOid = InvalidOid;
+ tmptup.t_data = tuple;
+
+ result = heap_getattr(&tmptup,
+ attrno,
+ tupDesc,
+ isNull);
+
+ ReleaseTupleDesc(tupDesc);
+
+ return result;
+}
+
+/*
+ * Number of items in a tlist (including any resjunk items!)
+ */
+int
+ExecTargetListLength(List *targetlist)
+{
+ /* This used to be more complex, but fjoins are dead */
+ return list_length(targetlist);
+}
+
+/*
+ * Number of items in a tlist, not including any resjunk items
+ */
+int
+ExecCleanTargetListLength(List *targetlist)
+{
+ int len = 0;
+ ListCell *tl;
+
+ foreach(tl, targetlist)
+ {
+ TargetEntry *curTle = castNode(TargetEntry, lfirst(tl));
+
+ if (!curTle->resjunk)
+ len++;
+ }
+ return len;
+}
rsi->returnMode = SFRM_Materialize;
rsi->setResult = fcache->tstore;
fcache->tstore = NULL;
- /* must copy desc because execQual will free it */
+ /* must copy desc because execSRF.c will free it */
if (fcache->junkFilter)
rsi->setDesc = CreateTupleDescCopy(fcache->junkFilter->jf_cleanTupType);
/*
* Check the qual (HAVING clause); if the group does not match, ignore it.
*/
- if (ExecQual(aggstate->ss.ps.qual, econtext, false))
+ if (ExecQual(aggstate->ss.ps.qual, econtext))
{
/*
* Form and return projection tuple using the aggregate results and
/*
* initialize child expressions
*
- * Note: ExecInitExpr finds Aggrefs for us, and also checks that no aggs
- * contain other agg calls in their arguments. This would make no sense
- * under SQL semantics anyway (and it's forbidden by the spec). Because
- * that is true, we don't need to worry about evaluating the aggs in any
- * particular order.
+ * We rely on the parser to have checked that no aggs contain other agg
+ * calls in their arguments. This would make no sense under SQL semantics
+ * (and it's forbidden by the spec). Because it is true, we don't need to
+ * worry about evaluating the aggs in any particular order.
+ *
+ * Note: execExpr.c finds Aggrefs for us, and adds their AggrefExprState
+ * nodes to aggstate->aggs. Aggrefs in the qual are found here; Aggrefs
+ * in the targetlist are found during ExecAssignProjectionInfo, below.
*/
- aggstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) aggstate);
- aggstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) aggstate);
+ aggstate->ss.ps.qual =
+ ExecInitQual(node->plan.qual, (PlanState *) aggstate);
/*
* Initialize child nodes.
ExecAssignProjectionInfo(&aggstate->ss.ps, NULL);
/*
- * get the count of aggregates in targetlist and quals
+ * We should now have found all Aggrefs in the targetlist and quals.
*/
numaggs = aggstate->numaggs;
Assert(numaggs == list_length(aggstate->aggs));
foreach(l, aggstate->aggs)
{
AggrefExprState *aggrefstate = (AggrefExprState *) lfirst(l);
- Aggref *aggref = (Aggref *) aggrefstate->xprstate.expr;
+ Aggref *aggref = aggrefstate->aggref;
AggStatePerAgg peragg;
AggStatePerTrans pertrans;
int existing_aggno;
/* and then create a projection for that targetlist */
aggstate->evaldesc = ExecTypeFromTL(combined_inputeval, false);
aggstate->evalslot = ExecInitExtraTupleSlot(estate);
- combined_inputeval = (List *) ExecInitExpr((Expr *) combined_inputeval,
- (PlanState *) aggstate);
aggstate->evalproj = ExecBuildProjectionInfo(combined_inputeval,
aggstate->tmpcontext,
aggstate->evalslot,
+ &aggstate->ss.ps,
NULL);
ExecSetSlotDescriptor(aggstate->evalslot, aggstate->evaldesc);
naggs = aggstate->numaggs;
pertrans->aggfilter = ExecInitExpr(aggref->aggfilter,
(PlanState *) aggstate);
- pertrans->aggdirectargs = (List *) ExecInitExpr((Expr *) aggref->aggdirectargs,
- (PlanState *) aggstate);
+ pertrans->aggdirectargs = ExecInitExprList(aggref->aggdirectargs,
+ (PlanState *) aggstate);
/*
* Complain if the aggregate's arguments contain any aggregates; nested
econtext->ecxt_scantuple = slot;
ResetExprContext(econtext);
- if (!ExecQual(node->bitmapqualorig, econtext, false))
+ if (!ExecQual(node->bitmapqualorig, econtext))
{
/* Fails recheck, so drop it and loop back for another */
InstrCountFiltered2(node, 1);
ResetExprContext(econtext);
- return ExecQual(node->bitmapqualorig, econtext, false);
+ return ExecQual(node->bitmapqualorig, econtext);
}
/* ----------------------------------------------------------------
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
- scanstate->bitmapqualorig = (List *)
- ExecInitExpr((Expr *) node->bitmapqualorig,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+ scanstate->bitmapqualorig =
+ ExecInitQual(node->bitmapqualorig, (PlanState *) scanstate);
/*
* tuple table initialization
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
/*
* tuple table initialization
ExecAssignExprContext(estate, &css->ss.ps);
/* initialize child expressions */
- css->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) cscan->scan.plan.targetlist,
- (PlanState *) css);
- css->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) cscan->scan.plan.qual,
- (PlanState *) css);
+ css->ss.ps.qual =
+ ExecInitQual(cscan->scan.plan.qual, (PlanState *) css);
/* tuple table initialization */
ExecInitScanTupleSlot(estate, &css->ss);
!fdwroutine->RecheckForeignScan(node, slot))
return false;
- return ExecQual(node->fdw_recheck_quals, econtext, false);
+ return ExecQual(node->fdw_recheck_quals, econtext);
}
/* ----------------------------------------------------------------
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
- scanstate->fdw_recheck_quals = (List *)
- ExecInitExpr((Expr *) node->fdw_recheck_quals,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+ scanstate->fdw_recheck_quals =
+ ExecInitQual(node->fdw_recheck_quals, (PlanState *) scanstate);
/*
* tuple table initialization
*/
typedef struct FunctionScanPerFuncState
{
- ExprState *funcexpr; /* state of the expression being evaluated */
+ SetExprState *setexpr; /* state of the expression being evaluated */
TupleDesc tupdesc; /* desc of the function result type */
int colcount; /* expected number of result columns */
Tuplestorestate *tstore; /* holds the function result set */
if (tstore == NULL)
{
node->funcstates[0].tstore = tstore =
- ExecMakeTableFunctionResult(node->funcstates[0].funcexpr,
+ ExecMakeTableFunctionResult(node->funcstates[0].setexpr,
node->ss.ps.ps_ExprContext,
node->argcontext,
node->funcstates[0].tupdesc,
if (fs->tstore == NULL)
{
fs->tstore =
- ExecMakeTableFunctionResult(fs->funcexpr,
+ ExecMakeTableFunctionResult(fs->setexpr,
node->ss.ps.ps_ExprContext,
node->argcontext,
fs->tupdesc,
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState));
Oid funcrettype;
TupleDesc tupdesc;
- fs->funcexpr = ExecInitExpr((Expr *) funcexpr, (PlanState *) scanstate);
+ fs->setexpr =
+ ExecInitTableFunctionResult((Expr *) funcexpr,
+ scanstate->ss.ps.ps_ExprContext,
+ &scanstate->ss.ps);
/*
* Don't allocate the tuplestores; the actual calls to the functions
/*
* initialize child expressions
*/
- gatherstate->ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) gatherstate);
- gatherstate->ps.qual = (List *)
- ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) gatherstate);
+ gatherstate->ps.qual =
+ ExecInitQual(node->plan.qual, (PlanState *) gatherstate);
/*
* tuple table initialization
/*
* initialize child expressions
*/
- gm_state->ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) gm_state);
- gm_state->ps.qual = (List *)
- ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) gm_state);
+ gm_state->ps.qual =
+ ExecInitQual(node->plan.qual, &gm_state->ps);
/*
* tuple table initialization
* Check the qual (HAVING clause); if the group does not match, ignore
* it and fall into scan loop.
*/
- if (ExecQual(node->ss.ps.qual, econtext, false))
+ if (ExecQual(node->ss.ps.qual, econtext))
{
/*
* Form and return a projection tuple using the first input tuple.
* Check the qual (HAVING clause); if the group does not match, ignore
* it and loop back to scan the rest of the group.
*/
- if (ExecQual(node->ss.ps.qual, econtext, false))
+ if (ExecQual(node->ss.ps.qual, econtext))
{
/*
* Form and return a projection tuple using the first input tuple.
/*
* initialize child expressions
*/
- grpstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) grpstate);
- grpstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) grpstate);
+ grpstate->ss.ps.qual =
+ ExecInitQual(node->plan.qual, (PlanState *) grpstate);
/*
* initialize child nodes
/*
* initialize child expressions
*/
- hashstate->ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) hashstate);
- hashstate->ps.qual = (List *)
- ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) hashstate);
+ hashstate->ps.qual =
+ ExecInitQual(node->plan.qual, (PlanState *) hashstate);
/*
* initialize child nodes
ExecScanHashBucket(HashJoinState *hjstate,
ExprContext *econtext)
{
- List *hjclauses = hjstate->hashclauses;
+ ExprState *hjclauses = hjstate->hashclauses;
HashJoinTable hashtable = hjstate->hj_HashTable;
HashJoinTuple hashTuple = hjstate->hj_CurTuple;
uint32 hashvalue = hjstate->hj_CurHashValue;
/* reset temp memory each time to avoid leaks from qual expr */
ResetExprContext(econtext);
- if (ExecQual(hjclauses, econtext, false))
+ if (ExecQual(hjclauses, econtext))
{
hjstate->hj_CurTuple = hashTuple;
return true;
{
PlanState *outerNode;
HashState *hashNode;
- List *joinqual;
- List *otherqual;
+ ExprState *joinqual;
+ ExprState *otherqual;
ExprContext *econtext;
HashJoinTable hashtable;
TupleTableSlot *outerTupleSlot;
* Only the joinquals determine tuple match status, but all
* quals must pass to actually return the tuple.
*/
- if (joinqual == NIL || ExecQual(joinqual, econtext, false))
+ if (joinqual == NULL || ExecQual(joinqual, econtext))
{
node->hj_MatchedOuter = true;
HeapTupleHeaderSetMatch(HJTUPLE_MINTUPLE(node->hj_CurTuple));
if (node->js.jointype == JOIN_SEMI)
node->hj_JoinState = HJ_NEED_NEW_OUTER;
- if (otherqual == NIL ||
- ExecQual(otherqual, econtext, false))
+ if (otherqual == NULL || ExecQual(otherqual, econtext))
return ExecProject(node->js.ps.ps_ProjInfo);
else
InstrCountFiltered2(node, 1);
*/
econtext->ecxt_innertuple = node->hj_NullInnerTupleSlot;
- if (otherqual == NIL ||
- ExecQual(otherqual, econtext, false))
+ if (otherqual == NULL || ExecQual(otherqual, econtext))
return ExecProject(node->js.ps.ps_ProjInfo);
else
InstrCountFiltered2(node, 1);
*/
econtext->ecxt_outertuple = node->hj_NullOuterTupleSlot;
- if (otherqual == NIL ||
- ExecQual(otherqual, econtext, false))
+ if (otherqual == NULL || ExecQual(otherqual, econtext))
return ExecProject(node->js.ps.ps_ProjInfo);
else
InstrCountFiltered2(node, 1);
/*
* initialize child expressions
*/
- hjstate->js.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->join.plan.targetlist,
- (PlanState *) hjstate);
- hjstate->js.ps.qual = (List *)
- ExecInitExpr((Expr *) node->join.plan.qual,
- (PlanState *) hjstate);
+ hjstate->js.ps.qual =
+ ExecInitQual(node->join.plan.qual, (PlanState *) hjstate);
hjstate->js.jointype = node->join.jointype;
- hjstate->js.joinqual = (List *)
- ExecInitExpr((Expr *) node->join.joinqual,
- (PlanState *) hjstate);
- hjstate->hashclauses = (List *)
- ExecInitExpr((Expr *) node->hashclauses,
- (PlanState *) hjstate);
+ hjstate->js.joinqual =
+ ExecInitQual(node->join.joinqual, (PlanState *) hjstate);
+ hjstate->hashclauses =
+ ExecInitQual(node->hashclauses, (PlanState *) hjstate);
/*
* initialize child nodes
lclauses = NIL;
rclauses = NIL;
hoperators = NIL;
- foreach(l, hjstate->hashclauses)
+ foreach(l, node->hashclauses)
{
- FuncExprState *fstate = castNode(FuncExprState, lfirst(l));
- OpExpr *hclause = castNode(OpExpr, fstate->xprstate.expr);
+ OpExpr *hclause = castNode(OpExpr, lfirst(l));
- lclauses = lappend(lclauses, linitial(fstate->args));
- rclauses = lappend(rclauses, lsecond(fstate->args));
+ lclauses = lappend(lclauses, ExecInitExpr(linitial(hclause->args),
+ (PlanState *) hjstate));
+ rclauses = lappend(rclauses, ExecInitExpr(lsecond(hclause->args),
+ (PlanState *) hjstate));
hoperators = lappend_oid(hoperators, hclause->opno);
}
hjstate->hj_OuterHashKeys = lclauses;
{
econtext->ecxt_scantuple = slot;
ResetExprContext(econtext);
- if (!ExecQual(node->indexqual, econtext, false))
+ if (!ExecQual(node->indexqual, econtext))
{
/* Fails recheck, so drop it and loop back for another */
InstrCountFiltered2(node, 1);
* Note: we don't initialize all of the indexorderby expression, only the
* sub-parts corresponding to runtime keys (see below).
*/
- indexstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) indexstate);
- indexstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) indexstate);
- indexstate->indexqual = (List *)
- ExecInitExpr((Expr *) node->indexqual,
- (PlanState *) indexstate);
+ indexstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate);
+ indexstate->indexqual =
+ ExecInitQual(node->indexqual, (PlanState *) indexstate);
/*
* tuple table initialization
{
econtext->ecxt_scantuple = slot;
ResetExprContext(econtext);
- if (!ExecQual(node->indexqualorig, econtext, false))
+ if (!ExecQual(node->indexqualorig, econtext))
{
/* Fails recheck, so drop it and loop back for another */
InstrCountFiltered2(node, 1);
{
econtext->ecxt_scantuple = slot;
ResetExprContext(econtext);
- if (!ExecQual(node->indexqualorig, econtext, false))
+ if (!ExecQual(node->indexqualorig, econtext))
{
/* Fails recheck, so drop it and loop back for another */
InstrCountFiltered2(node, 1);
ResetExprContext(econtext);
- return ExecQual(node->indexqualorig, econtext, false);
+ return ExecQual(node->indexqualorig, econtext);
}
* would be nice to improve that. (Problem is that any SubPlans present
* in the expression must be found now...)
*/
- indexstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) indexstate);
- indexstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) indexstate);
- indexstate->indexqualorig = (List *)
- ExecInitExpr((Expr *) node->indexqualorig,
- (PlanState *) indexstate);
- indexstate->indexorderbyorig = (List *)
- ExecInitExpr((Expr *) node->indexorderbyorig,
- (PlanState *) indexstate);
+ indexstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate);
+ indexstate->indexqualorig =
+ ExecInitQual(node->indexqualorig, (PlanState *) indexstate);
+ indexstate->indexorderbyorig =
+ ExecInitExprList(node->indexorderbyorig, (PlanState *) indexstate);
/*
* tuple table initialization
MJFillOuter(MergeJoinState *node)
{
ExprContext *econtext = node->js.ps.ps_ExprContext;
- List *otherqual = node->js.ps.qual;
+ ExprState *otherqual = node->js.ps.qual;
ResetExprContext(econtext);
econtext->ecxt_outertuple = node->mj_OuterTupleSlot;
econtext->ecxt_innertuple = node->mj_NullInnerTupleSlot;
- if (ExecQual(otherqual, econtext, false))
+ if (ExecQual(otherqual, econtext))
{
/*
* qualification succeeded. now form the desired projection tuple and
MJFillInner(MergeJoinState *node)
{
ExprContext *econtext = node->js.ps.ps_ExprContext;
- List *otherqual = node->js.ps.qual;
+ ExprState *otherqual = node->js.ps.qual;
ResetExprContext(econtext);
econtext->ecxt_outertuple = node->mj_NullOuterTupleSlot;
econtext->ecxt_innertuple = node->mj_InnerTupleSlot;
- if (ExecQual(otherqual, econtext, false))
+ if (ExecQual(otherqual, econtext))
{
/*
* qualification succeeded. now form the desired projection tuple and
TupleTableSlot *
ExecMergeJoin(MergeJoinState *node)
{
- List *joinqual;
- List *otherqual;
+ ExprState *joinqual;
+ ExprState *otherqual;
bool qualResult;
int compareResult;
PlanState *innerPlan;
innerTupleSlot = node->mj_InnerTupleSlot;
econtext->ecxt_innertuple = innerTupleSlot;
- qualResult = (joinqual == NIL ||
- ExecQual(joinqual, econtext, false));
+ qualResult = (joinqual == NULL ||
+ ExecQual(joinqual, econtext));
MJ_DEBUG_QUAL(joinqual, qualResult);
if (qualResult)
if (node->js.jointype == JOIN_SEMI)
node->mj_JoinState = EXEC_MJ_NEXTOUTER;
- qualResult = (otherqual == NIL ||
- ExecQual(otherqual, econtext, false));
+ qualResult = (otherqual == NULL ||
+ ExecQual(otherqual, econtext));
MJ_DEBUG_QUAL(otherqual, qualResult);
if (qualResult)
/*
* initialize child expressions
*/
- mergestate->js.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->join.plan.targetlist,
- (PlanState *) mergestate);
- mergestate->js.ps.qual = (List *)
- ExecInitExpr((Expr *) node->join.plan.qual,
- (PlanState *) mergestate);
+ mergestate->js.ps.qual =
+ ExecInitQual(node->join.plan.qual, (PlanState *) mergestate);
mergestate->js.jointype = node->join.jointype;
- mergestate->js.joinqual = (List *)
- ExecInitExpr((Expr *) node->join.joinqual,
- (PlanState *) mergestate);
+ mergestate->js.joinqual =
+ ExecInitQual(node->join.joinqual, (PlanState *) mergestate);
mergestate->mj_ConstFalseJoin = false;
/* mergeclauses are handled below */
{
ExprContext *econtext = mtstate->ps.ps_ExprContext;
Relation relation = resultRelInfo->ri_RelationDesc;
- List *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
+ ExprState *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere;
HeapTupleData tuple;
HeapUpdateFailureData hufd;
LockTupleMode lockmode;
econtext->ecxt_innertuple = excludedSlot;
econtext->ecxt_outertuple = NULL;
- if (!ExecQual(onConflictSetWhere, econtext, false))
+ if (!ExecQual(onConflictSetWhere, econtext))
{
ReleaseBuffer(buffer);
InstrCountFiltered1(&mtstate->ps, 1);
mtstate = makeNode(ModifyTableState);
mtstate->ps.plan = (Plan *) node;
mtstate->ps.state = estate;
- mtstate->ps.targetlist = NIL; /* not actually used */
mtstate->operation = operation;
mtstate->canSetTag = node->canSetTag;
foreach(ll, wcoList)
{
WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitExpr((Expr *) wco->qual,
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
mtstate->mt_plans[i]);
wcoExprs = lappend(wcoExprs, wcoExpr);
foreach(ll, mapped_wcoList)
{
WithCheckOption *wco = (WithCheckOption *) lfirst(ll);
- ExprState *wcoExpr = ExecInitExpr((Expr *) wco->qual,
- mtstate->mt_plans[i]);
+ ExprState *wcoExpr = ExecInitQual((List *) wco->qual,
+ mtstate->mt_plans[i]);
wcoExprs = lappend(wcoExprs, wcoExpr);
}
slot = mtstate->ps.ps_ResultTupleSlot;
/* Need an econtext too */
- econtext = CreateExprContext(estate);
- mtstate->ps.ps_ExprContext = econtext;
+ if (mtstate->ps.ps_ExprContext == NULL)
+ ExecAssignExprContext(estate, &mtstate->ps);
+ econtext = mtstate->ps.ps_ExprContext;
/*
* Build a projection for each result rel.
foreach(l, node->returningLists)
{
List *rlist = (List *) lfirst(l);
- List *rliststate;
- rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rliststate, econtext, slot,
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
resultRelInfo++;
}
for (i = 0; i < mtstate->mt_num_partitions; i++)
{
Relation partrel = resultRelInfo->ri_RelationDesc;
- List *rlist,
- *rliststate;
+ List *rlist;
/* varno = node->nominalRelation */
rlist = map_partition_varattnos(returningList,
node->nominalRelation,
partrel, rel);
- rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps);
resultRelInfo->ri_projectReturning =
- ExecBuildProjectionInfo(rliststate, econtext, slot,
+ ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
resultRelInfo++;
}
if (node->onConflictAction == ONCONFLICT_UPDATE)
{
ExprContext *econtext;
- ExprState *setexpr;
TupleDesc tupDesc;
/* insert may only have one plan, inheritance is not expanded */
mtstate->mt_conflproj = ExecInitExtraTupleSlot(mtstate->ps.state);
ExecSetSlotDescriptor(mtstate->mt_conflproj, tupDesc);
- /* build UPDATE SET expression and projection state */
- setexpr = ExecInitExpr((Expr *) node->onConflictSet, &mtstate->ps);
+ /* build UPDATE SET projection state */
resultRelInfo->ri_onConflictSetProj =
- ExecBuildProjectionInfo((List *) setexpr, econtext,
- mtstate->mt_conflproj,
+ ExecBuildProjectionInfo(node->onConflictSet, econtext,
+ mtstate->mt_conflproj, &mtstate->ps,
resultRelInfo->ri_RelationDesc->rd_att);
/* build DO UPDATE WHERE clause expression */
{
ExprState *qualexpr;
- qualexpr = ExecInitExpr((Expr *) node->onConflictWhere,
+ qualexpr = ExecInitQual((List *) node->onConflictWhere,
&mtstate->ps);
- resultRelInfo->ri_onConflictSetWhere = (List *) qualexpr;
+ resultRelInfo->ri_onConflictSetWhere = qualexpr;
}
}
PlanState *outerPlan;
TupleTableSlot *outerTupleSlot;
TupleTableSlot *innerTupleSlot;
- List *joinqual;
- List *otherqual;
+ ExprState *joinqual;
+ ExprState *otherqual;
ExprContext *econtext;
ListCell *lc;
ENL1_printf("testing qualification for outer-join tuple");
- if (otherqual == NIL || ExecQual(otherqual, econtext, false))
+ if (otherqual == NULL || ExecQual(otherqual, econtext))
{
/*
* qualification was satisfied so we project and return
*/
ENL1_printf("testing qualification");
- if (ExecQual(joinqual, econtext, false))
+ if (ExecQual(joinqual, econtext))
{
node->nl_MatchedOuter = true;
if (node->js.jointype == JOIN_SEMI)
node->nl_NeedNewOuter = true;
- if (otherqual == NIL || ExecQual(otherqual, econtext, false))
+ if (otherqual == NULL || ExecQual(otherqual, econtext))
{
/*
* qualification was satisfied so we project and return the
/*
* initialize child expressions
*/
- nlstate->js.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->join.plan.targetlist,
- (PlanState *) nlstate);
- nlstate->js.ps.qual = (List *)
- ExecInitExpr((Expr *) node->join.plan.qual,
- (PlanState *) nlstate);
+ nlstate->js.ps.qual =
+ ExecInitQual(node->join.plan.qual, (PlanState *) nlstate);
nlstate->js.jointype = node->join.jointype;
- nlstate->js.joinqual = (List *)
- ExecInitExpr((Expr *) node->join.joinqual,
- (PlanState *) nlstate);
+ nlstate->js.joinqual =
+ ExecInitQual(node->join.joinqual, (PlanState *) nlstate);
/*
* initialize child nodes
#include "executor/executor.h"
#include "executor/nodeProjectSet.h"
+#include "nodes/nodeFuncs.h"
#include "utils/memutils.h"
{
TupleTableSlot *resultSlot = node->ps.ps_ResultTupleSlot;
ExprContext *econtext = node->ps.ps_ExprContext;
- bool hassrf PG_USED_FOR_ASSERTS_ONLY = false;
+ bool hassrf PG_USED_FOR_ASSERTS_ONLY;
bool hasresult;
int argno;
- ListCell *lc;
ExecClearTuple(resultSlot);
*/
node->pending_srf_tuples = false;
- hasresult = false;
- argno = 0;
- foreach(lc, node->ps.targetlist)
+ hassrf = hasresult = false;
+ for (argno = 0; argno < node->nelems; argno++)
{
- GenericExprState *gstate = (GenericExprState *) lfirst(lc);
+ Node *elem = node->elems[argno];
ExprDoneCond *isdone = &node->elemdone[argno];
Datum *result = &resultSlot->tts_values[argno];
bool *isnull = &resultSlot->tts_isnull[argno];
*isnull = true;
hassrf = true;
}
- else if (IsA(gstate->arg, FuncExprState) &&
- ((FuncExprState *) gstate->arg)->funcReturnsSet)
+ else if (IsA(elem, SetExprState))
{
/*
* Evaluate SRF - possibly continuing previously started output.
*/
- *result = ExecMakeFunctionResultSet((FuncExprState *) gstate->arg,
+ *result = ExecMakeFunctionResultSet((SetExprState *) elem,
econtext, isnull, isdone);
if (*isdone != ExprEndResult)
else
{
/* Non-SRF tlist expression, just evaluate normally. */
- *result = ExecEvalExpr(gstate->arg, econtext, isnull);
+ *result = ExecEvalExpr((ExprState *) elem, econtext, isnull);
*isdone = ExprSingleResult;
}
-
- argno++;
}
/* ProjectSet should not be used if there's no SRFs */
ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
{
ProjectSetState *state;
+ ListCell *lc;
+ int off;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD)));
*/
ExecInitResultTupleSlot(estate, &state->ps);
- /*
- * initialize child expressions
- */
- state->ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) state);
+ /* We don't support any qual on ProjectSet nodes */
Assert(node->plan.qual == NIL);
/*
*/
ExecAssignResultTypeFromTL(&state->ps);
- /* Create workspace for per-SRF is-done state */
+ /* Create workspace for per-tlist-entry expr state & SRF-is-done state */
state->nelems = list_length(node->plan.targetlist);
+ state->elems = (Node **)
+ palloc(sizeof(Node *) * state->nelems);
state->elemdone = (ExprDoneCond *)
palloc(sizeof(ExprDoneCond) * state->nelems);
+ /*
+ * Build expressions to evaluate targetlist. We can't use
+ * ExecBuildProjectionInfo here, since that doesn't deal with SRFs.
+ * Instead compile each expression separately, using
+ * ExecInitFunctionResultSet where applicable.
+ */
+ off = 0;
+ foreach(lc, node->plan.targetlist)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(lc);
+ Expr *expr = te->expr;
+
+ if ((IsA(expr, FuncExpr) &&((FuncExpr *) expr)->funcretset) ||
+ (IsA(expr, OpExpr) &&((OpExpr *) expr)->opretset))
+ {
+ state->elems[off] = (Node *)
+ ExecInitFunctionResultSet(expr, state->ps.ps_ExprContext,
+ &state->ps);
+ }
+ else
+ {
+ Assert(!expression_returns_set((Node *) expr));
+ state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps);
+ }
+
+ off++;
+ }
+
return state;
}
*/
if (node->rs_checkqual)
{
- bool qualResult = ExecQual((List *) node->resconstantqual,
- econtext,
- false);
+ bool qualResult = ExecQual(node->resconstantqual, econtext);
node->rs_checkqual = false;
if (!qualResult)
/*
* initialize child expressions
*/
- resstate->ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) resstate);
- resstate->ps.qual = (List *)
- ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) resstate);
- resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual,
- (PlanState *) resstate);
+ resstate->ps.qual =
+ ExecInitQual(node->plan.qual, (PlanState *) resstate);
+ resstate->resconstantqual =
+ ExecInitQual((List *) node->resconstantqual, (PlanState *) resstate);
/*
* initialize child nodes
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
-
- scanstate->args = (List *)
- ExecInitExpr((Expr *) tsc->args,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
+
+ scanstate->args = ExecInitExprList(tsc->args, (PlanState *) scanstate);
scanstate->repeatable =
- ExecInitExpr(tsc->repeatable,
- (PlanState *) scanstate);
+ ExecInitExpr(tsc->repeatable, (PlanState *) scanstate);
/*
* tuple table initialization
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->plan.qual,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->plan.qual, (PlanState *) scanstate);
/*
* tuple table initialization
#include "utils/memutils.h"
-static Datum ExecSubPlan(SubPlanState *node,
- ExprContext *econtext,
- bool *isNull);
-static Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node,
- ExprContext *econtext,
- bool *isNull);
static Datum ExecHashSubPlan(SubPlanState *node,
ExprContext *econtext,
bool *isNull);
* This is the main entry point for execution of a regular SubPlan.
* ----------------------------------------------------------------
*/
-static Datum
+Datum
ExecSubPlan(SubPlanState *node,
ExprContext *econtext,
bool *isNull)
{
- SubPlan *subplan = (SubPlan *) node->xprstate.expr;
+ SubPlan *subplan = node->subplan;
/* Set non-null as default */
*isNull = false;
ExprContext *econtext,
bool *isNull)
{
- SubPlan *subplan = (SubPlan *) node->xprstate.expr;
+ SubPlan *subplan = node->subplan;
PlanState *planstate = node->planstate;
TupleTableSlot *slot;
ExprContext *econtext,
bool *isNull)
{
- SubPlan *subplan = (SubPlan *) node->xprstate.expr;
+ SubPlan *subplan = node->subplan;
PlanState *planstate = node->planstate;
SubLinkType subLinkType = subplan->subLinkType;
MemoryContext oldcontext;
static void
buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
{
- SubPlan *subplan = (SubPlan *) node->xprstate.expr;
+ SubPlan *subplan = node->subplan;
PlanState *planstate = node->planstate;
int ncols = list_length(subplan->paramIds);
ExprContext *innerecontext = node->innerecontext;
* potential for a double free attempt. (XXX possibly no longer needed,
* but can't hurt.)
*/
- ExecClearTuple(node->projRight->pi_slot);
+ ExecClearTuple(node->projRight->pi_state.resultslot);
MemoryContextSwitchTo(oldcontext);
}
SubPlanState *sstate = makeNode(SubPlanState);
EState *estate = parent->state;
- sstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecSubPlan;
- sstate->xprstate.expr = (Expr *) subplan;
+ sstate->subplan = subplan;
/* Link the SubPlanState to already-initialized subplan */
sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
/* Initialize subexpressions */
sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
- sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
+ sstate->args = ExecInitExprList(subplan->args, parent);
/*
* initialize my state
TupleTableSlot *slot;
List *oplist,
*lefttlist,
- *righttlist,
- *leftptlist,
- *rightptlist;
+ *righttlist;
ListCell *l;
/* We need a memory context to hold the hash table(s) */
* use the sub-select's output tuples directly, but that is not the
* case if we had to insert any run-time coercions of the sub-select's
* output datatypes; anyway this avoids storing any resjunk columns
- * that might be in the sub-select's output.) Run through the
+ * that might be in the sub-select's output.) Run through the
* combining expressions to build tlists for the lefthand and
- * righthand sides. We need both the ExprState list (for ExecProject)
- * and the underlying parse Exprs (for ExecTypeFromTL).
+ * righthand sides.
*
* We also extract the combining operators themselves to initialize
* the equality and hashing functions for the hash tables.
*/
- if (IsA(sstate->testexpr->expr, OpExpr))
+ if (IsA(subplan->testexpr, OpExpr))
{
/* single combining operator */
- oplist = list_make1(sstate->testexpr);
+ oplist = list_make1(subplan->testexpr);
}
- else if (and_clause((Node *) sstate->testexpr->expr))
+ else if (and_clause((Node *) subplan->testexpr))
{
/* multiple combining operators */
- oplist = castNode(BoolExprState, sstate->testexpr)->args;
+ oplist = castNode(BoolExpr, subplan->testexpr)->args;
}
else
{
/* shouldn't see anything else in a hashable subplan */
elog(ERROR, "unrecognized testexpr type: %d",
- (int) nodeTag(sstate->testexpr->expr));
+ (int) nodeTag(subplan->testexpr));
oplist = NIL; /* keep compiler quiet */
}
Assert(list_length(oplist) == ncols);
lefttlist = righttlist = NIL;
- leftptlist = rightptlist = NIL;
sstate->tab_hash_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
sstate->tab_eq_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
sstate->lhs_hash_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo));
i = 1;
foreach(l, oplist)
{
- FuncExprState *fstate = castNode(FuncExprState, lfirst(l));
- OpExpr *opexpr = castNode(OpExpr, fstate->xprstate.expr);
- ExprState *exstate;
+ OpExpr *opexpr = castNode(OpExpr, lfirst(l));
Expr *expr;
TargetEntry *tle;
- GenericExprState *tlestate;
Oid rhs_eq_oper;
Oid left_hashfn;
Oid right_hashfn;
- Assert(list_length(fstate->args) == 2);
+ Assert(list_length(opexpr->args) == 2);
/* Process lefthand argument */
- exstate = (ExprState *) linitial(fstate->args);
- expr = exstate->expr;
+ expr = (Expr *) linitial(opexpr->args);
tle = makeTargetEntry(expr,
i,
NULL,
false);
- tlestate = makeNode(GenericExprState);
- tlestate->xprstate.expr = (Expr *) tle;
- tlestate->xprstate.evalfunc = NULL;
- tlestate->arg = exstate;
- lefttlist = lappend(lefttlist, tlestate);
- leftptlist = lappend(leftptlist, tle);
+ lefttlist = lappend(lefttlist, tle);
/* Process righthand argument */
- exstate = (ExprState *) lsecond(fstate->args);
- expr = exstate->expr;
+ expr = (Expr *) lsecond(opexpr->args);
tle = makeTargetEntry(expr,
i,
NULL,
false);
- tlestate = makeNode(GenericExprState);
- tlestate->xprstate.expr = (Expr *) tle;
- tlestate->xprstate.evalfunc = NULL;
- tlestate->arg = exstate;
- righttlist = lappend(righttlist, tlestate);
- rightptlist = lappend(rightptlist, tle);
+ righttlist = lappend(righttlist, tle);
/* Lookup the equality function (potentially cross-type) */
fmgr_info(opexpr->opfuncid, &sstate->cur_eq_funcs[i - 1]);
* (hack alert!). The righthand expressions will be evaluated in our
* own innerecontext.
*/
- tupDesc = ExecTypeFromTL(leftptlist, false);
+ tupDesc = ExecTypeFromTL(lefttlist, false);
slot = ExecInitExtraTupleSlot(estate);
ExecSetSlotDescriptor(slot, tupDesc);
sstate->projLeft = ExecBuildProjectionInfo(lefttlist,
NULL,
slot,
+ parent,
NULL);
- tupDesc = ExecTypeFromTL(rightptlist, false);
+ tupDesc = ExecTypeFromTL(righttlist, false);
slot = ExecInitExtraTupleSlot(estate);
ExecSetSlotDescriptor(slot, tupDesc);
sstate->projRight = ExecBuildProjectionInfo(righttlist,
sstate->innerecontext,
slot,
+ sstate->planstate,
NULL);
}
void
ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
{
- SubPlan *subplan = (SubPlan *) node->xprstate.expr;
+ SubPlan *subplan = node->subplan;
PlanState *planstate = node->planstate;
SubLinkType subLinkType = subplan->subLinkType;
MemoryContext oldcontext;
ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent)
{
PlanState *planstate = node->planstate;
- SubPlan *subplan = (SubPlan *) node->xprstate.expr;
+ SubPlan *subplan = node->subplan;
EState *estate = parent->state;
ListCell *l;
SubPlan *subplan2;
Cost cost1;
Cost cost2;
+ ListCell *lc;
- asstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecAlternativeSubPlan;
- asstate->xprstate.expr = (Expr *) asplan;
+ asstate->subplan = asplan;
/*
* Initialize subplans. (Can we get away with only initializing the one
* we're going to use?)
*/
- asstate->subplans = (List *) ExecInitExpr((Expr *) asplan->subplans,
- parent);
+ foreach(lc, asplan->subplans)
+ {
+ SubPlan *sp = castNode(SubPlan, lfirst(lc));
+ SubPlanState *sps = ExecInitSubPlan(sp, parent);
+
+ asstate->subplans = lappend(asstate->subplans, sps);
+ parent->subPlan = lappend(parent->subPlan, sps);
+ }
/*
* Select the one to be used. For this, we need an estimate of the number
* Note: in future we might consider changing to different subplans on the
* fly, in case the original rowcount estimate turns out to be way off.
*/
-static Datum
+Datum
ExecAlternativeSubPlan(AlternativeSubPlanState *node,
ExprContext *econtext,
bool *isNull)
/*
* initialize child expressions
*/
- subquerystate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) subquerystate);
- subquerystate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) subquerystate);
+ subquerystate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) subquerystate);
/*
* tuple table initialization
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps);
/*
* tuple table initialization
scanstate->ns_names = tf->ns_names;
- scanstate->ns_uris = (List *)
- ExecInitExpr((Expr *) tf->ns_uris, (PlanState *) scanstate);
+ scanstate->ns_uris =
+ ExecInitExprList(tf->ns_uris, (PlanState *) scanstate);
scanstate->docexpr =
ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate);
scanstate->rowexpr =
ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate);
- scanstate->colexprs = (List *)
- ExecInitExpr((Expr *) tf->colexprs, (PlanState *) scanstate);
- scanstate->coldefexprs = (List *)
- ExecInitExpr((Expr *) tf->coldefexprs, (PlanState *) scanstate);
+ scanstate->colexprs =
+ ExecInitExprList(tf->colexprs, (PlanState *) scanstate);
+ scanstate->coldefexprs =
+ ExecInitExprList(tf->coldefexprs, (PlanState *) scanstate);
scanstate->notnulls = tf->notnulls;
((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \
((Var *) (node))->varlevelsup == 0)
-static void TidListCreate(TidScanState *tidstate);
+/* one element in tss_tidexprs */
+typedef struct TidExpr
+{
+ ExprState *exprstate; /* ExprState for a TID-yielding subexpr */
+ bool isarray; /* if true, it yields tid[] not just tid */
+ CurrentOfExpr *cexpr; /* alternatively, we can have CURRENT OF */
+} TidExpr;
+
+static void TidExprListCreate(TidScanState *tidstate);
+static void TidListEval(TidScanState *tidstate);
static int itemptr_comparator(const void *a, const void *b);
static TupleTableSlot *TidNext(TidScanState *node);
+/*
+ * Extract the qual subexpressions that yield TIDs to search for,
+ * and compile them into ExprStates if they're ordinary expressions.
+ *
+ * CURRENT OF is a special case that we can't compile usefully;
+ * just drop it into the TidExpr list as-is.
+ */
+static void
+TidExprListCreate(TidScanState *tidstate)
+{
+ TidScan *node = (TidScan *) tidstate->ss.ps.plan;
+ ListCell *l;
+
+ tidstate->tss_tidexprs = NIL;
+ tidstate->tss_isCurrentOf = false;
+
+ foreach(l, node->tidquals)
+ {
+ Expr *expr = (Expr *) lfirst(l);
+ TidExpr *tidexpr = (TidExpr *) palloc0(sizeof(TidExpr));
+
+ if (is_opclause(expr))
+ {
+ Node *arg1;
+ Node *arg2;
+
+ arg1 = get_leftop(expr);
+ arg2 = get_rightop(expr);
+ if (IsCTIDVar(arg1))
+ tidexpr->exprstate = ExecInitExpr((Expr *) arg2,
+ &tidstate->ss.ps);
+ else if (IsCTIDVar(arg2))
+ tidexpr->exprstate = ExecInitExpr((Expr *) arg1,
+ &tidstate->ss.ps);
+ else
+ elog(ERROR, "could not identify CTID variable");
+ tidexpr->isarray = false;
+ }
+ else if (expr && IsA(expr, ScalarArrayOpExpr))
+ {
+ ScalarArrayOpExpr *saex = (ScalarArrayOpExpr *) expr;
+
+ Assert(IsCTIDVar(linitial(saex->args)));
+ tidexpr->exprstate = ExecInitExpr(lsecond(saex->args),
+ &tidstate->ss.ps);
+ tidexpr->isarray = true;
+ }
+ else if (expr && IsA(expr, CurrentOfExpr))
+ {
+ CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
+
+ tidexpr->cexpr = cexpr;
+ tidstate->tss_isCurrentOf = true;
+ }
+ else
+ elog(ERROR, "could not identify CTID expression");
+
+ tidstate->tss_tidexprs = lappend(tidstate->tss_tidexprs, tidexpr);
+ }
+
+ /* CurrentOfExpr could never appear OR'd with something else */
+ Assert(list_length(tidstate->tss_tidexprs) == 1 ||
+ !tidstate->tss_isCurrentOf);
+}
+
/*
* Compute the list of TIDs to be visited, by evaluating the expressions
* for them.
* (The result is actually an array, not a list.)
*/
static void
-TidListCreate(TidScanState *tidstate)
+TidListEval(TidScanState *tidstate)
{
- List *evalList = tidstate->tss_tidquals;
ExprContext *econtext = tidstate->ss.ps.ps_ExprContext;
BlockNumber nblocks;
ItemPointerData *tidList;
* are simple OpExprs or CurrentOfExprs. If there are any
* ScalarArrayOpExprs, we may have to enlarge the array.
*/
- numAllocTids = list_length(evalList);
+ numAllocTids = list_length(tidstate->tss_tidexprs);
tidList = (ItemPointerData *)
palloc(numAllocTids * sizeof(ItemPointerData));
numTids = 0;
- tidstate->tss_isCurrentOf = false;
- foreach(l, evalList)
+ foreach(l, tidstate->tss_tidexprs)
{
- ExprState *exstate = (ExprState *) lfirst(l);
- Expr *expr = exstate->expr;
+ TidExpr *tidexpr = (TidExpr *) lfirst(l);
ItemPointer itemptr;
bool isNull;
- if (is_opclause(expr))
+ if (tidexpr->exprstate && !tidexpr->isarray)
{
- FuncExprState *fexstate = (FuncExprState *) exstate;
- Node *arg1;
- Node *arg2;
-
- arg1 = get_leftop(expr);
- arg2 = get_rightop(expr);
- if (IsCTIDVar(arg1))
- exstate = (ExprState *) lsecond(fexstate->args);
- else if (IsCTIDVar(arg2))
- exstate = (ExprState *) linitial(fexstate->args);
- else
- elog(ERROR, "could not identify CTID variable");
-
itemptr = (ItemPointer)
- DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+ DatumGetPointer(ExecEvalExprSwitchContext(tidexpr->exprstate,
econtext,
&isNull));
if (!isNull &&
tidList[numTids++] = *itemptr;
}
}
- else if (expr && IsA(expr, ScalarArrayOpExpr))
+ else if (tidexpr->exprstate && tidexpr->isarray)
{
- ScalarArrayOpExprState *saexstate = (ScalarArrayOpExprState *) exstate;
Datum arraydatum;
ArrayType *itemarray;
Datum *ipdatums;
int ndatums;
int i;
- exstate = (ExprState *) lsecond(saexstate->fxprstate.args);
- arraydatum = ExecEvalExprSwitchContext(exstate,
+ arraydatum = ExecEvalExprSwitchContext(tidexpr->exprstate,
econtext,
&isNull);
if (isNull)
pfree(ipdatums);
pfree(ipnulls);
}
- else if (expr && IsA(expr, CurrentOfExpr))
+ else
{
- CurrentOfExpr *cexpr = (CurrentOfExpr *) expr;
ItemPointerData cursor_tid;
- if (execCurrentOf(cexpr, econtext,
+ Assert(tidexpr->cexpr);
+ if (execCurrentOf(tidexpr->cexpr, econtext,
RelationGetRelid(tidstate->ss.ss_currentRelation),
&cursor_tid))
{
numAllocTids * sizeof(ItemPointerData));
}
tidList[numTids++] = cursor_tid;
- tidstate->tss_isCurrentOf = true;
}
}
- else
- elog(ERROR, "could not identify CTID expression");
}
/*
* First time through, compute the list of TIDs to be visited
*/
if (node->tss_TidList == NULL)
- TidListCreate(node);
+ TidListEval(node);
tidList = node->tss_TidList;
numTids = node->tss_NumTids;
+ /*
+ * We use node->tss_htup as the tuple pointer; note this can't just be a
+ * local variable here, as the scan tuple slot will keep a pointer to it.
+ */
tuple = &(node->tss_htup);
/*
/*
* initialize child expressions
*/
- tidstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) tidstate);
- tidstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) tidstate);
-
- tidstate->tss_tidquals = (List *)
- ExecInitExpr((Expr *) node->tidquals,
- (PlanState *) tidstate);
+ tidstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate);
+
+ TidExprListCreate(tidstate);
/*
* tuple table initialization
* is a SubPlan, and there shouldn't be any (any subselects in the
* VALUES list should be InitPlans).
*/
- exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL);
+ exprstatelist = ExecInitExprList(exprlist, NULL);
/* parser should have checked all sublists are the same length */
Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts);
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
/*
* get info about values list
winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate);
winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate);
- winstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->plan.targetlist,
- (PlanState *) winstate);
-
/*
* WindowAgg nodes never have quals, since they can only occur at the
* logical top level of a query (ie, after any WHERE or HAVING filters)
*/
Assert(node->plan.qual == NIL);
- winstate->ss.ps.qual = NIL;
+ winstate->ss.ps.qual = NULL;
/*
* initialize child nodes
foreach(l, winstate->funcs)
{
WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l);
- WindowFunc *wfunc = (WindowFunc *) wfuncstate->xprstate.expr;
+ WindowFunc *wfunc = wfuncstate->wfunc;
WindowStatePerFunc perfuncstate;
AclResult aclresult;
int i;
/*
* initialize child expressions
*/
- scanstate->ss.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->scan.plan.targetlist,
- (PlanState *) scanstate);
- scanstate->ss.ps.qual = (List *)
- ExecInitExpr((Expr *) node->scan.plan.qual,
- (PlanState *) scanstate);
+ scanstate->ss.ps.qual =
+ ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate);
/*
* tuple table initialization
/*
* Aggref and WindowFunc nodes are (and should be) treated like Vars,
* ie, zero execution cost in the current model, because they behave
- * essentially like Vars in execQual.c. We disregard the costs of
+ * essentially like Vars at execution. We disregard the costs of
* their input expressions for the same reason. The actual execution
* costs of the aggregate/window functions and their arguments have to
* be factored into plan-node-specific costing of the Agg or WindowAgg
* bloat the sort dataset, and because it might cause unexpected output order
* if the sort isn't stable. However there's a constraint on that: all SRFs
* in the tlist should be evaluated at the same plan step, so that they can
- * run in sync in ExecTargetList. So if any SRFs are in sort columns, we
+ * run in sync in nodeProjectSet. So if any SRFs are in sort columns, we
* mustn't postpone any SRFs. (Note that in principle that policy should
* probably get applied to the group/window input targetlists too, but we
* have not done that historically.) Lastly, expensive expressions are
* Else, make a scalar (argisrow == false) NullTest
* for this field. Scalar semantics are required
* because IS [NOT] NULL doesn't recurse; see comments
- * in ExecEvalNullTest().
+ * in ExecEvalRowNullInt().
*/
newntest = makeNode(NullTest);
newntest->arg = (Expr *) relem;
* FALSE: drop (does not affect result)
* TRUE: force result to TRUE
* NULL: keep only one
- * We must keep one NULL input because ExecEvalOr returns NULL when no input
- * is TRUE and at least one is NULL. We don't actually include the NULL
+ * We must keep one NULL input because OR expressions evaluate to NULL when no
+ * input is TRUE and at least one is NULL. We don't actually include the NULL
* here, that's supposed to be done by the caller.
*
* The output arguments *haveNull and *forceTrue must be initialized FALSE
* TRUE: drop (does not affect result)
* FALSE: force result to FALSE
* NULL: keep only one
- * We must keep one NULL input because ExecEvalAnd returns NULL when no input
- * is FALSE and at least one is NULL. We don't actually include the NULL
- * here, that's supposed to be done by the caller.
+ * We must keep one NULL input because AND expressions evaluate to NULL when
+ * no input is FALSE and at least one is NULL. We don't actually include the
+ * NULL here, that's supposed to be done by the caller.
*
* The output arguments *haveNull and *forceFalse must be initialized FALSE
* by the caller. They will be set TRUE if a null constant or false constant,
fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt);
/* Look up constraints for domain */
- InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt);
+ InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true);
/* We don't make an ExprContext until needed */
my_extra->econtext = NULL;
/*
* domain_check_input - apply the cached checks.
*
- * This is extremely similar to ExecEvalCoerceToDomain in execQual.c.
+ * This is roughly similar to the handling of CoerceToDomain nodes in
+ * execExpr*.c, but we execute each constraint separately, rather than
+ * compiling them in-line within a larger expression.
*/
static void
domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
break;
case DOM_CONSTRAINT_CHECK:
{
- Datum conResult;
- bool conIsNull;
-
/* Make the econtext if we didn't already */
if (econtext == NULL)
{
/*
* Set up value to be returned by CoerceToDomainValue
- * nodes. Unlike ExecEvalCoerceToDomain, this econtext
- * couldn't be shared with anything else, so no need to
- * save and restore fields. But we do need to protect the
- * passed-in value against being changed by called
- * functions. (It couldn't be a R/W expanded object for
- * most uses, but that seems possible for domain_check().)
+ * nodes. Unlike in the generic expression case, this
+ * econtext couldn't be shared with anything else, so no
+ * need to save and restore fields. But we do need to
+ * protect the passed-in value against being changed by
+ * called functions. (It couldn't be a R/W expanded
+ * object for most uses, but that seems possible for
+ * domain_check().)
*/
econtext->domainValue_datum =
MakeExpandedObjectReadOnly(value, isnull,
my_extra->constraint_ref.tcache->typlen);
econtext->domainValue_isNull = isnull;
- conResult = ExecEvalExprSwitchContext(con->check_expr,
- econtext,
- &conIsNull);
-
- if (!conIsNull &&
- !DatumGetBool(conResult))
+ if (!ExecCheck(con->check_exprstate, econtext))
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("value for domain %s violates check constraint \"%s\"",
foreach(lc2, ps->subPlan)
{
SubPlanState *sstate = (SubPlanState *) lfirst(lc2);
- SubPlan *subplan = (SubPlan *) sstate->xprstate.expr;
+ SubPlan *subplan = sstate->subplan;
ListCell *lc3;
ListCell *lc4;
continue;
/* No parameters to be had here. */
- Assert(((SubPlan *) sstate->xprstate.expr)->parParam == NIL);
+ Assert(sstate->subplan->parParam == NIL);
/* Keep looking, but we are emerging from an initplan. */
in_same_plan_level = false;
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
-#include "executor/executor.h"
#include "executor/spi.h"
#include "executor/tablefunc.h"
#include "fmgr.h"
xmltype *
-xmlelement(XmlExprState *xmlExpr, ExprContext *econtext)
+xmlelement(XmlExpr *xexpr,
+ Datum *named_argvalue, bool *named_argnull,
+ Datum *argvalue, bool *argnull)
{
#ifdef USE_LIBXML
- XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr;
xmltype *result;
List *named_arg_strings;
List *arg_strings;
volatile xmlTextWriterPtr writer = NULL;
/*
- * We first evaluate all the arguments, then start up libxml and create
- * the result. This avoids issues if one of the arguments involves a call
- * to some other function or subsystem that wants to use libxml on its own
- * terms.
+ * All arguments are already evaluated, and their values are passed in the
+ * named_argvalue/named_argnull or argvalue/argnull arrays. This avoids
+ * issues if one of the arguments involves a call to some other function
+ * or subsystem that wants to use libxml on its own terms. We examine the
+ * original XmlExpr to identify the numbers and types of the arguments.
*/
named_arg_strings = NIL;
i = 0;
- foreach(arg, xmlExpr->named_args)
+ foreach(arg, xexpr->named_args)
{
- ExprState *e = (ExprState *) lfirst(arg);
- Datum value;
- bool isnull;
+ Expr *e = (Expr *) lfirst(arg);
char *str;
- value = ExecEvalExpr(e, econtext, &isnull);
- if (isnull)
+ if (named_argnull[i])
str = NULL;
else
- str = map_sql_value_to_xml_value(value, exprType((Node *) e->expr), false);
+ str = map_sql_value_to_xml_value(named_argvalue[i],
+ exprType((Node *) e),
+ false);
named_arg_strings = lappend(named_arg_strings, str);
i++;
}
arg_strings = NIL;
- foreach(arg, xmlExpr->args)
+ i = 0;
+ foreach(arg, xexpr->args)
{
- ExprState *e = (ExprState *) lfirst(arg);
- Datum value;
- bool isnull;
+ Expr *e = (Expr *) lfirst(arg);
char *str;
- value = ExecEvalExpr(e, econtext, &isnull);
/* here we can just forget NULL elements immediately */
- if (!isnull)
+ if (!argnull[i])
{
- str = map_sql_value_to_xml_value(value,
- exprType((Node *) e->expr), true);
+ str = map_sql_value_to_xml_value(argvalue[i],
+ exprType((Node *) e),
+ true);
arg_strings = lappend(arg_strings, str);
}
+ i++;
}
- /* now safe to run libxml */
xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
PG_TRY();
* this struct for the common case of a constraint-less domain; we just set
* domainData to NULL to indicate that.
*
- * Within a DomainConstraintCache, we abuse the DomainConstraintState node
- * type a bit: check_expr fields point to expression plan trees, not plan
- * state trees. When needed, expression state trees are built by flat-copying
- * the DomainConstraintState nodes and applying ExecInitExpr to check_expr.
- * Such a state tree is not part of the DomainConstraintCache, but is
+ * Within a DomainConstraintCache, we store expression plan trees, but the
+ * check_exprstate fields of the DomainConstraintState nodes are just NULL.
+ * When needed, expression evaluation nodes are built by flat-copying the
+ * DomainConstraintState nodes and applying ExecInitExpr to check_expr.
+ * Such a node tree is not part of the DomainConstraintCache, but is
* considered to belong to a DomainConstraintRef.
*/
struct DomainConstraintCache
r = makeNode(DomainConstraintState);
r->constrainttype = DOM_CONSTRAINT_CHECK;
r->name = pstrdup(NameStr(c->conname));
- /* Must cast here because we're not storing an expr state node */
- r->check_expr = (ExprState *) check_expr;
+ r->check_expr = check_expr;
+ r->check_exprstate = NULL;
MemoryContextSwitchTo(oldcxt);
r->constrainttype = DOM_CONSTRAINT_NOTNULL;
r->name = pstrdup("NOT NULL");
r->check_expr = NULL;
+ r->check_exprstate = NULL;
/* lcons to apply the nullness check FIRST */
dcc->constraints = lcons(r, dcc->constraints);
newr = makeNode(DomainConstraintState);
newr->constrainttype = r->constrainttype;
newr->name = r->name;
- /* Must cast here because cache items contain expr plan trees */
- newr->check_expr = ExecInitExpr((Expr *) r->check_expr, NULL);
+ newr->check_expr = r->check_expr;
+ newr->check_exprstate = ExecInitExpr(r->check_expr, NULL);
result = lappend(result, newr);
}
*
* Caller must tell us the MemoryContext in which the DomainConstraintRef
* lives. The ref will be cleaned up when that context is reset/deleted.
+ *
+ * Caller must also tell us whether it wants check_exprstate fields to be
+ * computed in the DomainConstraintState nodes attached to this ref.
+ * If it doesn't, we need not make a copy of the DomainConstraintState list.
*/
void
InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
- MemoryContext refctx)
+ MemoryContext refctx, bool need_exprstate)
{
/* Look up the typcache entry --- we assume it survives indefinitely */
ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO);
+ ref->need_exprstate = need_exprstate;
/* For safety, establish the callback before acquiring a refcount */
ref->refctx = refctx;
ref->dcc = NULL;
{
ref->dcc = ref->tcache->domainData;
ref->dcc->dccRefCount++;
- ref->constraints = prep_domain_constraints(ref->dcc->constraints,
- ref->refctx);
+ if (ref->need_exprstate)
+ ref->constraints = prep_domain_constraints(ref->dcc->constraints,
+ ref->refctx);
+ else
+ ref->constraints = ref->dcc->constraints;
}
else
ref->constraints = NIL;
{
ref->dcc = dcc;
dcc->dccRefCount++;
- ref->constraints = prep_domain_constraints(dcc->constraints,
- ref->refctx);
+ if (ref->need_exprstate)
+ ref->constraints = prep_domain_constraints(dcc->constraints,
+ ref->refctx);
+ else
+ ref->constraints = dcc->constraints;
}
}
}
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * execExpr.h
+ * Low level infrastructure related to expression evaluation
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/execExpr.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef EXEC_EXPR_H
+#define EXEC_EXPR_H
+
+#include "nodes/execnodes.h"
+
+/* forward reference to avoid circularity */
+struct ArrayRefState;
+
+/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */
+/* expression's interpreter has been initialized */
+#define EEO_FLAG_INTERPRETER_INITIALIZED (1 << 1)
+/* jump-threading is in use */
+#define EEO_FLAG_DIRECT_THREADED (1 << 2)
+
+/*
+ * Discriminator for ExprEvalSteps.
+ *
+ * Identifies the operation to be executed and which member in the
+ * ExprEvalStep->d union is valid.
+ *
+ * The order of entries needs to be kept in sync with the dispatch_table[]
+ * array in execExprInterp.c:ExecInterpExpr().
+ */
+typedef enum ExprEvalOp
+{
+ /* entire expression has been evaluated completely, return */
+ EEOP_DONE,
+
+ /* apply slot_getsomeattrs on corresponding tuple slot */
+ EEOP_INNER_FETCHSOME,
+ EEOP_OUTER_FETCHSOME,
+ EEOP_SCAN_FETCHSOME,
+
+ /* compute non-system Var value */
+ /* "FIRST" variants are used only the first time through */
+ EEOP_INNER_VAR_FIRST,
+ EEOP_INNER_VAR,
+ EEOP_OUTER_VAR_FIRST,
+ EEOP_OUTER_VAR,
+ EEOP_SCAN_VAR_FIRST,
+ EEOP_SCAN_VAR,
+
+ /* compute system Var value */
+ EEOP_INNER_SYSVAR,
+ EEOP_OUTER_SYSVAR,
+ EEOP_SCAN_SYSVAR,
+
+ /* compute wholerow Var */
+ EEOP_WHOLEROW,
+
+ /* compute non-system Var value, assign it into ExprState's resultslot */
+ /* (these are not used if _FIRST checks would be needed) */
+ EEOP_ASSIGN_INNER_VAR,
+ EEOP_ASSIGN_OUTER_VAR,
+ EEOP_ASSIGN_SCAN_VAR,
+
+ /* assign ExprState's resvalue/resnull to a column of its resultslot */
+ EEOP_ASSIGN_TMP,
+ /* ditto, applying MakeExpandedObjectReadOnly() */
+ EEOP_ASSIGN_TMP_MAKE_RO,
+
+ /* evaluate Const value */
+ EEOP_CONST,
+
+ /*
+ * Evaluate function call (including OpExprs etc). For speed, we
+ * distinguish in the opcode whether the function is strict and/or
+ * requires usage stats tracking.
+ */
+ EEOP_FUNCEXPR,
+ EEOP_FUNCEXPR_STRICT,
+ EEOP_FUNCEXPR_FUSAGE,
+ EEOP_FUNCEXPR_STRICT_FUSAGE,
+
+ /*
+ * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST
+ * subexpressions are special-cased for performance. Since AND always has
+ * at least two subexpressions, FIRST and LAST never apply to the same
+ * subexpression.
+ */
+ EEOP_BOOL_AND_STEP_FIRST,
+ EEOP_BOOL_AND_STEP,
+ EEOP_BOOL_AND_STEP_LAST,
+
+ /* similarly for boolean OR expression */
+ EEOP_BOOL_OR_STEP_FIRST,
+ EEOP_BOOL_OR_STEP,
+ EEOP_BOOL_OR_STEP_LAST,
+
+ /* evaluate boolean NOT expression */
+ EEOP_BOOL_NOT_STEP,
+
+ /* simplified version of BOOL_AND_STEP for use by ExecQual() */
+ EEOP_QUAL,
+
+ /* unconditional jump to another step */
+ EEOP_JUMP,
+
+ /* conditional jumps based on current result value */
+ EEOP_JUMP_IF_NULL,
+ EEOP_JUMP_IF_NOT_NULL,
+ EEOP_JUMP_IF_NOT_TRUE,
+
+ /* perform NULL tests for scalar values */
+ EEOP_NULLTEST_ISNULL,
+ EEOP_NULLTEST_ISNOTNULL,
+
+ /* perform NULL tests for row values */
+ EEOP_NULLTEST_ROWISNULL,
+ EEOP_NULLTEST_ROWISNOTNULL,
+
+ /* evaluate a BooleanTest expression */
+ EEOP_BOOLTEST_IS_TRUE,
+ EEOP_BOOLTEST_IS_NOT_TRUE,
+ EEOP_BOOLTEST_IS_FALSE,
+ EEOP_BOOLTEST_IS_NOT_FALSE,
+
+ /* evaluate PARAM_EXEC/EXTERN parameters */
+ EEOP_PARAM_EXEC,
+ EEOP_PARAM_EXTERN,
+
+ /* return CaseTestExpr value */
+ EEOP_CASE_TESTVAL,
+
+ /* apply MakeExpandedObjectReadOnly() to target value */
+ EEOP_MAKE_READONLY,
+
+ /* evaluate assorted special-purpose expression types */
+ EEOP_IOCOERCE,
+ EEOP_DISTINCT,
+ EEOP_NULLIF,
+ EEOP_SQLVALUEFUNCTION,
+ EEOP_CURRENTOFEXPR,
+ EEOP_ARRAYEXPR,
+ EEOP_ARRAYCOERCE,
+ EEOP_ROW,
+
+ /*
+ * Compare two individual elements of each of two compared ROW()
+ * expressions. Skip to ROWCOMPARE_FINAL if elements are not equal.
+ */
+ EEOP_ROWCOMPARE_STEP,
+
+ /* evaluate boolean value based on previous ROWCOMPARE_STEP operations */
+ EEOP_ROWCOMPARE_FINAL,
+
+ /* evaluate GREATEST() or LEAST() */
+ EEOP_MINMAX,
+
+ /* evaluate FieldSelect expression */
+ EEOP_FIELDSELECT,
+
+ /*
+ * Deform tuple before evaluating new values for individual fields in a
+ * FieldStore expression.
+ */
+ EEOP_FIELDSTORE_DEFORM,
+
+ /*
+ * Form the new tuple for a FieldStore expression. Individual fields will
+ * have been evaluated into columns of the tuple deformed by the preceding
+ * DEFORM step.
+ */
+ EEOP_FIELDSTORE_FORM,
+
+ /* Process an array subscript; short-circuit expression to NULL if NULL */
+ EEOP_ARRAYREF_SUBSCRIPT,
+
+ /*
+ * Compute old array element/slice when an ArrayRef assignment expression
+ * contains ArrayRef/FieldStore subexpressions. Value is accessed using
+ * the CaseTest mechanism.
+ */
+ EEOP_ARRAYREF_OLD,
+
+ /* compute new value for ArrayRef assignment expression */
+ EEOP_ARRAYREF_ASSIGN,
+
+ /* compute element/slice for ArrayRef fetch expression */
+ EEOP_ARRAYREF_FETCH,
+
+ /* evaluate value for CoerceToDomainValue */
+ EEOP_DOMAIN_TESTVAL,
+
+ /* evaluate a domain's NOT NULL constraint */
+ EEOP_DOMAIN_NOTNULL,
+
+ /* evaluate a single domain CHECK constraint */
+ EEOP_DOMAIN_CHECK,
+
+ /* evaluate assorted special-purpose expression types */
+ EEOP_CONVERT_ROWTYPE,
+ EEOP_SCALARARRAYOP,
+ EEOP_XMLEXPR,
+ EEOP_AGGREF,
+ EEOP_GROUPING_FUNC,
+ EEOP_WINDOW_FUNC,
+ EEOP_SUBPLAN,
+ EEOP_ALTERNATIVE_SUBPLAN,
+
+ /* non-existent operation, used e.g. to check array lengths */
+ EEOP_LAST
+} ExprEvalOp;
+
+
+typedef struct ExprEvalStep
+{
+ /*
+ * Instruction to be executed. During instruction preparation this is an
+ * enum ExprEvalOp, but later it can be changed to some other type, e.g. a
+ * pointer for computed goto (that's why it's an intptr_t).
+ */
+ intptr_t opcode;
+
+ /* where to store the result of this step */
+ Datum *resvalue;
+ bool *resnull;
+
+ /*
+ * Inline data for the operation. Inline data is faster to access, but
+ * also bloats the size of all instructions. The union should be kept to
+ * no more than 40 bytes on 64-bit systems (so that the entire struct is
+ * no more than 64 bytes, a single cacheline on common systems).
+ */
+ union
+ {
+ /* for EEOP_INNER/OUTER/SCAN_FETCHSOME */
+ struct
+ {
+ /* attribute number up to which to fetch (inclusive) */
+ int last_var;
+ } fetch;
+
+ /* for EEOP_INNER/OUTER/SCAN_[SYS]VAR[_FIRST] */
+ struct
+ {
+ /* attnum is attr number - 1 for regular VAR ... */
+ /* but it's just the normal (negative) attr number for SYSVAR */
+ int attnum;
+ Oid vartype; /* type OID of variable */
+ } var;
+
+ /* for EEOP_WHOLEROW */
+ struct
+ {
+ Var *var; /* original Var node in plan tree */
+ bool first; /* first time through, need to initialize? */
+ bool slow; /* need runtime check for nulls? */
+ TupleDesc tupdesc; /* descriptor for resulting tuples */
+ JunkFilter *junkFilter; /* JunkFilter to remove resjunk cols */
+ } wholerow;
+
+ /* for EEOP_ASSIGN_*_VAR */
+ struct
+ {
+ /* target index in ExprState->resultslot->tts_values/nulls */
+ int resultnum;
+ /* source attribute number - 1 */
+ int attnum;
+ } assign_var;
+
+ /* for EEOP_ASSIGN_TMP[_MAKE_RO] */
+ struct
+ {
+ /* target index in ExprState->resultslot->tts_values/nulls */
+ int resultnum;
+ } assign_tmp;
+
+ /* for EEOP_CONST */
+ struct
+ {
+ /* constant's value */
+ Datum value;
+ bool isnull;
+ } constval;
+
+ /* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */
+ struct
+ {
+ FmgrInfo *finfo; /* function's lookup data */
+ FunctionCallInfo fcinfo_data; /* arguments etc */
+ /* faster to access without additional indirection: */
+ PGFunction fn_addr; /* actual call address */
+ int nargs; /* number of arguments */
+ } func;
+
+ /* for EEOP_BOOL_*_STEP */
+ struct
+ {
+ bool *anynull; /* track if any input was NULL */
+ int jumpdone; /* jump here if result determined */
+ } boolexpr;
+
+ /* for EEOP_QUAL */
+ struct
+ {
+ int jumpdone; /* jump here on false or null */
+ } qualexpr;
+
+ /* for EEOP_JUMP[_CONDITION] */
+ struct
+ {
+ int jumpdone; /* target instruction's index */
+ } jump;
+
+ /* for EEOP_NULLTEST_ROWIS[NOT]NULL */
+ struct
+ {
+ /* cached tupdesc pointer - filled at runtime */
+ TupleDesc argdesc;
+ } nulltest_row;
+
+ /* for EEOP_PARAM_EXEC/EXTERN */
+ struct
+ {
+ int paramid; /* numeric ID for parameter */
+ Oid paramtype; /* OID of parameter's datatype */
+ } param;
+
+ /* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */
+ struct
+ {
+ Datum *value; /* value to return */
+ bool *isnull;
+ } casetest;
+
+ /* for EEOP_MAKE_READONLY */
+ struct
+ {
+ Datum *value; /* value to coerce to read-only */
+ bool *isnull;
+ } make_readonly;
+
+ /* for EEOP_IOCOERCE */
+ struct
+ {
+ /* lookup and call info for source type's output function */
+ FmgrInfo *finfo_out;
+ FunctionCallInfo fcinfo_data_out;
+ /* lookup and call info for result type's input function */
+ FmgrInfo *finfo_in;
+ FunctionCallInfo fcinfo_data_in;
+ } iocoerce;
+
+ /* for EEOP_SQLVALUEFUNCTION */
+ struct
+ {
+ SQLValueFunction *svf;
+ } sqlvaluefunction;
+
+ /* for EEOP_ARRAYEXPR */
+ struct
+ {
+ Datum *elemvalues; /* element values get stored here */
+ bool *elemnulls;
+ int nelems; /* length of the above arrays */
+ Oid elemtype; /* array element type */
+ int16 elemlength; /* typlen of the array element type */
+ bool elembyval; /* is the element type pass-by-value? */
+ char elemalign; /* typalign of the element type */
+ bool multidims; /* is array expression multi-D? */
+ } arrayexpr;
+
+ /* for EEOP_ARRAYCOERCE */
+ struct
+ {
+ ArrayCoerceExpr *coerceexpr;
+ Oid resultelemtype; /* element type of result array */
+ FmgrInfo *elemfunc; /* lookup info for element coercion
+ * function */
+ struct ArrayMapState *amstate; /* workspace for array_map */
+ } arraycoerce;
+
+ /* for EEOP_ROW */
+ struct
+ {
+ TupleDesc tupdesc; /* descriptor for result tuples */
+ /* workspace for the values constituting the row: */
+ Datum *elemvalues;
+ bool *elemnulls;
+ } row;
+
+ /* for EEOP_ROWCOMPARE_STEP */
+ struct
+ {
+ /* lookup and call data for column comparison function */
+ FmgrInfo *finfo;
+ FunctionCallInfo fcinfo_data;
+ PGFunction fn_addr;
+ /* target for comparison resulting in NULL */
+ int jumpnull;
+ /* target for comparison yielding inequality */
+ int jumpdone;
+ } rowcompare_step;
+
+ /* for EEOP_ROWCOMPARE_FINAL */
+ struct
+ {
+ RowCompareType rctype;
+ } rowcompare_final;
+
+ /* for EEOP_MINMAX */
+ struct
+ {
+ /* workspace for argument values */
+ Datum *values;
+ bool *nulls;
+ int nelems;
+ /* is it GREATEST or LEAST? */
+ MinMaxOp op;
+ /* lookup and call data for comparison function */
+ FmgrInfo *finfo;
+ FunctionCallInfo fcinfo_data;
+ } minmax;
+
+ /* for EEOP_FIELDSELECT */
+ struct
+ {
+ AttrNumber fieldnum; /* field number to extract */
+ Oid resulttype; /* field's type */
+ /* cached tupdesc pointer - filled at runtime */
+ TupleDesc argdesc;
+ } fieldselect;
+
+ /* for EEOP_FIELDSTORE_DEFORM / FIELDSTORE_FORM */
+ struct
+ {
+ /* original expression node */
+ FieldStore *fstore;
+
+ /* cached tupdesc pointer - filled at runtime */
+ /* note that a DEFORM and FORM pair share the same tupdesc */
+ TupleDesc *argdesc;
+
+ /* workspace for column values */
+ Datum *values;
+ bool *nulls;
+ int ncolumns;
+ } fieldstore;
+
+ /* for EEOP_ARRAYREF_SUBSCRIPT */
+ struct
+ {
+ /* too big to have inline */
+ struct ArrayRefState *state;
+ int off; /* 0-based index of this subscript */
+ bool isupper; /* is it upper or lower subscript? */
+ int jumpdone; /* jump here on null */
+ } arrayref_subscript;
+
+ /* for EEOP_ARRAYREF_OLD / ASSIGN / FETCH */
+ struct
+ {
+ /* too big to have inline */
+ struct ArrayRefState *state;
+ } arrayref;
+
+ /* for EEOP_DOMAIN_NOTNULL / DOMAIN_CHECK */
+ struct
+ {
+ /* name of constraint */
+ char *constraintname;
+ /* where the result of a CHECK constraint will be stored */
+ Datum *checkvalue;
+ bool *checknull;
+ /* OID of domain type */
+ Oid resulttype;
+ } domaincheck;
+
+ /* for EEOP_CONVERT_ROWTYPE */
+ struct
+ {
+ ConvertRowtypeExpr *convert; /* original expression */
+ /* these three fields are filled at runtime: */
+ TupleDesc indesc; /* tupdesc for input type */
+ TupleDesc outdesc; /* tupdesc for output type */
+ TupleConversionMap *map; /* column mapping */
+ bool initialized; /* initialized for current types? */
+ } convert_rowtype;
+
+ /* for EEOP_SCALARARRAYOP */
+ struct
+ {
+ /* element_type/typlen/typbyval/typalign are filled at runtime */
+ Oid element_type; /* InvalidOid if not yet filled */
+ bool useOr; /* use OR or AND semantics? */
+ int16 typlen; /* array element type storage info */
+ bool typbyval;
+ char typalign;
+ FmgrInfo *finfo; /* function's lookup data */
+ FunctionCallInfo fcinfo_data; /* arguments etc */
+ /* faster to access without additional indirection: */
+ PGFunction fn_addr; /* actual call address */
+ } scalararrayop;
+
+ /* for EEOP_XMLEXPR */
+ struct
+ {
+ XmlExpr *xexpr; /* original expression node */
+ /* workspace for evaluating named args, if any */
+ Datum *named_argvalue;
+ bool *named_argnull;
+ /* workspace for evaluating unnamed args, if any */
+ Datum *argvalue;
+ bool *argnull;
+ } xmlexpr;
+
+ /* for EEOP_AGGREF */
+ struct
+ {
+ /* out-of-line state, modified by nodeAgg.c */
+ AggrefExprState *astate;
+ } aggref;
+
+ /* for EEOP_GROUPING_FUNC */
+ struct
+ {
+ AggState *parent; /* parent Agg */
+ List *clauses; /* integer list of column numbers */
+ } grouping_func;
+
+ /* for EEOP_WINDOW_FUNC */
+ struct
+ {
+ /* out-of-line state, modified by nodeWindowFunc.c */
+ WindowFuncExprState *wfstate;
+ } window_func;
+
+ /* for EEOP_SUBPLAN */
+ struct
+ {
+ /* out-of-line state, created by nodeSubplan.c */
+ SubPlanState *sstate;
+ } subplan;
+
+ /* for EEOP_ALTERNATIVE_SUBPLAN */
+ struct
+ {
+ /* out-of-line state, created by nodeSubplan.c */
+ AlternativeSubPlanState *asstate;
+ } alternative_subplan;
+ } d;
+} ExprEvalStep;
+
+
+/* Non-inline data for array operations */
+typedef struct ArrayRefState
+{
+ bool isassignment; /* is it assignment, or just fetch? */
+
+ Oid refelemtype; /* OID of the array element type */
+ int16 refattrlength; /* typlen of array type */
+ int16 refelemlength; /* typlen of the array element type */
+ bool refelembyval; /* is the element type pass-by-value? */
+ char refelemalign; /* typalign of the element type */
+
+ /* numupper and upperprovided[] are filled at compile time */
+ /* at runtime, extracted subscript datums get stored in upperindex[] */
+ int numupper;
+ bool upperprovided[MAXDIM];
+ int upperindex[MAXDIM];
+
+ /* similarly for lower indexes, if any */
+ int numlower;
+ bool lowerprovided[MAXDIM];
+ int lowerindex[MAXDIM];
+
+ /* subscript expressions get evaluated into here */
+ Datum subscriptvalue;
+ bool subscriptnull;
+
+ /* for assignment, new value to assign is evaluated into here */
+ Datum replacevalue;
+ bool replacenull;
+
+ /* if we have a nested assignment, ARRAYREF_OLD puts old value here */
+ Datum prevvalue;
+ bool prevnull;
+} ArrayRefState;
+
+
+extern void ExecReadyInterpretedExpr(ExprState *state);
+
+extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
+
+/*
+ * Non fast-path execution functions. These are externs instead of statics in
+ * execExprInterp.c, because that allows them to be used by other methods of
+ * expression evaluation, reducing code duplication.
+ */
+extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalRow(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalMinMax(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern bool ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
+extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+
+#endif /* EXEC_EXPR_H */
#undef EXEC_NESTLOOPDEBUG
*/
-/* ----------------
- * EXEC_EVALDEBUG is a flag which turns on debugging of
- * ExecEval and ExecTargetList() stuff by EV_printf() in execQual.c
- * ----------------
-#undef EXEC_EVALDEBUG
- */
-
/* ----------------
* EXEC_SORTDEBUG is a flag which turns on debugging of
* the ExecSort() stuff by SO_printf() in nodeSort.c
#define ENL1_printf(message)
#endif /* EXEC_NESTLOOPDEBUG */
-/* ----------------
- * exec eval / target list debugging defines
- * ----------------
- */
-#ifdef EXEC_EVALDEBUG
-#define EV_nodeDisplay(l) nodeDisplay(l)
-#define EV_printf(s) printf(s)
-#define EV1_printf(s, a) printf(s, a)
-#else
-#define EV_nodeDisplay(l)
-#define EV_printf(s)
-#define EV1_printf(s, a)
-#endif /* EXEC_EVALDEBUG */
-
/* ----------------
* sort node debugging defines
* ----------------
#define MJ_DEBUG_PROC_NODE(slot)
#endif /* EXEC_MERGEJOINDEBUG */
-#endif /* ExecDebugIncluded */
+#endif /* EXECDEBUG_H */
#define EXEC_FLAG_WITH_NO_DATA 0x0080 /* rel scannability doesn't matter */
-/*
- * ExecEvalExpr was formerly a function containing a switch statement;
- * now it's just a macro invoking the function pointed to by an ExprState
- * node. Beware of double evaluation of the ExprState argument!
- */
-#define ExecEvalExpr(expr, econtext, isNull) \
- ((*(expr)->evalfunc) (expr, econtext, isNull))
-
-
/* Hook for plugins to get control in ExecutorStart() */
typedef void (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags);
extern PGDLLIMPORT ExecutorStart_hook_type ExecutorStart_hook;
extern bool ExecShutdownNode(PlanState *node);
/*
- * prototypes from functions in execQual.c
+ * prototypes from functions in execExpr.c
*/
-extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno,
- bool *isNull);
-extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname,
- bool *isNull);
-extern Tuplestorestate *ExecMakeTableFunctionResult(ExprState *funcexpr,
+extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
+extern ExprState *ExecInitQual(List *qual, PlanState *parent);
+extern ExprState *ExecInitCheck(List *qual, PlanState *parent);
+extern List *ExecInitExprList(List *nodes, PlanState *parent);
+extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
+ ExprContext *econtext,
+ TupleTableSlot *slot,
+ PlanState *parent,
+ TupleDesc inputDesc);
+extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
+extern ExprState *ExecPrepareQual(List *qual, EState *estate);
+extern ExprState *ExecPrepareCheck(List *qual, EState *estate);
+extern List *ExecPrepareExprList(List *nodes, EState *estate);
+
+/*
+ * ExecEvalExpr
+ *
+ * Evaluate expression identified by "state" in the execution context
+ * given by "econtext". *isNull is set to the is-null flag for the result,
+ * and the Datum value is the function result.
+ *
+ * The caller should already have switched into the temporary memory
+ * context econtext->ecxt_per_tuple_memory. The convenience entry point
+ * ExecEvalExprSwitchContext() is provided for callers who don't prefer to
+ * do the switch in an outer loop.
+ */
+#ifndef FRONTEND
+static inline Datum
+ExecEvalExpr(ExprState *state,
+ ExprContext *econtext,
+ bool *isNull)
+{
+ return (*state->evalfunc) (state, econtext, isNull);
+}
+#endif
+
+/*
+ * ExecEvalExprSwitchContext
+ *
+ * Same as ExecEvalExpr, but get into the right allocation context explicitly.
+ */
+#ifndef FRONTEND
+static inline Datum
+ExecEvalExprSwitchContext(ExprState *state,
+ ExprContext *econtext,
+ bool *isNull)
+{
+ Datum retDatum;
+ MemoryContext oldContext;
+
+ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ retDatum = (*state->evalfunc) (state, econtext, isNull);
+ MemoryContextSwitchTo(oldContext);
+ return retDatum;
+}
+#endif
+
+/*
+ * ExecProject
+ *
+ * Projects a tuple based on projection info and stores it in the slot passed
+ * to ExecBuildProjectInfo().
+ *
+ * Note: the result is always a virtual tuple; therefore it may reference
+ * the contents of the exprContext's scan tuples and/or temporary results
+ * constructed in the exprContext. If the caller wishes the result to be
+ * valid longer than that data will be valid, he must call ExecMaterializeSlot
+ * on the result slot.
+ */
+#ifndef FRONTEND
+static inline TupleTableSlot *
+ExecProject(ProjectionInfo *projInfo)
+{
+ ExprContext *econtext = projInfo->pi_exprContext;
+ ExprState *state = &projInfo->pi_state;
+ TupleTableSlot *slot = state->resultslot;
+ bool isnull;
+
+ /*
+ * Clear any former contents of the result slot. This makes it safe for
+ * us to use the slot's Datum/isnull arrays as workspace.
+ */
+ ExecClearTuple(slot);
+
+ /* Run the expression, discarding scalar result from the last column. */
+ (void) ExecEvalExprSwitchContext(state, econtext, &isnull);
+
+ /*
+ * Successfully formed a result row. Mark the result slot as containing a
+ * valid virtual tuple (inlined version of ExecStoreVirtualTuple()).
+ */
+ slot->tts_isempty = false;
+ slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
+
+ return slot;
+}
+#endif
+
+/*
+ * ExecQual - evaluate a qual prepared with ExecInitQual (possibly via
+ * ExecPrepareQual). Returns true if qual is satisfied, else false.
+ *
+ * Note: ExecQual used to have a third argument "resultForNull". The
+ * behavior of this function now corresponds to resultForNull == false.
+ * If you want the resultForNull == true behavior, see ExecCheck.
+ */
+#ifndef FRONTEND
+static inline bool
+ExecQual(ExprState *state, ExprContext *econtext)
+{
+ Datum ret;
+ bool isnull;
+
+ /* short-circuit (here and in ExecInitQual) for empty restriction list */
+ if (state == NULL)
+ return true;
+
+ /* verify that expression was compiled using ExecInitQual */
+ Assert(state->flags & EEO_FLAG_IS_QUAL);
+
+ ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
+
+ /* EEOP_QUAL should never return NULL */
+ Assert(!isnull);
+
+ return DatumGetBool(ret);
+}
+#endif
+
+extern bool ExecCheck(ExprState *state, ExprContext *context);
+
+/*
+ * prototypes from functions in execSRF.c
+ */
+extern SetExprState *ExecInitTableFunctionResult(Expr *expr,
+ ExprContext *econtext, PlanState *parent);
+extern Tuplestorestate *ExecMakeTableFunctionResult(SetExprState *setexpr,
ExprContext *econtext,
MemoryContext argContext,
TupleDesc expectedDesc,
bool randomAccess);
-extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache,
+extern SetExprState *ExecInitFunctionResultSet(Expr *expr,
+ ExprContext *econtext, PlanState *parent);
+extern Datum ExecMakeFunctionResultSet(SetExprState *fcache,
ExprContext *econtext,
bool *isNull,
ExprDoneCond *isDone);
-extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext,
- bool *isNull);
-extern ExprState *ExecInitExpr(Expr *node, PlanState *parent);
-extern ExprState *ExecPrepareExpr(Expr *node, EState *estate);
-extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull);
-extern int ExecTargetListLength(List *targetlist);
-extern int ExecCleanTargetListLength(List *targetlist);
-extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo);
/*
* prototypes from functions in execScan.c
extern void ExecAssignResultType(PlanState *planstate, TupleDesc tupDesc);
extern void ExecAssignResultTypeFromTL(PlanState *planstate);
extern TupleDesc ExecGetResultType(PlanState *planstate);
-extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList,
- ExprContext *econtext,
- TupleTableSlot *slot,
- TupleDesc inputDesc);
extern void ExecAssignProjectionInfo(PlanState *planstate,
TupleDesc inputDesc);
extern void ExecFreeExprContext(PlanState *planstate);
extern void UnregisterExprContextCallback(ExprContext *econtext,
ExprContextCallbackFunction function,
Datum arg);
+
extern void ExecLockNonLeafAppendTables(List *partitioned_rels, EState *estate);
+extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname,
+ bool *isNull);
+extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno,
+ bool *isNull);
+
+extern int ExecTargetListLength(List *targetlist);
+extern int ExecCleanTargetListLength(List *targetlist);
+
/*
* prototypes from functions in execIndexing.c
*/
extern AlternativeSubPlanState *ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent);
+extern Datum ExecSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull);
+
+extern Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node, ExprContext *econtext, bool *isNull);
+
extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent);
extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext);
* arguments, rather than about the function itself. But it's convenient
* to store it here rather than in FunctionCallInfoData, where it might more
* logically belong.
+ *
+ * fn_extra is available for use by the called function; all other fields
+ * should be treated as read-only after the struct is created.
*/
typedef struct FmgrInfo
{
/*
* This struct is the data actually passed to an fmgr-called function.
+ *
+ * The called function is expected to set isnull, and possibly resultinfo or
+ * fields in whatever resultinfo points to. It should not change any other
+ * fields. (In particular, scribbling on the argument arrays is a bad idea,
+ * since some callers assume they can re-call with the same arguments.)
*/
typedef struct FunctionCallInfoData
{
#include "storage/condition_variable.h"
+/* ----------------
+ * ExprState node
+ *
+ * ExprState is the top-level node for expression evaluation.
+ * It contains instructions (in ->steps) to evaluate the expression.
+ * ----------------
+ */
+struct ExprState; /* forward references in this file */
+struct ExprContext;
+struct ExprEvalStep; /* avoid including execExpr.h everywhere */
+
+typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression,
+ struct ExprContext *econtext,
+ bool *isNull);
+
+/* Bits in ExprState->flags (see also execExpr.h for private flag bits): */
+/* expression is for use with ExecQual() */
+#define EEO_FLAG_IS_QUAL (1 << 0)
+
+typedef struct ExprState
+{
+ Node tag;
+
+ uint8 flags; /* bitmask of EEO_FLAG_* bits, see above */
+
+ /*
+ * Storage for result value of a scalar expression, or for individual
+ * column results within expressions built by ExecBuildProjectionInfo().
+ */
+ bool resnull;
+ Datum resvalue;
+
+ /*
+ * If projecting a tuple result, this slot holds the result; else NULL.
+ */
+ TupleTableSlot *resultslot;
+
+ /*
+ * Instructions to compute expression's return value.
+ */
+ struct ExprEvalStep *steps;
+
+ /*
+ * Function that actually evaluates the expression. This can be set to
+ * different values depending on the complexity of the expression.
+ */
+ ExprStateEvalFunc evalfunc;
+
+ /* original expression tree, for debugging only */
+ Expr *expr;
+
+ /*
+ * XXX: following only needed during "compilation", could be thrown away.
+ */
+
+ int steps_len; /* number of steps currently */
+ int steps_alloc; /* allocated length of steps array */
+
+ Datum *innermost_caseval;
+ bool *innermost_casenull;
+
+ Datum *innermost_domainval;
+ bool *innermost_domainnull;
+} ExprState;
+
+
/* ----------------
* IndexInfo information
*
List *ii_Expressions; /* list of Expr */
List *ii_ExpressionsState; /* list of ExprState */
List *ii_Predicate; /* list of Expr */
- List *ii_PredicateState; /* list of ExprState */
+ ExprState *ii_PredicateState;
Oid *ii_ExclusionOps; /* array with one entry per column */
Oid *ii_ExclusionProcs; /* array with one entry per column */
uint16 *ii_ExclusionStrats; /* array with one entry per column */
* that is, form new tuples by evaluation of targetlist expressions.
* Nodes which need to do projections create one of these.
*
+ * The target tuple slot is kept in ProjectionInfo->pi_state.resultslot.
* ExecProject() evaluates the tlist, forms a tuple, and stores it
* in the given slot. Note that the result will be a "virtual" tuple
* unless ExecMaterializeSlot() is then called to force it to be
* converted to a physical tuple. The slot must have a tupledesc
* that matches the output of the tlist!
- *
- * The planner very often produces tlists that consist entirely of
- * simple Var references (lower levels of a plan tree almost always
- * look like that). And top-level tlists are often mostly Vars too.
- * We therefore optimize execution of simple-Var tlist entries.
- * The pi_targetlist list actually contains only the tlist entries that
- * aren't simple Vars, while those that are Vars are processed using the
- * varSlotOffsets/varNumbers/varOutputCols arrays.
- *
- * The lastXXXVar fields are used to optimize fetching of fields from
- * input tuples: they let us do a slot_getsomeattrs() call to ensure
- * that all needed attributes are extracted in one pass.
- *
- * targetlist target list for projection (non-Var expressions only)
- * exprContext expression context in which to evaluate targetlist
- * slot slot to place projection result in
- * directMap true if varOutputCols[] is an identity map
- * numSimpleVars number of simple Vars found in original tlist
- * varSlotOffsets array indicating which slot each simple Var is from
- * varNumbers array containing input attr numbers of simple Vars
- * varOutputCols array containing output attr numbers of simple Vars
- * lastInnerVar highest attnum from inner tuple slot (0 if none)
- * lastOuterVar highest attnum from outer tuple slot (0 if none)
- * lastScanVar highest attnum from scan tuple slot (0 if none)
* ----------------
*/
typedef struct ProjectionInfo
{
NodeTag type;
- List *pi_targetlist;
+ /* instructions to evaluate projection */
+ ExprState pi_state;
+ /* expression context in which to evaluate expression */
ExprContext *pi_exprContext;
- TupleTableSlot *pi_slot;
- bool pi_directMap;
- int pi_numSimpleVars;
- int *pi_varSlotOffsets;
- int *pi_varNumbers;
- int *pi_varOutputCols;
- int pi_lastInnerVar;
- int pi_lastOuterVar;
- int pi_lastScanVar;
} ProjectionInfo;
/* ----------------
IndexInfo **ri_IndexRelationInfo;
TriggerDesc *ri_TrigDesc;
FmgrInfo *ri_TrigFunctions;
- List **ri_TrigWhenExprs;
+ ExprState **ri_TrigWhenExprs;
Instrumentation *ri_TrigInstrument;
struct FdwRoutine *ri_FdwRoutine;
void *ri_FdwState;
bool ri_usesFdwDirectModify;
List *ri_WithCheckOptions;
List *ri_WithCheckOptionExprs;
- List **ri_ConstraintExprs;
+ ExprState **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
ProjectionInfo *ri_projectReturning;
ProjectionInfo *ri_onConflictSetProj;
- List *ri_onConflictSetWhere;
+ ExprState *ri_onConflictSetWhere;
List *ri_PartitionCheck;
- List *ri_PartitionCheckExpr;
+ ExprState *ri_PartitionCheckExpr;
Relation ri_PartitionRoot;
} ResultRelInfo;
/* ----------------------------------------------------------------
- * Expression State Trees
- *
- * Each executable expression tree has a parallel ExprState tree.
- *
- * Unlike PlanState, there is not an exact one-for-one correspondence between
- * ExprState node types and Expr node types. Many Expr node types have no
- * need for node-type-specific run-time state, and so they can use plain
- * ExprState or GenericExprState as their associated ExprState node type.
+ * Expression State Nodes
+ *
+ * Formerly, there was a separate executor expression state node corresponding
+ * to each node in a planned expression tree. That's no longer the case; for
+ * common expression node types, all the execution info is embedded into
+ * step(s) in a single ExprState node. But we still have a few executor state
+ * node types for selected expression node types, mostly those in which info
+ * has to be shared with other parts of the execution state tree.
* ----------------------------------------------------------------
*/
-/* ----------------
- * ExprState node
- *
- * ExprState is the common superclass for all ExprState-type nodes.
- *
- * It can also be instantiated directly for leaf Expr nodes that need no
- * local run-time state (such as Var, Const, or Param).
- *
- * To save on dispatch overhead, each ExprState node contains a function
- * pointer to the routine to execute to evaluate the node.
- * ----------------
- */
-
-typedef struct ExprState ExprState;
-
-typedef Datum (*ExprStateEvalFunc) (ExprState *expression,
- ExprContext *econtext,
- bool *isNull);
-
-struct ExprState
-{
- NodeTag type;
- Expr *expr; /* associated Expr node */
- ExprStateEvalFunc evalfunc; /* routine to run to execute node */
-};
-
-/* ----------------
- * GenericExprState node
- *
- * This is used for Expr node types that need no local run-time state,
- * but have one child Expr node.
- * ----------------
- */
-typedef struct GenericExprState
-{
- ExprState xprstate;
- ExprState *arg; /* state of my child node */
-} GenericExprState;
-
-/* ----------------
- * WholeRowVarExprState node
- * ----------------
- */
-typedef struct WholeRowVarExprState
-{
- ExprState xprstate;
- struct PlanState *parent; /* parent PlanState, or NULL if none */
- TupleDesc wrv_tupdesc; /* descriptor for resulting tuples */
- JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */
-} WholeRowVarExprState;
-
/* ----------------
* AggrefExprState node
* ----------------
*/
typedef struct AggrefExprState
{
- ExprState xprstate;
+ NodeTag type;
+ Aggref *aggref; /* expression plan node */
int aggno; /* ID number for agg within its plan node */
} AggrefExprState;
-/* ----------------
- * GroupingFuncExprState node
- *
- * The list of column numbers refers to the input tuples of the Agg node to
- * which the GroupingFunc belongs, and may contain 0 for references to columns
- * that are only present in grouping sets processed by different Agg nodes (and
- * which are therefore always considered "grouping" here).
- * ----------------
- */
-typedef struct GroupingFuncExprState
-{
- ExprState xprstate;
- struct AggState *aggstate;
- List *clauses; /* integer list of column numbers */
-} GroupingFuncExprState;
-
/* ----------------
* WindowFuncExprState node
* ----------------
*/
typedef struct WindowFuncExprState
{
- ExprState xprstate;
- List *args; /* states of argument expressions */
+ NodeTag type;
+ WindowFunc *wfunc; /* expression plan node */
+ List *args; /* ExprStates for argument expressions */
ExprState *aggfilter; /* FILTER expression */
int wfuncno; /* ID number for wfunc within its plan node */
} WindowFuncExprState;
-/* ----------------
- * ArrayRefExprState node
- *
- * Note: array types can be fixed-length (typlen > 0), but only when the
- * element type is itself fixed-length. Otherwise they are varlena structures
- * and have typlen = -1. In any case, an array type is never pass-by-value.
- * ----------------
- */
-typedef struct ArrayRefExprState
-{
- ExprState xprstate;
- List *refupperindexpr; /* states for child nodes */
- List *reflowerindexpr;
- ExprState *refexpr;
- ExprState *refassgnexpr;
- int16 refattrlength; /* typlen of array type */
- int16 refelemlength; /* typlen of the array element type */
- bool refelembyval; /* is the element type pass-by-value? */
- char refelemalign; /* typalign of the element type */
-} ArrayRefExprState;
/* ----------------
- * FuncExprState node
+ * SetExprState node
*
- * Although named for FuncExpr, this is also used for OpExpr, DistinctExpr,
- * and NullIf nodes; be careful to check what xprstate.expr is actually
- * pointing at!
+ * State for evaluating a potentially set-returning expression (like FuncExpr
+ * or OpExpr). In some cases, like some of the expressions in ROWS FROM(...)
+ * the expression might not be a SRF, but nonetheless it uses the same
+ * machinery as SRFs; it will be treated as a SRF returning a single row.
* ----------------
*/
-typedef struct FuncExprState
+typedef struct SetExprState
{
- ExprState xprstate;
- List *args; /* states of argument expressions */
+ NodeTag type;
+ Expr *expr; /* expression plan node */
+ List *args; /* ExprStates for argument expressions */
+
+ /*
+ * In ROWS FROM, functions can be inlined, removing the FuncExpr normally
+ * inside. In such a case this is the compiled expression (which cannot
+ * return a set), which'll be evaluated using regular ExecEvalExpr().
+ */
+ ExprState *elidedFuncState;
/*
* Function manager's lookup info for the target function. If func.fn_oid
/*
* Flag to remember whether we have registered a shutdown callback for
- * this FuncExprState. We do so only if funcResultStore or setArgsValid
+ * this SetExprState. We do so only if funcResultStore or setArgsValid
* has been set at least once (since all the callback is for is to release
* the tuplestore or clear setArgsValid).
*/
* argument values between calls, when setArgsValid is true.
*/
FunctionCallInfoData fcinfo_data;
-} FuncExprState;
-
-/* ----------------
- * ScalarArrayOpExprState node
- *
- * This is a FuncExprState plus some additional data.
- * ----------------
- */
-typedef struct ScalarArrayOpExprState
-{
- FuncExprState fxprstate;
- /* Cached info about array element type */
- Oid element_type;
- int16 typlen;
- bool typbyval;
- char typalign;
-} ScalarArrayOpExprState;
-
-/* ----------------
- * BoolExprState node
- * ----------------
- */
-typedef struct BoolExprState
-{
- ExprState xprstate;
- List *args; /* states of argument expression(s) */
-} BoolExprState;
+} SetExprState;
/* ----------------
* SubPlanState node
*/
typedef struct SubPlanState
{
- ExprState xprstate;
+ NodeTag type;
+ SubPlan *subplan; /* expression plan node */
struct PlanState *planstate; /* subselect plan's state tree */
struct PlanState *parent; /* parent plan node's state tree */
ExprState *testexpr; /* state of combining expression */
*/
typedef struct AlternativeSubPlanState
{
- ExprState xprstate;
- List *subplans; /* states of alternative subplans */
+ NodeTag type;
+ AlternativeSubPlan *subplan; /* expression plan node */
+ List *subplans; /* SubPlanStates of alternative subplans */
int active; /* list index of the one we're using */
} AlternativeSubPlanState;
-/* ----------------
- * FieldSelectState node
- * ----------------
- */
-typedef struct FieldSelectState
-{
- ExprState xprstate;
- ExprState *arg; /* input expression */
- TupleDesc argdesc; /* tupdesc for most recent input */
-} FieldSelectState;
-
-/* ----------------
- * FieldStoreState node
- * ----------------
- */
-typedef struct FieldStoreState
-{
- ExprState xprstate;
- ExprState *arg; /* input tuple value */
- List *newvals; /* new value(s) for field(s) */
- TupleDesc argdesc; /* tupdesc for most recent input */
-} FieldStoreState;
-
-/* ----------------
- * CoerceViaIOState node
- * ----------------
- */
-typedef struct CoerceViaIOState
-{
- ExprState xprstate;
- ExprState *arg; /* input expression */
- FmgrInfo outfunc; /* lookup info for source output function */
- FmgrInfo infunc; /* lookup info for result input function */
- Oid intypioparam; /* argument needed for input function */
-} CoerceViaIOState;
-
-/* ----------------
- * ArrayCoerceExprState node
- * ----------------
- */
-typedef struct ArrayCoerceExprState
-{
- ExprState xprstate;
- ExprState *arg; /* input array value */
- Oid resultelemtype; /* element type of result array */
- FmgrInfo elemfunc; /* lookup info for element coercion function */
- /* use struct pointer to avoid including array.h here */
- struct ArrayMapState *amstate; /* workspace for array_map */
-} ArrayCoerceExprState;
-
-/* ----------------
- * ConvertRowtypeExprState node
- * ----------------
- */
-typedef struct ConvertRowtypeExprState
-{
- ExprState xprstate;
- ExprState *arg; /* input tuple value */
- TupleDesc indesc; /* tupdesc for source rowtype */
- TupleDesc outdesc; /* tupdesc for result rowtype */
- /* use "struct" so we needn't include tupconvert.h here */
- struct TupleConversionMap *map;
- bool initialized;
-} ConvertRowtypeExprState;
-
-/* ----------------
- * CaseExprState node
- * ----------------
- */
-typedef struct CaseExprState
-{
- ExprState xprstate;
- ExprState *arg; /* implicit equality comparison argument */
- List *args; /* the arguments (list of WHEN clauses) */
- ExprState *defresult; /* the default result (ELSE clause) */
- int16 argtyplen; /* if arg is provided, its typlen */
-} CaseExprState;
-
-/* ----------------
- * CaseWhenState node
- * ----------------
- */
-typedef struct CaseWhenState
-{
- ExprState xprstate;
- ExprState *expr; /* condition expression */
- ExprState *result; /* substitution result */
-} CaseWhenState;
-
-/* ----------------
- * ArrayExprState node
- *
- * Note: ARRAY[] expressions always produce varlena arrays, never fixed-length
- * arrays.
- * ----------------
- */
-typedef struct ArrayExprState
-{
- ExprState xprstate;
- List *elements; /* states for child nodes */
- int16 elemlength; /* typlen of the array element type */
- bool elembyval; /* is the element type pass-by-value? */
- char elemalign; /* typalign of the element type */
-} ArrayExprState;
-
-/* ----------------
- * RowExprState node
- * ----------------
- */
-typedef struct RowExprState
-{
- ExprState xprstate;
- List *args; /* the arguments */
- TupleDesc tupdesc; /* descriptor for result tuples */
-} RowExprState;
-
-/* ----------------
- * RowCompareExprState node
- * ----------------
- */
-typedef struct RowCompareExprState
-{
- ExprState xprstate;
- List *largs; /* the left-hand input arguments */
- List *rargs; /* the right-hand input arguments */
- FmgrInfo *funcs; /* array of comparison function info */
- Oid *collations; /* array of collations to use */
-} RowCompareExprState;
-
-/* ----------------
- * CoalesceExprState node
- * ----------------
- */
-typedef struct CoalesceExprState
-{
- ExprState xprstate;
- List *args; /* the arguments */
-} CoalesceExprState;
-
-/* ----------------
- * MinMaxExprState node
- * ----------------
- */
-typedef struct MinMaxExprState
-{
- ExprState xprstate;
- List *args; /* the arguments */
- FmgrInfo cfunc; /* lookup info for comparison func */
-} MinMaxExprState;
-
-/* ----------------
- * XmlExprState node
- * ----------------
- */
-typedef struct XmlExprState
-{
- ExprState xprstate;
- List *named_args; /* ExprStates for named arguments */
- List *args; /* ExprStates for other arguments */
-} XmlExprState;
-
-/* ----------------
- * NullTestState node
- * ----------------
- */
-typedef struct NullTestState
-{
- ExprState xprstate;
- ExprState *arg; /* input expression */
- /* used only if input is of composite type: */
- TupleDesc argdesc; /* tupdesc for most recent input */
-} NullTestState;
-
-/* ----------------
- * CoerceToDomainState node
- * ----------------
- */
-typedef struct CoerceToDomainState
-{
- ExprState xprstate;
- ExprState *arg; /* input expression */
- /* Cached set of constraints that need to be checked */
- /* use struct pointer to avoid including typcache.h here */
- struct DomainConstraintRef *constraint_ref;
-} CoerceToDomainState;
-
/*
* DomainConstraintState - one item to check during CoerceToDomain
*
- * Note: this is just a Node, and not an ExprState, because it has no
- * corresponding Expr to link to. Nonetheless it is part of an ExprState
- * tree, so we give it a name following the xxxState convention.
+ * Note: we consider this to be part of an ExprState tree, so we give it
+ * a name following the xxxState convention. But there's no directly
+ * associated plan-tree node.
*/
typedef enum DomainConstraintType
{
NodeTag type;
DomainConstraintType constrainttype; /* constraint type */
char *name; /* name of constraint (for error msgs) */
- ExprState *check_expr; /* for CHECK, a boolean expression */
+ Expr *check_expr; /* for CHECK, a boolean expression */
+ ExprState *check_exprstate; /* check_expr's eval state, or NULL */
} DomainConstraintState;
* state trees parallel links in the associated plan tree (except for the
* subPlan list, which does not exist in the plan tree).
*/
- List *targetlist; /* target list to be computed at this node */
- List *qual; /* implicitly-ANDed qual conditions */
+ ExprState *qual; /* boolean qual condition */
struct PlanState *lefttree; /* input plan tree(s) */
struct PlanState *righttree;
List *initPlan; /* Init SubPlanState nodes (un-correlated expr
/* ----------------
* ProjectSetState information
+ *
+ * Note: at least one of the "elems" will be a SetExprState; the rest are
+ * regular ExprStates.
* ----------------
*/
typedef struct ProjectSetState
{
PlanState ps; /* its first field is NodeTag */
+ Node **elems; /* array of expression states */
ExprDoneCond *elemdone; /* array of per-SRF is-done states */
int nelems; /* length of elemdone[] array */
bool pending_srf_tuples; /* still evaluating srfs in tlist? */
typedef struct IndexScanState
{
ScanState ss; /* its first field is NodeTag */
- List *indexqualorig;
+ ExprState *indexqualorig;
List *indexorderbyorig;
ScanKey iss_ScanKeys;
int iss_NumScanKeys;
typedef struct IndexOnlyScanState
{
ScanState ss; /* its first field is NodeTag */
- List *indexqual;
+ ExprState *indexqual;
ScanKey ioss_ScanKeys;
int ioss_NumScanKeys;
ScanKey ioss_OrderByKeys;
typedef struct BitmapHeapScanState
{
ScanState ss; /* its first field is NodeTag */
- List *bitmapqualorig;
+ ExprState *bitmapqualorig;
TIDBitmap *tbm;
TBMIterator *tbmiterator;
TBMIterateResult *tbmres;
/* ----------------
* TidScanState information
*
+ * tidexprs list of TidExpr structs (see nodeTidscan.c)
* isCurrentOf scan has a CurrentOfExpr qual
* NumTids number of tids in this scan
* TidPtr index of currently fetched tid
* TidList evaluated item pointers (array of size NumTids)
+ * htup currently-fetched tuple, if any
* ----------------
*/
typedef struct TidScanState
{
ScanState ss; /* its first field is NodeTag */
- List *tss_tidquals; /* list of ExprState nodes */
+ List *tss_tidexprs;
bool tss_isCurrentOf;
int tss_NumTids;
int tss_TidPtr;
typedef struct ForeignScanState
{
ScanState ss; /* its first field is NodeTag */
- List *fdw_recheck_quals; /* original quals not in ss.ps.qual */
+ ExprState *fdw_recheck_quals; /* original quals not in ss.ps.qual */
Size pscan_len; /* size of parallel coordination information */
/* use struct pointer to avoid including fdwapi.h here */
struct FdwRoutine *fdwroutine;
{
PlanState ps;
JoinType jointype;
- List *joinqual; /* JOIN quals (in addition to ps.qual) */
+ ExprState *joinqual; /* JOIN quals (in addition to ps.qual) */
} JoinState;
/* ----------------
typedef struct HashJoinState
{
JoinState js; /* its first field is NodeTag */
- List *hashclauses; /* list of ExprState nodes */
+ ExprState *hashclauses;
List *hj_OuterHashKeys; /* list of ExprState nodes */
List *hj_InnerHashKeys; /* list of ExprState nodes */
List *hj_HashOperators; /* list of operator OIDs */
/*
* TAGS FOR EXPRESSION STATE NODES (execnodes.h)
*
- * These correspond (not always one-for-one) to primitive nodes derived
- * from Expr.
+ * ExprState represents the evaluation state for a whole expression tree.
+ * Most Expr-based plan nodes do not have a corresponding expression state
+ * node, they're fully handled within execExpr* - but sometimes the state
+ * needs to be shared with other parts of the executor, as for example
+ * with AggrefExprState, which nodeAgg.c has to modify.
*/
T_ExprState,
- T_GenericExprState,
- T_WholeRowVarExprState,
T_AggrefExprState,
- T_GroupingFuncExprState,
T_WindowFuncExprState,
- T_ArrayRefExprState,
- T_FuncExprState,
- T_ScalarArrayOpExprState,
- T_BoolExprState,
+ T_SetExprState,
T_SubPlanState,
T_AlternativeSubPlanState,
- T_FieldSelectState,
- T_FieldStoreState,
- T_CoerceViaIOState,
- T_ArrayCoerceExprState,
- T_ConvertRowtypeExprState,
- T_CaseExprState,
- T_CaseWhenState,
- T_ArrayExprState,
- T_RowExprState,
- T_RowCompareExprState,
- T_CoalesceExprState,
- T_MinMaxExprState,
- T_XmlExprState,
- T_NullTestState,
- T_CoerceToDomainState,
T_DomainConstraintState,
/*
List *constraints; /* list of DomainConstraintState nodes */
MemoryContext refctx; /* context holding DomainConstraintRef */
TypeCacheEntry *tcache; /* typcache entry for domain type */
+ bool need_exprstate; /* does caller need check_exprstate? */
/* Management data --- treat these fields as private to typcache.c */
DomainConstraintCache *dcc; /* current constraints, or NULL if none */
extern TypeCacheEntry *lookup_type_cache(Oid type_id, int flags);
extern void InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
- MemoryContext refctx);
+ MemoryContext refctx, bool need_exprstate);
extern void UpdateDomainConstraintRef(DomainConstraintRef *ref);
const char *msg);
extern xmltype *xmlconcat(List *args);
-extern xmltype *xmlelement(XmlExprState *xmlExpr, ExprContext *econtext);
+extern xmltype *xmlelement(XmlExpr *xexpr,
+ Datum *named_argvalue, bool *named_argnull,
+ Datum *argvalue, bool *argnull);
extern xmltype *xmlparse(text *data, XmlOptionType xmloption, bool preserve_whitespace);
extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is_null);
extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
{
plpgsql_CastHashKey key; /* hash key --- MUST BE FIRST */
Expr *cast_expr; /* cast expression, or NULL if no-op cast */
- /* The ExprState tree is valid only when cast_lxid matches current LXID */
+ /* ExprState is valid only when cast_lxid matches current LXID */
ExprState *cast_exprstate; /* expression's eval tree */
bool cast_in_use; /* true while we're executing eval tree */
LocalTransactionId cast_lxid;
/*
* Evaluate the subscripts, switch into left-to-right order.
- * Like ExecEvalArrayRef(), complain if any subscript is null.
+ * Like the expression built by ExecInitArrayRef(), complain
+ * if any subscript is null.
*/
for (i = 0; i < nsubscripts; i++)
{
-- Nested CASE expressions
--
-- This test exercises a bug caused by aliasing econtext->caseValue_isNull
--- with the isNull argument of the inner CASE's ExecEvalCase() call. After
+-- with the isNull argument of the inner CASE's CaseExpr evaluation. After
-- evaluating the vol(null) expression in the inner CASE's second WHEN-clause,
-- the isNull flag for the case test value incorrectly became true, causing
-- the third WHEN-clause not to match. The volatile function calls are needed
DROP FUNCTION testfunc1(int); -- ok
-- restore to sanity
GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC;
--- verify privilege checks on coercions
+-- verify privilege checks on array-element coercions
BEGIN;
-SELECT NULL::int4[]::int8[];
- int8
-------
-
-(1 row)
-
SELECT '{1}'::int4[]::int8[];
int8
------
(1 row)
REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC;
-SELECT NULL::int4[]::int8[];
- int8
-------
-
-(1 row)
-
SELECT '{1}'::int4[]::int8[]; --superuser, suceed
int8
------
(1 row)
SET SESSION AUTHORIZATION regress_user4;
-SELECT NULL::int4[]::int8[]; --other user, no elements to convert
- int8
-------
-
-(1 row)
-
SELECT '{1}'::int4[]::int8[]; --other user, fail
ERROR: permission denied for function int8
ROLLBACK;
--
-- This test exercises a bug caused by aliasing econtext->caseValue_isNull
--- with the isNull argument of the inner CASE's ExecEvalCase() call. After
+-- with the isNull argument of the inner CASE's CaseExpr evaluation. After
-- evaluating the vol(null) expression in the inner CASE's second WHEN-clause,
-- the isNull flag for the case test value incorrectly became true, causing
-- the third WHEN-clause not to match. The volatile function calls are needed
-- restore to sanity
GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC;
--- verify privilege checks on coercions
+-- verify privilege checks on array-element coercions
BEGIN;
-SELECT NULL::int4[]::int8[];
SELECT '{1}'::int4[]::int8[];
REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC;
-SELECT NULL::int4[]::int8[];
SELECT '{1}'::int4[]::int8[]; --superuser, suceed
SET SESSION AUTHORIZATION regress_user4;
-SELECT NULL::int4[]::int8[]; --other user, no elements to convert
SELECT '{1}'::int4[]::int8[]; --other user, fail
ROLLBACK;
ArrayMetaState
ArrayParseState
ArrayRef
-ArrayRefExprState
+ArrayRefState
ArrayRemapInfo
ArrayType
AsyncQueueControl
ExprDoneCond
ExprState
ExprStateEvalFunc
+ExprEvalOp
+ExprEvalStep
ExtensibleNode
ExtensibleNodeEntry
ExtensibleNodeMethods
LWLockTranche
LabelProvider
LargeObjectDesc
+LastAttnumInfo
Latch
LerpFunc
LexDescr
SetConstraintState
SetConstraintStateData
SetConstraintTriggerData
+SetExprState
SetFunctionReturnMode
SetOp
SetOpCmd