From: Andres Freund Date: Tue, 14 Mar 2017 03:22:10 +0000 (-0700) Subject: Faster expression evaluation and targetlist projection. X-Git-Url: http://git.postgresql.org/gitweb/static/gitweb.js?a=commitdiff_plain;h=2488bf212f8eec6ce616832ef2fd98e955734f9f;p=users%2Fandresfreund%2Fpostgres.git Faster expression evaluation and targetlist projection. This replaces the recursive tree-walk based expression evaluation method with a non-recursive opcode dispatched one. This both leads to significant performance improvements, and makes future just-in-time compilation of expressions easier. The speed gains primarily come from: - non-recursive implementation reduces stack usage / overhead - simple sub-expressions are implemented with a single jump, without function calls - sharing some state between different sub-expressions - reduced amount of indirect/hard to predict memory accesses by laying out operation metadata sequentially; including the avoidance of nearly all of the previously used linked lists - more code has been moved to expression initialization, avoiding constant re-checks at evaluation time Future just-in-time compilation (JIT) has become easier, as demonstrated by released patches intended to be merged in a later release, for primarily two reasons: Firstly, due to a stricter split between expression initialization and evaluation, less code has to be handled by the JIT. Secondly, due to the non-recursive nature of the generated "instructions", less critical code-paths can easily be shared between interpreted and compiled evaluation. While not implemented here, this also provides some basis to later reduce the per executor-startup overhead of expression evaluation. Author: Andres Freund Discussion: https://postgr.es/m/20161206034955.bh33paeralxbtluv@alap3.anarazel.de --- diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 990313a597..975aa88c5c 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -3414,7 +3414,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 8d42a347ea..d6be0915a7 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..1e75777acb 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -1618,8 +1618,8 @@ 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 b91df986c5..d124366db2 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -703,7 +703,7 @@ compute_index_stats(Relation onerel, double totalrows, TupleTableSlot *slot; EState *estate; ExprContext *econtext; - List *predicate; + ExprState *predicate; Datum *exprvals; bool *exprnulls; int numindexrows, @@ -729,9 +729,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)); @@ -754,9 +752,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/copy.c b/src/backend/commands/copy.c index 3102ab18c5..7295853e15 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -3406,7 +3406,7 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext, Assert(CurrentMemoryContext == econtext->ecxt_per_tuple_memory); values[defmap[i]] = ExecEvalExpr(defexprs[i], econtext, - &nulls[defmap[i]]); + &nulls[defmap[i]]); } return true; diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index c9b55ead3d..55d09c0e29 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 72bb06c760..fd0aeb06b9 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; @@ -556,7 +556,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 1cf0d2b971..46a362132c 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 1ddb72d164..07d0bb7af0 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 */ } 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 = ExecPrepareQual((List *) 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 (!ExecQual(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"))); @@ -7786,7 +7783,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) Datum val; char *conbin; Expr *origexpr; - List *exprstate; + ExprState *exprstate; TupleDesc tupdesc; HeapScanDesc scan; HeapTuple tuple; @@ -7817,8 +7814,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 = ExecPrepareQual(make_ands_implicit(origexpr), estate); econtext = GetPerTupleExprContext(estate); tupdesc = RelationGetDescr(rel); @@ -7838,7 +7834,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) { ExecStoreTuple(tuple, slot, InvalidBuffer, false); - if (!ExecQual(exprstate, econtext, true)) + if (!ExecQual(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..53fb9f557e 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; @@ -3089,7 +3089,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0); /* ExecQual 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..2c68e18a5b 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -12,9 +12,9 @@ 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 execInterpExpr.o execGrouping.o \ + execIndexing.o execJunk.o execMain.o execParallel.o execProcnode.o \ + execQual.o execReplication.o execScan.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/execExpr.c b/src/backend/executor/execExpr.c new file mode 100644 index 0000000000..4cf0883435 --- /dev/null +++ b/src/backend/executor/execExpr.c @@ -0,0 +1,2295 @@ +/*------------------------------------------------------------------------- + * + * execExpr.c + * Expression evaluation infrastructure. + * + * Expression evaluation now works by first converting expression trees + * (which went through planning first) into an ExprState using ExecInitExpr() + * et al. This converts the tree into a opcode based program (ExprEvalStep + * representing individual instructions); allocated as a flat array of + * steps. + * + * This flat representation has the big advantage that it can be implemented + * non-recursively, within a single function. This allows to interpret + * larger expressions from largely within a single function, keeping state + * between them. In contrast to that, tree-walk based approaches in contrast + * often run into performance issues due to function call / stack + * manipulation overhead. + * + * The ExprEvalStep representation is designed to be usable for interpreting + * the expression, as well as compiling into native code. Thus, if possible, + * as much complexity as possible should be handed by ExecInitExpr() (and + * helpers), instead of handled at execution time where both interpreted and + * compiled versions would need to deal with the complexity. Additionally + * checks for initialization at run time have a small but noticeable cost at + * every execution. + * + * The next step is preparing the ExprState for execution, using + * ExecInstantiateExpr(). This is internally done by ExecInitExpr() and other + * functions that prepare for expression evaluation. ExecInstantiateExpr() + * initializes the expression for the relevant method chosen to evaluate the + * expression. + * + * Note that a lot of the more complex expression evaluation steps, which are + * less performance critical than some of the simpler and more common ones, + * are implemented as separate functions outside the fast-path of interpreted + * expression, like e.g. ExecEvalRow(), so that the implementation can be + * shared between interpreted and compiled expression evaluation. That means + * that ExecInstantiateExpr() always has to initialize the expression for + * evaluation by execInterpExpr.c. It also means that these helper functions + * are not "allowed" to perform dispatch themselves, as the method of + * dispatch will vary based on the caller. + * + * 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/htup_details.h" +#include "access/nbtree.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_type.h" +#include "executor/execExpr.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" + + +/* + * Support for building execution state. + */ +static void ExecInstantiateExpr(ExprState *state); +static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state, + Datum *resv, bool *resnull); +static void ExprEvalPushStep(ExprState *es, ExprEvalStep *s); +static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, + Oid funcid, Oid inputcollid, PlanState *parent, + ExprState *state, Datum *resv, bool *resnull); +static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, + PlanState *parent, ExprState *state, + Datum *resv, bool *resnull); +static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, + PlanState *parent, ExprState *state, + Datum *resv, bool *resnull); +static void ExecInitExprSlots(ExprState *state, Node *node); + +/* support functions */ +static bool isAssignmentIndirectionExpr(Expr *expr); + + +/* + * 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. + * + * 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. + * + * 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 = makeNode(ExprState); + ExprEvalStep scratch; + + if (node == NULL) + return NULL; + + state->expr = node; + ExecInitExprSlots(state, (Node *) node); + + ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull); + + scratch.resvalue = &state->resvalue; + scratch.resnull = &state->resnull; + scratch.opcode = EEO_DONE; + ExprEvalPushStep(state, &scratch); + + ExecInstantiateExpr(state); + + return state; +} + +/* + * ExecInitQual: prepare a qual for execution + * + * Prepares for the evaluation of a conjunctive boolean expression (qual + * list) that 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, then the result of the + * conjunction is false. This makes this routine 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 = makeNode(ExprState); + ExprEvalStep scratch; + ListCell *lc; + List *adjust_bailout = NIL; + + /* short-circuit (here and in ExecQual) for empty restriction list */ + if (qual == NULL) + return NULL; + + Assert(IsA(qual, List)); + + /* + * ExecQual() needs to return false for expression returning NULL. That + * allows 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). + */ + state->expr = (Expr *) qual; + ExecInitExprSlots(state, (Node *) qual); + + scratch.opcode = EEO_QUAL; + 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 check whether it's false or NULL */ + scratch.d.qualexpr.jumpdone = -1; + ExprEvalPushStep(state, &scratch); + adjust_bailout = lappend_int(adjust_bailout, + state->steps_len - 1); + } + + /* adjust early bail out jump target */ + foreach(lc, adjust_bailout) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->d.qualexpr.jumpdone == -1); + as->d.qualexpr.jumpdone = state->steps_len; + } + + scratch.opcode = EEO_DONE; + ExprEvalPushStep(state, &scratch); + + ExecInstantiateExpr(state); + + return state; +} + +/* + * ExecInitCheck: prepare a check constraint for execution + * + * Prepares for the evaluation of a conjunctive boolean expression (qual + * list) that 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, then the result of the + * conjunction is true, since SQL specifies that NULL constraint conditions + * are not failures. + */ +ExprState * +ExecInitCheck(List *qual, PlanState *parent) +{ + Expr *expr; + + if (qual == NULL) + return NULL; + + Assert(IsA(qual, List)); + + if (list_length(qual) == 1) + expr = linitial(qual); + else + { + /* + * Just whip-up a boolean AND expression, that behaves just as needed. + * It'd be valid to implement short-circuiting behaviour on NULLs, but + * that doesn't seem warranted. + */ + expr = makeBoolExpr(AND_EXPR, qual, -1); + } + + return ExecInitExpr(expr, parent); +} + +/* ---------------- + * 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. + * + * This is implemented by internally building an ExprState that performs the + * projection. That way faster implementations of expression evaluation, e.g + * compiled to native code, can evaluate the whole projection in one go. + * ---------------- + */ +ProjectionInfo * +ExecBuildProjectionInfo(List *targetList, + ExprContext *econtext, + TupleTableSlot *slot, + PlanState *parent, + TupleDesc inputDesc) +{ + ProjectionInfo *projInfo = makeNode(ProjectionInfo); + ExprEvalStep scratch; + ListCell *lc; + ExprState *state; + + projInfo->pi_exprContext = econtext; + projInfo->pi_state.tag.type = T_ExprState; + state = &projInfo->pi_state; + state->expr = (Expr *) targetList; + state->resultslot = slot; + ExecInitExprSlots(state, (Node *) targetList); + + foreach(lc, targetList) + { + TargetEntry *tle; + Var *variable = NULL; + AttrNumber attnum; + bool isSimpleVar = false; + + Assert(IsA(lfirst(lc), TargetEntry)); + + tle = (TargetEntry *) lfirst(lc); + + if (tle->expr != NULL && + IsA(tle->expr, Var) && + ((Var *) tle->expr)->varattno > 0) + { + variable = (Var *) tle->expr; + attnum = variable->varattno; + + 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) + { + switch (variable->varno) + { + case INNER_VAR: /* get the tuple from the inner node */ + scratch.opcode = EEO_ASSIGN_INNER_VAR; + break; + + case OUTER_VAR: /* get the tuple from the outer node */ + scratch.opcode = EEO_ASSIGN_OUTER_VAR; + break; + + /* INDEX_VAR is handled by default case */ + default: /* get the tuple from the relation being + * scanned */ + scratch.opcode = EEO_ASSIGN_SCAN_VAR; + break; + } + + scratch.d.assign_var.attnum = attnum - 1; + scratch.d.assign_var.resultnum = tle->resno - 1; + ExprEvalPushStep(state, &scratch); + } + else + { + /* + * We can't directly point into the result slot for the contained + * expression, as the result slot (and the exprstate for that + * matter) can change below us. So we instead evaluate into a + * temporary value and then move. + */ + ExecInitExprRec(tle->expr, parent, state, &state->resvalue, &state->resnull); + if (get_typlen(exprType((Node *) tle->expr)) == -1) + scratch.opcode = EEO_ASSIGN_TMP_UNEXPAND; + else + scratch.opcode = EEO_ASSIGN_TMP; + scratch.d.assign_tmp.resultnum = tle->resno - 1; + ExprEvalPushStep(state, &scratch); + } + } + + scratch.resvalue = &state->resvalue; + scratch.resnull = &state->resnull; + scratch.opcode = EEO_DONE; + ExprEvalPushStep(state, &scratch); + + ExecInstantiateExpr(state); + + return projInfo; +} + +/* + * 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; +} + +/* + * 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 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 * +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 qual for execution outside a normal Plan + * tree context. + * + * See ExecPrepareExpr() and ExecInitQual() 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 nodes, and return list of + * ExprStates. + * + * See ExecPrepareExpr() for details. + */ +List * +ExecPrepareExprList(List *nodes, EState *estate) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, nodes) + { + Expr *e = lfirst(lc); + + result = lappend(result, ExecPrepareExpr(e, estate)); + } + + return result; +} + +/* + * ExecCheck - evaluate a check constraint prepared with ExecInitCheck + * (possibly via ExecPrepareCheck). + */ +bool +ExecCheck(ExprState *state, ExprContext *econtext) +{ + bool isnull; + Datum ret; + + ret = ExecEvalExprSwitchContext(state, econtext, &isnull); + + if (isnull) + return true; + return DatumGetBool(ret); +} + +/* + * Prepare an expression for execution. This has to be called for every + * ExprState before it can be executed. + * + * NB: While this currently only calls ExecInstantiateInterpretedExpr(), this + * will likely get extended to further expression evaluation methods. + * Therefore this should be used instead of ExecInstantiateInterpretedExpr(). + */ +static void +ExecInstantiateExpr(ExprState *state) +{ + ExecInstantiateInterpretedExpr(state); +} + +/* + * Append evaluation of node to ExprState, possibly recursing into + * sub-expressions of node. + */ +static void +ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state, Datum *resv, bool *resnull) +{ + ExprEvalStep scratch; + + /* + * Guard against stack overflow due to overly complex expressions. Because + * expression evaluation is not recursive, but expression planning is, + * we'll hit stack limits due to recursion either here, or when recursing + * into a separate expression evaluation inside a function call. This + * lets us avoid repeatedly doing the quite expensive stack depth checks + * during expression evaluation. + */ + check_stack_depth(); + + 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; + + /* varattno == InvalidAttrNumber means it's a whole-row Var */ + if (variable->varattno == InvalidAttrNumber) + { + ExecInitWholeRowVar(&scratch, variable, parent, state, resv, resnull); + + scratch.opcode = EEO_WHOLEROW; + } + else if (variable->varattno <= 0) + { + scratch.d.var.attnum = variable->varattno; + switch (variable->varno) + { + case INNER_VAR: + scratch.opcode = EEO_INNER_SYSVAR; + break; + case OUTER_VAR: + scratch.opcode = EEO_OUTER_SYSVAR; + break; + default: + scratch.opcode = EEO_SCAN_SYSVAR; + break; + } + } + else + { + switch (variable->varno) + { + case INNER_VAR: + scratch.opcode = EEO_INNER_VAR; + break; + case OUTER_VAR: + scratch.opcode = EEO_OUTER_VAR; + break; + default: + scratch.opcode = EEO_SCAN_VAR; + break; + } + scratch.d.var.attnum = variable->varattno - 1; + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_Const: + { + Const *con = (Const *) node; + + scratch.opcode = EEO_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 = EEO_PARAM_EXEC; + scratch.d.param.paramid = param->paramid; + scratch.d.param.paramtype = InvalidOid; + break; + } + case PARAM_EXTERN: + { + scratch.opcode = EEO_PARAM_EXTERN; + scratch.d.param.paramid = param->paramid; + scratch.d.param.paramtype = param->paramtype; + break; + } + default: + elog(ERROR, "unrecognized paramkind: %d", + (int) ((Param *) node)->paramkind); + break; + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_Aggref: + { + Aggref *aggref = (Aggref *) node; + AggrefExprState *astate = makeNode(AggrefExprState); + + scratch.opcode = EEO_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 = NULL; + + if (!parent || !IsA(parent, AggState) ||!IsA(parent->plan, Agg)) + elog(ERROR, "parent of GROUPING is not Agg node"); + + scratch.opcode = EEO_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 intialize agg using old style expressions */ + wfstate->args = NIL; + 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 = EEO_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, resv, resnull); + ExprEvalPushStep(state, &scratch); + break; + } + + case T_OpExpr: + { + OpExpr *op = (OpExpr *) node; + + ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid, + parent, state, resv, resnull); + ExprEvalPushStep(state, &scratch); + break; + } + + case T_DistinctExpr: + { + DistinctExpr *op = (DistinctExpr *) node; + + ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid, + parent, state, resv, resnull); + + /* + * Can't use normal function call, override opcode for + * 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 + */ + scratch.opcode = EEO_DISTINCT; + ExprEvalPushStep(state, &scratch); + break; + } + + case T_NullIfExpr: + { + NullIfExpr *op = (NullIfExpr *) node; + + ExecInitFunc(&scratch, node, op->args, op->opfuncid, op->inputcollid, + parent, state, resv, resnull); + + /* Can't use normal function call, override opcode for NULL() */ + + /* + * 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 + */ + scratch.opcode = EEO_NULLIF; + ExprEvalPushStep(state, &scratch); + break; + } + + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node; + FmgrInfo *finfo; + FunctionCallInfo fcinfo; + + 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); + + scratch.opcode = EEO_SCALARARRAYOP; + scratch.d.scalararrayop.opexpr = opexpr; + scratch.d.scalararrayop.finfo = finfo; + scratch.d.scalararrayop.fcinfo_data = fcinfo; + scratch.d.scalararrayop.fn_addr = fcinfo->flinfo->fn_addr; + + Assert(fcinfo->nargs == 2); + Assert(list_length(opexpr->args) == 2); + + /* evaluate scalar directly into function argument */ + ExecInitExprRec((Expr *) linitial(opexpr->args), parent, state, + &fcinfo->arg[0], &fcinfo->argnull[0]); + + /* + * Evaluate array argument into our return value, overwrite + * with comparison results afterwards. + */ + ExecInitExprRec((Expr *) lsecond(opexpr->args), parent, state, + resv, resnull); + + scratch.d.scalararrayop.element_type = InvalidOid; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_BoolExpr: + { + BoolExpr *boolexpr = (BoolExpr *) node; + ListCell *lc; + List *adjust_bailout = NIL; + int nargs = list_length(boolexpr->args); + int off = 0; + + /* allocate scratch memory used by all steps */ + scratch.d.boolexpr.value = palloc0(sizeof(Datum)); + scratch.d.boolexpr.isnull = palloc0(sizeof(bool)); + scratch.d.boolexpr.anynull = palloc0(sizeof(bool)); + + /* + * For each argument evaluate the argument itself, then + * perform the bool operation's appropriate handling. + */ + foreach(lc, boolexpr->args) + { + Expr *arg = (Expr *) lfirst(lc); + + switch (boolexpr->boolop) + { + case AND_EXPR: + Assert(list_length(boolexpr->args) >= 2); + + if (off == 0) + scratch.opcode = EEO_BOOL_AND_STEP_FIRST; + else if (off + 1 == nargs) + scratch.opcode = EEO_BOOL_AND_STEP_LAST; + else + scratch.opcode = EEO_BOOL_AND_STEP; + break; + case OR_EXPR: + Assert(list_length(boolexpr->args) >= 2); + + if (off == 0) + scratch.opcode = EEO_BOOL_OR_STEP_FIRST; + else if (off + 1 == nargs) + scratch.opcode = EEO_BOOL_OR_STEP_LAST; + else + scratch.opcode = EEO_BOOL_OR_STEP; + break; + case NOT_EXPR: + Assert(list_length(boolexpr->args) == 1); + + scratch.opcode = EEO_BOOL_NOT_STEP; + break; + default: + elog(ERROR, "unrecognized boolop: %d", + (int) boolexpr->boolop); + break; + } + + ExecInitExprRec(arg, parent, state, + scratch.d.boolexpr.value, + scratch.d.boolexpr.isnull); + scratch.d.boolexpr.jumpdone = -1; + ExprEvalPushStep(state, &scratch); + adjust_bailout = lappend_int(adjust_bailout, + state->steps_len - 1); + off++; + } + + /* adjust early bail out jump target */ + foreach(lc, adjust_bailout) + { + 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 = EEO_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 = EEO_ALTERNATIVE_SUBPLAN; + scratch.d.alternative_subplan.asstate = asstate; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_FieldSelect: + { + FieldSelect *fselect = (FieldSelect *) node; + + + /* evaluate argument */ + ExecInitExprRec(fselect->arg, parent, state, resv, resnull); + + scratch.opcode = EEO_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; + ListCell *l1, + *l2; + Datum *values; + bool *nulls; + TupleDesc *descp; + + /* FIXME: properly size workspace */ + values = (Datum *) palloc(sizeof(Datum) * MaxTupleAttributeNumber); + nulls = (bool *) palloc(sizeof(bool) * MaxTupleAttributeNumber); + descp = (TupleDesc *) palloc(sizeof(TupleDesc)); + *descp = NULL; + + /* prepare argument evaluation */ + ExecInitExprRec(fstore->arg, parent, state, resv, resnull); + + /* first deform the input tuple */ + scratch.opcode = EEO_FIELDSTORE_DEFORM; + scratch.d.fieldstore.argdesc = descp; + scratch.d.fieldstore.fstore = fstore; + scratch.d.fieldstore.values = values; + scratch.d.fieldstore.nulls = nulls; + ExprEvalPushStep(state, &scratch); + + /* evaluate new values, one step for each arg */ + forboth(l1, fstore->newvals, l2, fstore->fieldnums) + { + Expr *e = (Expr *) lfirst(l1); + AttrNumber fieldnum = lfirst_int(l2); + Datum *save_innermost_caseval = NULL; + bool *save_innermost_casenull = NULL; + + /* + * 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.) + */ + 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; + } + + /* then form result tuple */ + scratch.opcode = EEO_FIELDSTORE_FORM; + scratch.d.fieldstore.fstore = fstore; + scratch.d.fieldstore.argdesc = descp; + scratch.d.fieldstore.values = values; + scratch.d.fieldstore.nulls = nulls; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_RelabelType: + { + /* at runtime relabel doesn't need to do anything */ + RelabelType *relabel = (RelabelType *) node; + + ExecInitExprRec(relabel->arg, parent, state, resv, resnull); + break; + } + + case T_CoerceViaIO: + { + CoerceViaIO *iocoerce = (CoerceViaIO *) node; + Oid iofunc; + bool typisvarlena; + + /* evaluate argument */ + ExecInitExprRec(iocoerce->arg, parent, state, resv, resnull); + + /* + * Compute coercion by preparing both output / input calls, to + * be evaluated inside a single evaluation step for speed - + * this can be a very common operation. + */ + scratch.opcode = EEO_IOCOERCE; + + /* lookup the input type's output function */ + scratch.d.iocoerce.finfo_out = palloc0(sizeof(*scratch.d.iocoerce.finfo_out)); + scratch.d.iocoerce.fcinfo_data_out = palloc0(sizeof(*scratch.d.iocoerce.fcinfo_data_out)); + + 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(*scratch.d.iocoerce.finfo_in)); + scratch.d.iocoerce.fcinfo_data_in = palloc0(sizeof(*scratch.d.iocoerce.fcinfo_data_in)); + + getTypeInputInfo(iocoerce->resulttype, &iofunc, + &scratch.d.iocoerce.intypioparam); + 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); + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; + + /* evaluate argument */ + ExecInitExprRec(acoerce->arg, parent, state, resv, resnull); + + scratch.opcode = EEO_ARRAYCOERCE; + scratch.d.arraycoerce.coerceexpr = acoerce; + scratch.d.arraycoerce.resultelemtype = + get_element_type(acoerce->resulttype); + if (scratch.d.arraycoerce.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(scratch.d.arraycoerce.resultelemtype) == + scratch.d.arraycoerce.resultelemtype); + scratch.d.arraycoerce.elemfunc = + (FmgrInfo *) palloc(sizeof(FmgrInfo)); + scratch.d.arraycoerce.elemfunc->fn_oid = + InvalidOid; /* not initialized */ + scratch.d.arraycoerce.amstate = + (ArrayMapState *) palloc0(sizeof(ArrayMapState)); + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_ConvertRowtypeExpr: + { + ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; + + /* evaluate argument */ + ExecInitExprRec(convert->arg, parent, state, resv, resnull); + + /* and push conversion step */ + scratch.opcode = EEO_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; + ListCell *clause; + List *adjust_bailout = NIL; + ListCell *lc; + Datum *casevalue = palloc0(sizeof(Datum)); + bool *caseisnull = palloc0(sizeof(bool)); + Datum *save_innermost_caseval = NULL; + bool *save_innermost_casenull = NULL; + Datum *caseval = NULL; + bool *casenull = NULL; + + /* arg == NULL -> CASE WHEN foo */ + /* arg != NULL -> CASE foo WHEN blarg */ + if (caseExpr->arg != NULL) + { + caseval = palloc0(sizeof(Datum)); + casenull = palloc0(sizeof(bool)); + + ExecInitExprRec(caseExpr->arg, parent, state, + caseval, casenull); + } + + /* + * 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 caseValue + * 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. + */ + + + /* + * Prepare to evaluate each of the WHEN clauses in turn, as + * soon as one is true we return the corresponding result; and + * If none are true then we return the value of the default + * clause, or NULL if there is none. + */ + foreach(clause, caseExpr->args) + { + CaseWhen *when = (CaseWhen *) lfirst(clause); + int whenstep; + + /* evaluate condition */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_casenull = state->innermost_casenull; + state->innermost_caseval = caseval; + state->innermost_casenull = casenull; + + ExecInitExprRec(when->expr, parent, state, + casevalue, caseisnull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_casenull; + + scratch.opcode = EEO_CASE_WHEN_STEP; + scratch.d.casewhen.value = casevalue; + scratch.d.casewhen.isnull = caseisnull; + scratch.d.casewhen.jumpfalse = -1; /* computed later */ + ExprEvalPushStep(state, &scratch); + whenstep = state->steps_len - 1; + + /* evaluate result */ + ExecInitExprRec(when->result, parent, state, resv, resnull); + + scratch.opcode = EEO_CASE_THEN_STEP; + scratch.d.casewhen.value = casevalue; + scratch.d.casewhen.isnull = caseisnull; + scratch.d.casethen.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, &scratch); + + /* + * Don't know "address" of jump target yet, compute once + * the whole case expression is built. + */ + adjust_bailout = lappend_int(adjust_bailout, + state->steps_len - 1); + + /* adjust jump target for WHEN step, for the !match case */ + state->steps[whenstep].d.casewhen.jumpfalse = state->steps_len; + } + + if (caseExpr->defresult) + { + /* evaluate result, directly into result datum */ + ExecInitExprRec(caseExpr->defresult, parent, state, + resv, resnull); + } + else + { + /* statically return NULL */ + scratch.opcode = EEO_CONST; + scratch.d.constval.isnull = true; + scratch.d.constval.value = 0; + ExprEvalPushStep(state, &scratch); + } + + /* adjust early bail out jump target */ + foreach(lc, adjust_bailout) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->d.casethen.jumpdone == -1); + as->d.casethen.jumpdone = state->steps_len; + } + + break; + } + + case T_CaseTestExpr: + { + CaseTestExpr *casetestexpr = (CaseTestExpr *) node; + + scratch.d.casetest.value = state->innermost_caseval; + scratch.d.casetest.isnull = state->innermost_casenull; + + /* only check for extended datums if possible */ + if (get_typlen(casetestexpr->typeId) == -1) + scratch.opcode = EEO_CASE_TESTVAL_UNEXPAND; + else + scratch.opcode = EEO_CASE_TESTVAL; + + 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. + */ + scratch.opcode = EEO_ARRAYEXPR; + scratch.d.arrayexpr.arrayexpr = arrayexpr; + scratch.d.arrayexpr.nelems = nelems; + scratch.d.arrayexpr.elemvalues = + (Datum *) palloc(sizeof(Datum) * nelems); + scratch.d.arrayexpr.elemnulls = + (bool *) palloc(sizeof(bool) * nelems); + + /* 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 to collect 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; + + /* + * Evaluate by first building datums for each field, and then + * a final step forming the composite datum. + */ + scratch.opcode = EEO_ROW; + + /* space for the individual field datums */ + scratch.d.row.elemvalues = + (Datum *) palloc(sizeof(Datum) * nelems); + scratch.d.row.elemnulls = + (bool *) palloc(sizeof(bool) * nelems); + + /* 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); + } + + scratch.d.row.tupdesc = tupdesc; + + /* 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); + + /* 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); + } + + ExecInitExprRec(e, parent, state, + &scratch.d.row.elemvalues[i], + &scratch.d.row.elemnulls[i]); + i++; + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + int nopers = list_length(rcexpr->opnos); + List *adjust_bailout = 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 + * expression yields a != 0 result, jump to the final step. + */ + Assert(list_length(rcexpr->largs) == nopers); + Assert(list_length(rcexpr->rargs) == 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 expression 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 = EEO_ROWCOMPARE_STEP; + + /* jump targets computed later */ + scratch.d.rowcompare_step.jumpnull = -1; + scratch.d.rowcompare_step.jumpdone = -1; + + scratch.d.rowcompare_step.finfo = finfo; + scratch.d.rowcompare_step.fcinfo_data = fcinfo; + scratch.d.rowcompare_step.fn_addr = fcinfo->flinfo->fn_addr; + + ExprEvalPushStep(state, &scratch); + adjust_bailout = lappend_int(adjust_bailout, + state->steps_len - 1); + } + + /* and then compare the last result */ + scratch.opcode = EEO_ROWCOMPARE_FINAL; + scratch.d.rowcompare_final.rctype = rcexpr->rctype; + ExprEvalPushStep(state, &scratch); + + /* adjust early bail out jump targets */ + foreach(lc, adjust_bailout) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + 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_bailout = NIL; + ListCell *lc; + + Assert(list_length(coalesce->args) > 0); + + /* + * Prepare evaluation of all coalesced arguments, after each + * push a step that short-circuits if not null. + */ + foreach(lc, coalesce->args) + { + Expr *e = (Expr *) lfirst(lc); + + /* evaluate result, directly into result datum */ + ExecInitExprRec(e, parent, state, resv, resnull); + + /* then push step checking for NULLs */ + scratch.opcode = EEO_COALESCE; + scratch.d.coalesce.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, &scratch); + + adjust_bailout = lappend_int(adjust_bailout, + 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 early bail out jump target */ + foreach(lc, adjust_bailout) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->d.coalesce.jumpdone == -1); + as->d.coalesce.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. + */ + 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 = EEO_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 = EEO_SQLVALUEFUNCTION; + scratch.d.sqlvaluefunction.svf = svf; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + ListCell *arg; + int nnamed = list_length(xexpr->named_args); + int nargs = list_length(xexpr->args); + int off; + + scratch.opcode = EEO_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 = EEO_NULLTEST_ROWISNULL; + else + scratch.opcode = EEO_NULLTEST_ISNULL; + } + else if (ntest->nulltesttype == IS_NOT_NULL) + { + if (ntest->argisrow) + scratch.opcode = EEO_NULLTEST_ROWISNOTNULL; + else + scratch.opcode = EEO_NULLTEST_ISNOTNULL; + } + else + { + elog(ERROR, "unrecognized nulltesttype: %d", + (int) ntest->nulltesttype); + } + + /* first evaluate argument */ + 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 a bit + * debatable, because the types will be different, but it's + * efficient... The evaluation step will then store an actual + * boolean. + */ + ExecInitExprRec(btest->arg, parent, state, resv, resnull); + + switch (btest->booltesttype) + { + case IS_TRUE: + scratch.opcode = EEO_BOOLTEST_IS_TRUE; + break; + case IS_NOT_TRUE: + scratch.opcode = EEO_BOOLTEST_IS_NOT_TRUE; + break; + case IS_FALSE: + scratch.opcode = EEO_BOOLTEST_IS_FALSE; + break; + case IS_NOT_FALSE: + scratch.opcode = EEO_BOOLTEST_IS_NOT_FALSE; + break; + case IS_UNKNOWN: + scratch.opcode = EEO_BOOLTEST_IS_UNKNOWN; + break; + case IS_NOT_UNKNOWN: + scratch.opcode = EEO_BOOLTEST_IS_NOT_UNKNOWN; + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) btest->booltesttype); + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_CoerceToDomain: + { + CoerceToDomain *ctest = (CoerceToDomain *) node; + Datum *save_innermost_domainval = NULL; + bool *save_innermost_domainnull = NULL; + DomainConstraintRef *constraint_ref = + palloc(sizeof(DomainConstraintRef)); + ListCell *l; + + scratch.d.domaincheck.resulttype = ctest->resulttype; + scratch.d.domaincheck.checkvalue = (Datum *) palloc(sizeof(Datum)); + scratch.d.domaincheck.checknull = (bool *) palloc(sizeof(bool)); + + /* evaluate argument */ + ExecInitExprRec(ctest->arg, parent, state, resv, resnull); + + /* + * XXX: In contrast to the old implementation we're evaluating + * the set of to-be-checked constraints at query start - that + * seems perfectly sensible to me. But perhaps there's a + * reason the previous implementation did what it did? ISTM + * that was just a side-effect of using the typecache (which + * is longer lived than a single query). + */ + + /* Make sure we have up-to-date constraints */ + InitDomainConstraintRef(ctest->resulttype, + constraint_ref, + CurrentMemoryContext); + UpdateDomainConstraintRef(constraint_ref); + + /* + * 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 = resv; + state->innermost_domainnull = resnull; + + 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 = EEO_DOMAIN_NOTNULL; + ExprEvalPushStep(state, &scratch); + break; + case DOM_CONSTRAINT_CHECK: + /* evaluate check expression value */ + ExecInitExprRec(con->check_expr, parent, state, + scratch.d.domaincheck.checkvalue, + scratch.d.domaincheck.checknull); + + /* and then check result value */ + scratch.opcode = EEO_DOMAIN_CHECK; + ExprEvalPushStep(state, &scratch); + break; + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) con->constrainttype); + break; + } + } + + state->innermost_domainval = save_innermost_domainval; + state->innermost_domainnull = save_innermost_domainnull; + + break; + } + + case T_CoerceToDomainValue: + { + CoerceToDomainValue *domainval = (CoerceToDomainValue *) node; + + /* share datastructure with case testval */ + scratch.d.casetest.value = state->innermost_domainval; + scratch.d.casetest.isnull = state->innermost_domainnull; + + if (get_typlen(domainval->typeId) == -1) + scratch.opcode = EEO_DOMAIN_TESTVAL_UNEXPAND; + else + scratch.opcode = EEO_DOMAIN_TESTVAL; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_CurrentOfExpr: + { + scratch.opcode = EEO_CURRENTOFEXPR; + ExprEvalPushStep(state, &scratch); + break; + } + + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(node)); + break; + } +} + +static void +ExprEvalPushStep(ExprState *es, 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)); +} + +static void +ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, + Oid inputcollid, PlanState *parent, ExprState *state, + Datum *resv, bool *resnull) +{ + ListCell *lc; + AclResult aclresult; + int nargs = list_length(args); + FunctionCallInfo fcinfo; + int argno; + + /* 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))); + + /* Set up the primary fmgr lookup information */ + scratch->d.func.finfo = palloc0(sizeof(*scratch->d.func.finfo)); + scratch->d.func.fcinfo_data = palloc0(sizeof(*scratch->d.func.fcinfo_data)); + + fcinfo = scratch->d.func.fcinfo_data; + fmgr_info(funcid, scratch->d.func.finfo); + fmgr_info_set_expr((Node *) node, scratch->d.func.finfo); + InitFunctionCallInfoData(*fcinfo, scratch->d.func.finfo, + nargs, inputcollid, NULL, NULL); + scratch->d.func.fn_addr = scratch->d.func.fcinfo_data->flinfo->fn_addr; + if (scratch->d.func.finfo->fn_retset) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + } + + 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++; + } + + scratch->d.func.nargs = nargs; + + if (pgstat_track_functions <= scratch->d.func.finfo->fn_stats) + { + if (scratch->d.func.finfo->fn_strict && nargs > 0) + scratch->opcode = EEO_FUNCEXPR_STRICT; + else + scratch->opcode = EEO_FUNCEXPR; + } + else + { + if (scratch->d.func.finfo->fn_strict && nargs > 0) + scratch->opcode = EEO_FUNCEXPR_STRICT_FUSAGE; + else + scratch->opcode = EEO_FUNCEXPR_FUSAGE; + } +} + +static void +ExecInitExprSlots(ExprState *state, Node *node) +{ + ExprEvalStep scratch; + int last_outer = -1; + int last_inner = -1; + int last_scan = -1; + + /* + * Figure out which attributes we're going to need. + */ + ExecGetLastAttnums((Node *) node, + &last_outer, + &last_inner, + &last_scan); + if (last_inner > 0) + { + scratch.opcode = EEO_INNER_FETCHSOME; + scratch.d.fetch.last_var = last_inner; + ExprEvalPushStep(state, &scratch); + } + if (last_outer > 0) + { + scratch.opcode = EEO_OUTER_FETCHSOME; + scratch.d.fetch.last_var = last_outer; + ExprEvalPushStep(state, &scratch); + } + if (last_scan > 0) + { + scratch.opcode = EEO_SCAN_FETCHSOME; + scratch.d.fetch.last_var = last_scan; + ExprEvalPushStep(state, &scratch); + } +} + +static void +ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent, ExprState *state, Datum *resv, bool *resnull) +{ + scratch->d.wholerow.tupdesc = NULL; + scratch->d.wholerow.junkFilter = NULL; + scratch->d.wholerow.var = variable; + scratch->d.wholerow.first = true; + + /* + * 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 (junk_filter_needed) + { + /* If so, build the junkfilter in the query memory context */ + scratch->d.wholerow.junkFilter = + ExecInitJunkFilter(subplan->plan->targetlist, + ExecGetResultType(subplan)->tdhasoid, + ExecInitExtraTupleSlot(parent->state)); + } + } + } +} + +static void +ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent, + ExprState *state, Datum *resv, bool *resnull) +{ + List *adjust_bailout = NIL; + ListCell *lc; + bool isAssignment = (aref->refassgnexpr != NULL); + ArrayRefState *arefstate = palloc(sizeof(ArrayRefState)); + int i; + + 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->refupperindexpr), MAXDIM))); + + if (list_length(aref->reflowerindexpr) > 0 && + list_length(aref->refupperindexpr) != list_length(aref->reflowerindexpr)) + elog(ERROR, "upper and lower index lists are not same length"); + + arefstate->isassignment = isAssignment; + arefstate->refattrlength = get_typlen(aref->refarraytype); + arefstate->refelemtype = aref->refelemtype; + get_typlenbyvalalign(aref->refelemtype, + &arefstate->refelemlength, + &arefstate->refelembyval, + &arefstate->refelemalign); + + /* evaluate input */ + ExecInitExprRec(aref->refexpr, parent, state, + resv, resnull); + + /* + * If refexpr yields NULL, and it's a fetch, then result is NULL. In the + * assignment case, we'll create an empty input. + */ + if (!isAssignment) + { + scratch->opcode = EEO_ARRAYREF_CHECKINPUT; + scratch->d.arrayref.state = arefstate; + scratch->d.arrayref.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, scratch); + adjust_bailout = lappend_int(adjust_bailout, + state->steps_len - 1); + } + + /* evaluate upper subscripts */ + i = 0; + foreach(lc, aref->refupperindexpr) + { + Expr *e = (Expr *) lfirst(lc); + + if (!e) + { + arefstate->upperprovided[i] = false; + i++; + continue; + } + + arefstate->upperprovided[i] = true; + + ExecInitExprRec(e, parent, state, + &arefstate->upper[i], &arefstate->uppernull[i]); + + scratch->opcode = EEO_ARRAYREF_CHECKSUBSCRIPT; + scratch->d.arrayref_checksubscript.state = arefstate; + scratch->d.arrayref_checksubscript.off = i; + scratch->d.arrayref_checksubscript.isupper = true; + scratch->d.arrayref_checksubscript.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, scratch); + adjust_bailout = lappend_int(adjust_bailout, + state->steps_len - 1); + i++; + } + arefstate->numupper = i; + + /* evaluate lower subscripts */ + i = 0; + foreach(lc, aref->reflowerindexpr) + { + Expr *e = (Expr *) lfirst(lc); + + if (!e) + { + arefstate->lowerprovided[i] = false; + i++; + continue; + } + + arefstate->lowerprovided[i] = true; + + ExecInitExprRec(e, parent, state, + &arefstate->lower[i], &arefstate->lowernull[i]); + + scratch->opcode = EEO_ARRAYREF_CHECKSUBSCRIPT; + scratch->d.arrayref_checksubscript.state = arefstate; + scratch->d.arrayref_checksubscript.off = i; + scratch->d.arrayref_checksubscript.isupper = false; + scratch->d.arrayref_checksubscript.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, scratch); + adjust_bailout = lappend_int(adjust_bailout, + state->steps_len - 1); + i++; + } + arefstate->numlower = i; + + if (isAssignment) + { + Datum *save_innermost_caseval = NULL; + bool *save_innermost_casenull = NULL; + + /* + * 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. + */ + if (isAssignmentIndirectionExpr(aref->refassgnexpr)) + { + scratch->opcode = EEO_ARRAYREF_OLD; + scratch->d.arrayref.state = arefstate; + ExprEvalPushStep(state, scratch); + } + + 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 */ + ExecInitExprRec(aref->refassgnexpr, parent, state, + &arefstate->replacevalue, &arefstate->replacenull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_casenull; + + scratch->opcode = EEO_ARRAYREF_ASSIGN; + scratch->d.arrayref.state = arefstate; + ExprEvalPushStep(state, scratch); + } + else + { + scratch->opcode = EEO_ARRAYREF_FETCH; + scratch->d.arrayref.state = arefstate; + ExprEvalPushStep(state, scratch); + } + + /* adjust early bail out jump target */ + foreach(lc, adjust_bailout) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + if (as->opcode == EEO_ARRAYREF_CHECKSUBSCRIPT) + { + Assert(as->d.arrayref_checksubscript.jumpdone == -1); + as->d.arrayref_checksubscript.jumpdone = state->steps_len; + } + else + { + Assert(as->d.arrayref.jumpdone == -1); + as->d.arrayref.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; +} 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/execInterpExpr.c b/src/backend/executor/execInterpExpr.c new file mode 100644 index 0000000000..21388f4086 --- /dev/null +++ b/src/backend/executor/execInterpExpr.c @@ -0,0 +1,3300 @@ +/*------------------------------------------------------------------------- + * + * execInterpExpr.c + * Opcode based expression evaluation. + * + * This file provides a "switch threaded" (all compilers) and "direct + * threaded" (gcc, clang and compatible) implementation of expression + * evaluation. The former is among st the fastest known methods of + * interpreting programs without resorting to assembly level work, or + * just-in-time compilation, but requires support for computed gotos. The + * latter is amongst the fastest approaches doable in standard C. + * + * Both work by using ExprEvalStep->opcode to dispatch into code blocks + * implementing the specific method. Switch based uses a plain switch() + * statement to perform the dispatch. This has the advantages of being plain C + * and allowing 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, causing bad branch + * prediction. Direct dispatch uses the label-as-values gcc extension - also + * adopted by some other compilers - to replace ExprEvalStep-> opcode with the + * address of the block implementing the instruction. This allows for better + * branch prediction (the jumps are happening from different locations) and + * fewer jumps (no jumps to common dispatch location needed). + * + * If computed gotos are available, ExecInstantiateInterpretedExpr will + * replace ->opcode with the address of relevant code-block and + * ExprState->flags will contain EEO_FLAG_JUMP_THREADED to "remember" that + * fact. + * + * For very simple instructions the overhead of the full interpreter + * "startup", as minimal as it is, is noticeable. Therefore + * ExecInstantiateInterpretedExpr() will choose to implement simple scalar Var + * and Const expressions using special cased routines (ExecJust*). + * Benchmarking shows anything more complex than those will benefit from the + * "full interpreter". + * + * Complex or uncommon instructions are not implemented in ExecInterpExpr. For + * one, there'd not be a noticeable performance benefit, but more importantly + * those complex routines are intended to be shared between different + * expression evaluation approaches. + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/executor/execInterprExpr.c + * + *------------------------------------------------------------------------- + */ +#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 "executor/execExpr.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 allow to disable locally in this file for development. + */ +#ifdef HAVE__COMPUTED_GOTO +#define EEO_USE_COMPUTED_GOTO +#endif /* HAVE__COMPUTED_GOTO */ + +/* + * Macros for opcode dispatch. + */ +#if defined(EEO_USE_COMPUTED_GOTO) +static void **dispatch_table = NULL; +#define EEO_SWITCH(d) +#define EEO_DISPATCH_DIRECT(op) goto *((void *) op->opcode) +#define EEO_CASE(name) CASE_##name: +#else +#define EEO_SWITCH(d) switch ((ExprEvalOp) d) +#define EEO_DISPATCH_DIRECT(op) goto starteval +#define EEO_CASE(name) case name: +#endif /* EEO_USE_COMPUTED_GOTO */ + +#define EEO_DISPATCH(op) \ + do \ + { \ + op++; \ + EEO_DISPATCH_DIRECT(op); \ + } \ + while (0) + + +static Datum ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull); +static void ExecPrepareInterp(void); + +/* support functions */ +static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, bool checkisnull); +static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, + TupleDesc *cache_field, ExprContext *econtext); +static void ShutdownTupleDescRef(Datum arg); + +/* fast-path evaluation functions */ +static Datum ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustInnerVar(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 ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull); + + +/* + * Prepare ExprState for interpreted execution. + */ +void +ExecInstantiateInterpretedExpr(ExprState *state) +{ + ExecPrepareInterp(); + + Assert(state->steps_len >= 1); + Assert(ExecEvalStepOp(state, &state->steps[state->steps_len - 1]) == EEO_DONE); + + /* + * Fast-path 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 anyways. + */ + if (state->steps_len == 3) + { + ExprEvalOp step0 = ExecEvalStepOp(state, &state->steps[0]); + ExprEvalOp step1 = ExecEvalStepOp(state, &state->steps[1]); + + if (step0 == EEO_INNER_FETCHSOME && step1 == EEO_INNER_VAR) + { + state->evalfunc = ExecJustInnerVar; + return; + } + else if (step0 == EEO_OUTER_FETCHSOME && step1 == EEO_OUTER_VAR) + { + state->evalfunc = ExecJustOuterVar; + return; + } + else if (step0 == EEO_SCAN_FETCHSOME && step1 == EEO_SCAN_VAR) + { + state->evalfunc = ExecJustScanVar; + return; + } + else if (step0 == EEO_INNER_FETCHSOME && step1 == EEO_ASSIGN_INNER_VAR) + { + state->evalfunc = ExecJustAssignInnerVar; + return; + } + else if (step0 == EEO_OUTER_FETCHSOME && step1 == EEO_ASSIGN_OUTER_VAR) + { + state->evalfunc = ExecJustAssignOuterVar; + return; + } + else if (step0 == EEO_SCAN_FETCHSOME && step1 == EEO_ASSIGN_SCAN_VAR) + { + state->evalfunc = ExecJustAssignScanVar; + return; + } + } + else if (state->steps_len == 2 && + ExecEvalStepOp(state, &state->steps[0]) == EEO_CONST) + { + state->evalfunc = ExecJustConst; + return; + } + +#if defined(EEO_USE_COMPUTED_GOTO) + + /* + * In the jump-threaded implementation, replace opcode with the address to + * jump to. To reverse, use ExecEvalStepOp(). + */ + { + int off; + + for (off = 0; off < state->steps_len; off++) + { + ExprEvalStep *op = &state->steps[off]; + + op->opcode = (size_t) dispatch_table[op->opcode]; + } + + state->flags |= EEO_FLAG_JUMP_THREADED; + } +#endif /* defined(EEO_USE_COMPUTED_GOTO) */ + + state->evalfunc = ExecInterpExpr; +} + + +static Datum +ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op; + const TupleTableSlot *resultslot; + TupleTableSlot *innerslot; + TupleTableSlot *outerslot; + TupleTableSlot *scanslot; + + /* + * This array has to be in the same order as ExprEvalOp. + */ +#if defined(EEO_USE_COMPUTED_GOTO) + static const void **dispatch_table[] = { + &&CASE_EEO_DONE, + &&CASE_EEO_INNER_FETCHSOME, + &&CASE_EEO_OUTER_FETCHSOME, + &&CASE_EEO_SCAN_FETCHSOME, + &&CASE_EEO_INNER_VAR, + &&CASE_EEO_OUTER_VAR, + &&CASE_EEO_SCAN_VAR, + &&CASE_EEO_ASSIGN_INNER_VAR, + &&CASE_EEO_ASSIGN_OUTER_VAR, + &&CASE_EEO_ASSIGN_SCAN_VAR, + &&CASE_EEO_ASSIGN_TMP, + &&CASE_EEO_ASSIGN_TMP_UNEXPAND, + &&CASE_EEO_INNER_SYSVAR, + &&CASE_EEO_OUTER_SYSVAR, + &&CASE_EEO_SCAN_SYSVAR, + &&CASE_EEO_CONST, + &&CASE_EEO_FUNCEXPR, + &&CASE_EEO_FUNCEXPR_STRICT, + &&CASE_EEO_FUNCEXPR_FUSAGE, + &&CASE_EEO_FUNCEXPR_STRICT_FUSAGE, + &&CASE_EEO_BOOL_AND_STEP_FIRST, + &&CASE_EEO_BOOL_AND_STEP, + &&CASE_EEO_BOOL_AND_STEP_LAST, + &&CASE_EEO_BOOL_OR_STEP_FIRST, + &&CASE_EEO_BOOL_OR_STEP, + &&CASE_EEO_BOOL_OR_STEP_LAST, + &&CASE_EEO_BOOL_NOT_STEP, + &&CASE_EEO_QUAL, + &&CASE_EEO_NULLTEST_ISNULL, + &&CASE_EEO_NULLTEST_ISNOTNULL, + &&CASE_EEO_NULLTEST_ROWISNULL, + &&CASE_EEO_NULLTEST_ROWISNOTNULL, + &&CASE_EEO_PARAM_EXEC, + &&CASE_EEO_PARAM_EXTERN, + &&CASE_EEO_CASE_WHEN_STEP, + &&CASE_EEO_CASE_THEN_STEP, + &&CASE_EEO_CASE_TESTVAL, + &&CASE_EEO_CASE_TESTVAL_UNEXPAND, + &&CASE_EEO_COALESCE, + &&CASE_EEO_BOOLTEST_IS_TRUE, + &&CASE_EEO_BOOLTEST_IS_NOT_TRUE, + &&CASE_EEO_BOOLTEST_IS_FALSE, + &&CASE_EEO_BOOLTEST_IS_NOT_FALSE, + &&CASE_EEO_BOOLTEST_IS_UNKNOWN, + &&CASE_EEO_BOOLTEST_IS_NOT_UNKNOWN, + &&CASE_EEO_WHOLEROW, + &&CASE_EEO_IOCOERCE, + &&CASE_EEO_DISTINCT, + &&CASE_EEO_NULLIF, + &&CASE_EEO_SQLVALUEFUNCTION, + &&CASE_EEO_CURRENTOFEXPR, + &&CASE_EEO_ARRAYEXPR, + &&CASE_EEO_ARRAYCOERCE, + &&CASE_EEO_ROW, + &&CASE_EEO_ROWCOMPARE_STEP, + &&CASE_EEO_ROWCOMPARE_FINAL, + &&CASE_EEO_MINMAX, + &&CASE_EEO_FIELDSELECT, + &&CASE_EEO_FIELDSTORE_DEFORM, + &&CASE_EEO_FIELDSTORE_FORM, + &&CASE_EEO_ARRAYREF_CHECKINPUT, + &&CASE_EEO_ARRAYREF_CHECKSUBSCRIPT, + &&CASE_EEO_ARRAYREF_OLD, + &&CASE_EEO_ARRAYREF_ASSIGN, + &&CASE_EEO_ARRAYREF_FETCH, + &&CASE_EEO_CONVERT_ROWTYPE, + &&CASE_EEO_SCALARARRAYOP, + &&CASE_EEO_DOMAIN_TESTVAL, + &&CASE_EEO_DOMAIN_TESTVAL_UNEXPAND, + &&CASE_EEO_DOMAIN_NOTNULL, + &&CASE_EEO_DOMAIN_CHECK, + &&CASE_EEO_XMLEXPR, + &&CASE_EEO_AGGREF, + &&CASE_EEO_GROUPING_FUNC, + &&CASE_EEO_WINDOW_FUNC, + &&CASE_EEO_SUBPLAN, + &&CASE_EEO_ALTERNATIVE_SUBPLAN, + &&CASE_EEO_LAST + }; + + StaticAssertStmt(EEO_LAST + 1 == lengthof(dispatch_table), + "dispatch_table out of whack with ExprEvalOp"); +#endif + +#ifdef EEO_USE_COMPUTED_GOTO + if (unlikely(state == NULL)) + { + return PointerGetDatum(dispatch_table); + } +#endif + + /* 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_DIRECT(op); +#else +starteval: +#endif + EEO_SWITCH(op->opcode) + { + EEO_CASE(EEO_DONE) + { + goto out; + } + + EEO_CASE(EEO_INNER_FETCHSOME) + { + /* XXX: worthwhile to check tts_nvalid inline first? */ + slot_getsomeattrs(innerslot, op->d.fetch.last_var); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_OUTER_FETCHSOME) + { + slot_getsomeattrs(outerslot, op->d.fetch.last_var); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_SCAN_FETCHSOME) + { + slot_getsomeattrs(scanslot, op->d.fetch.last_var); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_INNER_VAR) + { + int attnum = op->d.var.attnum; + + /* + * Can't assert tts_nvalid, as wholerow var evaluation or such + * could have materialized the slot - but the contents are still + * valid :/ + */ + Assert(op->d.var.attnum >= 0); + *op->resnull = innerslot->tts_isnull[attnum]; + *op->resvalue = innerslot->tts_values[attnum]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_OUTER_VAR) + { + int attnum = op->d.var.attnum; + + /* See EEO_INNER_VAR comments */ + Assert(op->d.var.attnum >= 0); + *op->resnull = outerslot->tts_isnull[attnum]; + *op->resvalue = outerslot->tts_values[attnum]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_SCAN_VAR) + { + int attnum = op->d.var.attnum; + + /* See EEO_INNER_VAR comments */ + Assert(op->d.var.attnum >= 0); + *op->resnull = scanslot->tts_isnull[attnum]; + *op->resvalue = scanslot->tts_values[attnum]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ASSIGN_INNER_VAR) + { + size_t resultnum = op->d.assign_var.resultnum; + size_t attnum = op->d.assign_var.attnum; + + resultslot->tts_values[resultnum] = innerslot->tts_values[attnum]; + resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ASSIGN_OUTER_VAR) + { + size_t resultnum = op->d.assign_var.resultnum; + size_t attnum = op->d.assign_var.attnum; + + resultslot->tts_values[resultnum] = outerslot->tts_values[attnum]; + resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ASSIGN_SCAN_VAR) + { + size_t resultnum = op->d.assign_var.resultnum; + size_t attnum = op->d.assign_var.attnum; + + resultslot->tts_values[resultnum] = scanslot->tts_values[attnum]; + resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ASSIGN_TMP) + { + size_t resultnum = op->d.assign_tmp.resultnum; + + resultslot->tts_values[resultnum] = state->resvalue; + resultslot->tts_isnull[resultnum] = state->resnull; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ASSIGN_TMP_UNEXPAND) + { + size_t 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_DISPATCH(op); + } + + EEO_CASE(EEO_INNER_SYSVAR) + { + int attnum = op->d.var.attnum; + + Assert(op->d.var.attnum < 0); + *op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum, + innerslot->tts_tupleDescriptor, + op->resnull); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_OUTER_SYSVAR) + { + int attnum = op->d.var.attnum; + + Assert(op->d.var.attnum < 0); + *op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum, + scanslot->tts_tupleDescriptor, + op->resnull); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_SCAN_SYSVAR) + { + int attnum = op->d.var.attnum; + + Assert(op->d.var.attnum < 0); + *op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum, + scanslot->tts_tupleDescriptor, + op->resnull); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_CONST) + { + *op->resnull = op->d.constval.isnull; + *op->resvalue = op->d.constval.value; + + EEO_DISPATCH(op); + } + + /* + * 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!), separate all of them into separate opcodes. + */ + EEO_CASE(EEO_FUNCEXPR) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + + fcinfo->isnull = false; + *op->resvalue = (op->d.func.fn_addr) (fcinfo); + *op->resnull = fcinfo->isnull; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_FUNCEXPR_STRICT) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + int argno; + bool *argnull = fcinfo->argnull; + + /* strict function, 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_DISPATCH(op); + } + + EEO_CASE(EEO_FUNCEXPR_FUSAGE) + { + PgStat_FunctionCallUsage fcusage; + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + + 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_DISPATCH(op); + } + + EEO_CASE(EEO_FUNCEXPR_STRICT_FUSAGE) + { + PgStat_FunctionCallUsage fcusage; + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + int argno; + bool *argnull = fcinfo->argnull; + + /* strict function, 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; + strictfail_fusage: + pgstat_end_function_usage(&fcusage, true); + + EEO_DISPATCH(op); + } + + /* + * 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. + */ + EEO_CASE(EEO_BOOL_AND_STEP_FIRST) + { + *op->d.boolexpr.anynull = false; + + /* + * Fallthrough (can't be last - ANDs have two arguments at least). + */ + } + + EEO_CASE(EEO_BOOL_AND_STEP) + { + if (*op->d.boolexpr.isnull) + { + *op->d.boolexpr.anynull = true; + } + else if (!DatumGetBool(*op->d.boolexpr.value)) + { + *op->resnull = false; + *op->resvalue = BoolGetDatum(false); + /* bail out early */ + op = &state->steps[op->d.boolexpr.jumpdone]; + + EEO_DISPATCH_DIRECT(op); + } + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_BOOL_AND_STEP_LAST) + { + if (*op->d.boolexpr.isnull) + { + *op->resnull = true; + *op->resvalue = 0; + } + else if (!DatumGetBool(*op->d.boolexpr.value)) + { + *op->resnull = false; + *op->resvalue = BoolGetDatum(false); + + /* + * 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->resnull = true; + *op->resvalue = 0; + } + else + { + *op->resnull = false; + *op->resvalue = BoolGetDatum(true); + } + + EEO_DISPATCH(op); + } + + /* + * 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. + */ + EEO_CASE(EEO_BOOL_OR_STEP_FIRST) + { + *op->d.boolexpr.anynull = false; + + /* + * Fallthrough (can't be last - ORs have two arguments at least). + */ + } + + EEO_CASE(EEO_BOOL_OR_STEP) + { + if (*op->d.boolexpr.isnull) + { + *op->d.boolexpr.anynull = true; + } + else if (DatumGetBool(*op->d.boolexpr.value)) + { + *op->resnull = false; + *op->resvalue = BoolGetDatum(true); + + /* bail out early */ + op = &state->steps[op->d.boolexpr.jumpdone]; + + EEO_DISPATCH_DIRECT(op); + } + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_BOOL_OR_STEP_LAST) + { + if (*op->d.boolexpr.isnull) + { + *op->resnull = true; + *op->resvalue = 0; + } + else if (DatumGetBool(*op->d.boolexpr.value)) + { + *op->resnull = false; + *op->resvalue = BoolGetDatum(true); + + /* + * 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->resnull = true; + *op->resvalue = 0; + } + else + { + *op->resnull = false; + *op->resvalue = BoolGetDatum(false); + } + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_BOOL_NOT_STEP) + { + /* + * If the expression evaluates to null, then just return null + * back. + */ + *op->resnull = *op->d.boolexpr.isnull; + + /* + * evaluation of 'not' is simple.. expr is false, then return + * 'true' and vice versa. + */ + *op->resvalue = BoolGetDatum(!DatumGetBool(*op->d.boolexpr.value)); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_QUAL) + { + /* special case for ExecQual() */ + + if (*op->resnull || + !DatumGetBool(*op->resvalue)) + { + /* bail out early */ + *op->resnull = false; + *op->resvalue = BoolGetDatum(false); + op = &state->steps[op->d.qualexpr.jumpdone]; + + EEO_DISPATCH_DIRECT(op); + } + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_NULLTEST_ISNULL) + { + *op->resvalue = BoolGetDatum(*op->resnull); + *op->resnull = false; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_NULLTEST_ISNOTNULL) + { + *op->resvalue = BoolGetDatum(!*op->resnull); + *op->resnull = false; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_NULLTEST_ROWISNULL) + { + /* out of line implementation: too large */ + ExecEvalRowNull(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_NULLTEST_ROWISNOTNULL) + { + /* out of line implementation: too large */ + ExecEvalRowNotNull(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_PARAM_EXEC) + { + /* out of line implementation: too large */ + ExecEvalParamExec(state, op, econtext); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_PARAM_EXTERN) + { + /* out of line implementation: too large */ + ExecEvalParamExtern(state, op, econtext); + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_CASE_WHEN_STEP) + { + if (!*op->d.casewhen.isnull + && DatumGetBool(*op->d.casewhen.value)) + { + EEO_DISPATCH(op); + } + else + { + op = &state->steps[op->d.casewhen.jumpfalse]; + + EEO_DISPATCH_DIRECT(op); + } + } + + EEO_CASE(EEO_CASE_THEN_STEP) + { + /* results already placed in correct place during preceding steps */ + op = &state->steps[op->d.casethen.jumpdone]; + + EEO_DISPATCH_DIRECT(op); + } + + EEO_CASE(EEO_CASE_TESTVAL) + { + /* + * Normally upper parts in 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 enoiugh to + * worry about a 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_DISPATCH(op); + } + + EEO_CASE(EEO_CASE_TESTVAL_UNEXPAND) + { + /* + * See EEO_CASE_TESTVAL comment. + */ + 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; + } + + /* Since caseValue_datum may be read multiple times, force to R/O */ + if (!*op->resnull) + { + *op->resvalue = + MakeExpandedObjectReadOnlyInternal(*op->resvalue); + } + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_DOMAIN_TESTVAL) + { + /* + * See EEO_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_DISPATCH(op); + } + + EEO_CASE(EEO_DOMAIN_TESTVAL_UNEXPAND) + { + /* + * See EEO_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; + } + + /* + * Since domainValue_datum may be read multiple times, force to + * R/O + */ + if (!*op->resnull) + { + *op->resvalue = + MakeExpandedObjectReadOnlyInternal(*op->resvalue); + } + + EEO_DISPATCH(op); + } + + /* + * Evaluate a single argument to COALESCE and jump-out if a NOT NULL + * one has been found. + */ + EEO_CASE(EEO_COALESCE) + { + if (!*op->resnull) + { + op = &state->steps[op->d.coalesce.jumpdone]; + + EEO_DISPATCH_DIRECT(op); + } + + EEO_DISPATCH(op); + } + + /* BooleanTest implementations for all booltesttypes */ + EEO_CASE(EEO_BOOLTEST_IS_TRUE) + { + if (*op->resnull) + *op->resvalue = false; + else + *op->resvalue = *op->resvalue; + *op->resnull = false; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_BOOLTEST_IS_NOT_FALSE) + { + if (*op->resnull) + *op->resvalue = true; + else + *op->resvalue = *op->resvalue; + *op->resnull = false; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_BOOLTEST_IS_FALSE) + { + if (*op->resnull) + *op->resvalue = false; + else + *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue)); + *op->resnull = false; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_BOOLTEST_IS_NOT_TRUE) + { + if (*op->resnull) + *op->resvalue = true; + else + *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue)); + *op->resnull = false; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_BOOLTEST_IS_UNKNOWN) + { + *op->resvalue = BoolGetDatum(*op->resnull); + *op->resnull = false; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_BOOLTEST_IS_NOT_UNKNOWN) + { + *op->resvalue = BoolGetDatum(!*op->resnull); + *op->resnull = false; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_WHOLEROW) + { + /* too complex for an inline implementation */ + ExecEvalWholeRowVar(state, op, econtext); + + EEO_DISPATCH(op); + } + + /* + * Evaluate a CoerceViaIO node. This can be quite a hot path, so + * inline as much work as possible. + */ + EEO_CASE(EEO_IOCOERCE) + { + 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; + + str = DatumGetPointer(FunctionCallInvoke(fcinfo_out)); + 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] = BoolGetDatum(*op->resnull); + fcinfo_in->arg[1] = op->d.iocoerce.intypioparam; + fcinfo_in->argnull[1] = false; + fcinfo_in->arg[2] = -1; + fcinfo_in->argnull[2] = false; + + 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_DISPATCH(op); + } + + /* + * 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 already known. If neither is NULL, then + * proceed to evaluate the comparison 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(). + */ + EEO_CASE(EEO_DISTINCT) + { + 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->resnull = false; + *op->resvalue = BoolGetDatum(false); + } + else if (fcinfo->argnull[0] || fcinfo->argnull[1]) + { + /* Only one is NULL? Then is distinct... */ + *op->resnull = false; + *op->resvalue = BoolGetDatum(true); + } + else + { + fcinfo->isnull = false; + *op->resvalue = (op->d.func.fn_addr) (fcinfo); + /* Must invert result of "=" */ + *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue)); + *op->resnull = fcinfo->isnull; + } + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_NULLIF) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + + /* if either argument is NULL they can't be equal */ + if (!fcinfo->argnull[0] && !fcinfo->argnull[1]) + { + bool result; + + fcinfo->isnull = false; + result = (op->d.func.fn_addr) (fcinfo); + + /* if the arguments are equal return null */ + if (!fcinfo->isnull && DatumGetBool(result)) + { + *op->resnull = true; + *op->resvalue = true; + + EEO_DISPATCH(op); + } + } + *op->resnull = fcinfo->argnull[0]; + *op->resvalue = fcinfo->arg[0]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_SQLVALUEFUNCTION) + { + /* + * Doesn't seem worthwhile to have an inline implementation + * efficency-wise. + */ + ExecEvalSQLValueFunction(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_CURRENTOFEXPR) + { + /* error invocation uses space, and shouldn't ever occur */ + ExecEvalCurrentOfExpr(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ARRAYEXPR) + { + /* too complex for an inline implementation */ + ExecEvalArrayExpr(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ARRAYCOERCE) + { + /* too complex for an inline implementation */ + ExecEvalArrayCoerce(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ROW) + { + /* too complex for an inline implementation */ + ExecEvalRow(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ROWCOMPARE_STEP) + { + FunctionCallInfo fcinfo = op->d.rowcompare_step.fcinfo_data; + + /* force NULL result */ + if (op->d.rowcompare_step.finfo->fn_strict && + (fcinfo->argnull[0] || fcinfo->argnull[1])) + { + *op->resnull = true; + op = &state->steps[op->d.rowcompare_step.jumpnull]; + + EEO_DISPATCH_DIRECT(op); + } + + fcinfo->isnull = false; + *op->resvalue = (op->d.rowcompare_step.fn_addr) (fcinfo); + + /* force NULL result */ + if (fcinfo->isnull) + { + *op->resnull = true; + op = &state->steps[op->d.rowcompare_step.jumpnull]; + + EEO_DISPATCH_DIRECT(op); + } + + /* no need to compare remaining columns */ + if (DatumGetInt32(*op->resvalue) != 0) + { + op = &state->steps[op->d.rowcompare_step.jumpdone]; + + EEO_DISPATCH_DIRECT(op); + } + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_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_DISPATCH(op); + } + + EEO_CASE(EEO_MINMAX) + { + /* too complex for an inline implementation */ + ExecEvalMinMax(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_FIELDSELECT) + { + /* too complex for an inline implementation */ + ExecEvalFieldSelect(state, op, econtext); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_FIELDSTORE_DEFORM) + { + /* too complex for an inline implementation */ + ExecEvalFieldStoreDeForm(state, op, econtext); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_FIELDSTORE_FORM) + { + /* too complex for an inline implementation */ + ExecEvalFieldStoreForm(state, op, econtext); + + EEO_DISPATCH(op); + } + + /* + * Verify input for an ArrayRef reference (not assignment). If the + * array is null, return the entire result is NULL. + */ + EEO_CASE(EEO_ARRAYREF_CHECKINPUT) + { + Assert(!op->d.arrayref.state->isassignment); + + /* + * If refexpr yields NULL, and it's a fetch, then result is NULL. + */ + if (*op->resnull && + !op->d.arrayref.state->isassignment) + { + op = &state->steps[op->d.arrayref.jumpdone]; + + EEO_DISPATCH_DIRECT(op); + } + + EEO_DISPATCH(op); + } + + /* + * Check whether a subscript is NULL and handle that. + */ + EEO_CASE(EEO_ARRAYREF_CHECKSUBSCRIPT) + { + /* too complex for an inline implementation */ + if (ExecEvalArrayRefCheckSubscript(state, op)) + { + op++; + } + else + { + op = &state->steps[op->d.arrayref_checksubscript.jumpdone]; + } + + EEO_DISPATCH_DIRECT(op); + } + + /* + * Fetch the old value in an arrayref, if referenced (via a + * CaseTestExpr) inside the assignment expression. + */ + EEO_CASE(EEO_ARRAYREF_OLD) + { + /* too complex for an inline implementation */ + ExecEvalArrayRefOld(state, op); + + EEO_DISPATCH(op); + } + + /* + * Perform ArrayRef assignment + */ + EEO_CASE(EEO_ARRAYREF_ASSIGN) + { + /* too complex for an inline implementation */ + ExecEvalArrayRefAssign(state, op); + + EEO_DISPATCH(op); + } + + /* + * Fetch subset of an array. + */ + EEO_CASE(EEO_ARRAYREF_FETCH) + { + /* too complex for an inline implementation */ + ExecEvalArrayRefFetch(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_CONVERT_ROWTYPE) + { + /* too complex for an inline implementation */ + ExecEvalConvertRowtype(state, op, econtext); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_SCALARARRAYOP) + { + /* too complex for an inline implementation */ + ExecEvalScalarArrayOp(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_DOMAIN_NOTNULL) + { + /* too complex for an inline implementation */ + ExecEvalConstraintNotNull(state, op); + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_DOMAIN_CHECK) + { + /* too complex for an inline implementation */ + ExecEvalConstraintCheck(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_XMLEXPR) + { + /* too complex for an inline implementation */ + ExecEvalXmlExpr(state, op); + + EEO_DISPATCH(op); + } + + /* + * Returns a Datum whose value is the value of the precomputed + * aggregate found in the given expression context. + */ + EEO_CASE(EEO_AGGREF) + { + AggrefExprState *aggref = op->d.aggref.astate; + + Assert(econtext->ecxt_aggvalues != NULL); + + *op->resnull = econtext->ecxt_aggnulls[aggref->aggno]; + *op->resvalue = econtext->ecxt_aggvalues[aggref->aggno]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_GROUPING_FUNC) + { + /* too complex/uncommon for an inline implementation */ + ExecEvalGroupingFunc(state, op); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_WINDOW_FUNC) + { + WindowFuncExprState *wfunc = op->d.window_func.wfstate; + + Assert(econtext->ecxt_aggvalues != NULL); + + *op->resnull = econtext->ecxt_aggnulls[wfunc->wfuncno]; + *op->resvalue = econtext->ecxt_aggvalues[wfunc->wfuncno]; + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_SUBPLAN) + { + /* too complex for an inline implementation */ + ExecEvalSubPlan(state, op, econtext); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_ALTERNATIVE_SUBPLAN) + { + /* too complex for an inline implementation */ + ExecEvalAlternativeSubPlan(state, op, econtext); + + EEO_DISPATCH(op); + } + + EEO_CASE(EEO_LAST) + { + /* unreachable */ + Assert(false); + goto out; + } + } +out: + *isnull = state->resnull; + return state->resvalue; +} + +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; + + return slot_getattr(slot, attnum, isnull); +} + +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; + + return slot_getattr(slot, attnum, isnull); +} + +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; + + return slot_getattr(slot, attnum, isnull); +} + +static Datum +ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[0]; + + *isnull = op->d.constval.isnull; + return op->d.constval.value; +} + +static Datum +ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.assign_var.attnum + 1; + size_t resultnum = op->d.assign_var.resultnum; + TupleTableSlot *inslot = econtext->ecxt_outertuple; + TupleTableSlot *outslot = state->resultslot; + + outslot->tts_values[resultnum] = + slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]); + return 0; +} + +static Datum +ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.assign_var.attnum + 1; + size_t resultnum = op->d.assign_var.resultnum; + TupleTableSlot *inslot = econtext->ecxt_innertuple; + TupleTableSlot *outslot = state->resultslot; + + outslot->tts_values[resultnum] = + slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]); + return 0; +} + +static Datum +ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.assign_var.attnum + 1; + size_t resultnum = op->d.assign_var.resultnum; + TupleTableSlot *inslot = econtext->ecxt_scantuple; + TupleTableSlot *outslot = state->resultslot; + + outslot->tts_values[resultnum] = + slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]); + return 0; +} + +static void +ExecPrepareInterp(void) +{ + static bool prepared = false; + + if (prepared) + return; + +#if defined(EEO_USE_COMPUTED_GOTO) + if (dispatch_table == NULL) + dispatch_table = (void **) DatumGetPointer(ExecInterpExpr(NULL, NULL, NULL)); +#endif + + prepared = true; +} + +ExprEvalOp +ExecEvalStepOp(ExprState *state, ExprEvalStep *op) +{ +#if defined(EEO_USE_COMPUTED_GOTO) + if (state->flags & EEO_FLAG_JUMP_THREADED) + { + int i; + + for (i = 0; i < EEO_LAST; i++) + { + if ((void *) op->opcode == dispatch_table[i]) + { + return (ExprEvalOp) i; + } + } + elog(ERROR, "unknown opcode"); + } +#endif + return (ExprEvalOp) op->opcode; +} + +/* + * Computes the value for a PARAM_EXEC parameter. + */ +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)) + { + /* + * XXX: Worthwhile to extract this into a separate opcode? + */ + ExecSetParamPlan(prm->execPlan, econtext); + /* ExecSetParamPlan should have processed this param... */ + Assert(prm->execPlan == NULL); + } + *op->resvalue = prm->value; + *op->resnull = prm->isnull; +} + +/* + * Computes the value for a PARAM_EXTERN parameter. + */ +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 */ + /* XXX: separate opcode */ + if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) + (*paramInfo->paramFetch) (paramInfo, paramId); + + if (likely(OidIsValid(prm->ptype))) + { + /* safety check in case hook did something unexpected */ + /* XXX: assert instead? */ + 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 SQLValueFunction expressions. + */ +void +ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op) +{ + FunctionCallInfoData fcinfo; + SQLValueFunction *svf = op->d.sqlvaluefunction.svf; + + *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; + } +} + +void +ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op) +{ + /* + * 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. + */ + 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) +{ + ExecEvalRowNullInt(state, op, true); +} + +/* + * Evaluate NullTest / IS NOT NULL for rows. + */ +void +ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op) +{ + ExecEvalRowNullInt(state, op, false); +} + +static void +ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, 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 && checkisnull) + { + *op->resvalue = BoolGetDatum(true); + return; + } + else if (isnull) + { + *op->resvalue = BoolGetDatum(false); + 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 = lookup_rowtype_tupdesc(tupType, tupTypmod); + + /* + * 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) + { + ReleaseTupleDesc(tupDesc); + *op->resvalue = BoolGetDatum(false); + return; + } + } + else + { + /* non-null field disproves IS NULL */ + if (checkisnull) + { + ReleaseTupleDesc(tupDesc); + *op->resvalue = BoolGetDatum(false); + return; + } + } + } + + ReleaseTupleDesc(tupDesc); + *op->resvalue = BoolGetDatum(true); +} + + +/* + * Evaluate ARRAY[] expressions. + */ +void +ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op) +{ + ArrayType *result; + ArrayExpr *arrayExpr = op->d.arrayexpr.arrayexpr; + Oid element_type = arrayExpr->element_typeid; + int ndims = 0; + int dims[MAXDIM]; + int lbs[MAXDIM]; + int nelems = op->d.arrayexpr.nelems; + + /* Set non-null as default */ + *op->resnull = false; + + if (!arrayExpr->multidims) + { + /* Elements are presumably of scalar type */ + Datum *dvalues = op->d.arrayexpr.elemvalues; + bool *dnulls = op->d.arrayexpr.elemnulls; + + ndims = 1; + + /* 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 */ + 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++) + { + bool eisnull; + Datum arraydatum; + 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. + */ +void +ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op) +{ + ArrayCoerceExpr *acoerce = op->d.arraycoerce.coerceexpr; + Datum result; + FunctionCallInfoData locfcinfo; + + /* input is in *step->resvalue/resnull */ + result = *op->resvalue; + if (*op->resnull) + { + return; /* 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) = op->d.arraycoerce.resultelemtype; + *op->resvalue = PointerGetDatum(array); + return; + } + + /* Initialize function cache if first time through */ + if (op->d.arraycoerce.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(acoerce->elemfuncid, op->d.arraycoerce.elemfunc); + fmgr_info_set_expr((Node *) acoerce, op->d.arraycoerce.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, op->d.arraycoerce.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; + + *op->resvalue = array_map(&locfcinfo, op->d.arraycoerce.resultelemtype, + op->d.arraycoerce.amstate); +} + +/* + * Evaluate ROW() expressions. + * + * The individual columns have already been evaluated into + * op->d.row.elemvalues/nulls. + */ +void +ExecEvalRow(ExprState *state, ExprEvalStep *op) +{ + HeapTuple tuple; + + /* Set non-null as default */ + *op->resnull = false; + + /* 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); +} + +/* + * Evaluate GREATEST()/LEAST() type 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) +{ + int off; + 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; + + /* set at initialization */ + Assert(fcinfo->argnull[0] == false); + Assert(fcinfo->argnull[1] == false); + + *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->isnull = false; + fcinfo->arg[0] = *op->resvalue; + fcinfo->arg[1] = values[off]; + Assert(!fcinfo->isnull); + + cmpresult = DatumGetInt32(FunctionCallInvoke(fcinfo)); + + 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. + */ +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; + + tupDatum = *op->resvalue; + + /* this test covers the isDone exception too: */ + if (*op->resnull) + return; + + 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 ExecEvalScalarVar, 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; + + *op->resvalue = heap_getattr(&tmptup, + fieldnum, + tupDesc, + op->resnull); +} + +/* + * Deform tuple before evaluating the individual new values as part of a + * FieldStore expression. + */ +void +ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + /* Lookup tupdesc if first time through or after rescan */ + get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1, + op->d.fieldstore.argdesc, econtext); + + if (*op->resnull) + { + /* Convert null input tuple into an all-nulls row */ + memset(op->d.fieldstore.nulls, true, + MaxTupleAttributeNumber * 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, *op->d.fieldstore.argdesc, + op->d.fieldstore.values, + op->d.fieldstore.nulls); + } +} + +/* + * Compute the new wholerow datum after each individual row part of a + * FieldStore expression has been evaluated. + */ +void +ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + HeapTuple tuple; + + /* Result is never null */ + *op->resnull = false; + + tuple = heap_form_tuple(*op->d.fieldstore.argdesc, + op->d.fieldstore.values, + op->d.fieldstore.nulls); + + *op->resvalue = HeapTupleGetDatum(tuple); +} + +void +ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op) +{ + ArrayRefState *arefstate = op->d.arrayref.state; + + if (*op->resnull) + { + arefstate->prevnull = true; + arefstate->prevvalue = (Datum) 0; + } + else if (arefstate->numlower == 0) + { + arefstate->prevvalue = + array_get_element(*op->resvalue, + arefstate->numupper, + arefstate->upperindex, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign, + &arefstate->prevnull); + } + else + { + /* 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); + } +} + +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 && + (*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) + { + *op->resvalue = + array_set_element(*op->resvalue, arefstate->numupper, + arefstate->upperindex, + arefstate->replacevalue, + arefstate->replacenull, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign); + } + else + { + *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); + } +} + +void +ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op) +{ + ArrayRefState *arefstate = op->d.arrayref.state; + + if (arefstate->numlower == 0) + { + + *op->resvalue = + array_get_element(*op->resvalue, + arefstate->numupper, + arefstate->upperindex, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign, + op->resnull); + } + else + { + *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); + } +} + +bool +ExecEvalArrayRefCheckSubscript(ExprState *state, ExprEvalStep *op) +{ + ArrayRefState *arefstate = op->d.arrayref_checksubscript.state; + int off = op->d.arrayref_checksubscript.off; + bool *nulls; + Datum *values; + int *indexes; + + if (op->d.arrayref_checksubscript.isupper) + { + nulls = arefstate->uppernull; + values = arefstate->upper; + indexes = arefstate->upperindex; + } + else + { + nulls = arefstate->lowernull; + values = arefstate->lower; + indexes = arefstate->lowerindex; + } + + /* If any index expr yields NULL, result is NULL or error */ + if (nulls[off]) + { + 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 */ + indexes[off] = values[off]; + + return true; +} + +/* + * Evaluate a rowtype coercion operation. This may require rearranging field + * positions. + */ +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; + + tupDatum = *op->resvalue; + + /* this test covers the isDone exception too: */ + if (*op->resnull) + return; + + 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 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) == 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); + +} + +/* + * 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. + */ +void +ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op) +{ + ScalarArrayOpExpr *opexpr = op->d.scalararrayop.opexpr; + bool useOr = opexpr->useOr; + FunctionCallInfo fcinfo = op->d.scalararrayop.fcinfo_data; + 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; + } + + /* no "bad" nulls, 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->resnull = false; + *op->resvalue = BoolGetDatum(!useOr); + 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; + + 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->resnull = resultnull; + *op->resvalue = result; +} + +/* + * Evaluate a NOT NULL 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 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. + */ +void +ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op) +{ + XmlExpr *xexpr = op->d.xmlexpr.xexpr; + Datum value; + ListCell *arg; + ListCell *narg; + 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 (list_length(values) > 0) + { + *op->resnull = false; + *op->resvalue = PointerGetDatum(xmlconcat(values)); + } + } + break; + + case IS_XMLFOREST: + { + Datum *argvalue = op->d.xmlexpr.named_argvalue; + bool *argnull = op->d.xmlexpr.named_argnull; + StringInfoData buf; + + initStringInfo(&buf); + + i = 0; + forboth(arg, xexpr->named_args, narg, xexpr->arg_names) + { + char *argname = strVal(lfirst(narg)); + Expr *e = (Expr *) lfirst(arg); + + 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) + { + pfree(buf.data); + } + else + { + text *result; + + result = cstring_to_text_with_len(buf.data, buf.len); + pfree(buf.data); + + *op->resvalue = PointerGetDatum(result); + } + } + break; + + case IS_XMLELEMENT: + *op->resnull = false; + *op->resvalue = PointerGetDatum( + xmlelement(xexpr, + op->d.xmlexpr.named_argnull, + op->d.xmlexpr.named_argvalue, + op->d.xmlexpr.argnull, + op->d.xmlexpr.argvalue)); + 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->resnull = false; + *op->resvalue = PointerGetDatum( + xmlparse(data, xexpr->xmloption, preserve_whitespace)); + } + 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->resnull = false; + *op->resvalue = PointerGetDatum(xmlroot(data, + version, + standalone)); + } + 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->resnull = false; + *op->resvalue = PointerGetDatum( + xmltotext_with_xmloption(DatumGetXmlP(value), + xexpr->xmloption)); + } + 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]) + break; + + value = argvalue[0]; + *op->resnull = false; + *op->resvalue = + BoolGetDatum(xml_is_document(DatumGetXmlP(value))); + } + break; + default: + elog(ERROR, "unrecognized XML operation"); + } +} + +/* + * 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; + int attnum = 0; + Bitmapset *grouped_cols = op->d.grouping_func.parent->grouped_cols; + ListCell *lc; + + *op->resnull = false; + + foreach(lc, op->d.grouping_func.clauses) + { + attnum = lfirst_int(lc); + + result = result << 1; + + if (!bms_is_member(attnum, grouped_cols)) + result = result | 1; + } + + *op->resvalue = Int32GetDatum(result); +} + +/* + * 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; + bool needslow = false; + 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); + + /* + * First time through, perform initialization. + * + * XXX: It'd be great if this could be moved to the the expression + * initialization phase, but due to using slots that's currently not + * feasible. + */ + if (op->d.wholerow.first) + { + /* + * 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 */ + op->d.wholerow.tupdesc = BlessTupleDesc(output_tupdesc); + + op->d.wholerow.first = false; + } + + if (needslow) + { + TupleDesc tupleDesc = slot->tts_tupleDescriptor; + TupleDesc var_tupdesc = op->d.wholerow.tupdesc; + int i; + + tuple = ExecFetchSlotTuple(slot); + + + /* 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, op->d.wholerow.tupdesc->tdtypeid); + HeapTupleHeaderSetTypMod(dtuple, op->d.wholerow.tupdesc->tdtypmod); + + *op->resnull = false; + *op->resvalue = PointerGetDatum(dtuple); +} + +/* + * 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; +} + +/* + * 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; +} diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index f5cd65d8a0..ede675fa61 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1246,7 +1246,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_TrigFunctions = (FmgrInfo *) palloc0(n * sizeof(FmgrInfo)); - resultRelInfo->ri_TrigWhenExprs = (List **) + resultRelInfo->ri_TrigWhenExprs = (ExprState **) palloc0(n * sizeof(List *)); if (instrument_options) resultRelInfo->ri_TrigInstrument = InstrAlloc(n, instrument_options); @@ -1689,7 +1689,6 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, ConstrCheck *check = rel->rd_att->constr->check; ExprContext *econtext; MemoryContext oldContext; - List *qual; int i; /* @@ -1701,13 +1700,15 @@ 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 */ + List *qual; + + /* ExecPrepareQual wants implicit-AND form */ qual = make_ands_implicit(stringToNode(check[i].ccbin)); - resultRelInfo->ri_ConstraintExprs[i] = (List *) - ExecPrepareExpr((Expr *) qual, estate); + resultRelInfo->ri_ConstraintExprs[i] = + ExecPrepareCheck(qual, estate); } MemoryContextSwitchTo(oldContext); } @@ -1724,14 +1725,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, /* And evaluate the constraints */ for (i = 0; i < ncheck; i++) { - qual = resultRelInfo->ri_ConstraintExprs[i]; + ExprState *qual = 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(qual, econtext)) return check[i].ccname; } @@ -1759,8 +1760,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); } /* @@ -1776,7 +1776,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); } /* @@ -1956,11 +1956,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..d250627bbe 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * execQual.c - * Routines to evaluate qualification and targetlist expressions + * Former 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 @@ -12,28 +12,6 @@ * *------------------------------------------------------------------------- */ -/* - * 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" @@ -62,5058 +40,1018 @@ /* 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 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(FuncExprState *fcache, +static void ExecPrepareTuplestoreResult(SetExprState *sexpr, 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). +/* + * GetAttributeByName + * GetAttributeByNum * - * 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. - *---------- + * 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. */ -static Datum -ExecEvalArrayRef(ArrayRefExprState *astate, - ExprContext *econtext, - bool *isNull) +Datum +GetAttributeByNum(HeapTupleHeader tuple, + AttrNumber attrno, + 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; + Datum result; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; - array_source = ExecEvalExpr(astate->refexpr, - econtext, - isNull); + if (!AttributeNumberIsValid(attrno)) + elog(ERROR, "invalid attribute number %d", attrno); - /* - * 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; - } + if (isNull == NULL) + elog(ERROR, "a NULL isNull pointer was passed"); - foreach(l, astate->refupperindexpr) + if (tuple == NULL) { - 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; - } + /* Kinda bogus but compatible with old behavior... */ + *isNull = true; + return (Datum) 0; } - 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; + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - if (isAssignment) - { - Datum sourceData; - Datum save_datum; - bool save_isNull; + /* + * 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; - /* - * 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; + result = heap_getattr(&tmptup, + attrno, + tupDesc, + 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; - } + ReleaseTupleDesc(tupDesc); - /* - * Evaluate the value to be assigned into the array. - */ - sourceData = ExecEvalExpr(astate->refassgnexpr, - econtext, - &eisnull); + return result; +} - econtext->caseValue_datum = save_datum; - econtext->caseValue_isNull = save_isNull; +Datum +GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) +{ + AttrNumber attrno; + Datum result; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; + int i; - /* - * 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; + if (attname == NULL) + elog(ERROR, "invalid attribute name"); - /* - * 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 (isNull == NULL) + elog(ERROR, "a NULL isNull pointer was passed"); - 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 (tuple == NULL) + { + /* Kinda bogus but compatible with old behavior... */ + *isNull = true; + return (Datum) 0; } - 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); -} + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); -/* - * 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)) + attrno = InvalidAttrNumber; + for (i = 0; i < tupDesc->natts; i++) { - FieldStore *fstore = (FieldStore *) exprstate->expr; - - if (fstore->arg && IsA(fstore->arg, CaseTestExpr)) - return true; + if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0) + { + attrno = tupDesc->attrs[i]->attnum; + break; + } } - else if (IsA(exprstate, ArrayRefExprState)) - { - ArrayRef *arrayRef = (ArrayRef *) exprstate->expr; - if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr)) - return true; - } - return false; -} + if (attrno == InvalidAttrNumber) + elog(ERROR, "attribute \"%s\" does not exist", attname); -/* ---------------------------------------------------------------- - * 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"); + /* + * 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; - *isNull = econtext->ecxt_aggnulls[aggref->aggno]; - return econtext->ecxt_aggvalues[aggref->aggno]; -} + result = heap_getattr(&tmptup, + attrno, + tupDesc, + isNull); -/* ---------------------------------------------------------------- - * 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"); + ReleaseTupleDesc(tupDesc); - *isNull = econtext->ecxt_aggnulls[wfunc->wfuncno]; - return econtext->ecxt_aggvalues[wfunc->wfuncno]; + return result; } -/* ---------------------------------------------------------------- - * 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. - * ---------------------------------------------------------------- +/* + * init_sexpr - initialize a SetExprState node during first use */ -static Datum -ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, - bool *isNull) +static void +init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr, + MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF) { - Var *variable = (Var *) exprstate->expr; - TupleTableSlot *slot; - AttrNumber attnum; + AclResult aclresult; - /* 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; + /* 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); - case OUTER_VAR: /* get the tuple from the outer node */ - slot = econtext->ecxt_outertuple; - break; + /* + * 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))); - /* INDEX_VAR is handled by default case */ + /* Set up the primary fmgr lookup information */ + fmgr_info_cxt(foid, &(sexpr->func), sexprCxt); + fmgr_info_set_expr((Node *) sexpr->expr, &(sexpr->func)); - default: /* get the tuple from the relation being - * scanned */ - slot = econtext->ecxt_scantuple; - break; - } + /* Initialize the function call parameter struct as well */ + InitFunctionCallInfoData(sexpr->fcinfo_data, &(sexpr->func), + list_length(sexpr->args), + input_collation, NULL, NULL); - attnum = variable->varattno; + /* 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"))); - /* This was checked by ExecInitExpr */ - Assert(attnum != InvalidAttrNumber); + /* Otherwise, ExecInitExpr should have marked the sexpr correctly */ + Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet); - /* - * 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) + /* If function returns set, prepare expected tuple descriptor */ + if (sexpr->func.fn_retset && needDescForSRF) { - TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; - Form_pg_attribute attr; + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + MemoryContext oldcontext; - if (attnum > slot_tupdesc->natts) /* should never happen */ - elog(ERROR, "attribute number %d exceeds number of columns %d", - attnum, slot_tupdesc->natts); + functypclass = get_expr_result_type(sexpr->func.fn_expr, + &funcrettype, + &tupdesc); - attr = slot_tupdesc->attrs[attnum - 1]; + /* Must save tupdesc in sexpr's context */ + oldcontext = MemoryContextSwitchTo(sexprCxt); - /* can't check type if dropped, since atttypid is probably 0 */ - if (!attr->attisdropped) + if (functypclass == TYPEFUNC_COMPOSITE) { - 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)))); + /* 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; } - } - /* Skip the checking on future executions of node */ - exprstate->evalfunc = ExecEvalScalarVarFast; + MemoryContextSwitchTo(oldcontext); + } + else + sexpr->funcResultDesc = NULL; - /* Fetch the value from the slot */ - return slot_getattr(slot, attnum, isNull); + /* Initialize additional state */ + sexpr->funcResultStore = NULL; + sexpr->funcResultSlot = NULL; + sexpr->shutdown_reg = false; } -/* ---------------------------------------------------------------- - * ExecEvalScalarVarFast - * - * Returns a Datum for a scalar variable. - * ---------------------------------------------------------------- +/* + * callback function in case a SetExpr needs to be shut down before it has + * been run to completion */ -static Datum -ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext, - bool *isNull) +static void +ShutdownSetExpr(Datum arg) { - Var *variable = (Var *) exprstate->expr; - TupleTableSlot *slot; - AttrNumber attnum; + SetExprState *sexpr = (SetExprState *) DatumGetPointer(arg); - /* 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 */ + /* If we have a slot, make sure it's let go of any tuplestore pointer */ + if (sexpr->funcResultSlot) + ExecClearTuple(sexpr->funcResultSlot); - default: /* get the tuple from the relation being - * scanned */ - slot = econtext->ecxt_scantuple; - break; - } + /* Release any open tuplestore */ + if (sexpr->funcResultStore) + tuplestore_end(sexpr->funcResultStore); + sexpr->funcResultStore = NULL; - attnum = variable->varattno; + /* Clear any active set-argument state */ + sexpr->setArgsValid = false; - /* Fetch the value from the slot */ - return slot_getattr(slot, attnum, isNull); + /* execUtils will deregister the callback... */ + sexpr->shutdown_reg = false; } -/* ---------------------------------------------------------------- - * 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. - * ---------------------------------------------------------------- +/* + * Evaluate arguments for a function. */ -static Datum -ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext, - bool *isNull) +static void +ExecEvalFuncArgs(FunctionCallInfo fcinfo, + List *argList, + ExprContext *econtext) { - 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); + int i; + ListCell *arg; - /* Get the input slot we want */ - switch (variable->varno) + i = 0; + foreach(arg, argList) { - 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 */ + ExprState *argstate = (ExprState *) lfirst(arg); - default: /* get the tuple from the relation being - * scanned */ - slot = econtext->ecxt_scantuple; - break; + fcinfo->arg[i] = ExecEvalExpr(argstate, + econtext, + &fcinfo->argnull[i]); + i++; } - /* - * 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); - } - } - } + Assert(i == fcinfo->nargs); +} - /* Apply the junkfilter if any */ - if (wrvstate->wrv_junkFilter != NULL) - slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); +/* + * 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 the Var identifies a named composite type, we must check that the - * actual tuple type is compatible with it. - */ - if (variable->vartype != RECORDOID) + if (sexpr->funcResultSlot == NULL) { - TupleDesc var_tupdesc; - TupleDesc slot_tupdesc; - int i; + /* Create a slot so we can read data out of the tuplestore */ + TupleDesc slotDesc; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(sexpr->func.fn_mcxt); /* - * 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. + * If we were not able to determine the result rowtype from context, + * and the function didn't return a tupdesc, we have to fail. */ - 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++) + if (sexpr->funcResultDesc) + slotDesc = sexpr->funcResultDesc; + else if (resultDesc) { - 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 */ + /* 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 */ } - /* - * 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); + sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc); 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 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 (econtext->ecxt_estate && - variable->varno <= list_length(econtext->ecxt_estate->es_range_table)) + if (resultDesc) { - RangeTblEntry *rte = rt_fetch(variable->varno, - econtext->ecxt_estate->es_range_table); + if (sexpr->funcResultDesc) + tupledesc_match(sexpr->funcResultDesc, resultDesc); - if (rte->eref) - ExecTypeSetColNames(output_tupdesc, rte->eref->colnames); + /* + * 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); } - /* 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) + /* Register cleanup callback if we didn't already */ + if (!sexpr->shutdown_reg) { - 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; + RegisterExprContextCallback(econtext, + ShutdownSetExpr, + PointerGetDatum(sexpr)); + sexpr->shutdown_reg = true; } - - /* 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 +/* + * 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. * - * Returns a Datum for a whole-row variable, in the "slow" case where - * we can't just copy the subplan's output. - * ---------------------------------------------------------------- + * 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 Datum -ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext, - bool *isNull) +static void +tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) { - Var *variable = (Var *) wrvstate->xprstate.expr; - TupleTableSlot *slot; - HeapTuple tuple; - TupleDesc tupleDesc; - TupleDesc var_tupdesc; - HeapTupleHeader dtuple; int i; - *isNull = false; + 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))); - /* Get the input slot we want */ - switch (variable->varno) + for (i = 0; i < dst_tupdesc->natts; i++) { - 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; + Form_pg_attribute dattr = dst_tupdesc->attrs[i]; + Form_pg_attribute sattr = src_tupdesc->attrs[i]; - /* 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 (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 (!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) + if (dattr->attlen != sattr->attlen || + dattr->attalign != sattr->attalign) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), + 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))); } - - /* - * 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. +/* + * ExecMakeFunctionResultSet * - * 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. - * ---------------------------------------------------------------- + * 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). */ -static Datum -ExecEvalConst(ExprState *exprstate, ExprContext *econtext, - bool *isNull) +Datum +ExecMakeFunctionResultSet(SetExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) { - Const *con = (Const *) exprstate->expr; + List *arguments; + Datum result; + FunctionCallInfo fcinfo; + PgStat_FunctionCallUsage fcusage; + ReturnSetInfo rsinfo; + bool callit; + int i; - *isNull = con->constisnull; - return con->constvalue; -} +restart: -/* ---------------------------------------------------------------- - * 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; + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); /* - * PARAM_EXEC params (internal executor parameters) are stored in the - * ecxt_param_exec_vals array, and can be accessed by array index. + * 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. */ - prm = &(econtext->ecxt_param_exec_vals[thisParamId]); - if (prm->execPlan != NULL) + if (fcache->funcResultStore) { - /* Parameter not evaluated yet, so go do it */ - ExecSetParamPlan(prm->execPlan, econtext); - /* ExecSetParamPlan should have processed this param... */ - Assert(prm->execPlan == NULL); + 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; } - *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. + * 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. */ - if (paramInfo && - thisParamId > 0 && thisParamId <= paramInfo->numParams) + fcinfo = &fcache->fcinfo_data; + arguments = fcache->args; + if (!fcache->setArgsValid) + ExecEvalFuncArgs(fcinfo, arguments, econtext); + else { - ParamExternData *prm = ¶mInfo->params[thisParamId - 1]; + /* Reset flag (we may set it again below) */ + fcache->setArgsValid = false; + } - /* give hook a chance in case parameter is dynamic */ - if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) - (*paramInfo->paramFetch) (paramInfo, thisParamId); + /* + * Now call the function, passing the evaluated parameter values. + */ - 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)))); + /* 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; - *isNull = prm->isnull; - return prm->value; + /* + * 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; + } } } - 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 (callit) + { + pgstat_init_function_usage(fcinfo, &fcusage); - if (isNull == NULL) - elog(ERROR, "a NULL isNull pointer was passed"); + fcinfo->isnull = false; + rsinfo.isDone = ExprSingleResult; + result = FunctionCallInvoke(fcinfo); + *isNull = fcinfo->isnull; + *isDone = rsinfo.isDone; - if (tuple == NULL) + pgstat_end_function_usage(&fcusage, + rsinfo.isDone != ExprMultipleResult); + } + else { - /* Kinda bogus but compatible with old behavior... */ + /* for a strict SRF, result for NULL is an empty set */ + result = (Datum) 0; *isNull = true; - return (Datum) 0; + *isDone = ExprEndResult; } - 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); + /* 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; } -Datum -GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) +/* + * Prepare function call in nodeProjectSet.c (taretlist SRF) for execution. + */ +SetExprState * +ExecInitFunctionResultSet(Expr *expr, ExprContext *econtext, PlanState *parent) { - AttrNumber attrno; - Datum result; - Oid tupType; - int32 tupTypmod; - TupleDesc tupDesc; - HeapTupleData tmptup; - int i; - - if (attname == NULL) - elog(ERROR, "invalid attribute name"); + SetExprState *state = makeNode(SetExprState); + ListCell *lc; - if (isNull == NULL) - elog(ERROR, "a NULL isNull pointer was passed"); + state->funcReturnsSet = true; + state->expr = expr; + state->func.fn_oid = InvalidOid; - if (tuple == NULL) + /* + * Initialize metadata. The expression node could be either a FuncExpr or + * an OpExpr. + */ + if (IsA(expr, FuncExpr)) { - /* Kinda bogus but compatible with old behavior... */ - *isNull = true; - return (Datum) 0; - } - - tupType = HeapTupleHeaderGetTypeId(tuple); - tupTypmod = HeapTupleHeaderGetTypMod(tuple); - tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + FuncExpr *func = (FuncExpr *) expr; - attrno = InvalidAttrNumber; - for (i = 0; i < tupDesc->natts; i++) - { - if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0) + foreach(lc, func->args) { - attrno = tupDesc->attrs[i]->attnum; - break; + state->args = lappend(state->args, ExecInitExpr(lfirst(lc), parent)); } - } - if (attrno == InvalidAttrNumber) - elog(ERROR, "attribute \"%s\" does not exist", attname); + init_sexpr(func->funcid, func->inputcollid, state, + econtext->ecxt_per_query_memory, true, true); + } + else if (IsA(expr, OpExpr)) + { + OpExpr *op = (OpExpr *) expr; - /* - * 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; + foreach(lc, op->args) + { + state->args = lappend(state->args, ExecInitExpr(lfirst(lc), parent)); + } - result = heap_getattr(&tmptup, - attrno, - tupDesc, - isNull); + init_sexpr(op->opfuncid, op->inputcollid, state, + econtext->ecxt_per_query_memory, true, true); + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(expr)); - ReleaseTupleDesc(tupDesc); + /* shouldn't get here otherwise */ + Assert(state->func.fn_retset); - return result; + return state; } /* - * init_fcache - initialize a FuncExprState node during first use + * Prepare function call in nodeFunctionscan.c (FROM function/ROWS FROM) for + * execution. */ -static void -init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache, - MemoryContext fcacheCxt, bool allowSRF, bool needDescForSRF) +SetExprState * +ExecInitTableFunctionResult(Expr *expr, ExprContext *econtext, PlanState *parent) { - AclResult aclresult; + SetExprState *state = makeNode(SetExprState); + ListCell *lc; - /* 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); + state->funcReturnsSet = false; + state->expr = expr; + state->func.fn_oid = InvalidOid; /* - * 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? + * 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; the only difference is that we + * don't get a chance to pass a special ReturnSetInfo to any functions + * buried in the expression. */ - 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 (IsA(expr, FuncExpr)) + { + FuncExpr *func = (FuncExpr *) expr; - /* 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"))); + state->funcReturnsSet = func->funcretset; - /* Otherwise, ExecInitExpr should have marked the fcache correctly */ - Assert(fcache->func.fn_retset == fcache->funcReturnsSet); + foreach(lc, func->args) + { + state->args = lappend(state->args, ExecInitExpr(lfirst(lc), parent)); + } - /* If function returns set, prepare expected tuple descriptor */ - if (fcache->func.fn_retset && needDescForSRF) + init_sexpr(func->funcid, func->inputcollid, state, + econtext->ecxt_per_query_memory, func->funcretset, false); + } + else { - TypeFuncClass functypclass; - Oid funcrettype; - TupleDesc tupdesc; - MemoryContext oldcontext; + state->elidedFuncState = ExecInitExpr(expr, parent); + } - 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; + return state; +} - 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); +/* + * ExecMakeTableFunctionResult + * + * Evaluate a table function, producing a materialized result in a Tuplestore + * object. + */ +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; - /* - * 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); + callerContext = CurrentMemoryContext; - 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); + funcrettype = exprType((Node *) setexpr->expr); - 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); + returnsTuple = type_is_rowtype(funcrettype); - 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); + /* + * 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; - 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); + /* + * 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); - 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); + /* + * 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); - 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); + /* + * 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; - 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: + for (i = 0; i < fcinfo.nargs; i++) { - 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; + if (fcinfo.argnull[i]) + goto no_function_result; } - break; - case T_AlternativeSubPlan: - { - AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; - AlternativeSubPlanState *asstate; + } + } + else + { + /* Treat setexpr as a generic expression */ + InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); + } - if (!parent) - elog(ERROR, "AlternativeSubPlan found with no parent plan"); + /* + * Switch to short-lived context for calling the function or expression. + */ + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - asstate = ExecInitAlternativeSubPlan(asplan, parent); + /* + * Loop to handle the ValuePerCall protocol (which is also the same + * behavior needed in the generic ExecEvalExpr path). + */ + for (;;) + { + Datum result; - state = (ExprState *) asstate; - } - break; - case T_FieldSelect: - { - FieldSelect *fselect = (FieldSelect *) node; - FieldSelectState *fstate = makeNode(FieldSelectState); + CHECK_FOR_INTERRUPTS(); - 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); + /* + * 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); - 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); + /* Call the function or expression one time */ + if (!setexpr->elidedFuncState) + { + pgstat_init_function_usage(&fcinfo, &fcusage); - 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; + fcinfo.isnull = false; + rsinfo.isDone = ExprSingleResult; + result = FunctionCallInvoke(&fcinfo); - 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); + pgstat_end_function_usage(&fcusage, + rsinfo.isDone != ExprMultipleResult); + } + else + { + result = + ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo.isnull); + rsinfo.isDone = ExprSingleResult; + } - 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); + /* Which protocol does function want to use? */ + if (rsinfo.returnMode == SFRM_ValuePerCall) + { + /* + * Check for end of result set. + */ + if (rsinfo.isDone == ExprEndResult) + break; - cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype; - cstate->arg = ExecInitExpr(convert->arg, parent); - state = (ExprState *) cstate; - } - break; - case T_CaseExpr: + /* + * If first time through, build tuplestore for result. For a + * scalar function result type, also make a suitable tupdesc. + */ + if (first_time) { - 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) + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + rsinfo.setResult = tupstore; + if (!returnsTuple) { - 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); + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + "column", + funcrettype, + -1, + 0); + rsinfo.setDesc = tupdesc; } - cstate->args = outlist; - cstate->defresult = ExecInitExpr(caseexpr->defresult, parent); - if (caseexpr->arg) - cstate->argtyplen = get_typlen(exprType((Node *) caseexpr->arg)); - state = (ExprState *) cstate; + MemoryContextSwitchTo(oldcontext); } - 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: + /* + * Store current resultset item. + */ + if (returnsTuple) { - 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) + if (!fcinfo.isnull) { - Expr *e = (Expr *) lfirst(l); - ExprState *estate; + HeapTupleHeader td = DatumGetHeapTupleHeader(result); - if (!attrs[i]->attisdropped) + if (tupdesc == NULL) { /* - * 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. + * 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. */ - 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)))); + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), + HeapTupleHeaderGetTypMod(td)); + rsinfo.setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); } 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. + * Verify all later returned rows have same subtype; + * necessary in case the type is RECORD. */ - e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid); + 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"))); } - 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. + * tuplestore_puttuple needs a HeapTuple not a bare + * HeapTupleHeader, but it doesn't need all the fields. */ - 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; + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); + tuplestore_puttuple(tupstore, &tmptup); } - xstate->named_args = outlist; - - outlist = NIL; - foreach(arg, xexpr->args) + else { - Expr *e = (Expr *) lfirst(arg); - ExprState *estate; + /* + * 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; - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); + nullflags = (bool *) palloc(natts * sizeof(bool)); + memset(nullflags, true, natts * sizeof(bool)); + tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); } - 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: + else { - 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; + /* Scalar-type case: just store the function result */ + tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull); } - 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; - } + /* + * 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; - case T_List: - { - List *outlist = NIL; - ListCell *l; + } + else + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("unrecognized table-function returnMode: %d", + (int) rsinfo.returnMode))); - 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; + first_time = false; } - /* 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; +no_function_result: /* - * debugging stuff + * 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. */ - EV_printf("ExecQual: qual is "); - EV_nodeDisplay(qual); - EV_printf("\n"); + 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; - /* - * Run in short-lived per-tuple context while computing expressions. - */ - oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + nullflags = (bool *) palloc(natts * sizeof(bool)); + memset(nullflags, true, natts * sizeof(bool)); + tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); + } + } /* - * 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. + * 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. */ - result = true; - - foreach(l, qual) + if (rsinfo.setDesc) { - ExprState *clause = (ExprState *) lfirst(l); - Datum expr_value; - bool isNull; - - expr_value = ExecEvalExpr(clause, econtext, &isNull); + tupledesc_match(expectedDesc, rsinfo.setDesc); - if (isNull) - { - if (resultForNull == false) - { - result = false; /* treat NULL as FALSE */ - break; - } - } - else - { - if (!DatumGetBool(expr_value)) - { - result = false; /* definitely FALSE */ - break; - } - } + /* + * 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(oldContext); + MemoryContextSwitchTo(callerContext); - return result; + /* All done, pass back the tuplestore */ + return rsinfo.setResult; } /* @@ -5137,177 +1075,11 @@ ExecCleanTargetListLength(List *targetlist) foreach(tl, targetlist) { - TargetEntry *curTle = castNode(TargetEntry, lfirst(tl)); + TargetEntry *curTle = (TargetEntry *) lfirst(tl); + Assert(IsA(curTle, TargetEntry)); 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/execScan.c b/src/backend/executor/execScan.c index 65196795d7..0a30d46ff4 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; } @@ -187,7 +187,7 @@ ExecScan(ScanState *node, * when the qual is nil ... saves only a few cycles, but they add up * ... */ - if (!qual || ExecQual(qual, econtext, false)) + if (!qual || ExecQual(qual, econtext)) { /* * Found a satisfactory scan tuple. diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index d205101b89..5bc89f8554 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -470,136 +470,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 */ - ExecGetLastAttnums((Node *) variable, - &projInfo->pi_lastOuterVar, - &projInfo->pi_lastInnerVar, - &projInfo->pi_lastScanVar); - } - } - projInfo->pi_targetlist = exprlist; - projInfo->pi_numSimpleVars = numSimpleVars; - projInfo->pi_directMap = directMap; - - return projInfo; -} - /* * get_last_attnums_walker: expression walker for ExecBuildProjectionInfo * @@ -680,9 +550,10 @@ ExecAssignProjectionInfo(PlanState *planstate, TupleDesc inputDesc) { planstate->ps_ProjInfo = - ExecBuildProjectionInfo(planstate->targetlist, + ExecBuildProjectionInfo(planstate->plan->targetlist, planstate->ps_ExprContext, planstate->ps_ResultTupleSlot, + planstate, inputDesc); } diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index aa08152350..2f3355636d 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 @@ -2506,13 +2506,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) * 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. + * FIXME: adjust comment (now added in projection) */ - 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. @@ -2724,7 +2721,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 +3021,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 +3202,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..57a4ce3218 100644 --- a/src/backend/executor/nodeCustom.c +++ b/src/backend/executor/nodeCustom.c @@ -49,11 +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, + css->ss.ps.qual = + ExecInitQual(cscan->scan.plan.qual, (PlanState *) css); /* tuple table initialization */ 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..250efcb449 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,11 +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, + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState)); @@ -361,7 +358,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..6b0561f4e3 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -86,12 +86,9 @@ 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..1aa133925f 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,13 @@ 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, ExecInitExpr(linitial(hclause->args), (PlanState *) hjstate)); + rclauses = lappend(rclauses, ExecInitExpr(lsecond(hclause->args), (PlanState *) hjstate)); - lclauses = lappend(lclauses, linitial(fstate->args)); - rclauses = lappend(rclauses, lsecond(fstate->args)); 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 95e158970c..127a64eb31 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1151,7 +1151,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; @@ -1270,7 +1270,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); @@ -1645,7 +1645,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; @@ -1765,8 +1764,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) foreach(ll, 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); } @@ -1805,7 +1804,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) foreach(ll, mapped_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); @@ -1839,8 +1838,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. @@ -1849,11 +1849,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++; } @@ -1870,17 +1868,15 @@ 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, - resultRelInfo->ri_RelationDesc->rd_att); + ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps, + resultRelInfo->ri_RelationDesc->rd_att); resultRelInfo++; } } @@ -1905,7 +1901,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 */ @@ -1931,11 +1926,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 */ @@ -1943,10 +1937,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..60862cf142 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" @@ -122,7 +123,6 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) bool hassrf PG_USED_FOR_ASSERTS_ONLY = false; bool hasresult; int argno; - ListCell *lc; ExecClearTuple(resultSlot); @@ -133,10 +133,9 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) node->pending_srf_tuples = false; hasresult = false; - argno = 0; - foreach(lc, node->ps.targetlist) + 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,10 @@ 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 +201,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 +228,6 @@ 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); Assert(node->plan.qual == NIL); /* @@ -254,9 +247,38 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags) /* Create workspace for per-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. Can't use + * ExecBuildProjectionInfo here, since that doesn't deal with + * SRFs. Instead evaluate all expressions individually, 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 + { + state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps); + Assert(!expression_returns_set((Node *) expr)); + } + + 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->args = (List *) - ExecInitExpr((Expr *) tsc->args, - (PlanState *) scanstate); + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); + + scanstate->args = ExecInitExprList(tsc->args, (PlanState *) scanstate); scanstate->repeatable = - ExecInitExpr(tsc->repeatable, - (PlanState *) scanstate); + ExecInitExpr(tsc->repeatable, (PlanState *) scanstate); /* * tuple table initialization 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..ebc5a07245 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) */ @@ -800,27 +791,27 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) * 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; + Assert(IsA(subplan->testexpr, BoolExpr)); + 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 +819,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 +874,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, + parent->ps_ExprContext, 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 +912,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 +1089,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 +1140,21 @@ 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) + { + SubPlanState *sp = ExecInitSubPlan(lfirst(lc), parent); + asstate->subplans = + lappend(asstate->subplans, sp); + parent->subPlan = lappend(parent->subPlan, sp); + } /* * Select the one to be used. For this, we need an estimate of the number @@ -1209,7 +1192,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..8e589d916f 100644 --- a/src/backend/executor/nodeTableFuncscan.c +++ b/src/backend/executor/nodeTableFuncscan.c @@ -139,12 +139,9 @@ 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 +176,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..1763be5cf0 100644 --- a/src/backend/executor/nodeTidscan.c +++ b/src/backend/executor/nodeTidscan.c @@ -52,7 +52,8 @@ static TupleTableSlot *TidNext(TidScanState *node); static void TidListCreate(TidScanState *tidstate) { - List *evalList = tidstate->tss_tidquals; + TidScan *node = (TidScan *) tidstate->ss.ps.plan; + List *evalList = node->tidquals; ExprContext *econtext = tidstate->ss.ps.ps_ExprContext; BlockNumber nblocks; ItemPointerData *tidList; @@ -81,28 +82,29 @@ TidListCreate(TidScanState *tidstate) foreach(l, evalList) { - ExprState *exstate = (ExprState *) lfirst(l); - Expr *expr = exstate->expr; + Expr *expr = (Expr*) lfirst(l); ItemPointer itemptr; bool isNull; if (is_opclause(expr)) { - FuncExprState *fexstate = (FuncExprState *) exstate; + FuncExpr *fex = (FuncExpr *) expr; + ExprState *exprstate; Node *arg1; Node *arg2; arg1 = get_leftop(expr); arg2 = get_rightop(expr); if (IsCTIDVar(arg1)) - exstate = (ExprState *) lsecond(fexstate->args); + exprstate = ExecInitExpr((Expr *) lsecond(fex->args), + &tidstate->ss.ps); else if (IsCTIDVar(arg2)) - exstate = (ExprState *) linitial(fexstate->args); - else + exprstate = ExecInitExpr((Expr *) linitial(fex->args), + &tidstate->ss.ps); elog(ERROR, "could not identify CTID variable"); itemptr = (ItemPointer) - DatumGetPointer(ExecEvalExprSwitchContext(exstate, + DatumGetPointer(ExecEvalExprSwitchContext(exprstate, econtext, &isNull)); if (!isNull && @@ -121,7 +123,8 @@ TidListCreate(TidScanState *tidstate) } else if (expr && IsA(expr, ScalarArrayOpExpr)) { - ScalarArrayOpExprState *saexstate = (ScalarArrayOpExprState *) exstate; + ScalarArrayOpExpr *saex = (ScalarArrayOpExpr *) expr; + ExprState *exprstate; Datum arraydatum; ArrayType *itemarray; Datum *ipdatums; @@ -129,8 +132,8 @@ TidListCreate(TidScanState *tidstate) int ndatums; int i; - exstate = (ExprState *) lsecond(saexstate->fxprstate.args); - arraydatum = ExecEvalExprSwitchContext(exstate, + exprstate = ExecInitExpr(lsecond(saex->args), &tidstate->ss.ps); + arraydatum = ExecEvalExprSwitchContext(exprstate, econtext, &isNull); if (isNull) @@ -470,16 +473,8 @@ 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->tss_tidquals = (List *) - ExecInitExpr((Expr *) node->tidquals, - (PlanState *) tidstate); + tidstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) 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/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..f85d3c8111 100644 --- a/src/backend/utils/adt/domains.c +++ b/src/backend/utils/adt/domains.c @@ -122,7 +122,8 @@ 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 similar to the handling of CoerceToDomain nodes in + * execExpr.c/execInterpExpr.c. */ static void domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) @@ -165,19 +166,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, + conResult = ExecEvalExprSwitchContext(con->check_exprstate, econtext, &conIsNull); diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5c823250bc..d777f1a2ee 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -6919,7 +6919,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; @@ -6960,7 +6960,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..06ade88736 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,9 @@ xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg) xmltype * -xmlelement(XmlExprState *xmlExpr, ExprContext *econtext) +xmlelement(XmlExpr *xexpr, bool *named_argnull, Datum *named_argvalue, bool *argnull, Datum *argvalue) { #ifdef USE_LIBXML - XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr; xmltype *result; List *named_arg_strings; List *arg_strings; @@ -635,45 +633,40 @@ 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. 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. */ 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 */ diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 6992634c39..07cae44069 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -102,6 +102,7 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL; * the DomainConstraintState nodes and applying ExecInitExpr to check_expr. * Such a state tree is not part of the DomainConstraintCache, but is * considered to belong to a DomainConstraintRef. + * FIXME: update. */ struct DomainConstraintCache { @@ -779,8 +780,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 +860,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 +948,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); } diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h new file mode 100644 index 0000000000..003f4faede --- /dev/null +++ b/src/include/executor/execExpr.h @@ -0,0 +1,456 @@ +/*------------------------------------------------------------------------- + * + * 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" + +struct ArrayRefState; + +#define EEO_FLAG_JUMP_THREADED (1 << 0) + +typedef enum ExprEvalOp +{ + EEO_DONE, + EEO_INNER_FETCHSOME, + EEO_OUTER_FETCHSOME, + EEO_SCAN_FETCHSOME, + EEO_INNER_VAR, + EEO_OUTER_VAR, + EEO_SCAN_VAR, + EEO_ASSIGN_INNER_VAR, + EEO_ASSIGN_OUTER_VAR, + EEO_ASSIGN_SCAN_VAR, + EEO_ASSIGN_TMP, + EEO_ASSIGN_TMP_UNEXPAND, + EEO_INNER_SYSVAR, + EEO_OUTER_SYSVAR, + EEO_SCAN_SYSVAR, + EEO_CONST, + EEO_FUNCEXPR, + EEO_FUNCEXPR_STRICT, + EEO_FUNCEXPR_FUSAGE, + EEO_FUNCEXPR_STRICT_FUSAGE, + EEO_BOOL_AND_STEP_FIRST, + EEO_BOOL_AND_STEP, + EEO_BOOL_AND_STEP_LAST, + EEO_BOOL_OR_STEP_FIRST, + EEO_BOOL_OR_STEP, + EEO_BOOL_OR_STEP_LAST, + EEO_BOOL_NOT_STEP, + EEO_QUAL, + EEO_NULLTEST_ISNULL, + EEO_NULLTEST_ISNOTNULL, + EEO_NULLTEST_ROWISNULL, + EEO_NULLTEST_ROWISNOTNULL, + EEO_PARAM_EXEC, + EEO_PARAM_EXTERN, + EEO_CASE_WHEN_STEP, + EEO_CASE_THEN_STEP, + EEO_CASE_TESTVAL, + EEO_CASE_TESTVAL_UNEXPAND, + EEO_COALESCE, + EEO_BOOLTEST_IS_TRUE, + EEO_BOOLTEST_IS_NOT_TRUE, + EEO_BOOLTEST_IS_FALSE, + EEO_BOOLTEST_IS_NOT_FALSE, + EEO_BOOLTEST_IS_UNKNOWN, + EEO_BOOLTEST_IS_NOT_UNKNOWN, + EEO_WHOLEROW, + EEO_IOCOERCE, + EEO_DISTINCT, + EEO_NULLIF, + EEO_SQLVALUEFUNCTION, + EEO_CURRENTOFEXPR, + EEO_ARRAYEXPR, + EEO_ARRAYCOERCE, + EEO_ROW, + EEO_ROWCOMPARE_STEP, + EEO_ROWCOMPARE_FINAL, + EEO_MINMAX, + EEO_FIELDSELECT, + EEO_FIELDSTORE_DEFORM, + EEO_FIELDSTORE_FORM, + EEO_ARRAYREF_CHECKINPUT, + EEO_ARRAYREF_CHECKSUBSCRIPT, + EEO_ARRAYREF_OLD, + EEO_ARRAYREF_ASSIGN, + EEO_ARRAYREF_FETCH, + EEO_CONVERT_ROWTYPE, + EEO_SCALARARRAYOP, + EEO_DOMAIN_TESTVAL, + EEO_DOMAIN_TESTVAL_UNEXPAND, + EEO_DOMAIN_NOTNULL, + EEO_DOMAIN_CHECK, + EEO_XMLEXPR, + EEO_AGGREF, + EEO_GROUPING_FUNC, + EEO_WINDOW_FUNC, + EEO_SUBPLAN, + EEO_ALTERNATIVE_SUBPLAN, + EEO_LAST +} ExprEvalOp; + + +typedef struct ExprEvalStep +{ + /* + * Instruction to be executed. During instruction preparation this is an + * ExprEvalOp, but during execution it can be swapped out to some other + * type, e.g. a pointer for computed goto (that's why it's a size_t). + */ + size_t opcode; + + /* target for the result of the current instruction */ + bool *resnull; + Datum *resvalue; + + /* + * Data for an operation. Inline stored data is faster to access, but also + * bloats the size of all instructions. The union should be kept below 48 + * bytes (so the entire struct is below 64bytes, a single cacheline on + * common systems). + */ + union + { + struct + { + int attnum; + } var; + + struct + { + Var *var; + bool first; /* first time through, initialize */ + TupleDesc tupdesc; /* descriptor for resulting tuples */ + JunkFilter *junkFilter; /* JunkFilter to remove resjunk cols */ + } wholerow; + + struct + { + Datum value; + bool isnull; + } constval; + + struct + { + FmgrInfo *finfo; + FunctionCallInfo fcinfo_data; + /* faster to access without additional indirection */ + PGFunction fn_addr; + int nargs; + } func; + + struct + { + Datum *value; + bool *isnull; + bool *anynull; + int jumpdone; + } boolexpr; + + struct + { + int jumpdone; + } qualexpr; + + struct + { + TupleDesc argdesc; + } nulltest_row; + + struct + { + int paramid; + int paramtype; + } param; + + struct + { + Datum *value; + bool *isnull; + int jumpfalse; + } casewhen; + + struct + { + Datum *value; + bool *isnull; + int jumpdone; + } casethen; + + struct + { + Datum *value; + bool *isnull; + } casetest; + + struct + { + int jumpdone; + } coalesce; + + struct + { + /* lookup info for source output function */ + FmgrInfo *finfo_out; + FunctionCallInfo fcinfo_data_out; + /* lookup info for result input function */ + FmgrInfo *finfo_in; + FunctionCallInfo fcinfo_data_in; + Oid intypioparam; /* argument needed for input function */ + } iocoerce; + + struct + { + SQLValueFunction *svf; + } sqlvaluefunction; + + struct + { + ArrayExpr *arrayexpr; + Datum *elemvalues; + bool *elemnulls; + int nelems; + int16 elemlength; /* typlen of the array element type */ + bool elembyval; /* is the element type pass-by-value? */ + char elemalign; /* typalign of the element type */ + } arrayexpr; + + 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; + + struct + { + RowExpr *rowexpr; + /* the arguments */ + Datum *elemvalues; + bool *elemnulls; + TupleDesc tupdesc; /* descriptor for result tuples */ + } row; + + struct + { + FmgrInfo *finfo; + FunctionCallInfo fcinfo_data; + PGFunction fn_addr; + int jumpnull; + int jumpdone; + } rowcompare_step; + + struct + { + RowCompareType rctype; + } rowcompare_final; + + struct + { + /* the arguments */ + Datum *values; + bool *nulls; + int nelems; + + MinMaxOp op; + FmgrInfo *finfo; + FunctionCallInfo fcinfo_data; + } minmax; + + struct + { + /* tupdesc for most recent input */ + TupleDesc argdesc; + AttrNumber fieldnum; + Oid resulttype; + } fieldselect; + + struct + { + /* tupdesc for most recent input */ + TupleDesc *argdesc; + FieldStore *fstore; + + /* the arguments */ + Datum *values; + bool *nulls; + } fieldstore; + + struct + { + /* too big to have inline */ + struct ArrayRefState *state; + int jumpdone; + } arrayref; + + struct + { + /* too big to have inline */ + struct ArrayRefState *state; + int off; + int jumpdone; + bool isupper; + } arrayref_checksubscript; + + struct + { + ConvertRowtypeExpr *convert; + TupleDesc indesc; + TupleDesc outdesc; + TupleConversionMap *map; + bool initialized; + } convert_rowtype; + + struct + { + ScalarArrayOpExpr *opexpr; + Oid element_type; + int16 typlen; + bool typbyval; + char typalign; + FmgrInfo *finfo; + FunctionCallInfo fcinfo_data; + PGFunction fn_addr; + } scalararrayop; + + struct + { + char *constraintname; + Datum *checkvalue; + bool *checknull; + Oid resulttype; + } domaincheck; + + struct + { + XmlExpr *xexpr; + Datum *named_argvalue; + bool *named_argnull; + Datum *argvalue; + bool *argnull; + } xmlexpr; + + struct + { + AggrefExprState *astate; + } aggref; + + struct + { + AggState *parent; + List *clauses; + } grouping_func; + + struct + { + WindowFuncExprState *wfstate; + } window_func; + + struct + { + SubPlanState *sstate; + } subplan; + + struct + { + AlternativeSubPlanState *asstate; + } alternative_subplan; + + struct + { + size_t resultnum; + int attnum; + } assign_var; + + struct + { + size_t resultnum; + } assign_tmp; + + struct + { + int last_var; + } fetch; + } d; +} ExprEvalStep; + + +typedef struct ArrayRefState +{ + bool isassignment; + int numupper; + Datum upper[MAXDIM]; + int upperindex[MAXDIM]; + bool uppernull[MAXDIM]; + bool upperprovided[MAXDIM]; + int numlower; + Datum lower[MAXDIM]; + int lowerindex[MAXDIM]; + bool lowernull[MAXDIM]; + bool lowerprovided[MAXDIM]; + + Oid refelemtype; + 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 */ + + Datum replacevalue; + bool replacenull; + + Datum prevvalue; + bool prevnull; +} ArrayRefState; + +extern void ExecInstantiateInterpretedExpr(ExprState *state); + +extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op); + +/* + * Non fast-path execution functions. These are externs instead of static in + * execInterpExpr.c, because that allows them to be used by other methods of + * expression evaluation. + */ +extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op); +extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op); +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 ExecEvalArrayRefCheckSubscript(ExprState *state, ExprEvalStep *op); +extern void ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op); +extern void ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op); +extern void ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op); +extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op); +extern void ExecEvalRowNotNull(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..d828a3f907 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 * ---------------- diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index f722f33a66..1e6609f166 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -70,8 +70,8 @@ * 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)) +#define ExecEvalExpr(state, econtext, isNull) \ + (state)->evalfunc((state), econtext, isNull) /* Hook for plugins to get control in ExecutorStart() */ @@ -247,23 +247,118 @@ 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 Tuplestorestate *ExecMakeTableFunctionResult(SetExprState *setexpr, ExprContext *econtext, MemoryContext argContext, TupleDesc expectedDesc, bool randomAccess); -extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache, +extern Datum ExecMakeFunctionResultSet(SetExprState *setexpr, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext, - bool *isNull); +extern SetExprState *ExecInitFunctionResultSet(Expr *expr, ExprContext *econtext, PlanState *parent); +extern SetExprState *ExecInitTableFunctionResult(Expr *expr, ExprContext *econtext, PlanState *parent); + +/* + * prototypes from functions in execExpr.c + */ extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); +extern List *ExecInitExprList(List *nodes, PlanState *parent); extern ExprState *ExecPrepareExpr(Expr *node, EState *estate); -extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull); +extern List *ExecPrepareExprList(List *nodes, EState *estate); +extern ExprState *ExecPrepareQual(List *qual, EState *estate); +extern ExprState *ExecPrepareCheck(List *qual, EState *estate); +extern ExprState *ExecInitQual(List *qual, PlanState *parent); +extern ExprState *ExecInitCheck(List *qual, PlanState *parent); + +extern bool ExecCheck(ExprState *state, ExprContext *context); extern int ExecTargetListLength(List *targetlist); extern int ExecCleanTargetListLength(List *targetlist); -extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo); + +/* + * 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 = ExecEvalExpr(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) +{ + bool isnull; + ExprContext *econtext = projInfo->pi_exprContext; + ExprState *state = &projInfo->pi_state; + TupleTableSlot *slot = state->resultslot; + + /* + * 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); + + ExecEvalExprSwitchContext(state, econtext, &isnull); + + /* + * Successfully formed a result row. Mark the result slot as containing a + * valid virtual tuple. + */ + slot->tts_isempty = false; + slot->tts_nvalid = slot->tts_tupleDescriptor->natts; + + return slot; +} +#endif + +/* + * ExecQual - evaluate a qual prepared with ExecInitQual (possibly via + * ExecPrepareQual). + */ +#ifndef FRONTEND +static inline bool +ExecQual(ExprState *state, ExprContext *econtext) +{ + bool isnull; + Datum ret; + + /* short-circuit (here and in ExecInitQual) for empty restriction list */ + if (state == NULL) + return true; + + ret = ExecEvalExprSwitchContext(state, econtext, &isnull); + + /* EEO_QUAL should never return NULL */ + Assert(!isnull); + + return DatumGetBool(ret); +} +#endif /* * prototypes from functions in execScan.c @@ -361,6 +456,7 @@ extern void ExecGetLastAttnums(Node *node, extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList, ExprContext *econtext, TupleTableSlot *slot, + PlanState *planstate, TupleDesc inputDesc); extern void ExecAssignProjectionInfo(PlanState *planstate, TupleDesc inputDesc); diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h index 0f821dc8f6..0bdaa548df 100644 --- a/src/include/executor/nodeSubplan.h +++ b/src/include/executor/nodeSubplan.h @@ -20,6 +20,9 @@ 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/nodes/execnodes.h b/src/include/nodes/execnodes.h index f856f6036f..8d30382746 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -30,60 +30,6 @@ #include "storage/condition_variable.h" -/* ---------------- - * IndexInfo information - * - * this struct holds the information needed to construct new index - * entries for a particular index. Used for both index_build and - * retail creation of index entries. - * - * NumIndexAttrs number of columns in this index - * KeyAttrNumbers underlying-rel attribute numbers used as keys - * (zeroes indicate expressions) - * Expressions expr trees for expression entries, or NIL if none - * ExpressionsState exec state for expressions, or NIL if none - * Predicate partial-index predicate, or NIL if none - * PredicateState exec state for predicate, or NIL if none - * ExclusionOps Per-column exclusion operators, or NULL if none - * ExclusionProcs Underlying function OIDs for ExclusionOps - * ExclusionStrats Opclass strategy numbers for ExclusionOps - * UniqueOps Theses are like Exclusion*, but for unique indexes - * UniqueProcs - * UniqueStrats - * Unique is it a unique index? - * ReadyForInserts is it valid for inserts? - * Concurrent are we doing a concurrent index build? - * BrokenHotChain did we detect any broken HOT chains? - * AmCache private cache area for index AM - * Context memory context holding this IndexInfo - * - * ii_Concurrent and ii_BrokenHotChain are used only during index build; - * they're conventionally set to false otherwise. - * ---------------- - */ -typedef struct IndexInfo -{ - NodeTag type; - int ii_NumIndexAttrs; - AttrNumber ii_KeyAttrNumbers[INDEX_MAX_KEYS]; - List *ii_Expressions; /* list of Expr */ - List *ii_ExpressionsState; /* list of ExprState */ - List *ii_Predicate; /* list of Expr */ - List *ii_PredicateState; /* list of ExprState */ - 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 */ - Oid *ii_UniqueOps; /* array with one entry per column */ - Oid *ii_UniqueProcs; /* array with one entry per column */ - uint16 *ii_UniqueStrats; /* array with one entry per column */ - bool ii_Unique; - bool ii_ReadyForInserts; - bool ii_Concurrent; - bool ii_BrokenHotChain; - void *ii_AmCache; - MemoryContext ii_Context; -} IndexInfo; - /* ---------------- * ExprContext_CB * @@ -207,6 +153,50 @@ typedef struct ReturnSetInfo TupleDesc setDesc; /* actual descriptor for returned tuples */ } ReturnSetInfo; +/* ---------------- + * ExprState node + * + * ExprState is the top-level node for expression evaluation. It contains + * instructions (in ->steps) to evaluate the expression. + * ---------------- + */ +typedef struct ExprState ExprState; + +typedef Datum (*ExprStateEvalFunc) (ExprState *expression, + ExprContext *econtext, + bool *isNull); +struct ExprState +{ + Node tag; + + bool resnull; + int8 flags; + Datum resvalue; + + /* forward declaration, to avoid including execExpr.h everywhere */ + struct ExprEvalStep *steps; + + TupleTableSlot *resultslot; /* slot for projections */ + + /* evaluate expression */ + ExprStateEvalFunc evalfunc; + + /* original expression, for debugging only */ + Expr *expr; + + /* + * XXX: following only needed during "compilation", could be thrown away. + */ + size_t steps_len; + size_t steps_alloc; + + Datum *innermost_caseval; + bool *innermost_casenull; + + Datum *innermost_domainval; + bool *innermost_domainnull; +}; + /* ---------------- * ProjectionInfo node information * @@ -214,51 +204,16 @@ typedef struct ReturnSetInfo * that is, form new tuples by evaluation of targetlist expressions. * Nodes which need to do projections create one of these. * - * 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) + * pi_state instructions to evaluate projection * 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; + ExprState pi_state; 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; /* ---------------- @@ -299,6 +254,60 @@ typedef struct JunkFilter AttrNumber jf_junkAttNo; } JunkFilter; +/* ---------------- + * IndexInfo information + * + * this struct holds the information needed to construct new index + * entries for a particular index. Used for both index_build and + * retail creation of index entries. + * + * NumIndexAttrs number of columns in this index + * KeyAttrNumbers underlying-rel attribute numbers used as keys + * (zeroes indicate expressions) + * Expressions expr trees for expression entries, or NIL if none + * ExpressionsState exec state for expressions, or NIL if none + * Predicate partial-index predicate, or NIL if none + * PredicateState exec state for predicate, or NIL if none + * ExclusionOps Per-column exclusion operators, or NULL if none + * ExclusionProcs Underlying function OIDs for ExclusionOps + * ExclusionStrats Opclass strategy numbers for ExclusionOps + * UniqueOps Theses are like Exclusion*, but for unique indexes + * UniqueProcs + * UniqueStrats + * Unique is it a unique index? + * ReadyForInserts is it valid for inserts? + * Concurrent are we doing a concurrent index build? + * BrokenHotChain did we detect any broken HOT chains? + * AmCache private cache area for index AM + * Context memory context holding this IndexInfo + * + * ii_Concurrent and ii_BrokenHotChain are used only during index build; + * they're conventionally set to false otherwise. + * ---------------- + */ +typedef struct IndexInfo +{ + NodeTag type; + int ii_NumIndexAttrs; + AttrNumber ii_KeyAttrNumbers[INDEX_MAX_KEYS]; + List *ii_Expressions; /* list of Expr */ + List *ii_ExpressionsState; /* list of ExprState */ + List *ii_Predicate; /* list of Expr */ + 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 */ + Oid *ii_UniqueOps; /* array with one entry per column */ + Oid *ii_UniqueProcs; /* array with one entry per column */ + uint16 *ii_UniqueStrats; /* array with one entry per column */ + bool ii_Unique; + bool ii_ReadyForInserts; + bool ii_Concurrent; + bool ii_BrokenHotChain; + void *ii_AmCache; + MemoryContext ii_Context; +} IndexInfo; + /* ---------------- * ResultRelInfo information * @@ -340,20 +349,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; @@ -436,7 +445,7 @@ typedef struct EState bool *es_epqScanDone; /* true if EPQ tuple has been fetched */ /* The per-query shared memory area to use for parallel execution. */ - struct dsa_area *es_query_dsa; + struct dsa_area *es_query_dsa; } EState; @@ -562,142 +571,53 @@ typedef tuplehash_iterator TupleHashIterator; #define ScanTupleHashTable(htable, iter) \ tuplehash_iterate(htable->hashtab, iter) - -/* ---------------------------------------------------------------- - * Expression State Trees - * - * Each executable expression tree has a parallel ExprState tree. - * - * Unlike PlanState, there is not an exact one-for-one correspondence between - * ExprState node types and Expr node types. Many Expr node types have no - * need for node-type-specific run-time state, and so they can use plain - * ExprState or GenericExprState as their associated ExprState node type. - * ---------------------------------------------------------------- - */ - -/* ---------------- - * 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; 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; + NodeTag type; + WindowFunc *wfunc; List *args; /* states of 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 uses the same machinery as SRFs + * (i.e. are treated like a SRF returning a single row). * ---------------- */ -typedef struct FuncExprState +typedef struct SetExprState { - ExprState xprstate; + NodeTag type; + Expr *expr; List *args; /* states of argument expressions */ + /* + * In ROWS FROM functions possibly can be inlined removing the FuncExpr + * normally inside, this is the containing expression (which cannot return + * a set) which'll be evaluated using the normal ExecEvalExpr(). + */ + ExprState *elidedFuncState; + /* * Function manager's lookup info for the target function. If func.fn_oid * is InvalidOid, we haven't initialized it yet (nor any of the following @@ -750,33 +670,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 +678,8 @@ typedef struct BoolExprState */ typedef struct SubPlanState { - ExprState xprstate; + NodeTag type; + SubPlan *subplan; struct PlanState *planstate; /* subselect plan's state tree */ struct PlanState *parent; /* parent plan node's state tree */ ExprState *testexpr; /* state of combining expression */ @@ -814,197 +709,12 @@ typedef struct SubPlanState */ typedef struct AlternativeSubPlanState { - ExprState xprstate; + NodeTag type; + AlternativeSubPlan *subplan; List *subplans; /* states 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 * @@ -1023,7 +733,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 evaluation state */ } DomainConstraintState; @@ -1060,8 +771,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; /* implicitly-ANDed qual conditions */ struct PlanState *lefttree; /* input plan tree(s) */ struct PlanState *righttree; List *initPlan; /* Init SubPlanState nodes (un-correlated expr @@ -1138,6 +848,7 @@ typedef struct ResultState 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 +1083,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 +1129,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 +1245,7 @@ typedef struct ParallelBitmapHeapState typedef struct BitmapHeapScanState { ScanState ss; /* its first field is NodeTag */ - List *bitmapqualorig; + ExprState *bitmapqualorig; TIDBitmap *tbm; TBMIterator *tbmiterator; TBMIterateResult *tbmres; @@ -1563,7 +1274,6 @@ typedef struct BitmapHeapScanState typedef struct TidScanState { ScanState ss; /* its first field is NodeTag */ - List *tss_tidquals; /* list of ExprState nodes */ bool tss_isCurrentOf; int tss_NumTids; int tss_TidPtr; @@ -1712,7 +1422,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 +1469,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 +1567,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 2bc7a5df11..b39fcd197a 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -192,36 +192,19 @@ 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 nodes do not have a corresponding expression state + * node, they're solely handled within execExpr* - but sometimes the state + * needs to be shared with other parts of the executor, as e.g. the case + * for 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/xml.h b/src/include/utils/xml.h index e570b71c04..c6857e536d 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -61,7 +61,7 @@ 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, bool *named_argnull, Datum *named_argvalue, bool *argnull, Datum *argvalue); 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 196e518e0d..be4983e8c1 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; @@ -4711,7 +4711,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/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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3487f7becb..76ed6830a9 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -578,6 +578,8 @@ ExprContext_CB ExprDoneCond ExprState ExprStateEvalFunc +ExprEvalOp +ExprEvalStep ExtensibleNode ExtensibleNodeEntry ExtensibleNodeMethods @@ -1908,6 +1910,7 @@ Session SetConstraintState SetConstraintStateData SetConstraintTriggerData +SetExprState SetFunctionReturnMode SetOp SetOpCmd