diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index e8cb2d0b4d..03f14800b0 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -3421,7 +3421,7 @@ prepare_query_params(PlanState *node, * 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 *)); diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 6511c6064b..6cfce4f8dd 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -1084,7 +1084,7 @@ index_register(Oid heap, /* 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); diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 7924c30369..1eb163f539 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1658,7 +1658,7 @@ BuildIndexInfo(Relation index) /* 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) @@ -1774,9 +1774,8 @@ FormIndexDatum(IndexInfo *indexInfo, 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); } @@ -2208,7 +2207,7 @@ IndexBuildHeapRangeScan(Relation heapRelation, Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; double reltuples; - List *predicate; + ExprState *predicate; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -2247,9 +2246,7 @@ IndexBuildHeapRangeScan(Relation heapRelation, 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 @@ -2552,9 +2549,9 @@ IndexBuildHeapRangeScan(Relation heapRelation, * 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; } @@ -2619,7 +2616,7 @@ IndexBuildHeapRangeScan(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; return reltuples; } @@ -2646,7 +2643,7 @@ IndexCheckExclusion(Relation heapRelation, HeapTuple heapTuple; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; - List *predicate; + ExprState *predicate; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -2672,9 +2669,7 @@ IndexCheckExclusion(Relation heapRelation, 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. @@ -2699,9 +2694,9 @@ IndexCheckExclusion(Relation heapRelation, /* * 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; } @@ -2732,7 +2727,7 @@ IndexCheckExclusion(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; } @@ -2962,7 +2957,7 @@ validate_index_heapscan(Relation heapRelation, HeapTuple heapTuple; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; - List *predicate; + ExprState *predicate; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -2992,9 +2987,7 @@ validate_index_heapscan(Relation heapRelation, 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 @@ -3121,9 +3114,9 @@ validate_index_heapscan(Relation heapRelation, * 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; } @@ -3177,7 +3170,7 @@ validate_index_heapscan(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; } diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index e01ef864f0..2b5b8e89bb 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -1618,8 +1618,7 @@ FormPartitionKeyDatum(PartitionDispatch pd, 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); diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 0e4231668d..29756eb14e 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -307,7 +307,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, 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; diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index c5b5c54bab..404acb2deb 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -713,7 +713,7 @@ compute_index_stats(Relation onerel, double totalrows, TupleTableSlot *slot; EState *estate; ExprContext *econtext; - List *predicate; + ExprState *predicate; Datum *exprvals; bool *exprnulls; int numindexrows, @@ -739,9 +739,7 @@ compute_index_stats(Relation onerel, double totalrows, 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)); @@ -764,9 +762,9 @@ compute_index_stats(Relation onerel, double totalrows, 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++; diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index b4c7466666..1036b96aae 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -2890,7 +2890,7 @@ ExplainSubPlans(List *plans, List *ancestors, 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 diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 9618032356..486179938c 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -179,7 +179,7 @@ CheckIndexCompatible(Oid oldId, 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; @@ -551,7 +551,7 @@ DefineIndex(Oid relationId, 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; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 992ba1c9a2..a924610977 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -391,7 +391,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params, } /* Prepare the expressions for execution */ - exprstates = (List *) ExecPrepareExpr((Expr *) params, estate); + exprstates = ExecPrepareExprList(params, estate); paramLI = (ParamListInfo) palloc(offsetof(ParamListInfoData, params) + @@ -407,7 +407,7 @@ EvaluateParams(PreparedStatement *pstmt, List *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]; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3b28e8c34f..96cf42a7f8 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -185,7 +185,7 @@ typedef struct NewConstraint 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; /* @@ -4262,7 +4262,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) CommandId mycid; BulkInsertState bistate; int hi_options; - List *partqualstate = NIL; + ExprState *partqualstate = NULL; /* * Open the relation(s). We have surely already locked the existing @@ -4315,8 +4315,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { 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 */ @@ -4331,9 +4330,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) if (tab->partition_constraint) { needscan = true; - partqualstate = (List *) - ExecPrepareExpr((Expr *) tab->partition_constraint, - estate); + partqualstate = ExecPrepareCheck(tab->partition_constraint, estate); } foreach(l, tab->newvals) @@ -4508,7 +4505,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) 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", @@ -4524,7 +4521,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) } } - 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"))); @@ -6607,8 +6604,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, 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); } @@ -7786,7 +7782,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) Datum val; char *conbin; Expr *origexpr; - List *exprstate; + ExprState *exprstate; TupleDesc tupdesc; HeapScanDesc scan; HeapTuple tuple; @@ -7817,8 +7813,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) 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); @@ -7838,7 +7833,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) { 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", diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index a1bb3e958c..f3b1a52682 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3057,7 +3057,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, if (trigger->tgqual) { TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); - List **predicate; + ExprState **predicate; ExprContext *econtext; TupleTableSlot *oldslot = NULL; TupleTableSlot *newslot = NULL; @@ -3078,7 +3078,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, * 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; @@ -3087,9 +3087,9 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, /* 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); } @@ -3137,7 +3137,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, */ econtext->ecxt_innertuple = oldslot; econtext->ecxt_outertuple = newslot; - if (!ExecQual(*predicate, econtext, false)) + if (!ExecQual(*predicate, econtext)) return false; } diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index d281906cd5..d1c1324399 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -12,9 +12,10 @@ subdir = src/backend/executor 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 \ diff --git a/src/backend/executor/README b/src/backend/executor/README index f1d1e4c76c..a0045067fb 100644 --- a/src/backend/executor/README +++ b/src/backend/executor/README @@ -44,21 +44,171 @@ Plan Trees and State Trees -------------------------- 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 diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c new file mode 100644 index 0000000000..766dbbb074 --- /dev/null +++ b/src/backend/executor/execExpr.c @@ -0,0 +1,2665 @@ +/*------------------------------------------------------------------------- + * + * 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; + } + } +} diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c new file mode 100644 index 0000000000..de7fe895f8 --- /dev/null +++ b/src/backend/executor/execExprInterp.c @@ -0,0 +1,3525 @@ +/*------------------------------------------------------------------------- + * + * 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", + 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); +} diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index 5242dee006..108060ac0f 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -327,23 +327,21 @@ ExecInsertIndexTuples(TupleTableSlot *slot, /* 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; } @@ -551,23 +549,21 @@ ExecCheckIndexConstraints(TupleTableSlot *slot, /* 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; } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index c28cf9c8ea..f2995f2e7b 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -600,8 +600,8 @@ ExecCheckRTEPerms(RangeTblEntry *rte) /* * 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; @@ -1275,8 +1275,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, 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); } @@ -1723,7 +1723,6 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, ConstrCheck *check = rel->rd_att->constr->check; ExprContext *econtext; MemoryContext oldContext; - List *qual; int i; /* @@ -1735,13 +1734,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, { 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); } @@ -1758,14 +1758,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, /* 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; } @@ -1793,8 +1793,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, { List *qual = resultRelInfo->ri_PartitionCheck; - resultRelInfo->ri_PartitionCheckExpr = (List *) - ExecPrepareExpr((Expr *) qual, estate); + resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(qual, estate); } /* @@ -1810,7 +1809,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, * 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); } /* @@ -1990,11 +1989,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, * 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; diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 90bef6f01f..e69de29bb2 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -1,5313 +0,0 @@ -/*------------------------------------------------------------------------- - * - * 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", - 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); -} diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c new file mode 100644 index 0000000000..4badd5c576 --- /dev/null +++ b/src/backend/executor/execSRF.c @@ -0,0 +1,924 @@ +/*------------------------------------------------------------------------- + * + * 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))); + } +} diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 65196795d7..4f131b3ee0 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -123,7 +123,7 @@ ExecScan(ScanState *node, ExecScanRecheckMtd recheckMtd) { ExprContext *econtext; - List *qual; + ExprState *qual; ProjectionInfo *projInfo; /* @@ -170,7 +170,7 @@ ExecScan(ScanState *node, if (TupIsNull(slot)) { if (projInfo) - return ExecClearTuple(projInfo->pi_slot); + return ExecClearTuple(projInfo->pi_state.resultslot); else return slot; } @@ -183,11 +183,11 @@ ExecScan(ScanState *node, /* * 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. diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index a72cffeb6e..2613ffbb71 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -31,6 +31,9 @@ * 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. @@ -44,11 +47,12 @@ #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); @@ -464,186 +468,6 @@ ExecGetResultType(PlanState *planstate) 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 @@ -659,9 +483,10 @@ ExecAssignProjectionInfo(PlanState *planstate, TupleDesc inputDesc) { planstate->ps_ProjInfo = - ExecBuildProjectionInfo(planstate->targetlist, + ExecBuildProjectionInfo(planstate->plan->targetlist, planstate->ps_ExprContext, planstate->ps_ResultTupleSlot, + planstate, inputDesc); } @@ -1009,3 +834,152 @@ ExecLockNonLeafAppendTables(List *partitioned_rels, EState *estate) } } } + +/* + * 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; +} diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 65e52cca63..3e4b0191c7 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -1279,7 +1279,7 @@ fmgr_sql(PG_FUNCTION_ARGS) 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); diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 3207ee460c..471acc4b3e 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -1639,7 +1639,7 @@ project_aggregates(AggState *aggstate) /* * 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 @@ -2501,18 +2501,17 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* * 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. @@ -2540,7 +2539,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) 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)); @@ -2724,7 +2723,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) 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; @@ -3024,11 +3023,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* 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); @@ -3206,8 +3204,8 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans, 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 diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c index 2e9ff7d1b9..19eb1755be 100644 --- a/src/backend/executor/nodeBitmapHeapscan.c +++ b/src/backend/executor/nodeBitmapHeapscan.c @@ -319,7 +319,7 @@ BitmapHeapNext(BitmapHeapScanState *node) 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); @@ -654,7 +654,7 @@ BitmapHeapRecheck(BitmapHeapScanState *node, TupleTableSlot *slot) ResetExprContext(econtext); - return ExecQual(node->bitmapqualorig, econtext, false); + return ExecQual(node->bitmapqualorig, econtext); } /* ---------------------------------------------------------------- @@ -837,15 +837,10 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c index 8f4e0f527e..bed7949c5a 100644 --- a/src/backend/executor/nodeCtescan.c +++ b/src/backend/executor/nodeCtescan.c @@ -242,12 +242,8 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c index d464748290..5d309828ef 100644 --- a/src/backend/executor/nodeCustom.c +++ b/src/backend/executor/nodeCustom.c @@ -49,12 +49,8 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags) 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); diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c index 3b6d1390eb..9ae1561404 100644 --- a/src/backend/executor/nodeForeignscan.c +++ b/src/backend/executor/nodeForeignscan.c @@ -101,7 +101,7 @@ ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot) !fdwroutine->RecheckForeignScan(node, slot)) return false; - return ExecQual(node->fdw_recheck_quals, econtext, false); + return ExecQual(node->fdw_recheck_quals, econtext); } /* ---------------------------------------------------------------- @@ -155,15 +155,10 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 972022784d..426527d2a2 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -35,7 +35,7 @@ */ 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 */ @@ -92,7 +92,7 @@ FunctionNext(FunctionScanState *node) 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, @@ -151,7 +151,7 @@ FunctionNext(FunctionScanState *node) if (fs->tstore == NULL) { fs->tstore = - ExecMakeTableFunctionResult(fs->funcexpr, + ExecMakeTableFunctionResult(fs->setexpr, node->ss.ps.ps_ExprContext, node->argcontext, fs->tupdesc, @@ -340,12 +340,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) /* * 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)); @@ -361,7 +357,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) 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 diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c index 32c97d390e..1e5b1b7675 100644 --- a/src/backend/executor/nodeGather.c +++ b/src/backend/executor/nodeGather.c @@ -81,12 +81,8 @@ ExecInitGather(Gather *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 72f30ab4e6..3f0c3ee4d1 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -86,12 +86,8 @@ ExecInitGatherMerge(GatherMerge *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c index 66c095bc72..af9ba4905e 100644 --- a/src/backend/executor/nodeGroup.c +++ b/src/backend/executor/nodeGroup.c @@ -85,7 +85,7 @@ ExecGroup(GroupState *node) * 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. @@ -139,7 +139,7 @@ ExecGroup(GroupState *node) * 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. @@ -188,12 +188,8 @@ ExecInitGroup(Group *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index e695d8834b..cfc6b96093 100644 --- a/src/backend/executor/nodeHash.c +++ b/src/backend/executor/nodeHash.c @@ -190,12 +190,8 @@ ExecInitHash(Hash *node, EState *estate, int eflags) /* * 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 @@ -1063,7 +1059,7 @@ bool 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; @@ -1097,7 +1093,7 @@ ExecScanHashBucket(HashJoinState *hjstate, /* 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; diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index c50d93f43d..f2c885afbe 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -63,8 +63,8 @@ ExecHashJoin(HashJoinState *node) { PlanState *outerNode; HashState *hashNode; - List *joinqual; - List *otherqual; + ExprState *joinqual; + ExprState *otherqual; ExprContext *econtext; HashJoinTable hashtable; TupleTableSlot *outerTupleSlot; @@ -275,7 +275,7 @@ ExecHashJoin(HashJoinState *node) * 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)); @@ -294,8 +294,7 @@ ExecHashJoin(HashJoinState *node) 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); @@ -322,8 +321,7 @@ ExecHashJoin(HashJoinState *node) */ 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); @@ -350,8 +348,7 @@ ExecHashJoin(HashJoinState *node) */ 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); @@ -411,19 +408,13 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) /* * 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 @@ -517,13 +508,14 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) 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; diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c index db7f2e120e..5550f6c0a4 100644 --- a/src/backend/executor/nodeIndexonlyscan.c +++ b/src/backend/executor/nodeIndexonlyscan.c @@ -211,7 +211,7 @@ IndexOnlyNext(IndexOnlyScanState *node) { 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); @@ -488,15 +488,10 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags) * 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 diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index cb6aff9137..5afd02e09d 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -149,7 +149,7 @@ IndexNext(IndexScanState *node) { 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); @@ -295,7 +295,7 @@ next_indextuple: { 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); @@ -415,7 +415,7 @@ IndexRecheck(IndexScanState *node, TupleTableSlot *slot) ResetExprContext(econtext); - return ExecQual(node->indexqualorig, econtext, false); + return ExecQual(node->indexqualorig, econtext); } @@ -921,18 +921,12 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags) * 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 diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c index 105e2dcedb..62784af304 100644 --- a/src/backend/executor/nodeMergejoin.c +++ b/src/backend/executor/nodeMergejoin.c @@ -452,14 +452,14 @@ static TupleTableSlot * 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 @@ -483,14 +483,14 @@ static TupleTableSlot * 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 @@ -598,8 +598,8 @@ ExecMergeTupleDump(MergeJoinState *mergestate) TupleTableSlot * ExecMergeJoin(MergeJoinState *node) { - List *joinqual; - List *otherqual; + ExprState *joinqual; + ExprState *otherqual; bool qualResult; int compareResult; PlanState *innerPlan; @@ -785,8 +785,8 @@ ExecMergeJoin(MergeJoinState *node) 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) @@ -808,8 +808,8 @@ ExecMergeJoin(MergeJoinState *node) 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) @@ -1455,16 +1455,11 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags) /* * 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 */ diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 29c6a6e1d8..0b524e0b7c 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1152,7 +1152,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, { 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; @@ -1271,7 +1271,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, econtext->ecxt_innertuple = excludedSlot; econtext->ecxt_outertuple = NULL; - if (!ExecQual(onConflictSetWhere, econtext, false)) + if (!ExecQual(onConflictSetWhere, econtext)) { ReleaseBuffer(buffer); InstrCountFiltered1(&mtstate->ps, 1); @@ -1646,7 +1646,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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; @@ -1778,7 +1777,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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); @@ -1818,8 +1817,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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); } @@ -1852,8 +1851,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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. @@ -1862,11 +1862,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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++; } @@ -1883,16 +1881,14 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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++; } @@ -1922,7 +1918,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (node->onConflictAction == ONCONFLICT_UPDATE) { ExprContext *econtext; - ExprState *setexpr; TupleDesc tupDesc; /* insert may only have one plan, inheritance is not expanded */ @@ -1948,11 +1943,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) 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 */ @@ -1960,10 +1954,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) { ExprState *qualexpr; - qualexpr = ExecInitExpr((Expr *) node->onConflictWhere, + qualexpr = ExecInitQual((List *) node->onConflictWhere, &mtstate->ps); - resultRelInfo->ri_onConflictSetWhere = (List *) qualexpr; + resultRelInfo->ri_onConflictSetWhere = qualexpr; } } diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c index cac7ba1b9b..53977e0b32 100644 --- a/src/backend/executor/nodeNestloop.c +++ b/src/backend/executor/nodeNestloop.c @@ -64,8 +64,8 @@ ExecNestLoop(NestLoopState *node) PlanState *outerPlan; TupleTableSlot *outerTupleSlot; TupleTableSlot *innerTupleSlot; - List *joinqual; - List *otherqual; + ExprState *joinqual; + ExprState *otherqual; ExprContext *econtext; ListCell *lc; @@ -176,7 +176,7 @@ ExecNestLoop(NestLoopState *node) 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 @@ -207,7 +207,7 @@ ExecNestLoop(NestLoopState *node) */ ENL1_printf("testing qualification"); - if (ExecQual(joinqual, econtext, false)) + if (ExecQual(joinqual, econtext)) { node->nl_MatchedOuter = true; @@ -225,7 +225,7 @@ ExecNestLoop(NestLoopState *node) 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 @@ -282,16 +282,11 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c index eae0f1dad9..01048cc826 100644 --- a/src/backend/executor/nodeProjectSet.c +++ b/src/backend/executor/nodeProjectSet.c @@ -24,6 +24,7 @@ #include "executor/executor.h" #include "executor/nodeProjectSet.h" +#include "nodes/nodeFuncs.h" #include "utils/memutils.h" @@ -119,10 +120,9 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) { 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); @@ -132,11 +132,10 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) */ 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]; @@ -151,13 +150,12 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) *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) @@ -169,11 +167,9 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) 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 */ @@ -204,6 +200,8 @@ ProjectSetState * 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))); @@ -229,12 +227,7 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags) */ 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); /* @@ -252,11 +245,41 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags) */ 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; } diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c index b5b50b21e9..a753a53419 100644 --- a/src/backend/executor/nodeResult.c +++ b/src/backend/executor/nodeResult.c @@ -77,9 +77,7 @@ ExecResult(ResultState *node) */ if (node->rs_checkqual) { - bool qualResult = ExecQual((List *) node->resconstantqual, - econtext, - false); + bool qualResult = ExecQual(node->resconstantqual, econtext); node->rs_checkqual = false; if (!qualResult) @@ -209,14 +207,10 @@ ExecInitResult(Result *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c index d38265e810..0247bd2347 100644 --- a/src/backend/executor/nodeSamplescan.c +++ b/src/backend/executor/nodeSamplescan.c @@ -164,19 +164,12 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags) /* * 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->args = (List *) - ExecInitExpr((Expr *) tsc->args, - (PlanState *) scanstate); + scanstate->args = ExecInitExprList(tsc->args, (PlanState *) scanstate); scanstate->repeatable = - ExecInitExpr(tsc->repeatable, - (PlanState *) scanstate); + ExecInitExpr(tsc->repeatable, (PlanState *) scanstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c index e61895de0a..5680464fa2 100644 --- a/src/backend/executor/nodeSeqscan.c +++ b/src/backend/executor/nodeSeqscan.c @@ -188,12 +188,8 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index 8f419a13ac..b3a025879a 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -39,12 +39,6 @@ #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); @@ -64,12 +58,12 @@ static bool slotNoNulls(TupleTableSlot *slot); * 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; @@ -95,7 +89,7 @@ ExecHashSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull) { - SubPlan *subplan = (SubPlan *) node->xprstate.expr; + SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; TupleTableSlot *slot; @@ -217,7 +211,7 @@ ExecScanSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull) { - SubPlan *subplan = (SubPlan *) node->xprstate.expr; + SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; SubLinkType subLinkType = subplan->subLinkType; MemoryContext oldcontext; @@ -462,7 +456,7 @@ ExecScanSubPlan(SubPlanState *node, 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; @@ -596,7 +590,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) * 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); } @@ -694,8 +688,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) 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, @@ -706,7 +699,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) /* 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 @@ -763,9 +756,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) TupleTableSlot *slot; List *oplist, *lefttlist, - *righttlist, - *leftptlist, - *rightptlist; + *righttlist; ListCell *l; /* We need a memory context to hold the hash table(s) */ @@ -792,35 +783,33 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) * 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)); @@ -828,45 +817,30 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) 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]); @@ -898,20 +872,22 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) * (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); } @@ -934,7 +910,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) 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; @@ -1111,7 +1087,7 @@ void 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; @@ -1162,16 +1138,22 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent) 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 @@ -1209,7 +1191,7 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent) * 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) diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c index 230a96f9d2..ae184700a6 100644 --- a/src/backend/executor/nodeSubqueryscan.c +++ b/src/backend/executor/nodeSubqueryscan.c @@ -120,12 +120,8 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c index 628f1ba074..e9df48044e 100644 --- a/src/backend/executor/nodeTableFuncscan.c +++ b/src/backend/executor/nodeTableFuncscan.c @@ -139,12 +139,8 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) /* * 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 @@ -179,16 +175,16 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) 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; diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c index 13ed886577..4860ec0f4d 100644 --- a/src/backend/executor/nodeTidscan.c +++ b/src/backend/executor/nodeTidscan.c @@ -38,11 +38,85 @@ ((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. @@ -50,9 +124,8 @@ static TupleTableSlot *TidNext(TidScanState *node); * (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; @@ -73,36 +146,21 @@ TidListCreate(TidScanState *tidstate) * 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 && @@ -119,9 +177,8 @@ TidListCreate(TidScanState *tidstate) 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; @@ -129,8 +186,7 @@ TidListCreate(TidScanState *tidstate) int ndatums; int i; - exstate = (ExprState *) lsecond(saexstate->fxprstate.args); - arraydatum = ExecEvalExprSwitchContext(exstate, + arraydatum = ExecEvalExprSwitchContext(tidexpr->exprstate, econtext, &isNull); if (isNull) @@ -159,12 +215,12 @@ TidListCreate(TidScanState *tidstate) 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)) { @@ -176,11 +232,8 @@ TidListCreate(TidScanState *tidstate) numAllocTids * sizeof(ItemPointerData)); } tidList[numTids++] = cursor_tid; - tidstate->tss_isCurrentOf = true; } } - else - elog(ERROR, "could not identify CTID expression"); } /* @@ -272,11 +325,15 @@ TidNext(TidScanState *node) * 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); /* @@ -470,16 +527,10 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags) /* * 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->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate); - tidstate->tss_tidquals = (List *) - ExecInitExpr((Expr *) node->tidquals, - (PlanState *) tidstate); + TidExprListCreate(tidstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c index 9883a8b130..9ee776c4c3 100644 --- a/src/backend/executor/nodeValuesscan.c +++ b/src/backend/executor/nodeValuesscan.c @@ -120,7 +120,7 @@ ValuesNext(ValuesScanState *node) * 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); @@ -242,12 +242,8 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 2a123e8452..628bc9f00b 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -1826,16 +1826,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) 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 @@ -1894,7 +1890,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) 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; diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c index 23b5b94985..d7616be065 100644 --- a/src/backend/executor/nodeWorktablescan.c +++ b/src/backend/executor/nodeWorktablescan.c @@ -156,12 +156,8 @@ ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags) /* * 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 diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index a129d1ecb3..57229059bd 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -3501,7 +3501,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) /* * 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 diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 68d74cb432..90619509a2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -5013,7 +5013,7 @@ make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc, * 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 diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index b19380e1b1..42bba543e9 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -3395,7 +3395,7 @@ eval_const_expressions_mutator(Node *node, * 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; @@ -3539,8 +3539,8 @@ eval_const_expressions_mutator(Node *node, * 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 @@ -3651,9 +3651,9 @@ simplify_or_arguments(List *args, * 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, diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c index c2ad440013..73deaa7e1c 100644 --- a/src/backend/utils/adt/domains.c +++ b/src/backend/utils/adt/domains.c @@ -107,7 +107,7 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) 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; @@ -122,7 +122,9 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) /* * 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) @@ -149,9 +151,6 @@ 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) { @@ -165,24 +164,20 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) /* * 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\"", diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 81c91039e4..d57d5568b2 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7000,7 +7000,7 @@ find_param_referent(Param *param, deparse_context *context, foreach(lc2, ps->subPlan) { SubPlanState *sstate = (SubPlanState *) lfirst(lc2); - SubPlan *subplan = (SubPlan *) sstate->xprstate.expr; + SubPlan *subplan = sstate->subplan; ListCell *lc3; ListCell *lc4; @@ -7041,7 +7041,7 @@ find_param_referent(Param *param, deparse_context *context, 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; diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 1908b13db5..2f87151bec 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -72,7 +72,6 @@ #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" @@ -620,10 +619,11 @@ xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg) 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; @@ -635,48 +635,47 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext) 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(); diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 6992634c39..0cf5001a75 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -96,11 +96,11 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL; * 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 @@ -779,8 +779,8 @@ load_domaintype_info(TypeCacheEntry *typentry) 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); @@ -859,6 +859,7 @@ load_domaintype_info(TypeCacheEntry *typentry) 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); @@ -946,8 +947,8 @@ prep_domain_constraints(List *constraints, MemoryContext execctx) 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); } @@ -962,13 +963,18 @@ prep_domain_constraints(List *constraints, MemoryContext execctx) * * 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; @@ -980,8 +986,11 @@ InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref, { 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; @@ -1032,8 +1041,11 @@ UpdateDomainConstraintRef(DomainConstraintRef *ref) { 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; } } } diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h new file mode 100644 index 0000000000..a665388232 --- /dev/null +++ b/src/include/executor/execExpr.h @@ -0,0 +1,642 @@ +/*------------------------------------------------------------------------- + * + * 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 */ diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h index cf44c3edbb..8b61520e18 100644 --- a/src/include/executor/execdebug.h +++ b/src/include/executor/execdebug.h @@ -37,13 +37,6 @@ #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 @@ -85,20 +78,6 @@ #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 * ---------------- @@ -146,4 +125,4 @@ #define MJ_DEBUG_PROC_NODE(slot) #endif /* EXEC_MERGEJOINDEBUG */ -#endif /* ExecDebugIncluded */ +#endif /* EXECDEBUG_H */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index a5c75e771f..d3849b93eb 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -65,15 +65,6 @@ #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; @@ -242,29 +233,155 @@ extern void ExecEndNode(PlanState *node); 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 @@ -355,10 +472,6 @@ extern void ExecAssignExprContext(EState *estate, PlanState *planstate); 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); @@ -376,8 +489,17 @@ extern void RegisterExprContextCallback(ExprContext *econtext, 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 */ diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h index 0f821dc8f6..0d3f52118b 100644 --- a/src/include/executor/nodeSubplan.h +++ b/src/include/executor/nodeSubplan.h @@ -20,6 +20,10 @@ extern SubPlanState *ExecInitSubPlan(SubPlan *subplan, PlanState *parent); 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); diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 0a155acee6..6128752ab1 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -49,6 +49,9 @@ typedef Datum (*PGFunction) (FunctionCallInfo fcinfo); * 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 { @@ -65,6 +68,11 @@ 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 { diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index f856f6036f..ff42895118 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -30,6 +30,72 @@ #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 * @@ -69,7 +135,7 @@ typedef struct IndexInfo 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 */ @@ -214,51 +280,21 @@ typedef struct ReturnSetInfo * 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; /* ---------------- @@ -340,20 +376,20 @@ typedef struct ResultRelInfo 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; @@ -564,139 +600,63 @@ typedef tuplehash_iterator TupleHashIterator; /* ---------------------------------------------------------------- - * Expression State Trees + * Expression State Nodes * - * 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. + * 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 @@ -738,7 +698,7 @@ typedef struct FuncExprState /* * 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). */ @@ -750,33 +710,7 @@ typedef struct FuncExprState * 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 @@ -784,7 +718,8 @@ typedef struct BoolExprState */ 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 */ @@ -814,203 +749,18 @@ typedef struct SubPlanState */ 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 { @@ -1023,7 +773,8 @@ typedef struct DomainConstraintState 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; @@ -1060,8 +811,7 @@ typedef struct PlanState * 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 @@ -1133,11 +883,15 @@ typedef struct ResultState /* ---------------- * 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? */ @@ -1372,7 +1126,7 @@ typedef struct typedef struct IndexScanState { ScanState ss; /* its first field is NodeTag */ - List *indexqualorig; + ExprState *indexqualorig; List *indexorderbyorig; ScanKey iss_ScanKeys; int iss_NumScanKeys; @@ -1418,7 +1172,7 @@ typedef struct IndexScanState typedef struct IndexOnlyScanState { ScanState ss; /* its first field is NodeTag */ - List *indexqual; + ExprState *indexqual; ScanKey ioss_ScanKeys; int ioss_NumScanKeys; ScanKey ioss_OrderByKeys; @@ -1534,7 +1288,7 @@ typedef struct ParallelBitmapHeapState typedef struct BitmapHeapScanState { ScanState ss; /* its first field is NodeTag */ - List *bitmapqualorig; + ExprState *bitmapqualorig; TIDBitmap *tbm; TBMIterator *tbmiterator; TBMIterateResult *tbmres; @@ -1554,16 +1308,18 @@ typedef struct BitmapHeapScanState /* ---------------- * 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; @@ -1712,7 +1468,7 @@ typedef struct WorkTableScanState 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; @@ -1759,7 +1515,7 @@ typedef struct JoinState { PlanState ps; JoinType jointype; - List *joinqual; /* JOIN quals (in addition to ps.qual) */ + ExprState *joinqual; /* JOIN quals (in addition to ps.qual) */ } JoinState; /* ---------------- @@ -1857,7 +1613,7 @@ typedef struct HashJoinTableData *HashJoinTable; 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 */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index fc883a6f3e..c83216943c 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -192,36 +192,18 @@ typedef enum NodeTag /* * 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, /* diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h index 90a1f6347a..1bf94e2548 100644 --- a/src/include/utils/typcache.h +++ b/src/include/utils/typcache.h @@ -132,6 +132,7 @@ typedef struct DomainConstraintRef 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 */ @@ -142,7 +143,7 @@ typedef struct DomainConstraintRef 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); diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index e570b71c04..195b9b3a97 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -61,7 +61,9 @@ extern void xml_ereport(PgXmlErrorContext *errcxt, int level, int sqlcode, 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); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 664fc42514..84af815537 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -145,7 +145,7 @@ typedef struct /* cast_hash table entry */ { 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; @@ -4710,7 +4710,8 @@ exec_assign_value(PLpgSQL_execstate *estate, /* * 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++) { diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out index 4cc4851475..36bf15c4ac 100644 --- a/src/test/regress/expected/case.out +++ b/src/test/regress/expected/case.out @@ -308,7 +308,7 @@ SELECT * FROM CASE_TBL; -- 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 diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 720675032a..f349980759 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -586,14 +586,8 @@ ERROR: must be owner of function testfunc1 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 ------ @@ -601,12 +595,6 @@ SELECT '{1}'::int4[]::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 ------ @@ -614,12 +602,6 @@ SELECT '{1}'::int4[]::int8[]; --superuser, suceed (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; diff --git a/src/test/regress/sql/case.sql b/src/test/regress/sql/case.sql index 59268f8cdf..66b6e98fb1 100644 --- a/src/test/regress/sql/case.sql +++ b/src/test/regress/sql/case.sql @@ -166,7 +166,7 @@ SELECT * FROM CASE_TBL; -- -- 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 diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index e3275febea..166e903012 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -398,15 +398,12 @@ 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[]; 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; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3487f7becb..15c72f5765 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -120,7 +120,7 @@ ArrayMapState ArrayMetaState ArrayParseState ArrayRef -ArrayRefExprState +ArrayRefState ArrayRemapInfo ArrayType AsyncQueueControl @@ -578,6 +578,8 @@ ExprContext_CB ExprDoneCond ExprState ExprStateEvalFunc +ExprEvalOp +ExprEvalStep ExtensibleNode ExtensibleNodeEntry ExtensibleNodeMethods @@ -1073,6 +1075,7 @@ LWLockPadded LWLockTranche LabelProvider LargeObjectDesc +LastAttnumInfo Latch LerpFunc LexDescr @@ -1908,6 +1911,7 @@ Session SetConstraintState SetConstraintStateData SetConstraintTriggerData +SetExprState SetFunctionReturnMode SetOp SetOpCmd