From b8d7f053c5c2bf2a7e8734fe3327f6a8bc711755 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Tue, 14 Mar 2017 15:45:36 -0700 Subject: [PATCH] Faster expression evaluation and targetlist projection. This replaces the old, recursive tree-walk based evaluation, with non-recursive, opcode dispatch based, expression evaluation. Projection is now implemented as part of expression evaluation. 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 performance-critical code-paths can easily be shared between interpreted and compiled evaluation. The new framework allows for significant future optimizations. E.g.: - basic infrastructure for to later reduce the per executor-startup overhead of expression evaluation, by caching state in prepared statements. That'd be helpful in OLTPish scenarios where initialization overhead is measurable. - optimizing the generated "code". A number of proposals for potential work has already been made. - optimizing the interpreter. Similarly a number of proposals have been made here too. The move of logic into the expression initialization step leads to some backward-incompatible changes: - Function permission checks are now done during expression initialization, whereas previously they were done during execution. In edge cases this can lead to errors being raised that previously wouldn't have been, e.g. a NULL array being coerced to a different array type previously didn't perform checks. - The set of domain constraints to be checked, is now evaluated once during expression initialization, previously it was re-built every time a domain check was evaluated. For normal queries this doesn't change much, but e.g. for plpgsql functions, which caches ExprStates, the old set could stick around longer. The behavior around might still change. Author: Andres Freund, with significant changes by Tom Lane, changes by Heikki Linnakangas Reviewed-By: Tom Lane, Heikki Linnakangas Discussion: https://postgr.es/m/20161206034955.bh33paeralxbtluv@alap3.anarazel.de --- contrib/postgres_fdw/postgres_fdw.c | 2 +- src/backend/bootstrap/bootstrap.c | 2 +- src/backend/catalog/index.c | 43 +- src/backend/catalog/partition.c | 3 +- src/backend/catalog/toasting.c | 2 +- src/backend/commands/analyze.c | 10 +- src/backend/commands/explain.c | 2 +- src/backend/commands/indexcmds.c | 4 +- src/backend/commands/prepare.c | 4 +- src/backend/commands/tablecmds.c | 25 +- src/backend/commands/trigger.c | 10 +- src/backend/executor/Makefile | 7 +- src/backend/executor/README | 176 +- src/backend/executor/execExpr.c | 2665 +++++++++++ src/backend/executor/execExprInterp.c | 3525 ++++++++++++++ src/backend/executor/execIndexing.c | 20 +- src/backend/executor/execMain.c | 39 +- src/backend/executor/execQual.c | 5313 --------------------- src/backend/executor/execSRF.c | 924 ++++ src/backend/executor/execScan.c | 10 +- src/backend/executor/execUtils.c | 338 +- src/backend/executor/functions.c | 2 +- src/backend/executor/nodeAgg.c | 34 +- src/backend/executor/nodeBitmapHeapscan.c | 17 +- src/backend/executor/nodeCtescan.c | 8 +- src/backend/executor/nodeCustom.c | 8 +- src/backend/executor/nodeForeignscan.c | 15 +- src/backend/executor/nodeFunctionscan.c | 19 +- src/backend/executor/nodeGather.c | 8 +- src/backend/executor/nodeGatherMerge.c | 8 +- src/backend/executor/nodeGroup.c | 12 +- src/backend/executor/nodeHash.c | 12 +- src/backend/executor/nodeHashjoin.c | 44 +- src/backend/executor/nodeIndexonlyscan.c | 15 +- src/backend/executor/nodeIndexscan.c | 24 +- src/backend/executor/nodeMergejoin.c | 33 +- src/backend/executor/nodeModifyTable.c | 38 +- src/backend/executor/nodeNestloop.c | 23 +- src/backend/executor/nodeProjectSet.c | 61 +- src/backend/executor/nodeResult.c | 16 +- src/backend/executor/nodeSamplescan.c | 17 +- src/backend/executor/nodeSeqscan.c | 8 +- src/backend/executor/nodeSubplan.c | 96 +- src/backend/executor/nodeSubqueryscan.c | 8 +- src/backend/executor/nodeTableFuncscan.c | 20 +- src/backend/executor/nodeTidscan.c | 139 +- src/backend/executor/nodeValuesscan.c | 10 +- src/backend/executor/nodeWindowAgg.c | 8 +- src/backend/executor/nodeWorktablescan.c | 8 +- src/backend/optimizer/path/costsize.c | 2 +- src/backend/optimizer/plan/planner.c | 2 +- src/backend/optimizer/util/clauses.c | 12 +- src/backend/utils/adt/domains.c | 29 +- src/backend/utils/adt/ruleutils.c | 4 +- src/backend/utils/adt/xml.c | 45 +- src/backend/utils/cache/typcache.c | 40 +- src/include/executor/execExpr.h | 642 +++ src/include/executor/execdebug.h | 23 +- src/include/executor/executor.h | 178 +- src/include/executor/nodeSubplan.h | 4 + src/include/fmgr.h | 8 + src/include/nodes/execnodes.h | 504 +- src/include/nodes/nodes.h | 30 +- src/include/utils/typcache.h | 3 +- src/include/utils/xml.h | 4 +- src/pl/plpgsql/src/pl_exec.c | 5 +- src/test/regress/expected/case.out | 2 +- src/test/regress/expected/privileges.out | 20 +- src/test/regress/sql/case.sql | 2 +- src/test/regress/sql/privileges.sql | 5 +- src/tools/pgindent/typedefs.list | 6 +- 71 files changed, 8871 insertions(+), 6534 deletions(-) create mode 100644 src/backend/executor/execExpr.c create mode 100644 src/backend/executor/execExprInterp.c create mode 100644 src/backend/executor/execSRF.c create mode 100644 src/include/executor/execExpr.h diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index e8cb2d0b4d..03f14800b0 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -3421,7 +3421,7 @@ prepare_query_params(PlanState *node, * benefit, and it'd require postgres_fdw to know more than is desirable * about Param evaluation.) */ - *param_exprs = (List *) ExecInitExpr((Expr *) fdw_exprs, node); + *param_exprs = ExecInitExprList(fdw_exprs, node); /* Allocate buffer for text form of query parameters. */ *param_values = (const char **) palloc0(numParams * sizeof(char *)); diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 6511c6064b..6cfce4f8dd 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -1084,7 +1084,7 @@ index_register(Oid heap, /* predicate will likely be null, but may as well copy it */ newind->il_info->ii_Predicate = (List *) copyObject(indexInfo->ii_Predicate); - newind->il_info->ii_PredicateState = NIL; + newind->il_info->ii_PredicateState = NULL; /* no exclusion constraints at bootstrap time, so no need to copy */ Assert(indexInfo->ii_ExclusionOps == NULL); Assert(indexInfo->ii_ExclusionProcs == NULL); diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 7924c30369..1eb163f539 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -1658,7 +1658,7 @@ BuildIndexInfo(Relation index) /* fetch index predicate if any */ ii->ii_Predicate = RelationGetIndexPredicate(index); - ii->ii_PredicateState = NIL; + ii->ii_PredicateState = NULL; /* fetch exclusion constraint info if any */ if (indexStruct->indisexclusion) @@ -1774,9 +1774,8 @@ FormIndexDatum(IndexInfo *indexInfo, indexInfo->ii_ExpressionsState == NIL) { /* First time through, set up expression evaluation state */ - indexInfo->ii_ExpressionsState = (List *) - ExecPrepareExpr((Expr *) indexInfo->ii_Expressions, - estate); + indexInfo->ii_ExpressionsState = + ExecPrepareExprList(indexInfo->ii_Expressions, estate); /* Check caller has set up context correctly */ Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot); } @@ -2208,7 +2207,7 @@ IndexBuildHeapRangeScan(Relation heapRelation, Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; double reltuples; - List *predicate; + ExprState *predicate; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -2247,9 +2246,7 @@ IndexBuildHeapRangeScan(Relation heapRelation, econtext->ecxt_scantuple = slot; /* Set up execution state for predicate, if any. */ - predicate = (List *) - ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, - estate); + predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); /* * Prepare for scan of the base relation. In a normal index build, we use @@ -2552,9 +2549,9 @@ IndexBuildHeapRangeScan(Relation heapRelation, * In a partial index, discard tuples that don't satisfy the * predicate. */ - if (predicate != NIL) + if (predicate != NULL) { - if (!ExecQual(predicate, econtext, false)) + if (!ExecQual(predicate, econtext)) continue; } @@ -2619,7 +2616,7 @@ IndexBuildHeapRangeScan(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; return reltuples; } @@ -2646,7 +2643,7 @@ IndexCheckExclusion(Relation heapRelation, HeapTuple heapTuple; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; - List *predicate; + ExprState *predicate; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -2672,9 +2669,7 @@ IndexCheckExclusion(Relation heapRelation, econtext->ecxt_scantuple = slot; /* Set up execution state for predicate, if any. */ - predicate = (List *) - ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, - estate); + predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); /* * Scan all live tuples in the base relation. @@ -2699,9 +2694,9 @@ IndexCheckExclusion(Relation heapRelation, /* * In a partial index, ignore tuples that don't satisfy the predicate. */ - if (predicate != NIL) + if (predicate != NULL) { - if (!ExecQual(predicate, econtext, false)) + if (!ExecQual(predicate, econtext)) continue; } @@ -2732,7 +2727,7 @@ IndexCheckExclusion(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; } @@ -2962,7 +2957,7 @@ validate_index_heapscan(Relation heapRelation, HeapTuple heapTuple; Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; - List *predicate; + ExprState *predicate; TupleTableSlot *slot; EState *estate; ExprContext *econtext; @@ -2992,9 +2987,7 @@ validate_index_heapscan(Relation heapRelation, econtext->ecxt_scantuple = slot; /* Set up execution state for predicate, if any. */ - predicate = (List *) - ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, - estate); + predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); /* * Prepare for scan of the base relation. We need just those tuples @@ -3121,9 +3114,9 @@ validate_index_heapscan(Relation heapRelation, * In a partial index, discard tuples that don't satisfy the * predicate. */ - if (predicate != NIL) + if (predicate != NULL) { - if (!ExecQual(predicate, econtext, false)) + if (!ExecQual(predicate, econtext)) continue; } @@ -3177,7 +3170,7 @@ validate_index_heapscan(Relation heapRelation, /* These may have been pointing to the now-gone estate */ indexInfo->ii_ExpressionsState = NIL; - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; } diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index e01ef864f0..2b5b8e89bb 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -1618,8 +1618,7 @@ FormPartitionKeyDatum(PartitionDispatch pd, GetPerTupleExprContext(estate)->ecxt_scantuple == slot); /* First time through, set up expression evaluation state */ - pd->keystate = (List *) ExecPrepareExpr((Expr *) pd->key->partexprs, - estate); + pd->keystate = ExecPrepareExprList(pd->key->partexprs, estate); } partexpr_item = list_head(pd->keystate); diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index 0e4231668d..29756eb14e 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -307,7 +307,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, indexInfo->ii_Expressions = NIL; indexInfo->ii_ExpressionsState = NIL; indexInfo->ii_Predicate = NIL; - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index c5b5c54bab..404acb2deb 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -713,7 +713,7 @@ compute_index_stats(Relation onerel, double totalrows, TupleTableSlot *slot; EState *estate; ExprContext *econtext; - List *predicate; + ExprState *predicate; Datum *exprvals; bool *exprnulls; int numindexrows, @@ -739,9 +739,7 @@ compute_index_stats(Relation onerel, double totalrows, econtext->ecxt_scantuple = slot; /* Set up execution state for predicate. */ - predicate = castNode(List, - ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, - estate)); + predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); /* Compute and save index expression values */ exprvals = (Datum *) palloc(numrows * attr_cnt * sizeof(Datum)); @@ -764,9 +762,9 @@ compute_index_stats(Relation onerel, double totalrows, ExecStoreTuple(heapTuple, slot, InvalidBuffer, false); /* If index is partial, check predicate */ - if (predicate != NIL) + if (predicate != NULL) { - if (!ExecQual(predicate, econtext, false)) + if (!ExecQual(predicate, econtext)) continue; } numindexrows++; diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index b4c7466666..1036b96aae 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -2890,7 +2890,7 @@ ExplainSubPlans(List *plans, List *ancestors, foreach(lst, plans) { SubPlanState *sps = (SubPlanState *) lfirst(lst); - SubPlan *sp = (SubPlan *) sps->xprstate.expr; + SubPlan *sp = sps->subplan; /* * There can be multiple SubPlan nodes referencing the same physical diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 9618032356..486179938c 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -179,7 +179,7 @@ CheckIndexCompatible(Oid oldId, indexInfo = makeNode(IndexInfo); indexInfo->ii_Expressions = NIL; indexInfo->ii_ExpressionsState = NIL; - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; @@ -551,7 +551,7 @@ DefineIndex(Oid relationId, indexInfo->ii_Expressions = NIL; /* for now */ indexInfo->ii_ExpressionsState = NIL; indexInfo->ii_Predicate = make_ands_implicit((Expr *) stmt->whereClause); - indexInfo->ii_PredicateState = NIL; + indexInfo->ii_PredicateState = NULL; indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 992ba1c9a2..a924610977 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -391,7 +391,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params, } /* Prepare the expressions for execution */ - exprstates = (List *) ExecPrepareExpr((Expr *) params, estate); + exprstates = ExecPrepareExprList(params, estate); paramLI = (ParamListInfo) palloc(offsetof(ParamListInfoData, params) + @@ -407,7 +407,7 @@ EvaluateParams(PreparedStatement *pstmt, List *params, i = 0; foreach(l, exprstates) { - ExprState *n = lfirst(l); + ExprState *n = (ExprState *) lfirst(l); ParamExternData *prm = ¶mLI->params[i]; prm->ptype = param_types[i]; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3b28e8c34f..96cf42a7f8 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -185,7 +185,7 @@ typedef struct NewConstraint Oid refindid; /* OID of PK's index, if FOREIGN */ Oid conid; /* OID of pg_constraint entry, if FOREIGN */ Node *qual; /* Check expr or CONSTR_FOREIGN Constraint */ - List *qualstate; /* Execution state for CHECK */ + ExprState *qualstate; /* Execution state for CHECK expr */ } NewConstraint; /* @@ -4262,7 +4262,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) CommandId mycid; BulkInsertState bistate; int hi_options; - List *partqualstate = NIL; + ExprState *partqualstate = NULL; /* * Open the relation(s). We have surely already locked the existing @@ -4315,8 +4315,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { case CONSTR_CHECK: needscan = true; - con->qualstate = (List *) - ExecPrepareExpr((Expr *) con->qual, estate); + con->qualstate = ExecPrepareExpr((Expr *) con->qual, estate); break; case CONSTR_FOREIGN: /* Nothing to do here */ @@ -4331,9 +4330,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) if (tab->partition_constraint) { needscan = true; - partqualstate = (List *) - ExecPrepareExpr((Expr *) tab->partition_constraint, - estate); + partqualstate = ExecPrepareCheck(tab->partition_constraint, estate); } foreach(l, tab->newvals) @@ -4508,7 +4505,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) switch (con->contype) { case CONSTR_CHECK: - if (!ExecQual(con->qualstate, econtext, true)) + if (!ExecCheck(con->qualstate, econtext)) ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("check constraint \"%s\" is violated by some row", @@ -4524,7 +4521,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) } } - if (partqualstate && !ExecQual(partqualstate, econtext, true)) + if (partqualstate && !ExecCheck(partqualstate, econtext)) ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("partition constraint is violated by some row"))); @@ -6607,8 +6604,7 @@ ATAddCheckConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, newcon = (NewConstraint *) palloc0(sizeof(NewConstraint)); newcon->name = ccon->name; newcon->contype = ccon->contype; - /* ExecQual wants implicit-AND format */ - newcon->qual = (Node *) make_ands_implicit((Expr *) ccon->expr); + newcon->qual = ccon->expr; tab->constraints = lappend(tab->constraints, newcon); } @@ -7786,7 +7782,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) Datum val; char *conbin; Expr *origexpr; - List *exprstate; + ExprState *exprstate; TupleDesc tupdesc; HeapScanDesc scan; HeapTuple tuple; @@ -7817,8 +7813,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) HeapTupleGetOid(constrtup)); conbin = TextDatumGetCString(val); origexpr = (Expr *) stringToNode(conbin); - exprstate = (List *) - ExecPrepareExpr((Expr *) make_ands_implicit(origexpr), estate); + exprstate = ExecPrepareExpr(origexpr, estate); econtext = GetPerTupleExprContext(estate); tupdesc = RelationGetDescr(rel); @@ -7838,7 +7833,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) { ExecStoreTuple(tuple, slot, InvalidBuffer, false); - if (!ExecQual(exprstate, econtext, true)) + if (!ExecCheck(exprstate, econtext)) ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("check constraint \"%s\" is violated by some row", diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index a1bb3e958c..f3b1a52682 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -3057,7 +3057,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, if (trigger->tgqual) { TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); - List **predicate; + ExprState **predicate; ExprContext *econtext; TupleTableSlot *oldslot = NULL; TupleTableSlot *newslot = NULL; @@ -3078,7 +3078,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, * nodetrees for it. Keep them in the per-query memory context so * they'll survive throughout the query. */ - if (*predicate == NIL) + if (*predicate == NULL) { Node *tgqual; @@ -3087,9 +3087,9 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, /* Change references to OLD and NEW to INNER_VAR and OUTER_VAR */ ChangeVarNodes(tgqual, PRS2_OLD_VARNO, INNER_VAR, 0); ChangeVarNodes(tgqual, PRS2_NEW_VARNO, OUTER_VAR, 0); - /* ExecQual wants implicit-AND form */ + /* ExecPrepareQual wants implicit-AND form */ tgqual = (Node *) make_ands_implicit((Expr *) tgqual); - *predicate = (List *) ExecPrepareExpr((Expr *) tgqual, estate); + *predicate = ExecPrepareQual((List *) tgqual, estate); MemoryContextSwitchTo(oldContext); } @@ -3137,7 +3137,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, */ econtext->ecxt_innertuple = oldslot; econtext->ecxt_outertuple = newslot; - if (!ExecQual(*predicate, econtext, false)) + if (!ExecQual(*predicate, econtext)) return false; } diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index d281906cd5..d1c1324399 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -12,9 +12,10 @@ subdir = src/backend/executor top_builddir = ../../.. include $(top_builddir)/src/Makefile.global -OBJS = execAmi.o execCurrent.o execGrouping.o execIndexing.o execJunk.o \ - execMain.o execParallel.o execProcnode.o execQual.o \ - execReplication.o execScan.o execTuples.o \ +OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \ + execGrouping.o execIndexing.o execJunk.o \ + execMain.o execParallel.o execProcnode.o \ + execReplication.o execScan.o execSRF.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ nodeBitmapAnd.o nodeBitmapOr.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o \ diff --git a/src/backend/executor/README b/src/backend/executor/README index f1d1e4c76c..a0045067fb 100644 --- a/src/backend/executor/README +++ b/src/backend/executor/README @@ -44,21 +44,171 @@ Plan Trees and State Trees -------------------------- The plan tree delivered by the planner contains a tree of Plan nodes (struct -types derived from struct Plan). Each Plan node may have expression trees -associated with it, to represent its target list, qualification conditions, -etc. During executor startup we build a parallel tree of identical structure -containing executor state nodes --- every plan and expression node type has -a corresponding executor state node type. Each node in the state tree has a -pointer to its corresponding node in the plan tree, plus executor state data -as needed to implement that node type. This arrangement allows the plan -tree to be completely read-only as far as the executor is concerned: all data -that is modified during execution is in the state tree. Read-only plan trees -make life much simpler for plan caching and reuse. +types derived from struct Plan). During executor startup we build a parallel +tree of identical structure containing executor state nodes --- every plan +node type has a corresponding executor state node type. Each node in the +state tree has a pointer to its corresponding node in the plan tree, plus +executor state data as needed to implement that node type. This arrangement +allows the plan tree to be completely read-only so far as the executor is +concerned: all data that is modified during execution is in the state tree. +Read-only plan trees make life much simpler for plan caching and reuse. + +Each Plan node may have expression trees associated with it, to represent +its target list, qualification conditions, etc. These trees are also +read-only to the executor, but the executor state for expression evaluation +does not mirror the Plan expression's tree shape, as explained below. +Rather, there's just one ExprState node per expression tree, although this +may have sub-nodes for some complex expression node types. Altogether there are four classes of nodes used in these trees: Plan nodes, -their corresponding PlanState nodes, Expr nodes, and their corresponding -ExprState nodes. (Actually, there are also List nodes, which are used as -"glue" in all four kinds of tree.) +their corresponding PlanState nodes, Expr nodes, and ExprState nodes. +(Actually, there are also List nodes, which are used as "glue" in all +three tree-based representations.) + + +Expression Trees and ExprState nodes +------------------------------------ + +Expression trees, in contrast to Plan trees, are not mirrored into a +corresponding tree of state nodes. Instead each separately executable +expression tree (e.g. a Plan's qual or targetlist) is represented by one +ExprState node. The ExprState node contains the information needed to +evaluate the expression in a compact, linear form. That compact form is +stored as a flat array in ExprState->steps[] (an array of ExprEvalStep, +not ExprEvalStep *). + +The reasons for choosing such a representation include: +- commonly the amount of work needed to evaluate one Expr-type node is + small enough that the overhead of having to perform a tree-walk + during evaluation is significant. +- the flat representation can be evaluated non-recursively within a single + function, reducing stack depth and function call overhead. +- such a representation is usable both for fast interpreted execution, + and for compiling into native code. + +The Plan-tree representation of an expression is compiled into an +ExprState node by ExecInitExpr(). As much complexity as possible should +be handled by ExecInitExpr() (and helpers), instead of execution time +where both interpreted and compiled versions would need to deal with the +complexity. Besides duplicating effort between execution approaches, +runtime initialization checks also have a small but noticeable cost every +time the expression is evaluated. Therefore, we allow ExecInitExpr() to +precompute information that we do not expect to vary across execution of a +single query, for example the set of CHECK constraint expressions to be +applied to a domain type. This could not be done at plan time without +greatly increasing the number of events that require plan invalidation. +(Previously, some information of this kind was rechecked on each +expression evaluation, but that seems like unnecessary overhead.) + + +Expression Initialization +------------------------- + +During ExecInitExpr() and similar routines, Expr trees are converted +into the flat representation. Each Expr node might be represented by +zero, one, or more ExprEvalSteps. + +Each ExprEvalStep's work is determined by its opcode (of enum ExprEvalOp) +and it stores the result of its work into the Datum variable and boolean +null flag variable pointed to by ExprEvalStep->resvalue/resnull. +Complex expressions are performed by chaining together several steps. +For example, "a + b" (one OpExpr, with two Var expressions) would be +represented as two steps to fetch the Var values, and one step for the +evaluation of the function underlying the + operator. The steps for the +Vars would have their resvalue/resnull pointing directly to the appropriate +arg[] and argnull[] array elements in the FunctionCallInfoData struct that +is used by the function evaluation step, thus avoiding extra work to copy +the result values around. + +The last entry in a completed ExprState->steps array is always an +EEOP_DONE step; this removes the need to test for end-of-array while +iterating. Also, if the expression contains any variable references (to +user columns of the ExprContext's INNER, OUTER, or SCAN tuples), the steps +array begins with EEOP_*_FETCHSOME steps that ensure that the relevant +tuples have been deconstructed to make the required columns directly +available (cf. slot_getsomeattrs()). This allows individual Var-fetching +steps to be little more than an array lookup. + +Most of ExecInitExpr()'s work is done by the recursive function +ExecInitExprRec() and its subroutines. ExecInitExprRec() maps one Expr +node into the steps required for execution, recursing as needed for +sub-expressions. + +Each ExecInitExprRec() call has to specify where that subexpression's +results are to be stored (via the resv/resnull parameters). This allows +the above scenario of evaluating a (sub-)expression directly into +fcinfo->arg/argnull, but also requires some care: target Datum/isnull +variables may not be shared with another ExecInitExprRec() unless the +results are only needed by steps executing before further usages of those +target Datum/isnull variables. Due to the non-recursiveness of the +ExprEvalStep representation that's usually easy to guarantee. + +ExecInitExprRec() pushes new operations into the ExprState->steps array +using ExprEvalPushStep(). To keep the steps as a consecutively laid out +array, ExprEvalPushStep() has to repalloc the entire array when there's +not enough space. Because of that it is *not* allowed to point directly +into any of the steps during expression initialization. Therefore, the +resv/resnull for a subexpression usually point to some storage that is +palloc'd separately from the steps array. For instance, the +FunctionCallInfoData for a function call step is separately allocated +rather than being part of the ExprEvalStep array. The overall result +of a complete expression is typically returned into the resvalue/resnull +fields of the ExprState node itself. + +Some steps, e.g. boolean expressions, allow skipping evaluation of +certain subexpressions. In the flat representation this amounts to +jumping to some later step rather than just continuing consecutively +with the next step. The target for such a jump is represented by +the integer index in the ExprState->steps array of the step to execute +next. (Compare the EEO_NEXT and EEO_JUMP macros in execExprInterp.c.) + +Typically, ExecInitExprRec() has to push a jumping step into the steps +array, then recursively generate steps for the subexpression that might +get skipped over, then go back and fix up the jump target index using +the now-known length of the subexpression's steps. This is handled by +adjust_jumps lists in execExpr.c. + +The last step in constructing an ExprState is to apply ExecReadyExpr(), +which readies it for execution using whichever execution method has been +selected. + + +Expression Evaluation +--------------------- + +To allow for different methods of expression evaluation, and for +better branch/jump target prediction, expressions are evaluated by +calling ExprState->evalfunc (via ExprEvalExpr() and friends). + +ExprReadyExpr() can choose the method of interpretation by setting +evalfunc to an appropriate function. The default execution function, +ExecInterpExpr, is implemented in execExprInterp.c; see its header +comment for details. Special-case evalfuncs are used for certain +especially-simple expressions. + +Note that a lot of the more complex expression evaluation steps, which are +less performance-critical than the simpler ones, are implemented as +separate functions outside the fast-path of expression execution, allowing +their implementation to be shared between interpreted and compiled +expression evaluation. This means that these helper functions are not +allowed to perform expression step dispatch themselves, as the method of +dispatch will vary based on the caller. The helpers therefore cannot call +for the execution of subexpressions; all subexpression results they need +must be computed by earlier steps. And dispatch to the following +expression step must be performed after returning from the helper. + + +Targetlist Evaluation +--------------------- + +ExecBuildProjectionInfo builds an ExprState that has the effect of +evaluating a targetlist into ExprState->resultslot. A generic targetlist +expression is executed by evaluating it as discussed above (storing the +result into the ExprState's resvalue/resnull fields) and then using an +EEOP_ASSIGN_TMP step to move the result into the appropriate tts_values[] +and tts_isnull[] array elements of the result slot. There are special +fast-path step types (EEOP_ASSIGN_*_VAR) to handle targetlist entries that +are simple Vars using only one step instead of two. Memory Management diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c new file mode 100644 index 0000000000..766dbbb074 --- /dev/null +++ b/src/backend/executor/execExpr.c @@ -0,0 +1,2665 @@ +/*------------------------------------------------------------------------- + * + * execExpr.c + * Expression evaluation infrastructure. + * + * During executor startup, we compile each expression tree (which has + * previously been processed by the parser and planner) into an ExprState, + * using ExecInitExpr() et al. This converts the tree into a flat array + * of ExprEvalSteps, which may be thought of as instructions in a program. + * At runtime, we'll execute steps, starting with the first, until we reach + * an EEOP_DONE opcode. + * + * This file contains the "compilation" logic. It is independent of the + * specific execution technology we use (switch statement, computed goto, + * JIT compilation, etc). + * + * See src/backend/executor/README for some background, specifically the + * "Expression Trees and ExprState nodes", "Expression Initialization", + * and "Expession Evaluation" sections. + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/execExpr.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/nbtree.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_type.h" +#include "executor/execExpr.h" +#include "executor/nodeSubplan.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/planner.h" +#include "pgstat.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/typcache.h" + + +typedef struct LastAttnumInfo +{ + AttrNumber last_inner; + AttrNumber last_outer; + AttrNumber last_scan; +} LastAttnumInfo; + +static void ExecReadyExpr(ExprState *state); +static void ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state, + Datum *resv, bool *resnull); +static void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s); +static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, + Oid funcid, Oid inputcollid, PlanState *parent, + ExprState *state); +static void ExecInitExprSlots(ExprState *state, Node *node); +static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info); +static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, + PlanState *parent); +static void ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, + PlanState *parent, ExprState *state, + Datum *resv, bool *resnull); +static bool isAssignmentIndirectionExpr(Expr *expr); +static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, + PlanState *parent, ExprState *state, + Datum *resv, bool *resnull); + + +/* + * ExecInitExpr: prepare an expression tree for execution + * + * This function builds and returns an ExprState implementing the given + * Expr node tree. The return ExprState can then be handed to ExecEvalExpr + * for execution. Because the Expr tree itself is read-only as far as + * ExecInitExpr and ExecEvalExpr are concerned, several different executions + * of the same plan tree can occur concurrently. (But note that an ExprState + * does mutate at runtime, so it can't be re-used concurrently.) + * + * This must be called in a memory context that will last as long as repeated + * executions of the expression are needed. Typically the context will be + * the same as the per-query context of the associated ExprContext. + * + * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to + * the lists of such nodes held by the parent PlanState (or more accurately, + * the AggrefExprState etc. nodes created for them are added). + * + * Note: there is no ExecEndExpr function; we assume that any resource + * cleanup needed will be handled by just releasing the memory context + * in which the state tree is built. Functions that require additional + * cleanup work can register a shutdown callback in the ExprContext. + * + * 'node' is the root of the expression tree to compile. + * 'parent' is the PlanState node that owns the expression. + * + * 'parent' may be NULL if we are preparing an expression that is not + * associated with a plan tree. (If so, it can't have aggs or subplans.) + * Such cases should usually come through ExecPrepareExpr, not directly here. + * + * Also, if 'node' is NULL, we just return NULL. This is convenient for some + * callers that may or may not have an expression that needs to be compiled. + * Note that a NULL ExprState pointer *cannot* be handed to ExecEvalExpr, + * although ExecQual and ExecCheck will accept one (and treat it as "true"). + */ +ExprState * +ExecInitExpr(Expr *node, PlanState *parent) +{ + ExprState *state; + ExprEvalStep scratch; + + /* Special case: NULL expression produces a NULL ExprState pointer */ + if (node == NULL) + return NULL; + + /* Initialize ExprState with empty step list */ + state = makeNode(ExprState); + state->expr = node; + + /* Insert EEOP_*_FETCHSOME steps as needed */ + ExecInitExprSlots(state, (Node *) node); + + /* Compile the expression proper */ + ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull); + + /* Finally, append a DONE step */ + scratch.opcode = EEOP_DONE; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + +/* + * ExecInitQual: prepare a qual for execution by ExecQual + * + * Prepares for the evaluation of a conjunctive boolean expression (qual list + * with implicit AND semantics) that returns true if none of the + * subexpressions are false. + * + * We must return true if the list is empty. Since that's a very common case, + * we optimize it a bit further by translating to a NULL ExprState pointer + * rather than setting up an ExprState that computes constant TRUE. (Some + * especially hot-spot callers of ExecQual detect this and avoid calling + * ExecQual at all.) + * + * If any of the subexpressions yield NULL, then the result of the conjunction + * is false. This makes ExecQual primarily useful for evaluating WHERE + * clauses, since SQL specifies that tuples with null WHERE results do not + * get selected. + */ +ExprState * +ExecInitQual(List *qual, PlanState *parent) +{ + ExprState *state; + ExprEvalStep scratch; + List *adjust_jumps = NIL; + ListCell *lc; + + /* short-circuit (here and in ExecQual) for empty restriction list */ + if (qual == NIL) + return NULL; + + Assert(IsA(qual, List)); + + state = makeNode(ExprState); + state->expr = (Expr *) qual; + /* mark expression as to be used with ExecQual() */ + state->flags = EEO_FLAG_IS_QUAL; + + /* Insert EEOP_*_FETCHSOME steps as needed */ + ExecInitExprSlots(state, (Node *) qual); + + /* + * ExecQual() needs to return false for an expression returning NULL. That + * allows us to short-circuit the evaluation the first time a NULL is + * encountered. As qual evaluation is a hot-path this warrants using a + * special opcode for qual evaluation that's simpler than BOOL_AND (which + * has more complex NULL handling). + */ + scratch.opcode = EEOP_QUAL; + + /* + * We can use ExprState's resvalue/resnull as target for each qual expr. + */ + scratch.resvalue = &state->resvalue; + scratch.resnull = &state->resnull; + + foreach(lc, qual) + { + Expr *node = (Expr *) lfirst(lc); + + /* first evaluate expression */ + ExecInitExprRec(node, parent, state, &state->resvalue, &state->resnull); + + /* then emit EEOP_QUAL to detect if it's false (or null) */ + scratch.d.qualexpr.jumpdone = -1; + ExprEvalPushStep(state, &scratch); + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + } + + /* adjust jump targets */ + foreach(lc, adjust_jumps) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->opcode == EEOP_QUAL); + Assert(as->d.qualexpr.jumpdone == -1); + as->d.qualexpr.jumpdone = state->steps_len; + } + + /* + * At the end, we don't need to do anything more. The last qual expr must + * have yielded TRUE, and since its result is stored in the desired output + * location, we're done. + */ + scratch.opcode = EEOP_DONE; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} + +/* + * ExecInitCheck: prepare a check constraint for execution by ExecCheck + * + * This is much like ExecInitQual/ExecQual, except that a null result from + * the conjunction is treated as TRUE. This behavior is appropriate for + * evaluating CHECK constraints, since SQL specifies that NULL constraint + * conditions are not failures. + * + * Note that like ExecInitQual, this expects input in implicit-AND format. + * Users of ExecCheck that have expressions in normal explicit-AND format + * can just apply ExecInitExpr to produce suitable input for ExecCheck. + */ +ExprState * +ExecInitCheck(List *qual, PlanState *parent) +{ + /* short-circuit (here and in ExecCheck) for empty restriction list */ + if (qual == NIL) + return NULL; + + Assert(IsA(qual, List)); + + /* + * Just convert the implicit-AND list to an explicit AND (if there's more + * than one entry), and compile normally. Unlike ExecQual, we can't + * short-circuit on NULL results, so the regular AND behavior is needed. + */ + return ExecInitExpr(make_ands_explicit(qual), parent); +} + +/* + * Call ExecInitExpr() on a list of expressions, return a list of ExprStates. + */ +List * +ExecInitExprList(List *nodes, PlanState *parent) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, nodes) + { + Expr *e = lfirst(lc); + + result = lappend(result, ExecInitExpr(e, parent)); + } + + return result; +} + +/* + * ExecBuildProjectionInfo + * + * Build a ProjectionInfo node for evaluating the given tlist in the given + * econtext, and storing the result into the tuple slot. (Caller must have + * ensured that tuple slot has a descriptor matching the tlist!) + * + * inputDesc can be NULL, but if it is not, we check to see whether simple + * Vars in the tlist match the descriptor. It is important to provide + * inputDesc for relation-scan plan nodes, as a cross check that the relation + * hasn't been changed since the plan was made. At higher levels of a plan, + * there is no need to recheck. + * + * This is implemented by internally building an ExprState that performs the + * whole projection in one go. + * + * Caution: before PG v10, the targetList was a list of ExprStates; now it + * should be the planner-created targetlist, since we do the compilation here. + */ +ProjectionInfo * +ExecBuildProjectionInfo(List *targetList, + ExprContext *econtext, + TupleTableSlot *slot, + PlanState *parent, + TupleDesc inputDesc) +{ + ProjectionInfo *projInfo = makeNode(ProjectionInfo); + ExprState *state; + ExprEvalStep scratch; + ListCell *lc; + + projInfo->pi_exprContext = econtext; + /* We embed ExprState into ProjectionInfo instead of doing extra palloc */ + projInfo->pi_state.tag.type = T_ExprState; + state = &projInfo->pi_state; + state->expr = (Expr *) targetList; + state->resultslot = slot; + + /* Insert EEOP_*_FETCHSOME steps as needed */ + ExecInitExprSlots(state, (Node *) targetList); + + /* Now compile each tlist column */ + foreach(lc, targetList) + { + TargetEntry *tle = castNode(TargetEntry, lfirst(lc)); + Var *variable = NULL; + AttrNumber attnum = 0; + bool isSafeVar = false; + + /* + * If tlist expression is a safe non-system Var, use the fast-path + * ASSIGN_*_VAR opcodes. "Safe" means that we don't need to apply + * CheckVarSlotCompatibility() during plan startup. If a source slot + * was provided, we make the equivalent tests here; if a slot was not + * provided, we assume that no check is needed because we're dealing + * with a non-relation-scan-level expression. + */ + if (tle->expr != NULL && + IsA(tle->expr, Var) && + ((Var *) tle->expr)->varattno > 0) + { + /* Non-system Var, but how safe is it? */ + variable = (Var *) tle->expr; + attnum = variable->varattno; + + if (inputDesc == NULL) + isSafeVar = true; /* can't check, just assume OK */ + else if (attnum <= inputDesc->natts) + { + Form_pg_attribute attr = inputDesc->attrs[attnum - 1]; + + /* + * If user attribute is dropped or has a type mismatch, don't + * use ASSIGN_*_VAR. Instead let the normal expression + * machinery handle it (which'll possibly error out). + */ + if (!attr->attisdropped && variable->vartype == attr->atttypid) + { + isSafeVar = true; + } + } + } + + if (isSafeVar) + { + /* Fast-path: just generate an EEOP_ASSIGN_*_VAR step */ + switch (variable->varno) + { + case INNER_VAR: + /* get the tuple from the inner node */ + scratch.opcode = EEOP_ASSIGN_INNER_VAR; + break; + + case OUTER_VAR: + /* get the tuple from the outer node */ + scratch.opcode = EEOP_ASSIGN_OUTER_VAR; + break; + + /* INDEX_VAR is handled by default case */ + + default: + /* get the tuple from the relation being scanned */ + scratch.opcode = EEOP_ASSIGN_SCAN_VAR; + break; + } + + scratch.d.assign_var.attnum = attnum - 1; + scratch.d.assign_var.resultnum = tle->resno - 1; + ExprEvalPushStep(state, &scratch); + } + else + { + /* + * Otherwise, compile the column expression normally. + * + * We can't tell the expression to evaluate directly into the + * result slot, as the result slot (and the exprstate for that + * matter) can change between executions. We instead evaluate + * into the ExprState's resvalue/resnull and then move. + */ + ExecInitExprRec(tle->expr, parent, state, + &state->resvalue, &state->resnull); + + /* + * Column might be referenced multiple times in upper nodes, so + * force value to R/O - but only if it could be an expanded datum. + */ + if (get_typlen(exprType((Node *) tle->expr)) == -1) + scratch.opcode = EEOP_ASSIGN_TMP_MAKE_RO; + else + scratch.opcode = EEOP_ASSIGN_TMP; + scratch.d.assign_tmp.resultnum = tle->resno - 1; + ExprEvalPushStep(state, &scratch); + } + } + + scratch.opcode = EEOP_DONE; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return projInfo; +} + +/* + * ExecPrepareExpr --- initialize for expression execution outside a normal + * Plan tree context. + * + * This differs from ExecInitExpr in that we don't assume the caller is + * already running in the EState's per-query context. Also, we run the + * passed expression tree through expression_planner() to prepare it for + * execution. (In ordinary Plan trees the regular planning process will have + * made the appropriate transformations on expressions, but for standalone + * expressions this won't have happened.) + */ +ExprState * +ExecPrepareExpr(Expr *node, EState *estate) +{ + ExprState *result; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + node = expression_planner(node); + + result = ExecInitExpr(node, NULL); + + MemoryContextSwitchTo(oldcontext); + + return result; +} + +/* + * ExecPrepareQual --- initialize for qual execution outside a normal + * Plan tree context. + * + * This differs from ExecInitQual in that we don't assume the caller is + * already running in the EState's per-query context. Also, we run the + * passed expression tree through expression_planner() to prepare it for + * execution. (In ordinary Plan trees the regular planning process will have + * made the appropriate transformations on expressions, but for standalone + * expressions this won't have happened.) + */ +ExprState * +ExecPrepareQual(List *qual, EState *estate) +{ + ExprState *result; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + qual = (List *) expression_planner((Expr *) qual); + + result = ExecInitQual(qual, NULL); + + MemoryContextSwitchTo(oldcontext); + + return result; +} + +/* + * ExecPrepareCheck -- initialize check constraint for execution outside a + * normal Plan tree context. + * + * See ExecPrepareExpr() and ExecInitCheck() for details. + */ +ExprState * +ExecPrepareCheck(List *qual, EState *estate) +{ + ExprState *result; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + qual = (List *) expression_planner((Expr *) qual); + + result = ExecInitCheck(qual, NULL); + + MemoryContextSwitchTo(oldcontext); + + return result; +} + +/* + * Call ExecPrepareExpr() on each member of a list of Exprs, and return + * a list of ExprStates. + * + * See ExecPrepareExpr() for details. + */ +List * +ExecPrepareExprList(List *nodes, EState *estate) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, nodes) + { + Expr *e = (Expr *) lfirst(lc); + + result = lappend(result, ExecPrepareExpr(e, estate)); + } + + return result; +} + +/* + * ExecCheck - evaluate a check constraint + * + * For check constraints, a null result is taken as TRUE, ie the constraint + * passes. + * + * The check constraint may have been prepared with ExecInitCheck + * (possibly via ExecPrepareCheck) if the caller had it in implicit-AND + * format, but a regular boolean expression prepared with ExecInitExpr or + * ExecPrepareExpr works too. + */ +bool +ExecCheck(ExprState *state, ExprContext *econtext) +{ + Datum ret; + bool isnull; + + /* short-circuit (here and in ExecInitCheck) for empty restriction list */ + if (state == NULL) + return true; + + /* verify that expression was not compiled using ExecInitQual */ + Assert(!(state->flags & EEO_FLAG_IS_QUAL)); + + ret = ExecEvalExprSwitchContext(state, econtext, &isnull); + + if (isnull) + return true; + + return DatumGetBool(ret); +} + +/* + * Prepare a compiled expression for execution. This has to be called for + * every ExprState before it can be executed. + * + * NB: While this currently only calls ExecReadyInterpretedExpr(), + * this will likely get extended to further expression evaluation methods. + * Therefore this should be used instead of directly calling + * ExecReadyInterpretedExpr(). + */ +static void +ExecReadyExpr(ExprState *state) +{ + ExecReadyInterpretedExpr(state); +} + +/* + * Append the steps necessary for the evaluation of node to ExprState->steps, + * possibly recursing into sub-expressions of node. + * + * node - expression to evaluate + * parent - parent executor node (or NULL if a standalone expression) + * state - ExprState to whose ->steps to append the necessary operations + * resv / resnull - where to store the result of the node into + */ +static void +ExecInitExprRec(Expr *node, PlanState *parent, ExprState *state, + Datum *resv, bool *resnull) +{ + ExprEvalStep scratch; + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + + /* Step's output location is always what the caller gave us */ + Assert(resv != NULL && resnull != NULL); + scratch.resvalue = resv; + scratch.resnull = resnull; + + /* cases should be ordered as they are in enum NodeTag */ + switch (nodeTag(node)) + { + case T_Var: + { + Var *variable = (Var *) node; + + if (variable->varattno == InvalidAttrNumber) + { + /* whole-row Var */ + ExecInitWholeRowVar(&scratch, variable, parent); + } + else if (variable->varattno <= 0) + { + /* system column */ + scratch.d.var.attnum = variable->varattno; + scratch.d.var.vartype = variable->vartype; + switch (variable->varno) + { + case INNER_VAR: + scratch.opcode = EEOP_INNER_SYSVAR; + break; + case OUTER_VAR: + scratch.opcode = EEOP_OUTER_SYSVAR; + break; + + /* INDEX_VAR is handled by default case */ + + default: + scratch.opcode = EEOP_SCAN_SYSVAR; + break; + } + } + else + { + /* regular user column */ + scratch.d.var.attnum = variable->varattno - 1; + scratch.d.var.vartype = variable->vartype; + /* select EEOP_*_FIRST opcode to force one-time checks */ + switch (variable->varno) + { + case INNER_VAR: + scratch.opcode = EEOP_INNER_VAR_FIRST; + break; + case OUTER_VAR: + scratch.opcode = EEOP_OUTER_VAR_FIRST; + break; + + /* INDEX_VAR is handled by default case */ + + default: + scratch.opcode = EEOP_SCAN_VAR_FIRST; + break; + } + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_Const: + { + Const *con = (Const *) node; + + scratch.opcode = EEOP_CONST; + scratch.d.constval.value = con->constvalue; + scratch.d.constval.isnull = con->constisnull; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_Param: + { + Param *param = (Param *) node; + + switch (param->paramkind) + { + case PARAM_EXEC: + scratch.opcode = EEOP_PARAM_EXEC; + scratch.d.param.paramid = param->paramid; + scratch.d.param.paramtype = param->paramtype; + break; + case PARAM_EXTERN: + scratch.opcode = EEOP_PARAM_EXTERN; + scratch.d.param.paramid = param->paramid; + scratch.d.param.paramtype = param->paramtype; + break; + default: + elog(ERROR, "unrecognized paramkind: %d", + (int) param->paramkind); + break; + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_Aggref: + { + Aggref *aggref = (Aggref *) node; + AggrefExprState *astate = makeNode(AggrefExprState); + + scratch.opcode = EEOP_AGGREF; + scratch.d.aggref.astate = astate; + astate->aggref = aggref; + + if (parent && IsA(parent, AggState)) + { + AggState *aggstate = (AggState *) parent; + + aggstate->aggs = lcons(astate, aggstate->aggs); + aggstate->numaggs++; + } + else + { + /* planner messed up */ + elog(ERROR, "Aggref found in non-Agg plan node"); + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_GroupingFunc: + { + GroupingFunc *grp_node = (GroupingFunc *) node; + Agg *agg; + + if (!parent || !IsA(parent, AggState) || + !IsA(parent->plan, Agg)) + elog(ERROR, "GroupingFunc found in non-Agg plan node"); + + scratch.opcode = EEOP_GROUPING_FUNC; + scratch.d.grouping_func.parent = (AggState *) parent; + + agg = (Agg *) (parent->plan); + + if (agg->groupingSets) + scratch.d.grouping_func.clauses = grp_node->cols; + else + scratch.d.grouping_func.clauses = NIL; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_WindowFunc: + { + WindowFunc *wfunc = (WindowFunc *) node; + WindowFuncExprState *wfstate = makeNode(WindowFuncExprState); + + wfstate->wfunc = wfunc; + + if (parent && IsA(parent, WindowAggState)) + { + WindowAggState *winstate = (WindowAggState *) parent; + int nfuncs; + + winstate->funcs = lcons(wfstate, winstate->funcs); + nfuncs = ++winstate->numfuncs; + if (wfunc->winagg) + winstate->numaggs++; + + /* for now initialize agg using old style expressions */ + wfstate->args = ExecInitExprList(wfunc->args, parent); + wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter, + parent); + + /* + * Complain if the windowfunc's arguments contain any + * windowfuncs; nested window functions are semantically + * nonsensical. (This should have been caught earlier, + * but we defend against it here anyway.) + */ + if (nfuncs != winstate->numfuncs) + ereport(ERROR, + (errcode(ERRCODE_WINDOWING_ERROR), + errmsg("window function calls cannot be nested"))); + } + else + { + /* planner messed up */ + elog(ERROR, "WindowFunc found in non-WindowAgg plan node"); + } + + scratch.opcode = EEOP_WINDOW_FUNC; + scratch.d.window_func.wfstate = wfstate; + ExprEvalPushStep(state, &scratch); + break; + } + + case T_ArrayRef: + { + ArrayRef *aref = (ArrayRef *) node; + + ExecInitArrayRef(&scratch, aref, parent, state, resv, resnull); + break; + } + + case T_FuncExpr: + { + FuncExpr *func = (FuncExpr *) node; + + ExecInitFunc(&scratch, node, + func->args, func->funcid, func->inputcollid, + parent, state); + ExprEvalPushStep(state, &scratch); + break; + } + + case T_OpExpr: + { + OpExpr *op = (OpExpr *) node; + + ExecInitFunc(&scratch, node, + op->args, op->opfuncid, op->inputcollid, + parent, state); + ExprEvalPushStep(state, &scratch); + break; + } + + case T_DistinctExpr: + { + DistinctExpr *op = (DistinctExpr *) node; + + ExecInitFunc(&scratch, node, + op->args, op->opfuncid, op->inputcollid, + parent, state); + + /* + * Change opcode of call instruction to EEOP_DISTINCT. + * + * XXX: historically we've not called the function usage + * pgstat infrastructure - that seems inconsistent given that + * we do so for normal function *and* operator evaluation. If + * we decided to do that here, we'd probably want separate + * opcodes for FUSAGE or not. + */ + scratch.opcode = EEOP_DISTINCT; + ExprEvalPushStep(state, &scratch); + break; + } + + case T_NullIfExpr: + { + NullIfExpr *op = (NullIfExpr *) node; + + ExecInitFunc(&scratch, node, + op->args, op->opfuncid, op->inputcollid, + parent, state); + + /* + * Change opcode of call instruction to EEOP_NULLIF. + * + * XXX: historically we've not called the function usage + * pgstat infrastructure - that seems inconsistent given that + * we do so for normal function *and* operator evaluation. If + * we decided to do that here, we'd probably want separate + * opcodes for FUSAGE or not. + */ + scratch.opcode = EEOP_NULLIF; + ExprEvalPushStep(state, &scratch); + break; + } + + case T_ScalarArrayOpExpr: + { + ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node; + Expr *scalararg; + Expr *arrayarg; + FmgrInfo *finfo; + FunctionCallInfo fcinfo; + AclResult aclresult; + + Assert(list_length(opexpr->args) == 2); + scalararg = (Expr *) linitial(opexpr->args); + arrayarg = (Expr *) lsecond(opexpr->args); + + /* Check permission to call function */ + aclresult = pg_proc_aclcheck(opexpr->opfuncid, + GetUserId(), + ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_PROC, + get_func_name(opexpr->opfuncid)); + InvokeFunctionExecuteHook(opexpr->opfuncid); + + /* Set up the primary fmgr lookup information */ + finfo = palloc0(sizeof(FmgrInfo)); + fcinfo = palloc0(sizeof(FunctionCallInfoData)); + fmgr_info(opexpr->opfuncid, finfo); + fmgr_info_set_expr((Node *) node, finfo); + InitFunctionCallInfoData(*fcinfo, finfo, 2, + opexpr->inputcollid, NULL, NULL); + + /* Evaluate scalar directly into left function argument */ + ExecInitExprRec(scalararg, parent, state, + &fcinfo->arg[0], &fcinfo->argnull[0]); + + /* + * Evaluate array argument into our return value. There's no + * danger in that, because the return value is guaranteed to + * be overwritten by EEOP_SCALARARRAYOP, and will not be + * passed to any other expression. + */ + ExecInitExprRec(arrayarg, parent, state, resv, resnull); + + /* And perform the operation */ + scratch.opcode = EEOP_SCALARARRAYOP; + scratch.d.scalararrayop.element_type = InvalidOid; + scratch.d.scalararrayop.useOr = opexpr->useOr; + scratch.d.scalararrayop.finfo = finfo; + scratch.d.scalararrayop.fcinfo_data = fcinfo; + scratch.d.scalararrayop.fn_addr = finfo->fn_addr; + ExprEvalPushStep(state, &scratch); + break; + } + + case T_BoolExpr: + { + BoolExpr *boolexpr = (BoolExpr *) node; + int nargs = list_length(boolexpr->args); + List *adjust_jumps = NIL; + int off; + ListCell *lc; + + /* allocate scratch memory used by all steps of AND/OR */ + if (boolexpr->boolop != NOT_EXPR) + scratch.d.boolexpr.anynull = (bool *) palloc(sizeof(bool)); + + /* + * For each argument evaluate the argument itself, then + * perform the bool operation's appropriate handling. + * + * We can evaluate each argument into our result area, since + * the short-circuiting logic means we only need to remember + * previous NULL values. + * + * AND/OR is split into separate STEP_FIRST (one) / STEP (zero + * or more) / STEP_LAST (one) steps, as each of those has to + * perform different work. The FIRST/LAST split is valid + * because AND/OR have at least two arguments. + */ + off = 0; + foreach(lc, boolexpr->args) + { + Expr *arg = (Expr *) lfirst(lc); + + /* Evaluate argument into our output variable */ + ExecInitExprRec(arg, parent, state, resv, resnull); + + /* Perform the appropriate step type */ + switch (boolexpr->boolop) + { + case AND_EXPR: + Assert(nargs >= 2); + + if (off == 0) + scratch.opcode = EEOP_BOOL_AND_STEP_FIRST; + else if (off + 1 == nargs) + scratch.opcode = EEOP_BOOL_AND_STEP_LAST; + else + scratch.opcode = EEOP_BOOL_AND_STEP; + break; + case OR_EXPR: + Assert(nargs >= 2); + + if (off == 0) + scratch.opcode = EEOP_BOOL_OR_STEP_FIRST; + else if (off + 1 == nargs) + scratch.opcode = EEOP_BOOL_OR_STEP_LAST; + else + scratch.opcode = EEOP_BOOL_OR_STEP; + break; + case NOT_EXPR: + Assert(nargs == 1); + + scratch.opcode = EEOP_BOOL_NOT_STEP; + break; + default: + elog(ERROR, "unrecognized boolop: %d", + (int) boolexpr->boolop); + break; + } + + scratch.d.boolexpr.jumpdone = -1; + ExprEvalPushStep(state, &scratch); + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + off++; + } + + /* adjust jump targets */ + foreach(lc, adjust_jumps) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->d.boolexpr.jumpdone == -1); + as->d.boolexpr.jumpdone = state->steps_len; + } + + break; + } + + case T_SubPlan: + { + SubPlan *subplan = (SubPlan *) node; + SubPlanState *sstate; + + if (!parent) + elog(ERROR, "SubPlan found with no parent plan"); + + sstate = ExecInitSubPlan(subplan, parent); + + /* add SubPlanState nodes to parent->subPlan */ + parent->subPlan = lappend(parent->subPlan, sstate); + + scratch.opcode = EEOP_SUBPLAN; + scratch.d.subplan.sstate = sstate; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_AlternativeSubPlan: + { + AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; + AlternativeSubPlanState *asstate; + + if (!parent) + elog(ERROR, "AlternativeSubPlan found with no parent plan"); + + asstate = ExecInitAlternativeSubPlan(asplan, parent); + + scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN; + scratch.d.alternative_subplan.asstate = asstate; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_FieldSelect: + { + FieldSelect *fselect = (FieldSelect *) node; + + /* evaluate row/record argument into result area */ + ExecInitExprRec(fselect->arg, parent, state, resv, resnull); + + /* and extract field */ + scratch.opcode = EEOP_FIELDSELECT; + scratch.d.fieldselect.fieldnum = fselect->fieldnum; + scratch.d.fieldselect.resulttype = fselect->resulttype; + scratch.d.fieldselect.argdesc = NULL; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_FieldStore: + { + FieldStore *fstore = (FieldStore *) node; + TupleDesc tupDesc; + TupleDesc *descp; + Datum *values; + bool *nulls; + int ncolumns; + ListCell *l1, + *l2; + + /* find out the number of columns in the composite type */ + tupDesc = lookup_rowtype_tupdesc(fstore->resulttype, -1); + ncolumns = tupDesc->natts; + DecrTupleDescRefCount(tupDesc); + + /* create workspace for column values */ + values = (Datum *) palloc(sizeof(Datum) * ncolumns); + nulls = (bool *) palloc(sizeof(bool) * ncolumns); + + /* create workspace for runtime tupdesc cache */ + descp = (TupleDesc *) palloc(sizeof(TupleDesc)); + *descp = NULL; + + /* emit code to evaluate the composite input value */ + ExecInitExprRec(fstore->arg, parent, state, resv, resnull); + + /* next, deform the input tuple into our workspace */ + scratch.opcode = EEOP_FIELDSTORE_DEFORM; + scratch.d.fieldstore.fstore = fstore; + scratch.d.fieldstore.argdesc = descp; + scratch.d.fieldstore.values = values; + scratch.d.fieldstore.nulls = nulls; + scratch.d.fieldstore.ncolumns = ncolumns; + ExprEvalPushStep(state, &scratch); + + /* evaluate new field values, store in workspace columns */ + forboth(l1, fstore->newvals, l2, fstore->fieldnums) + { + Expr *e = (Expr *) lfirst(l1); + AttrNumber fieldnum = lfirst_int(l2); + Datum *save_innermost_caseval; + bool *save_innermost_casenull; + + if (fieldnum <= 0 || fieldnum > ncolumns) + elog(ERROR, "field number %d is out of range in FieldStore", + fieldnum); + + /* + * Use the CaseTestExpr mechanism to pass down the old + * value of the field being replaced; this is needed in + * case the newval is itself a FieldStore or ArrayRef that + * has to obtain and modify the old value. It's safe to + * reuse the CASE mechanism because there cannot be a CASE + * between here and where the value would be needed, and a + * field assignment can't be within a CASE either. (So + * saving and restoring innermost_caseval is just + * paranoia, but let's do it anyway.) + */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_casenull = state->innermost_casenull; + state->innermost_caseval = &values[fieldnum - 1]; + state->innermost_casenull = &nulls[fieldnum - 1]; + + ExecInitExprRec(e, parent, state, + &values[fieldnum - 1], + &nulls[fieldnum - 1]); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_casenull; + } + + /* finally, form result tuple */ + scratch.opcode = EEOP_FIELDSTORE_FORM; + scratch.d.fieldstore.fstore = fstore; + scratch.d.fieldstore.argdesc = descp; + scratch.d.fieldstore.values = values; + scratch.d.fieldstore.nulls = nulls; + scratch.d.fieldstore.ncolumns = ncolumns; + ExprEvalPushStep(state, &scratch); + break; + } + + case T_RelabelType: + { + /* relabel doesn't need to do anything at runtime */ + RelabelType *relabel = (RelabelType *) node; + + ExecInitExprRec(relabel->arg, parent, state, resv, resnull); + break; + } + + case T_CoerceViaIO: + { + CoerceViaIO *iocoerce = (CoerceViaIO *) node; + Oid iofunc; + bool typisvarlena; + Oid typioparam; + FunctionCallInfo fcinfo_in; + + /* evaluate argument into step's result area */ + ExecInitExprRec(iocoerce->arg, parent, state, resv, resnull); + + /* + * Prepare both output and input function calls, to be + * evaluated inside a single evaluation step for speed - this + * can be a very common operation. + * + * We don't check permissions here as a type's input/output + * function are assumed to be executable by everyone. + */ + scratch.opcode = EEOP_IOCOERCE; + + /* lookup the source type's output function */ + scratch.d.iocoerce.finfo_out = palloc0(sizeof(FmgrInfo)); + scratch.d.iocoerce.fcinfo_data_out = palloc0(sizeof(FunctionCallInfoData)); + + getTypeOutputInfo(exprType((Node *) iocoerce->arg), + &iofunc, &typisvarlena); + fmgr_info(iofunc, scratch.d.iocoerce.finfo_out); + fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_out); + InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_out, + scratch.d.iocoerce.finfo_out, + 1, InvalidOid, NULL, NULL); + + /* lookup the result type's input function */ + scratch.d.iocoerce.finfo_in = palloc0(sizeof(FmgrInfo)); + scratch.d.iocoerce.fcinfo_data_in = palloc0(sizeof(FunctionCallInfoData)); + + getTypeInputInfo(iocoerce->resulttype, + &iofunc, &typioparam); + fmgr_info(iofunc, scratch.d.iocoerce.finfo_in); + fmgr_info_set_expr((Node *) node, scratch.d.iocoerce.finfo_in); + InitFunctionCallInfoData(*scratch.d.iocoerce.fcinfo_data_in, + scratch.d.iocoerce.finfo_in, + 3, InvalidOid, NULL, NULL); + + /* + * We can preload the second and third arguments for the input + * function, since they're constants. + */ + fcinfo_in = scratch.d.iocoerce.fcinfo_data_in; + fcinfo_in->arg[1] = ObjectIdGetDatum(typioparam); + fcinfo_in->argnull[1] = false; + fcinfo_in->arg[2] = Int32GetDatum(-1); + fcinfo_in->argnull[2] = false; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_ArrayCoerceExpr: + { + ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; + Oid resultelemtype; + + /* evaluate argument into step's result area */ + ExecInitExprRec(acoerce->arg, parent, state, resv, resnull); + + resultelemtype = get_element_type(acoerce->resulttype); + if (!OidIsValid(resultelemtype)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("target type is not an array"))); + /* Arrays over domains aren't supported yet */ + Assert(getBaseType(resultelemtype) == resultelemtype); + + scratch.opcode = EEOP_ARRAYCOERCE; + scratch.d.arraycoerce.coerceexpr = acoerce; + scratch.d.arraycoerce.resultelemtype = resultelemtype; + + if (OidIsValid(acoerce->elemfuncid)) + { + AclResult aclresult; + + /* Check permission to call function */ + aclresult = pg_proc_aclcheck(acoerce->elemfuncid, + GetUserId(), + ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_PROC, + get_func_name(acoerce->elemfuncid)); + InvokeFunctionExecuteHook(acoerce->elemfuncid); + + /* Set up the primary fmgr lookup information */ + scratch.d.arraycoerce.elemfunc = + (FmgrInfo *) palloc0(sizeof(FmgrInfo)); + fmgr_info(acoerce->elemfuncid, + scratch.d.arraycoerce.elemfunc); + fmgr_info_set_expr((Node *) acoerce, + scratch.d.arraycoerce.elemfunc); + + /* Set up workspace for array_map */ + scratch.d.arraycoerce.amstate = + (ArrayMapState *) palloc0(sizeof(ArrayMapState)); + } + else + { + /* Don't need workspace if there's no conversion func */ + scratch.d.arraycoerce.elemfunc = NULL; + scratch.d.arraycoerce.amstate = NULL; + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_ConvertRowtypeExpr: + { + ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; + + /* evaluate argument into step's result area */ + ExecInitExprRec(convert->arg, parent, state, resv, resnull); + + /* and push conversion step */ + scratch.opcode = EEOP_CONVERT_ROWTYPE; + scratch.d.convert_rowtype.convert = convert; + scratch.d.convert_rowtype.indesc = NULL; + scratch.d.convert_rowtype.outdesc = NULL; + scratch.d.convert_rowtype.map = NULL; + scratch.d.convert_rowtype.initialized = false; + + ExprEvalPushStep(state, &scratch); + break; + } + + /* note that CaseWhen expressions are handled within this block */ + case T_CaseExpr: + { + CaseExpr *caseExpr = (CaseExpr *) node; + List *adjust_jumps = NIL; + Datum *caseval = NULL; + bool *casenull = NULL; + ListCell *lc; + + /* + * If there's a test expression, we have to evaluate it and + * save the value where the CaseTestExpr placeholders can find + * it. + */ + if (caseExpr->arg != NULL) + { + /* Evaluate testexpr into caseval/casenull workspace */ + caseval = palloc(sizeof(Datum)); + casenull = palloc(sizeof(bool)); + + ExecInitExprRec(caseExpr->arg, parent, state, + caseval, casenull); + + /* + * Since value might be read multiple times, force to R/O + * - but only if it could be an expanded datum. + */ + if (get_typlen(exprType((Node *) caseExpr->arg)) == -1) + { + /* change caseval in-place */ + scratch.opcode = EEOP_MAKE_READONLY; + scratch.resvalue = caseval; + scratch.resnull = casenull; + scratch.d.make_readonly.value = caseval; + scratch.d.make_readonly.isnull = casenull; + ExprEvalPushStep(state, &scratch); + /* restore normal settings of scratch fields */ + scratch.resvalue = resv; + scratch.resnull = resnull; + } + } + + /* + * Prepare to evaluate each of the WHEN clauses in turn; as + * soon as one is true we return the value of the + * corresponding THEN clause. If none are true then we return + * the value of the ELSE clause, or NULL if there is none. + */ + foreach(lc, caseExpr->args) + { + CaseWhen *when = (CaseWhen *) lfirst(lc); + Datum *save_innermost_caseval; + bool *save_innermost_casenull; + int whenstep; + + /* + * Make testexpr result available to CaseTestExpr nodes + * within the condition. We must save and restore prior + * setting of innermost_caseval fields, in case this node + * is itself within a larger CASE. + * + * If there's no test expression, we don't actually need + * to save and restore these fields; but it's less code to + * just do so unconditionally. + */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_casenull = state->innermost_casenull; + state->innermost_caseval = caseval; + state->innermost_casenull = casenull; + + /* evaluate condition into CASE's result variables */ + ExecInitExprRec(when->expr, parent, state, resv, resnull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_casenull; + + /* If WHEN result isn't true, jump to next CASE arm */ + scratch.opcode = EEOP_JUMP_IF_NOT_TRUE; + scratch.d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, &scratch); + whenstep = state->steps_len - 1; + + /* + * If WHEN result is true, evaluate THEN result, storing + * it into the CASE's result variables. + */ + ExecInitExprRec(when->result, parent, state, resv, resnull); + + /* Emit JUMP step to jump to end of CASE's code */ + scratch.opcode = EEOP_JUMP; + scratch.d.jump.jumpdone = -1; /* computed later */ + ExprEvalPushStep(state, &scratch); + + /* + * Don't know address for that jump yet, compute once the + * whole CASE expression is built. + */ + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + + /* + * But we can set WHEN test's jump target now, to make it + * jump to the next WHEN subexpression or the ELSE. + */ + state->steps[whenstep].d.jump.jumpdone = state->steps_len; + } + + if (caseExpr->defresult) + { + /* evaluate ELSE expr into CASE's result variables */ + ExecInitExprRec(caseExpr->defresult, parent, state, + resv, resnull); + } + else + { + /* default ELSE is to return NULL */ + scratch.opcode = EEOP_CONST; + scratch.d.constval.value = (Datum) 0; + scratch.d.constval.isnull = true; + ExprEvalPushStep(state, &scratch); + } + + /* adjust jump targets */ + foreach(lc, adjust_jumps) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->opcode == EEOP_JUMP); + Assert(as->d.jump.jumpdone == -1); + as->d.jump.jumpdone = state->steps_len; + } + + break; + } + + case T_CaseTestExpr: + { + /* + * Read from location identified by innermost_caseval. Note + * that innermost_caseval could be NULL, if this node isn't + * actually within a CASE structure; some parts of the system + * abuse CaseTestExpr to cause a read of a value externally + * supplied in econtext->caseValue_datum. We'll take care of + * that scenario at runtime. + */ + scratch.opcode = EEOP_CASE_TESTVAL; + scratch.d.casetest.value = state->innermost_caseval; + scratch.d.casetest.isnull = state->innermost_casenull; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_ArrayExpr: + { + ArrayExpr *arrayexpr = (ArrayExpr *) node; + int nelems = list_length(arrayexpr->elements); + ListCell *lc; + int elemoff; + + /* + * Evaluate by computing each element, and then forming the + * array. Elements are computed into scratch arrays + * associated with the ARRAYEXPR step. + */ + scratch.opcode = EEOP_ARRAYEXPR; + scratch.d.arrayexpr.elemvalues = + (Datum *) palloc(sizeof(Datum) * nelems); + scratch.d.arrayexpr.elemnulls = + (bool *) palloc(sizeof(bool) * nelems); + scratch.d.arrayexpr.nelems = nelems; + + /* fill remaining fields of step */ + scratch.d.arrayexpr.multidims = arrayexpr->multidims; + scratch.d.arrayexpr.elemtype = arrayexpr->element_typeid; + + /* do one-time catalog lookup for type info */ + get_typlenbyvalalign(arrayexpr->element_typeid, + &scratch.d.arrayexpr.elemlength, + &scratch.d.arrayexpr.elembyval, + &scratch.d.arrayexpr.elemalign); + + /* prepare to evaluate all arguments */ + elemoff = 0; + foreach(lc, arrayexpr->elements) + { + Expr *e = (Expr *) lfirst(lc); + + ExecInitExprRec(e, parent, state, + &scratch.d.arrayexpr.elemvalues[elemoff], + &scratch.d.arrayexpr.elemnulls[elemoff]); + elemoff++; + } + + /* and then collect all into an array */ + ExprEvalPushStep(state, &scratch); + break; + } + + case T_RowExpr: + { + RowExpr *rowexpr = (RowExpr *) node; + int nelems = list_length(rowexpr->args); + TupleDesc tupdesc; + Form_pg_attribute *attrs; + int i; + ListCell *l; + + /* Build tupdesc to describe result tuples */ + if (rowexpr->row_typeid == RECORDOID) + { + /* generic record, use types of given expressions */ + tupdesc = ExecTypeFromExprList(rowexpr->args); + } + else + { + /* it's been cast to a named type, use that */ + tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1); + } + /* In either case, adopt RowExpr's column aliases */ + ExecTypeSetColNames(tupdesc, rowexpr->colnames); + /* Bless the tupdesc in case it's now of type RECORD */ + BlessTupleDesc(tupdesc); + + /* + * In the named-type case, the tupdesc could have more columns + * than are in the args list, since the type might have had + * columns added since the ROW() was parsed. We want those + * extra columns to go to nulls, so we make sure that the + * workspace arrays are large enough and then initialize any + * extra columns to read as NULLs. + */ + Assert(nelems <= tupdesc->natts); + nelems = Max(nelems, tupdesc->natts); + + /* + * Evaluate by first building datums for each field, and then + * a final step forming the composite datum. + */ + scratch.opcode = EEOP_ROW; + scratch.d.row.tupdesc = tupdesc; + + /* space for the individual field datums */ + scratch.d.row.elemvalues = + (Datum *) palloc(sizeof(Datum) * nelems); + scratch.d.row.elemnulls = + (bool *) palloc(sizeof(bool) * nelems); + /* as explained above, make sure any extra columns are null */ + memset(scratch.d.row.elemnulls, true, sizeof(bool) * nelems); + + /* Set up evaluation, skipping any deleted columns */ + attrs = tupdesc->attrs; + i = 0; + foreach(l, rowexpr->args) + { + Expr *e = (Expr *) lfirst(l); + + if (!attrs[i]->attisdropped) + { + /* + * Guard against ALTER COLUMN TYPE on rowtype since + * the RowExpr was created. XXX should we check + * typmod too? Not sure we can be sure it'll be the + * same. + */ + if (exprType((Node *) e) != attrs[i]->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("ROW() column has type %s instead of type %s", + format_type_be(exprType((Node *) e)), + format_type_be(attrs[i]->atttypid)))); + } + else + { + /* + * Ignore original expression and insert a NULL. We + * don't really care what type of NULL it is, so + * always make an int4 NULL. + */ + e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid); + } + + /* Evaluate column expr into appropriate workspace slot */ + ExecInitExprRec(e, parent, state, + &scratch.d.row.elemvalues[i], + &scratch.d.row.elemnulls[i]); + i++; + } + + /* And finally build the row value */ + ExprEvalPushStep(state, &scratch); + break; + } + + case T_RowCompareExpr: + { + RowCompareExpr *rcexpr = (RowCompareExpr *) node; + int nopers = list_length(rcexpr->opnos); + List *adjust_jumps = NIL; + ListCell *l_left_expr, + *l_right_expr, + *l_opno, + *l_opfamily, + *l_inputcollid; + ListCell *lc; + int off; + + /* + * Iterate over each field, prepare comparisons. To handle + * NULL results, prepare jumps to after the expression. If a + * comparison yields a != 0 result, jump to the final step. + */ + Assert(list_length(rcexpr->largs) == nopers); + Assert(list_length(rcexpr->rargs) == nopers); + Assert(list_length(rcexpr->opfamilies) == nopers); + Assert(list_length(rcexpr->inputcollids) == nopers); + + off = 0; + for (off = 0, + l_left_expr = list_head(rcexpr->largs), + l_right_expr = list_head(rcexpr->rargs), + l_opno = list_head(rcexpr->opnos), + l_opfamily = list_head(rcexpr->opfamilies), + l_inputcollid = list_head(rcexpr->inputcollids); + off < nopers; + off++, + l_left_expr = lnext(l_left_expr), + l_right_expr = lnext(l_right_expr), + l_opno = lnext(l_opno), + l_opfamily = lnext(l_opfamily), + l_inputcollid = lnext(l_inputcollid)) + { + Expr *left_expr = (Expr *) lfirst(l_left_expr); + Expr *right_expr = (Expr *) lfirst(l_right_expr); + Oid opno = lfirst_oid(l_opno); + Oid opfamily = lfirst_oid(l_opfamily); + Oid inputcollid = lfirst_oid(l_inputcollid); + int strategy; + Oid lefttype; + Oid righttype; + Oid proc; + FmgrInfo *finfo; + FunctionCallInfo fcinfo; + + get_op_opfamily_properties(opno, opfamily, false, + &strategy, + &lefttype, + &righttype); + proc = get_opfamily_proc(opfamily, + lefttype, + righttype, + BTORDER_PROC); + + /* Set up the primary fmgr lookup information */ + finfo = palloc0(sizeof(FmgrInfo)); + fcinfo = palloc0(sizeof(FunctionCallInfoData)); + fmgr_info(proc, finfo); + fmgr_info_set_expr((Node *) node, finfo); + InitFunctionCallInfoData(*fcinfo, finfo, 2, + inputcollid, NULL, NULL); + + /* + * If we enforced permissions checks on index support + * functions, we'd need to make a check here. But the + * index support machinery doesn't do that, and thus + * neither does this code. + */ + + /* evaluate left and right args directly into fcinfo */ + ExecInitExprRec(left_expr, parent, state, + &fcinfo->arg[0], &fcinfo->argnull[0]); + ExecInitExprRec(right_expr, parent, state, + &fcinfo->arg[1], &fcinfo->argnull[1]); + + scratch.opcode = EEOP_ROWCOMPARE_STEP; + scratch.d.rowcompare_step.finfo = finfo; + scratch.d.rowcompare_step.fcinfo_data = fcinfo; + scratch.d.rowcompare_step.fn_addr = finfo->fn_addr; + /* jump targets filled below */ + scratch.d.rowcompare_step.jumpnull = -1; + scratch.d.rowcompare_step.jumpdone = -1; + + ExprEvalPushStep(state, &scratch); + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + } + + /* + * We could have a zero-column rowtype, in which case the rows + * necessarily compare equal. + */ + if (nopers == 0) + { + scratch.opcode = EEOP_CONST; + scratch.d.constval.value = Int32GetDatum(0); + scratch.d.constval.isnull = false; + ExprEvalPushStep(state, &scratch); + } + + /* Finally, examine the last comparison result */ + scratch.opcode = EEOP_ROWCOMPARE_FINAL; + scratch.d.rowcompare_final.rctype = rcexpr->rctype; + ExprEvalPushStep(state, &scratch); + + /* adjust jump targetss */ + foreach(lc, adjust_jumps) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->opcode == EEOP_ROWCOMPARE_STEP); + Assert(as->d.rowcompare_step.jumpdone == -1); + Assert(as->d.rowcompare_step.jumpnull == -1); + + /* jump to comparison evaluation */ + as->d.rowcompare_step.jumpdone = state->steps_len - 1; + /* jump to the following expression */ + as->d.rowcompare_step.jumpnull = state->steps_len; + } + + break; + } + + case T_CoalesceExpr: + { + CoalesceExpr *coalesce = (CoalesceExpr *) node; + List *adjust_jumps = NIL; + ListCell *lc; + + /* We assume there's at least one arg */ + Assert(coalesce->args != NIL); + + /* + * Prepare evaluation of all coalesced arguments, after each + * one push a step that short-circuits if not null. + */ + foreach(lc, coalesce->args) + { + Expr *e = (Expr *) lfirst(lc); + + /* evaluate argument, directly into result datum */ + ExecInitExprRec(e, parent, state, resv, resnull); + + /* if it's not null, skip to end of COALESCE expr */ + scratch.opcode = EEOP_JUMP_IF_NOT_NULL; + scratch.d.jump.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, &scratch); + + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + } + + /* + * No need to add a constant NULL return - we only can get to + * the end of the expression if a NULL already is being + * returned. + */ + + /* adjust jump targets */ + foreach(lc, adjust_jumps) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->opcode == EEOP_JUMP_IF_NOT_NULL); + Assert(as->d.jump.jumpdone == -1); + as->d.jump.jumpdone = state->steps_len; + } + + break; + } + + case T_MinMaxExpr: + { + MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; + int nelems = list_length(minmaxexpr->args); + TypeCacheEntry *typentry; + FmgrInfo *finfo; + FunctionCallInfo fcinfo; + ListCell *lc; + int off; + + /* Look up the btree comparison function for the datatype */ + typentry = lookup_type_cache(minmaxexpr->minmaxtype, + TYPECACHE_CMP_PROC); + if (!OidIsValid(typentry->cmp_proc)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify a comparison function for type %s", + format_type_be(minmaxexpr->minmaxtype)))); + + /* + * If we enforced permissions checks on index support + * functions, we'd need to make a check here. But the index + * support machinery doesn't do that, and thus neither does + * this code. + */ + + /* Perform function lookup */ + finfo = palloc0(sizeof(FmgrInfo)); + fcinfo = palloc0(sizeof(FunctionCallInfoData)); + fmgr_info(typentry->cmp_proc, finfo); + fmgr_info_set_expr((Node *) node, finfo); + InitFunctionCallInfoData(*fcinfo, finfo, 2, + minmaxexpr->inputcollid, NULL, NULL); + + scratch.opcode = EEOP_MINMAX; + /* allocate space to store arguments */ + scratch.d.minmax.values = + (Datum *) palloc(sizeof(Datum) * nelems); + scratch.d.minmax.nulls = + (bool *) palloc(sizeof(bool) * nelems); + scratch.d.minmax.nelems = nelems; + + scratch.d.minmax.op = minmaxexpr->op; + scratch.d.minmax.finfo = finfo; + scratch.d.minmax.fcinfo_data = fcinfo; + + /* evaluate expressions into minmax->values/nulls */ + off = 0; + foreach(lc, minmaxexpr->args) + { + Expr *e = (Expr *) lfirst(lc); + + ExecInitExprRec(e, parent, state, + &scratch.d.minmax.values[off], + &scratch.d.minmax.nulls[off]); + off++; + } + + /* and push the final comparison */ + ExprEvalPushStep(state, &scratch); + break; + } + + case T_SQLValueFunction: + { + SQLValueFunction *svf = (SQLValueFunction *) node; + + scratch.opcode = EEOP_SQLVALUEFUNCTION; + scratch.d.sqlvaluefunction.svf = svf; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_XmlExpr: + { + XmlExpr *xexpr = (XmlExpr *) node; + int nnamed = list_length(xexpr->named_args); + int nargs = list_length(xexpr->args); + int off; + ListCell *arg; + + scratch.opcode = EEOP_XMLEXPR; + scratch.d.xmlexpr.xexpr = xexpr; + + /* allocate space for storing all the arguments */ + if (nnamed) + { + scratch.d.xmlexpr.named_argvalue = + (Datum *) palloc(sizeof(Datum) * nnamed); + scratch.d.xmlexpr.named_argnull = + (bool *) palloc(sizeof(bool) * nnamed); + } + else + { + scratch.d.xmlexpr.named_argvalue = NULL; + scratch.d.xmlexpr.named_argnull = NULL; + } + + if (nargs) + { + scratch.d.xmlexpr.argvalue = + (Datum *) palloc(sizeof(Datum) * nargs); + scratch.d.xmlexpr.argnull = + (bool *) palloc(sizeof(bool) * nargs); + } + else + { + scratch.d.xmlexpr.argvalue = NULL; + scratch.d.xmlexpr.argnull = NULL; + } + + /* prepare argument execution */ + off = 0; + foreach(arg, xexpr->named_args) + { + Expr *e = (Expr *) lfirst(arg); + + ExecInitExprRec(e, parent, state, + &scratch.d.xmlexpr.named_argvalue[off], + &scratch.d.xmlexpr.named_argnull[off]); + off++; + } + + off = 0; + foreach(arg, xexpr->args) + { + Expr *e = (Expr *) lfirst(arg); + + ExecInitExprRec(e, parent, state, + &scratch.d.xmlexpr.argvalue[off], + &scratch.d.xmlexpr.argnull[off]); + off++; + } + + /* and evaluate the actual XML expression */ + ExprEvalPushStep(state, &scratch); + break; + } + + case T_NullTest: + { + NullTest *ntest = (NullTest *) node; + + if (ntest->nulltesttype == IS_NULL) + { + if (ntest->argisrow) + scratch.opcode = EEOP_NULLTEST_ROWISNULL; + else + scratch.opcode = EEOP_NULLTEST_ISNULL; + } + else if (ntest->nulltesttype == IS_NOT_NULL) + { + if (ntest->argisrow) + scratch.opcode = EEOP_NULLTEST_ROWISNOTNULL; + else + scratch.opcode = EEOP_NULLTEST_ISNOTNULL; + } + else + { + elog(ERROR, "unrecognized nulltesttype: %d", + (int) ntest->nulltesttype); + } + /* initialize cache in case it's a row test */ + scratch.d.nulltest_row.argdesc = NULL; + + /* first evaluate argument into result variable */ + ExecInitExprRec(ntest->arg, parent, state, + resv, resnull); + + /* then push the test of that argument */ + ExprEvalPushStep(state, &scratch); + break; + } + + case T_BooleanTest: + { + BooleanTest *btest = (BooleanTest *) node; + + /* + * Evaluate argument, directly into result datum. That's ok, + * because resv/resnull is definitely not used anywhere else, + * and will get overwritten by the below EEOP_BOOLTEST_IS_* + * step. + */ + ExecInitExprRec(btest->arg, parent, state, resv, resnull); + + switch (btest->booltesttype) + { + case IS_TRUE: + scratch.opcode = EEOP_BOOLTEST_IS_TRUE; + break; + case IS_NOT_TRUE: + scratch.opcode = EEOP_BOOLTEST_IS_NOT_TRUE; + break; + case IS_FALSE: + scratch.opcode = EEOP_BOOLTEST_IS_FALSE; + break; + case IS_NOT_FALSE: + scratch.opcode = EEOP_BOOLTEST_IS_NOT_FALSE; + break; + case IS_UNKNOWN: + /* Same as scalar IS NULL test */ + scratch.opcode = EEOP_NULLTEST_ISNULL; + break; + case IS_NOT_UNKNOWN: + /* Same as scalar IS NOT NULL test */ + scratch.opcode = EEOP_NULLTEST_ISNOTNULL; + break; + default: + elog(ERROR, "unrecognized booltesttype: %d", + (int) btest->booltesttype); + } + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_CoerceToDomain: + { + CoerceToDomain *ctest = (CoerceToDomain *) node; + + ExecInitCoerceToDomain(&scratch, ctest, parent, state, + resv, resnull); + break; + } + + case T_CoerceToDomainValue: + { + /* + * Read from location identified by innermost_domainval. Note + * that innermost_domainval could be NULL, if we're compiling + * a standalone domain check rather than one embedded in a + * larger expression. In that case we must read from + * econtext->domainValue_datum. We'll take care of that + * scenario at runtime. + */ + scratch.opcode = EEOP_DOMAIN_TESTVAL; + /* we share instruction union variant with case testval */ + scratch.d.casetest.value = state->innermost_domainval; + scratch.d.casetest.isnull = state->innermost_domainnull; + + ExprEvalPushStep(state, &scratch); + break; + } + + case T_CurrentOfExpr: + { + scratch.opcode = EEOP_CURRENTOFEXPR; + ExprEvalPushStep(state, &scratch); + break; + } + + default: + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(node)); + break; + } +} + +/* + * Add another expression evaluation step to ExprState->steps. + * + * Note that this potentially re-allocates es->steps, therefore no pointer + * into that array may be used while the expression is still being built. + */ +static void +ExprEvalPushStep(ExprState *es, const ExprEvalStep *s) +{ + if (es->steps_alloc == 0) + { + es->steps_alloc = 16; + es->steps = palloc(sizeof(ExprEvalStep) * es->steps_alloc); + } + else if (es->steps_alloc == es->steps_len) + { + es->steps_alloc *= 2; + es->steps = repalloc(es->steps, + sizeof(ExprEvalStep) * es->steps_alloc); + } + + memcpy(&es->steps[es->steps_len++], s, sizeof(ExprEvalStep)); +} + +/* + * Perform setup necessary for the evaluation of a function-like expression, + * appending argument evaluation steps to the steps list in *state, and + * setting up *scratch so it is ready to be pushed. + * + * *scratch is not pushed here, so that callers may override the opcode, + * which is useful for function-like cases like DISTINCT. + */ +static void +ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args, Oid funcid, + Oid inputcollid, PlanState *parent, ExprState *state) +{ + int nargs = list_length(args); + AclResult aclresult; + FmgrInfo *flinfo; + FunctionCallInfo fcinfo; + int argno; + ListCell *lc; + + /* Check permission to call function */ + aclresult = pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(funcid)); + InvokeFunctionExecuteHook(funcid); + + /* + * Safety check on nargs. Under normal circumstances this should never + * fail, as parser should check sooner. But possibly it might fail if + * server has been compiled with FUNC_MAX_ARGS smaller than some functions + * declared in pg_proc? + */ + if (nargs > FUNC_MAX_ARGS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg_plural("cannot pass more than %d argument to a function", + "cannot pass more than %d arguments to a function", + FUNC_MAX_ARGS, + FUNC_MAX_ARGS))); + + /* Allocate function lookup data and parameter workspace for this call */ + scratch->d.func.finfo = palloc0(sizeof(FmgrInfo)); + scratch->d.func.fcinfo_data = palloc0(sizeof(FunctionCallInfoData)); + flinfo = scratch->d.func.finfo; + fcinfo = scratch->d.func.fcinfo_data; + + /* Set up the primary fmgr lookup information */ + fmgr_info(funcid, flinfo); + fmgr_info_set_expr((Node *) node, flinfo); + + /* Initialize function call parameter structure too */ + InitFunctionCallInfoData(*fcinfo, flinfo, + nargs, inputcollid, NULL, NULL); + + /* Keep extra copies of this info to save an indirection at runtime */ + scratch->d.func.fn_addr = flinfo->fn_addr; + scratch->d.func.nargs = nargs; + + /* We only support non-set functions here */ + if (flinfo->fn_retset) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + /* Build code to evaluate arguments directly into the fcinfo struct */ + argno = 0; + foreach(lc, args) + { + Expr *arg = (Expr *) lfirst(lc); + + if (IsA(arg, Const)) + { + /* + * Don't evaluate const arguments every round; especially + * interesting for constants in comparisons. + */ + Const *con = (Const *) arg; + + fcinfo->arg[argno] = con->constvalue; + fcinfo->argnull[argno] = con->constisnull; + } + else + { + ExecInitExprRec(arg, parent, state, + &fcinfo->arg[argno], &fcinfo->argnull[argno]); + } + argno++; + } + + /* Insert appropriate opcode depending on strictness and stats level */ + if (pgstat_track_functions <= flinfo->fn_stats) + { + if (flinfo->fn_strict && nargs > 0) + scratch->opcode = EEOP_FUNCEXPR_STRICT; + else + scratch->opcode = EEOP_FUNCEXPR; + } + else + { + if (flinfo->fn_strict && nargs > 0) + scratch->opcode = EEOP_FUNCEXPR_STRICT_FUSAGE; + else + scratch->opcode = EEOP_FUNCEXPR_FUSAGE; + } +} + +/* + * Add expression steps deforming the ExprState's inner/outer/scan slots + * as much as required by the expression. + */ +static void +ExecInitExprSlots(ExprState *state, Node *node) +{ + LastAttnumInfo info = {0, 0, 0}; + ExprEvalStep scratch; + + /* + * Figure out which attributes we're going to need. + */ + get_last_attnums_walker(node, &info); + + /* Emit steps as needed */ + if (info.last_inner > 0) + { + scratch.opcode = EEOP_INNER_FETCHSOME; + scratch.d.fetch.last_var = info.last_inner; + ExprEvalPushStep(state, &scratch); + } + if (info.last_outer > 0) + { + scratch.opcode = EEOP_OUTER_FETCHSOME; + scratch.d.fetch.last_var = info.last_outer; + ExprEvalPushStep(state, &scratch); + } + if (info.last_scan > 0) + { + scratch.opcode = EEOP_SCAN_FETCHSOME; + scratch.d.fetch.last_var = info.last_scan; + ExprEvalPushStep(state, &scratch); + } +} + +/* + * get_last_attnums_walker: expression walker for ExecInitExprSlots + */ +static bool +get_last_attnums_walker(Node *node, LastAttnumInfo *info) +{ + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *variable = (Var *) node; + AttrNumber attnum = variable->varattno; + + switch (variable->varno) + { + case INNER_VAR: + info->last_inner = Max(info->last_inner, attnum); + break; + + case OUTER_VAR: + info->last_outer = Max(info->last_outer, attnum); + break; + + /* INDEX_VAR is handled by default case */ + + default: + info->last_scan = Max(info->last_scan, attnum); + break; + } + return false; + } + + /* + * Don't examine the arguments or filters of Aggrefs or WindowFuncs, + * because those do not represent expressions to be evaluated within the + * calling expression's econtext. GroupingFunc arguments are never + * evaluated at all. + */ + if (IsA(node, Aggref)) + return false; + if (IsA(node, WindowFunc)) + return false; + if (IsA(node, GroupingFunc)) + return false; + return expression_tree_walker(node, get_last_attnums_walker, + (void *) info); +} + +/* + * Prepare step for the evaluation of a whole-row variable. + * The caller still has to push the step. + */ +static void +ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, PlanState *parent) +{ + /* fill in all but the target */ + scratch->opcode = EEOP_WHOLEROW; + scratch->d.wholerow.var = variable; + scratch->d.wholerow.first = true; + scratch->d.wholerow.slow = false; + scratch->d.wholerow.tupdesc = NULL; /* filled at runtime */ + scratch->d.wholerow.junkFilter = NULL; + + /* + * If the input tuple came from a subquery, it might contain "resjunk" + * columns (such as GROUP BY or ORDER BY columns), which we don't want to + * keep in the whole-row result. We can get rid of such columns by + * passing the tuple through a JunkFilter --- but to make one, we have to + * lay our hands on the subquery's targetlist. Fortunately, there are not + * very many cases where this can happen, and we can identify all of them + * by examining our parent PlanState. We assume this is not an issue in + * standalone expressions that don't have parent plans. (Whole-row Vars + * can occur in such expressions, but they will always be referencing + * table rows.) + */ + if (parent) + { + PlanState *subplan = NULL; + + switch (nodeTag(parent)) + { + case T_SubqueryScanState: + subplan = ((SubqueryScanState *) parent)->subplan; + break; + case T_CteScanState: + subplan = ((CteScanState *) parent)->cteplanstate; + break; + default: + break; + } + + if (subplan) + { + bool junk_filter_needed = false; + ListCell *tlist; + + /* Detect whether subplan tlist actually has any junk columns */ + foreach(tlist, subplan->plan->targetlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(tlist); + + if (tle->resjunk) + { + junk_filter_needed = true; + break; + } + } + + /* If so, build the junkfilter now */ + if (junk_filter_needed) + { + scratch->d.wholerow.junkFilter = + ExecInitJunkFilter(subplan->plan->targetlist, + ExecGetResultType(subplan)->tdhasoid, + ExecInitExtraTupleSlot(parent->state)); + } + } + } +} + +/* + * Prepare evaluation of an ArrayRef expression. + */ +static void +ExecInitArrayRef(ExprEvalStep *scratch, ArrayRef *aref, PlanState *parent, + ExprState *state, Datum *resv, bool *resnull) +{ + bool isAssignment = (aref->refassgnexpr != NULL); + ArrayRefState *arefstate = palloc0(sizeof(ArrayRefState)); + List *adjust_jumps = NIL; + ListCell *lc; + int i; + + /* Fill constant fields of ArrayRefState */ + arefstate->isassignment = isAssignment; + arefstate->refelemtype = aref->refelemtype; + arefstate->refattrlength = get_typlen(aref->refarraytype); + get_typlenbyvalalign(aref->refelemtype, + &arefstate->refelemlength, + &arefstate->refelembyval, + &arefstate->refelemalign); + + /* + * Evaluate array input. It's safe to do so into resv/resnull, because we + * won't use that as target for any of the other subexpressions, and it'll + * be overwritten by the final EEOP_ARRAYREF_FETCH/ASSIGN step, which is + * pushed last. + */ + ExecInitExprRec(aref->refexpr, parent, state, resv, resnull); + + /* + * If refexpr yields NULL, and it's a fetch, then result is NULL. We can + * implement this with just JUMP_IF_NULL, since we evaluated the array + * into the desired target location. + */ + if (!isAssignment) + { + scratch->opcode = EEOP_JUMP_IF_NULL; + scratch->d.jump.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, scratch); + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + } + + /* Verify subscript list lengths are within limit */ + if (list_length(aref->refupperindexpr) > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + list_length(aref->refupperindexpr), MAXDIM))); + + if (list_length(aref->reflowerindexpr) > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", + list_length(aref->reflowerindexpr), MAXDIM))); + + /* Evaluate upper subscripts */ + i = 0; + foreach(lc, aref->refupperindexpr) + { + Expr *e = (Expr *) lfirst(lc); + + /* When slicing, individual subscript bounds can be omitted */ + if (!e) + { + arefstate->upperprovided[i] = false; + i++; + continue; + } + + arefstate->upperprovided[i] = true; + + /* Each subscript is evaluated into subscriptvalue/subscriptnull */ + ExecInitExprRec(e, parent, state, + &arefstate->subscriptvalue, &arefstate->subscriptnull); + + /* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */ + scratch->opcode = EEOP_ARRAYREF_SUBSCRIPT; + scratch->d.arrayref_subscript.state = arefstate; + scratch->d.arrayref_subscript.off = i; + scratch->d.arrayref_subscript.isupper = true; + scratch->d.arrayref_subscript.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, scratch); + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + i++; + } + arefstate->numupper = i; + + /* Evaluate lower subscripts similarly */ + i = 0; + foreach(lc, aref->reflowerindexpr) + { + Expr *e = (Expr *) lfirst(lc); + + /* When slicing, individual subscript bounds can be omitted */ + if (!e) + { + arefstate->lowerprovided[i] = false; + i++; + continue; + } + + arefstate->lowerprovided[i] = true; + + /* Each subscript is evaluated into subscriptvalue/subscriptnull */ + ExecInitExprRec(e, parent, state, + &arefstate->subscriptvalue, &arefstate->subscriptnull); + + /* ... and then ARRAYREF_SUBSCRIPT saves it into step's workspace */ + scratch->opcode = EEOP_ARRAYREF_SUBSCRIPT; + scratch->d.arrayref_subscript.state = arefstate; + scratch->d.arrayref_subscript.off = i; + scratch->d.arrayref_subscript.isupper = false; + scratch->d.arrayref_subscript.jumpdone = -1; /* adjust later */ + ExprEvalPushStep(state, scratch); + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + i++; + } + arefstate->numlower = i; + + /* Should be impossible if parser is sane, but check anyway: */ + if (arefstate->numlower != 0 && + arefstate->numupper != arefstate->numlower) + elog(ERROR, "upper and lower index lists are not same length"); + + if (isAssignment) + { + Datum *save_innermost_caseval; + bool *save_innermost_casenull; + + /* + * We might have a nested-assignment situation, in which the + * refassgnexpr is itself a FieldStore or ArrayRef that needs to + * obtain and modify the previous value of the array element or slice + * being replaced. If so, we have to extract that value from the + * array and pass it down via the CaseTextExpr mechanism. It's safe + * to reuse the CASE mechanism because there cannot be a CASE between + * here and where the value would be needed, and an array assignment + * can't be within a CASE either. (So saving and restoring + * innermost_caseval is just paranoia, but let's do it anyway.) + * + * Since fetching the old element might be a nontrivial expense, do it + * only if the argument appears to actually need it. + */ + if (isAssignmentIndirectionExpr(aref->refassgnexpr)) + { + scratch->opcode = EEOP_ARRAYREF_OLD; + scratch->d.arrayref.state = arefstate; + ExprEvalPushStep(state, scratch); + } + + /* ARRAYREF_OLD puts extracted value into prevvalue/prevnull */ + save_innermost_caseval = state->innermost_caseval; + save_innermost_casenull = state->innermost_casenull; + state->innermost_caseval = &arefstate->prevvalue; + state->innermost_casenull = &arefstate->prevnull; + + /* evaluate replacement value into replacevalue/replacenull */ + ExecInitExprRec(aref->refassgnexpr, parent, state, + &arefstate->replacevalue, &arefstate->replacenull); + + state->innermost_caseval = save_innermost_caseval; + state->innermost_casenull = save_innermost_casenull; + + /* and perform the assignment */ + scratch->opcode = EEOP_ARRAYREF_ASSIGN; + scratch->d.arrayref.state = arefstate; + ExprEvalPushStep(state, scratch); + } + else + { + /* array fetch is much simpler */ + scratch->opcode = EEOP_ARRAYREF_FETCH; + scratch->d.arrayref.state = arefstate; + ExprEvalPushStep(state, scratch); + } + + /* adjust jump targets */ + foreach(lc, adjust_jumps) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + if (as->opcode == EEOP_ARRAYREF_SUBSCRIPT) + { + Assert(as->d.arrayref_subscript.jumpdone == -1); + as->d.arrayref_subscript.jumpdone = state->steps_len; + } + else + { + Assert(as->opcode == EEOP_JUMP_IF_NULL); + Assert(as->d.jump.jumpdone == -1); + as->d.jump.jumpdone = state->steps_len; + } + } +} + +/* + * Helper for preparing ArrayRef expressions for evaluation: is expr a nested + * FieldStore or ArrayRef that might need the old element value passed down? + * + * (We could use this in FieldStore too, but in that case passing the old + * value is so cheap there's no need.) + */ +static bool +isAssignmentIndirectionExpr(Expr *expr) +{ + if (expr == NULL) + return false; /* just paranoia */ + if (IsA(expr, FieldStore)) + { + FieldStore *fstore = (FieldStore *) expr; + + if (fstore->arg && IsA(fstore->arg, CaseTestExpr)) + return true; + } + else if (IsA(expr, ArrayRef)) + { + ArrayRef *arrayRef = (ArrayRef *) expr; + + if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr)) + return true; + } + return false; +} + +/* + * Prepare evaluation of a CoerceToDomain expression. + */ +static void +ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest, + PlanState *parent, ExprState *state, + Datum *resv, bool *resnull) +{ + ExprEvalStep scratch2; + DomainConstraintRef *constraint_ref; + Datum *domainval = NULL; + bool *domainnull = NULL; + Datum *save_innermost_domainval; + bool *save_innermost_domainnull; + ListCell *l; + + scratch->d.domaincheck.resulttype = ctest->resulttype; + /* we'll allocate workspace only if needed */ + scratch->d.domaincheck.checkvalue = NULL; + scratch->d.domaincheck.checknull = NULL; + + /* + * Evaluate argument - it's fine to directly store it into resv/resnull, + * if there's constraint failures there'll be errors, otherwise it's what + * needs to be returned. + */ + ExecInitExprRec(ctest->arg, parent, state, resv, resnull); + + /* + * Note: if the argument is of varlena type, it could be a R/W expanded + * object. We want to return the R/W pointer as the final result, but we + * have to pass a R/O pointer as the value to be tested by any functions + * in check expressions. We don't bother to emit a MAKE_READONLY step + * unless there's actually at least one check expression, though. Until + * we've tested that, domainval/domainnull are NULL. + */ + + /* + * Collect the constraints associated with the domain. + * + * Note: before PG v10 we'd recheck the set of constraints during each + * evaluation of the expression. Now we bake them into the ExprState + * during executor initialization. That means we don't need typcache.c to + * provide compiled exprs. + */ + constraint_ref = (DomainConstraintRef *) + palloc(sizeof(DomainConstraintRef)); + InitDomainConstraintRef(ctest->resulttype, + constraint_ref, + CurrentMemoryContext, + false); + + /* + * Compile code to check each domain constraint. NOTNULL constraints can + * just be applied on the resv/resnull value, but for CHECK constraints we + * need more pushups. + */ + foreach(l, constraint_ref->constraints) + { + DomainConstraintState *con = (DomainConstraintState *) lfirst(l); + + scratch->d.domaincheck.constraintname = con->name; + + switch (con->constrainttype) + { + case DOM_CONSTRAINT_NOTNULL: + scratch->opcode = EEOP_DOMAIN_NOTNULL; + ExprEvalPushStep(state, scratch); + break; + case DOM_CONSTRAINT_CHECK: + /* Allocate workspace for CHECK output if we didn't yet */ + if (scratch->d.domaincheck.checkvalue == NULL) + { + scratch->d.domaincheck.checkvalue = + (Datum *) palloc(sizeof(Datum)); + scratch->d.domaincheck.checknull = + (bool *) palloc(sizeof(bool)); + } + + /* + * If first time through, determine where CoerceToDomainValue + * nodes should read from. + */ + if (domainval == NULL) + { + /* + * Since value might be read multiple times, force to R/O + * - but only if it could be an expanded datum. + */ + if (get_typlen(ctest->resulttype) == -1) + { + /* Yes, so make output workspace for MAKE_READONLY */ + domainval = (Datum *) palloc(sizeof(Datum)); + domainnull = (bool *) palloc(sizeof(bool)); + + /* Emit MAKE_READONLY */ + scratch2.opcode = EEOP_MAKE_READONLY; + scratch2.resvalue = domainval; + scratch2.resnull = domainnull; + scratch2.d.make_readonly.value = resv; + scratch2.d.make_readonly.isnull = resnull; + ExprEvalPushStep(state, &scratch2); + } + else + { + /* No, so it's fine to read from resv/resnull */ + domainval = resv; + domainnull = resnull; + } + } + + /* + * Set up value to be returned by CoerceToDomainValue nodes. + * We must save and restore innermost_domainval/null fields, + * in case this node is itself within a check expression for + * another domain. + */ + save_innermost_domainval = state->innermost_domainval; + save_innermost_domainnull = state->innermost_domainnull; + state->innermost_domainval = domainval; + state->innermost_domainnull = domainnull; + + /* evaluate check expression value */ + ExecInitExprRec(con->check_expr, parent, state, + scratch->d.domaincheck.checkvalue, + scratch->d.domaincheck.checknull); + + state->innermost_domainval = save_innermost_domainval; + state->innermost_domainnull = save_innermost_domainnull; + + /* now test result */ + scratch->opcode = EEOP_DOMAIN_CHECK; + ExprEvalPushStep(state, scratch); + + break; + default: + elog(ERROR, "unrecognized constraint type: %d", + (int) con->constrainttype); + break; + } + } +} diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c new file mode 100644 index 0000000000..de7fe895f8 --- /dev/null +++ b/src/backend/executor/execExprInterp.c @@ -0,0 +1,3525 @@ +/*------------------------------------------------------------------------- + * + * execExprInterp.c + * Interpreted evaluation of an expression step list. + * + * This file provides either a "direct threaded" (for gcc, clang and + * compatible) or a "switch threaded" (for all compilers) implementation of + * expression evaluation. The former is amongst the fastest known methods + * of interpreting programs without resorting to assembly level work, or + * just-in-time compilation, but it requires support for computed gotos. + * The latter is amongst the fastest approaches doable in standard C. + * + * In either case we use ExprEvalStep->opcode to dispatch to the code block + * within ExecInterpExpr() that implements the specific opcode type. + * + * Switch-threading uses a plain switch() statement to perform the + * dispatch. This has the advantages of being plain C and allowing the + * compiler to warn if implementation of a specific opcode has been forgotten. + * The disadvantage is that dispatches will, as commonly implemented by + * compilers, happen from a single location, requiring more jumps and causing + * bad branch prediction. + * + * In direct threading, we use gcc's label-as-values extension - also adopted + * by some other compilers - to replace ExprEvalStep->opcode with the address + * of the block implementing the instruction. Dispatch to the next instruction + * is done by a "computed goto". This allows for better branch prediction + * (as the jumps are happening from different locations) and fewer jumps + * (as no preparatory jump to a common dispatch location is needed). + * + * When using direct threading, ExecReadyInterpretedExpr will replace + * each step's opcode field with the address of the relevant code block and + * ExprState->flags will contain EEO_FLAG_DIRECT_THREADED to remember that + * that's been done. + * + * For very simple instructions the overhead of the full interpreter + * "startup", as minimal as it is, is noticeable. Therefore + * ExecReadyInterpretedExpr will choose to implement simple scalar Var + * and Const expressions using special fast-path routines (ExecJust*). + * Benchmarking shows anything more complex than those may as well use the + * "full interpreter". + * + * Complex or uncommon instructions are not implemented in-line in + * ExecInterpExpr(), rather we call out to a helper function appearing later + * in this file. For one reason, there'd not be a noticeable performance + * benefit, but more importantly those complex routines are intended to be + * shared between different expression evaluation approaches. For instance + * a JIT compiler would generate calls to them. (This is why they are + * exported rather than being "static" in this file.) + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/executor/execExprInterp.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "executor/execExpr.h" +#include "executor/nodeSubplan.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "parser/parsetree.h" +#include "pgstat.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/lsyscache.h" +#include "utils/timestamp.h" +#include "utils/typcache.h" +#include "utils/xml.h" + + +/* + * Use computed-goto-based opcode dispatch when computed gotos are available. + * But use a separate symbol so that it's easy to adjust locally in this file + * for development and testing. + */ +#ifdef HAVE_COMPUTED_GOTO +#define EEO_USE_COMPUTED_GOTO +#endif /* HAVE_COMPUTED_GOTO */ + +/* + * Macros for opcode dispatch. + * + * EEO_SWITCH - just hides the switch if not in use. + * EEO_CASE - labels the implementation of named expression step type. + * EEO_DISPATCH - jump to the implementation of the step type for 'op'. + * EEO_OPCODE - compute opcode required by used expression evaluation method. + * EEO_NEXT - increment 'op' and jump to correct next step type. + * EEO_JUMP - jump to the specified step number within the current expression. + */ +#if defined(EEO_USE_COMPUTED_GOTO) + +/* to make dispatch_table accessible outside ExecInterpExpr() */ +static const void **dispatch_table = NULL; + +#define EEO_SWITCH() +#define EEO_CASE(name) CASE_##name: +#define EEO_DISPATCH() goto *((void *) op->opcode) +#define EEO_OPCODE(opcode) ((intptr_t) dispatch_table[opcode]) + +#else /* !EEO_USE_COMPUTED_GOTO */ + +#define EEO_SWITCH() starteval: switch ((ExprEvalOp) op->opcode) +#define EEO_CASE(name) case name: +#define EEO_DISPATCH() goto starteval +#define EEO_OPCODE(opcode) (opcode) + +#endif /* EEO_USE_COMPUTED_GOTO */ + +#define EEO_NEXT() \ + do { \ + op++; \ + EEO_DISPATCH(); \ + } while (0) + +#define EEO_JUMP(stepno) \ + do { \ + op = &state->steps[stepno]; \ + EEO_DISPATCH(); \ + } while (0) + + +static Datum ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull); +static void ExecInitInterpreter(void); + +/* support functions */ +static void CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype); +static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, + TupleDesc *cache_field, ExprContext *econtext); +static void ShutdownTupleDescRef(Datum arg); +static void ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, + ExprContext *econtext, bool checkisnull); + +/* fast-path evaluation functions */ +static Datum ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull); +static Datum ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull); + + +/* + * Prepare ExprState for interpreted execution. + */ +void +ExecReadyInterpretedExpr(ExprState *state) +{ + /* Ensure one-time interpreter setup has been done */ + ExecInitInterpreter(); + + /* Simple validity checks on expression */ + Assert(state->steps_len >= 1); + Assert(state->steps[state->steps_len - 1].opcode == EEOP_DONE); + + /* + * Don't perform redundant initialization. This is unreachable in current + * cases, but might be hit if there's additional expression evaluation + * methods that rely on interpreted execution to work. + */ + if (state->flags & EEO_FLAG_INTERPRETER_INITIALIZED) + return; + + /* DIRECT_THREADED should not already be set */ + Assert((state->flags & EEO_FLAG_DIRECT_THREADED) == 0); + + /* + * There shouldn't be any errors before the expression is fully + * initialized, and even if so, it'd lead to the expression being + * abandoned. So we can set the flag now and save some code. + */ + state->flags |= EEO_FLAG_INTERPRETER_INITIALIZED; + + /* + * Select fast-path evalfuncs for very simple expressions. "Starting up" + * the full interpreter is a measurable overhead for these. Plain Vars + * and Const seem to be the only ones where the intrinsic cost is small + * enough that the overhead of ExecInterpExpr matters. For more complex + * expressions it's cheaper to use ExecInterpExpr always. + */ + if (state->steps_len == 3) + { + ExprEvalOp step0 = state->steps[0].opcode; + ExprEvalOp step1 = state->steps[1].opcode; + + if (step0 == EEOP_INNER_FETCHSOME && + step1 == EEOP_INNER_VAR_FIRST) + { + state->evalfunc = ExecJustInnerVarFirst; + return; + } + else if (step0 == EEOP_OUTER_FETCHSOME && + step1 == EEOP_OUTER_VAR_FIRST) + { + state->evalfunc = ExecJustOuterVarFirst; + return; + } + else if (step0 == EEOP_SCAN_FETCHSOME && + step1 == EEOP_SCAN_VAR_FIRST) + { + state->evalfunc = ExecJustScanVarFirst; + return; + } + else if (step0 == EEOP_INNER_FETCHSOME && + step1 == EEOP_ASSIGN_INNER_VAR) + { + state->evalfunc = ExecJustAssignInnerVar; + return; + } + else if (step0 == EEOP_OUTER_FETCHSOME && + step1 == EEOP_ASSIGN_OUTER_VAR) + { + state->evalfunc = ExecJustAssignOuterVar; + return; + } + else if (step0 == EEOP_SCAN_FETCHSOME && + step1 == EEOP_ASSIGN_SCAN_VAR) + { + state->evalfunc = ExecJustAssignScanVar; + return; + } + } + else if (state->steps_len == 2 && + state->steps[0].opcode == EEOP_CONST) + { + state->evalfunc = ExecJustConst; + return; + } + +#if defined(EEO_USE_COMPUTED_GOTO) + + /* + * In the direct-threaded implementation, replace each opcode with the + * address to jump to. (Use ExecEvalStepOp() to get back the opcode.) + */ + { + int off; + + for (off = 0; off < state->steps_len; off++) + { + ExprEvalStep *op = &state->steps[off]; + + op->opcode = EEO_OPCODE(op->opcode); + } + + state->flags |= EEO_FLAG_DIRECT_THREADED; + } +#endif /* EEO_USE_COMPUTED_GOTO */ + + state->evalfunc = ExecInterpExpr; +} + + +/* + * Evaluate expression identified by "state" in the execution context + * given by "econtext". *isnull is set to the is-null flag for the result, + * and the Datum value is the function result. + * + * As a special case, return the dispatch table's address if state is NULL. + * This is used by ExecInitInterpreter to set up the dispatch_table global. + * (Only applies when EEO_USE_COMPUTED_GOTO is defined.) + */ +static Datum +ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op; + TupleTableSlot *resultslot; + TupleTableSlot *innerslot; + TupleTableSlot *outerslot; + TupleTableSlot *scanslot; + + /* + * This array has to be in the same order as enum ExprEvalOp. + */ +#if defined(EEO_USE_COMPUTED_GOTO) + static const void *const dispatch_table[] = { + &&CASE_EEOP_DONE, + &&CASE_EEOP_INNER_FETCHSOME, + &&CASE_EEOP_OUTER_FETCHSOME, + &&CASE_EEOP_SCAN_FETCHSOME, + &&CASE_EEOP_INNER_VAR_FIRST, + &&CASE_EEOP_INNER_VAR, + &&CASE_EEOP_OUTER_VAR_FIRST, + &&CASE_EEOP_OUTER_VAR, + &&CASE_EEOP_SCAN_VAR_FIRST, + &&CASE_EEOP_SCAN_VAR, + &&CASE_EEOP_INNER_SYSVAR, + &&CASE_EEOP_OUTER_SYSVAR, + &&CASE_EEOP_SCAN_SYSVAR, + &&CASE_EEOP_WHOLEROW, + &&CASE_EEOP_ASSIGN_INNER_VAR, + &&CASE_EEOP_ASSIGN_OUTER_VAR, + &&CASE_EEOP_ASSIGN_SCAN_VAR, + &&CASE_EEOP_ASSIGN_TMP, + &&CASE_EEOP_ASSIGN_TMP_MAKE_RO, + &&CASE_EEOP_CONST, + &&CASE_EEOP_FUNCEXPR, + &&CASE_EEOP_FUNCEXPR_STRICT, + &&CASE_EEOP_FUNCEXPR_FUSAGE, + &&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE, + &&CASE_EEOP_BOOL_AND_STEP_FIRST, + &&CASE_EEOP_BOOL_AND_STEP, + &&CASE_EEOP_BOOL_AND_STEP_LAST, + &&CASE_EEOP_BOOL_OR_STEP_FIRST, + &&CASE_EEOP_BOOL_OR_STEP, + &&CASE_EEOP_BOOL_OR_STEP_LAST, + &&CASE_EEOP_BOOL_NOT_STEP, + &&CASE_EEOP_QUAL, + &&CASE_EEOP_JUMP, + &&CASE_EEOP_JUMP_IF_NULL, + &&CASE_EEOP_JUMP_IF_NOT_NULL, + &&CASE_EEOP_JUMP_IF_NOT_TRUE, + &&CASE_EEOP_NULLTEST_ISNULL, + &&CASE_EEOP_NULLTEST_ISNOTNULL, + &&CASE_EEOP_NULLTEST_ROWISNULL, + &&CASE_EEOP_NULLTEST_ROWISNOTNULL, + &&CASE_EEOP_BOOLTEST_IS_TRUE, + &&CASE_EEOP_BOOLTEST_IS_NOT_TRUE, + &&CASE_EEOP_BOOLTEST_IS_FALSE, + &&CASE_EEOP_BOOLTEST_IS_NOT_FALSE, + &&CASE_EEOP_PARAM_EXEC, + &&CASE_EEOP_PARAM_EXTERN, + &&CASE_EEOP_CASE_TESTVAL, + &&CASE_EEOP_MAKE_READONLY, + &&CASE_EEOP_IOCOERCE, + &&CASE_EEOP_DISTINCT, + &&CASE_EEOP_NULLIF, + &&CASE_EEOP_SQLVALUEFUNCTION, + &&CASE_EEOP_CURRENTOFEXPR, + &&CASE_EEOP_ARRAYEXPR, + &&CASE_EEOP_ARRAYCOERCE, + &&CASE_EEOP_ROW, + &&CASE_EEOP_ROWCOMPARE_STEP, + &&CASE_EEOP_ROWCOMPARE_FINAL, + &&CASE_EEOP_MINMAX, + &&CASE_EEOP_FIELDSELECT, + &&CASE_EEOP_FIELDSTORE_DEFORM, + &&CASE_EEOP_FIELDSTORE_FORM, + &&CASE_EEOP_ARRAYREF_SUBSCRIPT, + &&CASE_EEOP_ARRAYREF_OLD, + &&CASE_EEOP_ARRAYREF_ASSIGN, + &&CASE_EEOP_ARRAYREF_FETCH, + &&CASE_EEOP_DOMAIN_TESTVAL, + &&CASE_EEOP_DOMAIN_NOTNULL, + &&CASE_EEOP_DOMAIN_CHECK, + &&CASE_EEOP_CONVERT_ROWTYPE, + &&CASE_EEOP_SCALARARRAYOP, + &&CASE_EEOP_XMLEXPR, + &&CASE_EEOP_AGGREF, + &&CASE_EEOP_GROUPING_FUNC, + &&CASE_EEOP_WINDOW_FUNC, + &&CASE_EEOP_SUBPLAN, + &&CASE_EEOP_ALTERNATIVE_SUBPLAN, + &&CASE_EEOP_LAST + }; + + StaticAssertStmt(EEOP_LAST + 1 == lengthof(dispatch_table), + "dispatch_table out of whack with ExprEvalOp"); + + if (unlikely(state == NULL)) + return PointerGetDatum(dispatch_table); +#else + Assert(state != NULL); +#endif /* EEO_USE_COMPUTED_GOTO */ + + /* setup state */ + op = state->steps; + resultslot = state->resultslot; + innerslot = econtext->ecxt_innertuple; + outerslot = econtext->ecxt_outertuple; + scanslot = econtext->ecxt_scantuple; + +#if defined(EEO_USE_COMPUTED_GOTO) + EEO_DISPATCH(); +#endif + + EEO_SWITCH() + { + EEO_CASE(EEOP_DONE) + { + goto out; + } + + EEO_CASE(EEOP_INNER_FETCHSOME) + { + /* XXX: worthwhile to check tts_nvalid inline first? */ + slot_getsomeattrs(innerslot, op->d.fetch.last_var); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_OUTER_FETCHSOME) + { + slot_getsomeattrs(outerslot, op->d.fetch.last_var); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_SCAN_FETCHSOME) + { + slot_getsomeattrs(scanslot, op->d.fetch.last_var); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_INNER_VAR_FIRST) + { + int attnum = op->d.var.attnum; + + /* + * First time through, check whether attribute matches Var. Might + * not be ok anymore, due to schema changes. + */ + CheckVarSlotCompatibility(innerslot, attnum + 1, op->d.var.vartype); + + /* Skip that check on subsequent evaluations */ + op->opcode = EEO_OPCODE(EEOP_INNER_VAR); + + /* FALL THROUGH to EEOP_INNER_VAR */ + } + + EEO_CASE(EEOP_INNER_VAR) + { + int attnum = op->d.var.attnum; + + /* + * Since we already extracted all referenced columns from the + * tuple with a FETCHSOME step, we can just grab the value + * directly out of the slot's decomposed-data arrays. But let's + * have an Assert to check that that did happen. + */ + Assert(attnum >= 0 && attnum < innerslot->tts_nvalid); + *op->resvalue = innerslot->tts_values[attnum]; + *op->resnull = innerslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_OUTER_VAR_FIRST) + { + int attnum = op->d.var.attnum; + + /* See EEOP_INNER_VAR_FIRST comments */ + + CheckVarSlotCompatibility(outerslot, attnum + 1, op->d.var.vartype); + op->opcode = EEO_OPCODE(EEOP_OUTER_VAR); + + /* FALL THROUGH to EEOP_OUTER_VAR */ + } + + EEO_CASE(EEOP_OUTER_VAR) + { + int attnum = op->d.var.attnum; + + /* See EEOP_INNER_VAR comments */ + + Assert(attnum >= 0 && attnum < outerslot->tts_nvalid); + *op->resvalue = outerslot->tts_values[attnum]; + *op->resnull = outerslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_SCAN_VAR_FIRST) + { + int attnum = op->d.var.attnum; + + /* See EEOP_INNER_VAR_FIRST comments */ + + CheckVarSlotCompatibility(scanslot, attnum + 1, op->d.var.vartype); + op->opcode = EEO_OPCODE(EEOP_SCAN_VAR); + + /* FALL THROUGH to EEOP_SCAN_VAR */ + } + + EEO_CASE(EEOP_SCAN_VAR) + { + int attnum = op->d.var.attnum; + + /* See EEOP_INNER_VAR comments */ + + Assert(attnum >= 0 && attnum < scanslot->tts_nvalid); + *op->resvalue = scanslot->tts_values[attnum]; + *op->resnull = scanslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_INNER_SYSVAR) + { + int attnum = op->d.var.attnum; + + /* these asserts must match defenses in slot_getattr */ + Assert(innerslot->tts_tuple != NULL); + Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr)); + /* heap_getsysattr has sufficient defenses against bad attnums */ + + *op->resvalue = heap_getsysattr(innerslot->tts_tuple, attnum, + innerslot->tts_tupleDescriptor, + op->resnull); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_OUTER_SYSVAR) + { + int attnum = op->d.var.attnum; + + /* these asserts must match defenses in slot_getattr */ + Assert(outerslot->tts_tuple != NULL); + Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr)); + + /* heap_getsysattr has sufficient defenses against bad attnums */ + *op->resvalue = heap_getsysattr(outerslot->tts_tuple, attnum, + outerslot->tts_tupleDescriptor, + op->resnull); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_SCAN_SYSVAR) + { + int attnum = op->d.var.attnum; + + /* these asserts must match defenses in slot_getattr */ + Assert(scanslot->tts_tuple != NULL); + Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr)); + /* heap_getsysattr has sufficient defenses against bad attnums */ + + *op->resvalue = heap_getsysattr(scanslot->tts_tuple, attnum, + scanslot->tts_tupleDescriptor, + op->resnull); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_WHOLEROW) + { + /* too complex for an inline implementation */ + ExecEvalWholeRowVar(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ASSIGN_INNER_VAR) + { + int resultnum = op->d.assign_var.resultnum; + int attnum = op->d.assign_var.attnum; + + /* + * We do not need CheckVarSlotCompatibility here; that was taken + * care of at compilation time. But see EEOP_INNER_VAR comments. + */ + Assert(attnum >= 0 && attnum < innerslot->tts_nvalid); + resultslot->tts_values[resultnum] = innerslot->tts_values[attnum]; + resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ASSIGN_OUTER_VAR) + { + int resultnum = op->d.assign_var.resultnum; + int attnum = op->d.assign_var.attnum; + + /* + * We do not need CheckVarSlotCompatibility here; that was taken + * care of at compilation time. But see EEOP_INNER_VAR comments. + */ + Assert(attnum >= 0 && attnum < outerslot->tts_nvalid); + resultslot->tts_values[resultnum] = outerslot->tts_values[attnum]; + resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ASSIGN_SCAN_VAR) + { + int resultnum = op->d.assign_var.resultnum; + int attnum = op->d.assign_var.attnum; + + /* + * We do not need CheckVarSlotCompatibility here; that was taken + * care of at compilation time. But see EEOP_INNER_VAR comments. + */ + Assert(attnum >= 0 && attnum < scanslot->tts_nvalid); + resultslot->tts_values[resultnum] = scanslot->tts_values[attnum]; + resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ASSIGN_TMP) + { + int resultnum = op->d.assign_tmp.resultnum; + + resultslot->tts_values[resultnum] = state->resvalue; + resultslot->tts_isnull[resultnum] = state->resnull; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ASSIGN_TMP_MAKE_RO) + { + int resultnum = op->d.assign_tmp.resultnum; + + resultslot->tts_isnull[resultnum] = state->resnull; + if (!resultslot->tts_isnull[resultnum]) + resultslot->tts_values[resultnum] = + MakeExpandedObjectReadOnlyInternal(state->resvalue); + else + resultslot->tts_values[resultnum] = state->resvalue; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_CONST) + { + *op->resnull = op->d.constval.isnull; + *op->resvalue = op->d.constval.value; + + EEO_NEXT(); + } + + /* + * Function-call implementations. Arguments have previously been + * evaluated directly into fcinfo->args. + * + * As both STRICT checks and function-usage are noticeable performance + * wise, and function calls are a very hot-path (they also back + * operators!), it's worth having so many separate opcodes. + */ + EEO_CASE(EEOP_FUNCEXPR) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + + fcinfo->isnull = false; + *op->resvalue = (op->d.func.fn_addr) (fcinfo); + *op->resnull = fcinfo->isnull; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_FUNCEXPR_STRICT) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + bool *argnull = fcinfo->argnull; + int argno; + + /* strict function, so check for NULL args */ + for (argno = 0; argno < op->d.func.nargs; argno++) + { + if (argnull[argno]) + { + *op->resnull = true; + goto strictfail; + } + } + fcinfo->isnull = false; + *op->resvalue = (op->d.func.fn_addr) (fcinfo); + *op->resnull = fcinfo->isnull; + + strictfail: + EEO_NEXT(); + } + + EEO_CASE(EEOP_FUNCEXPR_FUSAGE) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + PgStat_FunctionCallUsage fcusage; + + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; + *op->resvalue = (op->d.func.fn_addr) (fcinfo); + *op->resnull = fcinfo->isnull; + + pgstat_end_function_usage(&fcusage, true); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_FUNCEXPR_STRICT_FUSAGE) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + PgStat_FunctionCallUsage fcusage; + bool *argnull = fcinfo->argnull; + int argno; + + /* strict function, so check for NULL args */ + for (argno = 0; argno < op->d.func.nargs; argno++) + { + if (argnull[argno]) + { + *op->resnull = true; + goto strictfail_fusage; + } + } + + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; + *op->resvalue = (op->d.func.fn_addr) (fcinfo); + *op->resnull = fcinfo->isnull; + + pgstat_end_function_usage(&fcusage, true); + + strictfail_fusage: + EEO_NEXT(); + } + + /* + * If any of its clauses is FALSE, an AND's result is FALSE regardless + * of the states of the rest of the clauses, so we can stop evaluating + * and return FALSE immediately. If none are FALSE and one or more is + * NULL, we return NULL; otherwise we return TRUE. This makes sense + * when you interpret NULL as "don't know": perhaps one of the "don't + * knows" would have been FALSE if we'd known its value. Only when + * all the inputs are known to be TRUE can we state confidently that + * the AND's result is TRUE. + */ + EEO_CASE(EEOP_BOOL_AND_STEP_FIRST) + { + *op->d.boolexpr.anynull = false; + + /* + * EEOP_BOOL_AND_STEP_FIRST resets anynull, otherwise it's the + * same as EEOP_BOOL_AND_STEP - so fall through to that. + */ + + /* FALL THROUGH */ + } + + EEO_CASE(EEOP_BOOL_AND_STEP) + { + if (*op->resnull) + { + *op->d.boolexpr.anynull = true; + } + else if (!DatumGetBool(*op->resvalue)) + { + /* result is already set to FALSE, need not change it */ + /* bail out early */ + EEO_JUMP(op->d.boolexpr.jumpdone); + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_BOOL_AND_STEP_LAST) + { + if (*op->resnull) + { + /* result is already set to NULL, need not change it */ + } + else if (!DatumGetBool(*op->resvalue)) + { + /* result is already set to FALSE, need not change it */ + + /* + * No point jumping early to jumpdone - would be same target + * (as this is the last argument to the AND expression), + * except more expensive. + */ + } + else if (*op->d.boolexpr.anynull) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + } + else + { + /* result is already set to TRUE, need not change it */ + } + + EEO_NEXT(); + } + + /* + * If any of its clauses is TRUE, an OR's result is TRUE regardless of + * the states of the rest of the clauses, so we can stop evaluating + * and return TRUE immediately. If none are TRUE and one or more is + * NULL, we return NULL; otherwise we return FALSE. This makes sense + * when you interpret NULL as "don't know": perhaps one of the "don't + * knows" would have been TRUE if we'd known its value. Only when all + * the inputs are known to be FALSE can we state confidently that the + * OR's result is FALSE. + */ + EEO_CASE(EEOP_BOOL_OR_STEP_FIRST) + { + *op->d.boolexpr.anynull = false; + + /* + * EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same + * as EEOP_BOOL_OR_STEP - so fall through to that. + */ + + /* FALL THROUGH */ + } + + EEO_CASE(EEOP_BOOL_OR_STEP) + { + if (*op->resnull) + { + *op->d.boolexpr.anynull = true; + } + else if (DatumGetBool(*op->resvalue)) + { + /* result is already set to TRUE, need not change it */ + /* bail out early */ + EEO_JUMP(op->d.boolexpr.jumpdone); + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_BOOL_OR_STEP_LAST) + { + if (*op->resnull) + { + /* result is already set to NULL, need not change it */ + } + else if (DatumGetBool(*op->resvalue)) + { + /* result is already set to TRUE, need not change it */ + + /* + * No point jumping to jumpdone - would be same target (as + * this is the last argument to the AND expression), except + * more expensive. + */ + } + else if (*op->d.boolexpr.anynull) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + } + else + { + /* result is already set to FALSE, need not change it */ + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_BOOL_NOT_STEP) + { + /* + * Evaluation of 'not' is simple... if expr is false, then return + * 'true' and vice versa. It's safe to do this even on a + * nominally null value, so we ignore resnull; that means that + * NULL in produces NULL out, which is what we want. + */ + *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue)); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_QUAL) + { + /* simplified version of BOOL_AND_STEP for use by ExecQual() */ + + /* If argument (also result) is false or null ... */ + if (*op->resnull || + !DatumGetBool(*op->resvalue)) + { + /* ... bail out early, returning FALSE */ + *op->resnull = false; + *op->resvalue = BoolGetDatum(false); + EEO_JUMP(op->d.qualexpr.jumpdone); + } + + /* + * Otherwise, leave the TRUE value in place, in case this is the + * last qual. Then, TRUE is the correct answer. + */ + + EEO_NEXT(); + } + + EEO_CASE(EEOP_JUMP) + { + /* Unconditionally jump to target step */ + EEO_JUMP(op->d.jump.jumpdone); + } + + EEO_CASE(EEOP_JUMP_IF_NULL) + { + /* Transfer control if current result is null */ + if (*op->resnull) + EEO_JUMP(op->d.jump.jumpdone); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_JUMP_IF_NOT_NULL) + { + /* Transfer control if current result is non-null */ + if (!*op->resnull) + EEO_JUMP(op->d.jump.jumpdone); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_JUMP_IF_NOT_TRUE) + { + /* Transfer control if current result is null or false */ + if (*op->resnull || !DatumGetBool(*op->resvalue)) + EEO_JUMP(op->d.jump.jumpdone); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_NULLTEST_ISNULL) + { + *op->resvalue = BoolGetDatum(*op->resnull); + *op->resnull = false; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_NULLTEST_ISNOTNULL) + { + *op->resvalue = BoolGetDatum(!*op->resnull); + *op->resnull = false; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_NULLTEST_ROWISNULL) + { + /* out of line implementation: too large */ + ExecEvalRowNull(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_NULLTEST_ROWISNOTNULL) + { + /* out of line implementation: too large */ + ExecEvalRowNotNull(state, op, econtext); + + EEO_NEXT(); + } + + /* BooleanTest implementations for all booltesttypes */ + + EEO_CASE(EEOP_BOOLTEST_IS_TRUE) + { + if (*op->resnull) + *op->resvalue = BoolGetDatum(false); + else + *op->resvalue = *op->resvalue; + *op->resnull = false; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_BOOLTEST_IS_NOT_TRUE) + { + if (*op->resnull) + *op->resvalue = BoolGetDatum(true); + else + *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue)); + *op->resnull = false; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_BOOLTEST_IS_FALSE) + { + if (*op->resnull) + *op->resvalue = BoolGetDatum(false); + else + *op->resvalue = BoolGetDatum(!DatumGetBool(*op->resvalue)); + *op->resnull = false; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_BOOLTEST_IS_NOT_FALSE) + { + if (*op->resnull) + *op->resvalue = BoolGetDatum(true); + else + *op->resvalue = *op->resvalue; + *op->resnull = false; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_PARAM_EXEC) + { + /* out of line implementation: too large */ + ExecEvalParamExec(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_PARAM_EXTERN) + { + /* out of line implementation: too large */ + ExecEvalParamExtern(state, op, econtext); + EEO_NEXT(); + } + + EEO_CASE(EEOP_CASE_TESTVAL) + { + /* + * Normally upper parts of the expression tree have setup the + * values to be returned here, but some parts of the system + * currently misuse {caseValue,domainValue}_{datum,isNull} to set + * run-time data. So if no values have been set-up, use + * ExprContext's. This isn't pretty, but also not *that* ugly, + * and this is unlikely to be performance sensitive enough to + * worry about an extra branch. + */ + if (op->d.casetest.value) + { + *op->resvalue = *op->d.casetest.value; + *op->resnull = *op->d.casetest.isnull; + } + else + { + *op->resvalue = econtext->caseValue_datum; + *op->resnull = econtext->caseValue_isNull; + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_DOMAIN_TESTVAL) + { + /* + * See EEOP_CASE_TESTVAL comment. + */ + if (op->d.casetest.value) + { + *op->resvalue = *op->d.casetest.value; + *op->resnull = *op->d.casetest.isnull; + } + else + { + *op->resvalue = econtext->domainValue_datum; + *op->resnull = econtext->domainValue_isNull; + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_MAKE_READONLY) + { + /* + * Force a varlena value that might be read multiple times to R/O + */ + if (!*op->d.make_readonly.isnull) + *op->resvalue = + MakeExpandedObjectReadOnlyInternal(*op->d.make_readonly.value); + *op->resnull = *op->d.make_readonly.isnull; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_IOCOERCE) + { + /* + * Evaluate a CoerceViaIO node. This can be quite a hot path, so + * inline as much work as possible. The source value is in our + * result variable. + */ + char *str; + + /* call output function (similar to OutputFunctionCall) */ + if (*op->resnull) + { + /* output functions are not called on nulls */ + str = NULL; + } + else + { + FunctionCallInfo fcinfo_out; + + fcinfo_out = op->d.iocoerce.fcinfo_data_out; + fcinfo_out->arg[0] = *op->resvalue; + fcinfo_out->argnull[0] = false; + + fcinfo_out->isnull = false; + str = DatumGetCString(FunctionCallInvoke(fcinfo_out)); + + /* OutputFunctionCall assumes result isn't null */ + Assert(!fcinfo_out->isnull); + } + + /* call input function (similar to InputFunctionCall) */ + if (!op->d.iocoerce.finfo_in->fn_strict || str != NULL) + { + FunctionCallInfo fcinfo_in; + + fcinfo_in = op->d.iocoerce.fcinfo_data_in; + fcinfo_in->arg[0] = PointerGetDatum(str); + fcinfo_in->argnull[0] = *op->resnull; + /* second and third arguments are already set up */ + + fcinfo_in->isnull = false; + *op->resvalue = FunctionCallInvoke(fcinfo_in); + + /* Should get null result if and only if str is NULL */ + if (str == NULL) + { + Assert(*op->resnull); + Assert(fcinfo_in->isnull); + } + else + { + Assert(!*op->resnull); + Assert(!fcinfo_in->isnull); + } + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_DISTINCT) + { + /* + * IS DISTINCT FROM must evaluate arguments (already done into + * fcinfo->arg/argnull) to determine whether they are NULL; if + * either is NULL then the result is determined. If neither is + * NULL, then proceed to evaluate the comparison function, which + * is just the type's standard equality operator. We need not + * care whether that function is strict. Because the handling of + * nulls is different, we can't just reuse EEOP_FUNCEXPR. + */ + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + + /* check function arguments for NULLness */ + if (fcinfo->argnull[0] && fcinfo->argnull[1]) + { + /* Both NULL? Then is not distinct... */ + *op->resvalue = BoolGetDatum(false); + *op->resnull = false; + } + else if (fcinfo->argnull[0] || fcinfo->argnull[1]) + { + /* Only one is NULL? Then is distinct... */ + *op->resvalue = BoolGetDatum(true); + *op->resnull = false; + } + else + { + /* Neither null, so apply the equality function */ + Datum eqresult; + + fcinfo->isnull = false; + eqresult = (op->d.func.fn_addr) (fcinfo); + /* Must invert result of "="; safe to do even if null */ + *op->resvalue = BoolGetDatum(!DatumGetBool(eqresult)); + *op->resnull = fcinfo->isnull; + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_NULLIF) + { + /* + * The arguments are already evaluated into fcinfo->arg/argnull. + */ + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + + /* if either argument is NULL they can't be equal */ + if (!fcinfo->argnull[0] && !fcinfo->argnull[1]) + { + Datum result; + + fcinfo->isnull = false; + result = (op->d.func.fn_addr) (fcinfo); + + /* if the arguments are equal return null */ + if (!fcinfo->isnull && DatumGetBool(result)) + { + *op->resvalue = (Datum) 0; + *op->resnull = true; + + EEO_NEXT(); + } + } + + /* Arguments aren't equal, so return the first one */ + *op->resvalue = fcinfo->arg[0]; + *op->resnull = fcinfo->argnull[0]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_SQLVALUEFUNCTION) + { + /* + * Doesn't seem worthwhile to have an inline implementation + * efficiency-wise. + */ + ExecEvalSQLValueFunction(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_CURRENTOFEXPR) + { + /* error invocation uses space, and shouldn't ever occur */ + ExecEvalCurrentOfExpr(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ARRAYEXPR) + { + /* too complex for an inline implementation */ + ExecEvalArrayExpr(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ARRAYCOERCE) + { + /* too complex for an inline implementation */ + ExecEvalArrayCoerce(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ROW) + { + /* too complex for an inline implementation */ + ExecEvalRow(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ROWCOMPARE_STEP) + { + FunctionCallInfo fcinfo = op->d.rowcompare_step.fcinfo_data; + + /* force NULL result if strict fn and NULL input */ + if (op->d.rowcompare_step.finfo->fn_strict && + (fcinfo->argnull[0] || fcinfo->argnull[1])) + { + *op->resnull = true; + EEO_JUMP(op->d.rowcompare_step.jumpnull); + } + + /* Apply comparison function */ + fcinfo->isnull = false; + *op->resvalue = (op->d.rowcompare_step.fn_addr) (fcinfo); + + /* force NULL result if NULL function result */ + if (fcinfo->isnull) + { + *op->resnull = true; + EEO_JUMP(op->d.rowcompare_step.jumpnull); + } + *op->resnull = false; + + /* If unequal, no need to compare remaining columns */ + if (DatumGetInt32(*op->resvalue) != 0) + { + EEO_JUMP(op->d.rowcompare_step.jumpdone); + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ROWCOMPARE_FINAL) + { + int32 cmpresult = DatumGetInt32(*op->resvalue); + RowCompareType rctype = op->d.rowcompare_final.rctype; + + *op->resnull = false; + switch (rctype) + { + /* EQ and NE cases aren't allowed here */ + case ROWCOMPARE_LT: + *op->resvalue = BoolGetDatum(cmpresult < 0); + break; + case ROWCOMPARE_LE: + *op->resvalue = BoolGetDatum(cmpresult <= 0); + break; + case ROWCOMPARE_GE: + *op->resvalue = BoolGetDatum(cmpresult >= 0); + break; + case ROWCOMPARE_GT: + *op->resvalue = BoolGetDatum(cmpresult > 0); + break; + default: + Assert(false); + break; + } + + EEO_NEXT(); + } + + EEO_CASE(EEOP_MINMAX) + { + /* too complex for an inline implementation */ + ExecEvalMinMax(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_FIELDSELECT) + { + /* too complex for an inline implementation */ + ExecEvalFieldSelect(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_FIELDSTORE_DEFORM) + { + /* too complex for an inline implementation */ + ExecEvalFieldStoreDeForm(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_FIELDSTORE_FORM) + { + /* too complex for an inline implementation */ + ExecEvalFieldStoreForm(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ARRAYREF_SUBSCRIPT) + { + /* Process an array subscript */ + + /* too complex for an inline implementation */ + if (ExecEvalArrayRefSubscript(state, op)) + { + EEO_NEXT(); + } + else + { + /* Subscript is null, short-circuit ArrayRef to NULL */ + EEO_JUMP(op->d.arrayref_subscript.jumpdone); + } + } + + EEO_CASE(EEOP_ARRAYREF_OLD) + { + /* + * Fetch the old value in an arrayref assignment, in case it's + * referenced (via a CaseTestExpr) inside the assignment + * expression. + */ + + /* too complex for an inline implementation */ + ExecEvalArrayRefOld(state, op); + + EEO_NEXT(); + } + + /* + * Perform ArrayRef assignment + */ + EEO_CASE(EEOP_ARRAYREF_ASSIGN) + { + /* too complex for an inline implementation */ + ExecEvalArrayRefAssign(state, op); + + EEO_NEXT(); + } + + /* + * Fetch subset of an array. + */ + EEO_CASE(EEOP_ARRAYREF_FETCH) + { + /* too complex for an inline implementation */ + ExecEvalArrayRefFetch(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_CONVERT_ROWTYPE) + { + /* too complex for an inline implementation */ + ExecEvalConvertRowtype(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_SCALARARRAYOP) + { + /* too complex for an inline implementation */ + ExecEvalScalarArrayOp(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_DOMAIN_NOTNULL) + { + /* too complex for an inline implementation */ + ExecEvalConstraintNotNull(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_DOMAIN_CHECK) + { + /* too complex for an inline implementation */ + ExecEvalConstraintCheck(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_XMLEXPR) + { + /* too complex for an inline implementation */ + ExecEvalXmlExpr(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_AGGREF) + { + /* + * Returns a Datum whose value is the precomputed aggregate value + * found in the given expression context. + */ + AggrefExprState *aggref = op->d.aggref.astate; + + Assert(econtext->ecxt_aggvalues != NULL); + + *op->resvalue = econtext->ecxt_aggvalues[aggref->aggno]; + *op->resnull = econtext->ecxt_aggnulls[aggref->aggno]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_GROUPING_FUNC) + { + /* too complex/uncommon for an inline implementation */ + ExecEvalGroupingFunc(state, op); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_WINDOW_FUNC) + { + /* + * Like Aggref, just return a precomputed value from the econtext. + */ + WindowFuncExprState *wfunc = op->d.window_func.wfstate; + + Assert(econtext->ecxt_aggvalues != NULL); + + *op->resvalue = econtext->ecxt_aggvalues[wfunc->wfuncno]; + *op->resnull = econtext->ecxt_aggnulls[wfunc->wfuncno]; + + EEO_NEXT(); + } + + EEO_CASE(EEOP_SUBPLAN) + { + /* too complex for an inline implementation */ + ExecEvalSubPlan(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_ALTERNATIVE_SUBPLAN) + { + /* too complex for an inline implementation */ + ExecEvalAlternativeSubPlan(state, op, econtext); + + EEO_NEXT(); + } + + EEO_CASE(EEOP_LAST) + { + /* unreachable */ + Assert(false); + goto out; + } + } + +out: + *isnull = state->resnull; + return state->resvalue; +} + +/* + * Check whether a user attribute in a slot can be referenced by a Var + * expression. This should succeed unless there have been schema changes + * since the expression tree has been created. + */ +static void +CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype) +{ + /* + * What we have to check for here is the possibility of an attribute + * having been changed in type since the plan tree was created. Ideally + * the plan will get invalidated and not re-used, but just in case, we + * keep these defenses. Fortunately it's sufficient to check once on the + * first time through. + * + * System attributes don't require checking since their types never + * change. + * + * Note: we allow a reference to a dropped attribute. slot_getattr will + * force a NULL result in such cases. + * + * Note: ideally we'd check typmod as well as typid, but that seems + * impractical at the moment: in many cases the tupdesc will have been + * generated by ExecTypeFromTL(), and that can't guarantee to generate an + * accurate typmod in all cases, because some expression node types don't + * carry typmod. + */ + if (attnum > 0) + { + TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; + Form_pg_attribute attr; + + if (attnum > slot_tupdesc->natts) /* should never happen */ + elog(ERROR, "attribute number %d exceeds number of columns %d", + attnum, slot_tupdesc->natts); + + attr = slot_tupdesc->attrs[attnum - 1]; + + /* can't check type if dropped, since atttypid is probably 0 */ + if (!attr->attisdropped) + { + if (vartype != attr->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("attribute %d has wrong type", attnum), + errdetail("Table has type %s, but query expects %s.", + format_type_be(attr->atttypid), + format_type_be(vartype)))); + } + } +} + +/* + * get_cached_rowtype: utility function to lookup a rowtype tupdesc + * + * type_id, typmod: identity of the rowtype + * cache_field: where to cache the TupleDesc pointer in expression state node + * (field must be initialized to NULL) + * econtext: expression context we are executing in + * + * NOTE: because the shutdown callback will be called during plan rescan, + * must be prepared to re-do this during any node execution; cannot call + * just once during expression initialization. + */ +static TupleDesc +get_cached_rowtype(Oid type_id, int32 typmod, + TupleDesc *cache_field, ExprContext *econtext) +{ + TupleDesc tupDesc = *cache_field; + + /* Do lookup if no cached value or if requested type changed */ + if (tupDesc == NULL || + type_id != tupDesc->tdtypeid || + typmod != tupDesc->tdtypmod) + { + tupDesc = lookup_rowtype_tupdesc(type_id, typmod); + + if (*cache_field) + { + /* Release old tupdesc; but callback is already registered */ + ReleaseTupleDesc(*cache_field); + } + else + { + /* Need to register shutdown callback to release tupdesc */ + RegisterExprContextCallback(econtext, + ShutdownTupleDescRef, + PointerGetDatum(cache_field)); + } + *cache_field = tupDesc; + } + return tupDesc; +} + +/* + * Callback function to release a tupdesc refcount at econtext shutdown + */ +static void +ShutdownTupleDescRef(Datum arg) +{ + TupleDesc *cache_field = (TupleDesc *) DatumGetPointer(arg); + + if (*cache_field) + ReleaseTupleDesc(*cache_field); + *cache_field = NULL; +} + +/* + * Fast-path functions, for very simple expressions + */ + +/* Simple reference to inner Var, first time through */ +static Datum +ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.var.attnum + 1; + TupleTableSlot *slot = econtext->ecxt_innertuple; + + /* See ExecInterpExpr()'s comments for EEOP_INNER_VAR_FIRST */ + + CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype); + op->opcode = EEOP_INNER_VAR; /* just for cleanliness */ + state->evalfunc = ExecJustInnerVar; + + /* + * Since we use slot_getattr(), we don't need to implement the FETCHSOME + * step explicitly, and we also needn't Assert that the attnum is in range + * --- slot_getattr() will take care of any problems. + */ + return slot_getattr(slot, attnum, isnull); +} + +/* Simple reference to inner Var */ +static Datum +ExecJustInnerVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.var.attnum + 1; + TupleTableSlot *slot = econtext->ecxt_innertuple; + + /* See comments in ExecJustInnerVarFirst */ + return slot_getattr(slot, attnum, isnull); +} + +/* Simple reference to outer Var, first time through */ +static Datum +ExecJustOuterVarFirst(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.var.attnum + 1; + TupleTableSlot *slot = econtext->ecxt_outertuple; + + CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype); + op->opcode = EEOP_OUTER_VAR; /* just for cleanliness */ + state->evalfunc = ExecJustOuterVar; + + /* See comments in ExecJustInnerVarFirst */ + return slot_getattr(slot, attnum, isnull); +} + +/* Simple reference to outer Var */ +static Datum +ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.var.attnum + 1; + TupleTableSlot *slot = econtext->ecxt_outertuple; + + /* See comments in ExecJustInnerVarFirst */ + return slot_getattr(slot, attnum, isnull); +} + +/* Simple reference to scan Var, first time through */ +static Datum +ExecJustScanVarFirst(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.var.attnum + 1; + TupleTableSlot *slot = econtext->ecxt_scantuple; + + CheckVarSlotCompatibility(slot, attnum, op->d.var.vartype); + op->opcode = EEOP_SCAN_VAR; /* just for cleanliness */ + state->evalfunc = ExecJustScanVar; + + /* See comments in ExecJustInnerVarFirst */ + return slot_getattr(slot, attnum, isnull); +} + +/* Simple reference to scan Var */ +static Datum +ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.var.attnum + 1; + TupleTableSlot *slot = econtext->ecxt_scantuple; + + /* See comments in ExecJustInnerVarFirst */ + return slot_getattr(slot, attnum, isnull); +} + +/* Simple Const expression */ +static Datum +ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[0]; + + *isnull = op->d.constval.isnull; + return op->d.constval.value; +} + +/* Evaluate inner Var and assign to appropriate column of result tuple */ +static Datum +ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.assign_var.attnum + 1; + int resultnum = op->d.assign_var.resultnum; + TupleTableSlot *inslot = econtext->ecxt_innertuple; + TupleTableSlot *outslot = state->resultslot; + + /* + * We do not need CheckVarSlotCompatibility here; that was taken care of + * at compilation time. + * + * Since we use slot_getattr(), we don't need to implement the FETCHSOME + * step explicitly, and we also needn't Assert that the attnum is in range + * --- slot_getattr() will take care of any problems. + */ + outslot->tts_values[resultnum] = + slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]); + return 0; +} + +/* Evaluate outer Var and assign to appropriate column of result tuple */ +static Datum +ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.assign_var.attnum + 1; + int resultnum = op->d.assign_var.resultnum; + TupleTableSlot *inslot = econtext->ecxt_outertuple; + TupleTableSlot *outslot = state->resultslot; + + /* See comments in ExecJustAssignInnerVar */ + outslot->tts_values[resultnum] = + slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]); + return 0; +} + +/* Evaluate scan Var and assign to appropriate column of result tuple */ +static Datum +ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull) +{ + ExprEvalStep *op = &state->steps[1]; + int attnum = op->d.assign_var.attnum + 1; + int resultnum = op->d.assign_var.resultnum; + TupleTableSlot *inslot = econtext->ecxt_scantuple; + TupleTableSlot *outslot = state->resultslot; + + /* See comments in ExecJustAssignInnerVar */ + outslot->tts_values[resultnum] = + slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]); + return 0; +} + + +/* + * Do one-time initialization of interpretation machinery. + */ +static void +ExecInitInterpreter(void) +{ +#if defined(EEO_USE_COMPUTED_GOTO) + /* Set up externally-visible pointer to dispatch table */ + if (dispatch_table == NULL) + dispatch_table = (const void **) + DatumGetPointer(ExecInterpExpr(NULL, NULL, NULL)); +#endif +} + +/* + * Function to return the opcode of an expression step. + * + * When direct-threading is in use, ExprState->opcode isn't easily + * decipherable. This function returns the appropriate enum member. + * + * This currently is only supposed to be used in paths that aren't critical + * performance-wise. If that changes, we could add an inverse dispatch_table + * that's sorted on the address, so a binary search can be performed. + */ +ExprEvalOp +ExecEvalStepOp(ExprState *state, ExprEvalStep *op) +{ +#if defined(EEO_USE_COMPUTED_GOTO) + if (state->flags & EEO_FLAG_DIRECT_THREADED) + { + int i; + + for (i = 0; i < EEOP_LAST; i++) + { + if ((void *) op->opcode == dispatch_table[i]) + { + return (ExprEvalOp) i; + } + } + elog(ERROR, "unknown opcode"); + } +#endif + return (ExprEvalOp) op->opcode; +} + + +/* + * Out-of-line helper functions for complex instructions. + */ + +/* + * Evaluate a PARAM_EXEC parameter. + * + * PARAM_EXEC params (internal executor parameters) are stored in the + * ecxt_param_exec_vals array, and can be accessed by array index. + */ +void +ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + ParamExecData *prm; + + prm = &(econtext->ecxt_param_exec_vals[op->d.param.paramid]); + if (unlikely(prm->execPlan != NULL)) + { + /* Parameter not evaluated yet, so go do it */ + ExecSetParamPlan(prm->execPlan, econtext); + /* ExecSetParamPlan should have processed this param... */ + Assert(prm->execPlan == NULL); + } + *op->resvalue = prm->value; + *op->resnull = prm->isnull; +} + +/* + * Evaluate a PARAM_EXTERN parameter. + * + * PARAM_EXTERN parameters must be sought in ecxt_param_list_info. + */ +void +ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + ParamListInfo paramInfo = econtext->ecxt_param_list_info; + int paramId = op->d.param.paramid; + + if (likely(paramInfo && + paramId > 0 && paramId <= paramInfo->numParams)) + { + ParamExternData *prm = ¶mInfo->params[paramId - 1]; + + /* give hook a chance in case parameter is dynamic */ + if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) + (*paramInfo->paramFetch) (paramInfo, paramId); + + if (likely(OidIsValid(prm->ptype))) + { + /* safety check in case hook did something unexpected */ + if (unlikely(prm->ptype != op->d.param.paramtype)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)", + paramId, + format_type_be(prm->ptype), + format_type_be(op->d.param.paramtype)))); + *op->resvalue = prm->value; + *op->resnull = prm->isnull; + return; + } + } + + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no value found for parameter %d", paramId))); +} + +/* + * Evaluate a SQLValueFunction expression. + */ +void +ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op) +{ + SQLValueFunction *svf = op->d.sqlvaluefunction.svf; + FunctionCallInfoData fcinfo; + + *op->resnull = false; + + /* + * Note: current_schema() can return NULL. current_user() etc currently + * cannot, but might as well code those cases the same way for safety. + */ + switch (svf->op) + { + case SVFOP_CURRENT_DATE: + *op->resvalue = DateADTGetDatum(GetSQLCurrentDate()); + break; + case SVFOP_CURRENT_TIME: + case SVFOP_CURRENT_TIME_N: + *op->resvalue = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod)); + break; + case SVFOP_CURRENT_TIMESTAMP: + case SVFOP_CURRENT_TIMESTAMP_N: + *op->resvalue = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod)); + break; + case SVFOP_LOCALTIME: + case SVFOP_LOCALTIME_N: + *op->resvalue = TimeADTGetDatum(GetSQLLocalTime(svf->typmod)); + break; + case SVFOP_LOCALTIMESTAMP: + case SVFOP_LOCALTIMESTAMP_N: + *op->resvalue = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod)); + break; + case SVFOP_CURRENT_ROLE: + case SVFOP_CURRENT_USER: + case SVFOP_USER: + InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); + *op->resvalue = current_user(&fcinfo); + *op->resnull = fcinfo.isnull; + break; + case SVFOP_SESSION_USER: + InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); + *op->resvalue = session_user(&fcinfo); + *op->resnull = fcinfo.isnull; + break; + case SVFOP_CURRENT_CATALOG: + InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); + *op->resvalue = current_database(&fcinfo); + *op->resnull = fcinfo.isnull; + break; + case SVFOP_CURRENT_SCHEMA: + InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); + *op->resvalue = current_schema(&fcinfo); + *op->resnull = fcinfo.isnull; + break; + } +} + +/* + * Raise error if a CURRENT OF expression is evaluated. + * + * The planner should convert CURRENT OF into a TidScan qualification, or some + * other special handling in a ForeignScan node. So we have to be able to do + * ExecInitExpr on a CurrentOfExpr, but we shouldn't ever actually execute it. + * If we get here, we suppose we must be dealing with CURRENT OF on a foreign + * table whose FDW doesn't handle it, and complain accordingly. + */ +void +ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WHERE CURRENT OF is not supported for this table type"))); +} + +/* + * Evaluate NullTest / IS NULL for rows. + */ +void +ExecEvalRowNull(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + ExecEvalRowNullInt(state, op, econtext, true); +} + +/* + * Evaluate NullTest / IS NOT NULL for rows. + */ +void +ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + ExecEvalRowNullInt(state, op, econtext, false); +} + +/* Common code for IS [NOT] NULL on a row value */ +static void +ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, + ExprContext *econtext, bool checkisnull) +{ + Datum value = *op->resvalue; + bool isnull = *op->resnull; + HeapTupleHeader tuple; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; + int att; + + *op->resnull = false; + + /* NULL row variables are treated just as NULL scalar columns */ + if (isnull) + { + *op->resvalue = BoolGetDatum(checkisnull); + return; + } + + /* + * The SQL standard defines IS [NOT] NULL for a non-null rowtype argument + * as: + * + * "R IS NULL" is true if every field is the null value. + * + * "R IS NOT NULL" is true if no field is the null value. + * + * This definition is (apparently intentionally) not recursive; so our + * tests on the fields are primitive attisnull tests, not recursive checks + * to see if they are all-nulls or no-nulls rowtypes. + * + * The standard does not consider the possibility of zero-field rows, but + * here we consider them to vacuously satisfy both predicates. + */ + + tuple = DatumGetHeapTupleHeader(value); + + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + + /* Lookup tupdesc if first time through or if type changes */ + tupDesc = get_cached_rowtype(tupType, tupTypmod, + &op->d.nulltest_row.argdesc, + econtext); + + /* + * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + tmptup.t_data = tuple; + + for (att = 1; att <= tupDesc->natts; att++) + { + /* ignore dropped columns */ + if (tupDesc->attrs[att - 1]->attisdropped) + continue; + if (heap_attisnull(&tmptup, att)) + { + /* null field disproves IS NOT NULL */ + if (!checkisnull) + { + *op->resvalue = BoolGetDatum(false); + return; + } + } + else + { + /* non-null field disproves IS NULL */ + if (checkisnull) + { + *op->resvalue = BoolGetDatum(false); + return; + } + } + } + + *op->resvalue = BoolGetDatum(true); +} + +/* + * Evaluate an ARRAY[] expression. + * + * The individual array elements (or subarrays) have already been evaluated + * into op->d.arrayexpr.elemvalues[]/elemnulls[]. + */ +void +ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op) +{ + ArrayType *result; + Oid element_type = op->d.arrayexpr.elemtype; + int nelems = op->d.arrayexpr.nelems; + int ndims = 0; + int dims[MAXDIM]; + int lbs[MAXDIM]; + + /* Set non-null as default */ + *op->resnull = false; + + if (!op->d.arrayexpr.multidims) + { + /* Elements are presumably of scalar type */ + Datum *dvalues = op->d.arrayexpr.elemvalues; + bool *dnulls = op->d.arrayexpr.elemnulls; + + /* Shouldn't happen here, but if length is 0, return empty array */ + if (nelems == 0) + { + *op->resvalue = + PointerGetDatum(construct_empty_array(element_type)); + return; + } + + /* setup for 1-D array of the given length */ + ndims = 1; + dims[0] = nelems; + lbs[0] = 1; + + result = construct_md_array(dvalues, dnulls, ndims, dims, lbs, + element_type, + op->d.arrayexpr.elemlength, + op->d.arrayexpr.elembyval, + op->d.arrayexpr.elemalign); + } + else + { + /* Must be nested array expressions */ + int nbytes = 0; + int nitems = 0; + int outer_nelems = 0; + int elem_ndims = 0; + int *elem_dims = NULL; + int *elem_lbs = NULL; + bool firstone = true; + bool havenulls = false; + bool haveempty = false; + char **subdata; + bits8 **subbitmaps; + int *subbytes; + int *subnitems; + int32 dataoffset; + char *dat; + int iitem; + int elemoff; + int i; + + subdata = (char **) palloc(nelems * sizeof(char *)); + subbitmaps = (bits8 **) palloc(nelems * sizeof(bits8 *)); + subbytes = (int *) palloc(nelems * sizeof(int)); + subnitems = (int *) palloc(nelems * sizeof(int)); + + /* loop through and get data area from each element */ + for (elemoff = 0; elemoff < nelems; elemoff++) + { + Datum arraydatum; + bool eisnull; + ArrayType *array; + int this_ndims; + + arraydatum = op->d.arrayexpr.elemvalues[elemoff]; + eisnull = op->d.arrayexpr.elemnulls[elemoff]; + + /* temporarily ignore null subarrays */ + if (eisnull) + { + haveempty = true; + continue; + } + + array = DatumGetArrayTypeP(arraydatum); + + /* run-time double-check on element type */ + if (element_type != ARR_ELEMTYPE(array)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot merge incompatible arrays"), + errdetail("Array with element type %s cannot be " + "included in ARRAY construct with element type %s.", + format_type_be(ARR_ELEMTYPE(array)), + format_type_be(element_type)))); + + this_ndims = ARR_NDIM(array); + /* temporarily ignore zero-dimensional subarrays */ + if (this_ndims <= 0) + { + haveempty = true; + continue; + } + + if (firstone) + { + /* Get sub-array details from first member */ + elem_ndims = this_ndims; + ndims = elem_ndims + 1; + if (ndims <= 0 || ndims > MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions (%d) exceeds " \ + "the maximum allowed (%d)", ndims, MAXDIM))); + + elem_dims = (int *) palloc(elem_ndims * sizeof(int)); + memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int)); + elem_lbs = (int *) palloc(elem_ndims * sizeof(int)); + memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int)); + + firstone = false; + } + else + { + /* Check other sub-arrays are compatible */ + if (elem_ndims != this_ndims || + memcmp(elem_dims, ARR_DIMS(array), + elem_ndims * sizeof(int)) != 0 || + memcmp(elem_lbs, ARR_LBOUND(array), + elem_ndims * sizeof(int)) != 0) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("multidimensional arrays must have array " + "expressions with matching dimensions"))); + } + + subdata[outer_nelems] = ARR_DATA_PTR(array); + subbitmaps[outer_nelems] = ARR_NULLBITMAP(array); + subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array); + nbytes += subbytes[outer_nelems]; + subnitems[outer_nelems] = ArrayGetNItems(this_ndims, + ARR_DIMS(array)); + nitems += subnitems[outer_nelems]; + havenulls |= ARR_HASNULL(array); + outer_nelems++; + } + + /* + * If all items were null or empty arrays, return an empty array; + * otherwise, if some were and some weren't, raise error. (Note: we + * must special-case this somehow to avoid trying to generate a 1-D + * array formed from empty arrays. It's not ideal...) + */ + if (haveempty) + { + if (ndims == 0) /* didn't find any nonempty array */ + { + *op->resvalue = PointerGetDatum(construct_empty_array(element_type)); + return; + } + ereport(ERROR, + (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), + errmsg("multidimensional arrays must have array " + "expressions with matching dimensions"))); + } + + /* setup for multi-D array */ + dims[0] = outer_nelems; + lbs[0] = 1; + for (i = 1; i < ndims; i++) + { + dims[i] = elem_dims[i - 1]; + lbs[i] = elem_lbs[i - 1]; + } + + if (havenulls) + { + dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems); + nbytes += dataoffset; + } + else + { + dataoffset = 0; /* marker for no null bitmap */ + nbytes += ARR_OVERHEAD_NONULLS(ndims); + } + + result = (ArrayType *) palloc(nbytes); + SET_VARSIZE(result, nbytes); + result->ndim = ndims; + result->dataoffset = dataoffset; + result->elemtype = element_type; + memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); + memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); + + dat = ARR_DATA_PTR(result); + iitem = 0; + for (i = 0; i < outer_nelems; i++) + { + memcpy(dat, subdata[i], subbytes[i]); + dat += subbytes[i]; + if (havenulls) + array_bitmap_copy(ARR_NULLBITMAP(result), iitem, + subbitmaps[i], 0, + subnitems[i]); + iitem += subnitems[i]; + } + } + + *op->resvalue = PointerGetDatum(result); +} + +/* + * Evaluate an ArrayCoerceExpr expression. + * + * Source array is in step's result variable. + */ +void +ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op) +{ + ArrayCoerceExpr *acoerce = op->d.arraycoerce.coerceexpr; + Datum arraydatum; + FunctionCallInfoData locfcinfo; + + /* NULL array -> NULL result */ + if (*op->resnull) + return; + + arraydatum = *op->resvalue; + + /* + * If it's binary-compatible, modify the element type in the array header, + * but otherwise leave the array as we received it. + */ + if (!OidIsValid(acoerce->elemfuncid)) + { + /* Detoast input array if necessary, and copy in any case */ + ArrayType *array = DatumGetArrayTypePCopy(arraydatum); + + ARR_ELEMTYPE(array) = op->d.arraycoerce.resultelemtype; + *op->resvalue = PointerGetDatum(array); + return; + } + + /* + * Use array_map to apply the function to each array element. + * + * We pass on the desttypmod and isExplicit flags whether or not the + * function wants them. + * + * Note: coercion functions are assumed to not use collation. + */ + InitFunctionCallInfoData(locfcinfo, op->d.arraycoerce.elemfunc, 3, + InvalidOid, NULL, NULL); + locfcinfo.arg[0] = arraydatum; + locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod); + locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit); + locfcinfo.argnull[0] = false; + locfcinfo.argnull[1] = false; + locfcinfo.argnull[2] = false; + + *op->resvalue = array_map(&locfcinfo, op->d.arraycoerce.resultelemtype, + op->d.arraycoerce.amstate); +} + +/* + * Evaluate a ROW() expression. + * + * The individual columns have already been evaluated into + * op->d.row.elemvalues[]/elemnulls[]. + */ +void +ExecEvalRow(ExprState *state, ExprEvalStep *op) +{ + HeapTuple tuple; + + /* build tuple from evaluated field values */ + tuple = heap_form_tuple(op->d.row.tupdesc, + op->d.row.elemvalues, + op->d.row.elemnulls); + + *op->resvalue = HeapTupleGetDatum(tuple); + *op->resnull = false; +} + +/* + * Evaluate GREATEST() or LEAST() expression (note this is *not* MIN()/MAX()). + * + * All of the to-be-compared expressions have already been evaluated into + * op->d.minmax.values[]/nulls[]. + */ +void +ExecEvalMinMax(ExprState *state, ExprEvalStep *op) +{ + Datum *values = op->d.minmax.values; + bool *nulls = op->d.minmax.nulls; + FunctionCallInfo fcinfo = op->d.minmax.fcinfo_data; + MinMaxOp operator = op->d.minmax.op; + int off; + + /* set at initialization */ + Assert(fcinfo->argnull[0] == false); + Assert(fcinfo->argnull[1] == false); + + /* default to null result */ + *op->resnull = true; + + for (off = 0; off < op->d.minmax.nelems; off++) + { + /* ignore NULL inputs */ + if (nulls[off]) + continue; + + if (*op->resnull) + { + /* first nonnull input, adopt value */ + *op->resvalue = values[off]; + *op->resnull = false; + } + else + { + int cmpresult; + + /* apply comparison function */ + fcinfo->arg[0] = *op->resvalue; + fcinfo->arg[1] = values[off]; + + fcinfo->isnull = false; + cmpresult = DatumGetInt32(FunctionCallInvoke(fcinfo)); + if (fcinfo->isnull) /* probably should not happen */ + continue; + + if (cmpresult > 0 && operator == IS_LEAST) + *op->resvalue = values[off]; + else if (cmpresult < 0 && operator == IS_GREATEST) + *op->resvalue = values[off]; + } + } +} + +/* + * Evaluate a FieldSelect node. + * + * Source record is in step's result variable. + */ +void +ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + AttrNumber fieldnum = op->d.fieldselect.fieldnum; + Datum tupDatum; + HeapTupleHeader tuple; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + Form_pg_attribute attr; + HeapTupleData tmptup; + + /* NULL record -> NULL result */ + if (*op->resnull) + return; + + /* Get the composite datum and extract its type fields */ + tupDatum = *op->resvalue; + tuple = DatumGetHeapTupleHeader(tupDatum); + + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + + /* Lookup tupdesc if first time through or if type changes */ + tupDesc = get_cached_rowtype(tupType, tupTypmod, + &op->d.fieldselect.argdesc, + econtext); + + /* + * Find field's attr record. Note we don't support system columns here: a + * datum tuple doesn't have valid values for most of the interesting + * system columns anyway. + */ + if (fieldnum <= 0) /* should never happen */ + elog(ERROR, "unsupported reference to system column %d in FieldSelect", + fieldnum); + if (fieldnum > tupDesc->natts) /* should never happen */ + elog(ERROR, "attribute number %d exceeds number of columns %d", + fieldnum, tupDesc->natts); + attr = tupDesc->attrs[fieldnum - 1]; + + /* Check for dropped column, and force a NULL result if so */ + if (attr->attisdropped) + { + *op->resnull = true; + return; + } + + /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */ + /* As in CheckVarSlotCompatibility, we should but can't check typmod */ + if (op->d.fieldselect.resulttype != attr->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("attribute %d has wrong type", fieldnum), + errdetail("Table has type %s, but query expects %s.", + format_type_be(attr->atttypid), + format_type_be(op->d.fieldselect.resulttype)))); + + /* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + tmptup.t_data = tuple; + + /* extract the field */ + *op->resvalue = heap_getattr(&tmptup, + fieldnum, + tupDesc, + op->resnull); +} + +/* + * Deform source tuple, filling in the step's values/nulls arrays, before + * evaluating individual new values as part of a FieldStore expression. + * Subsequent steps will overwrite individual elements of the values/nulls + * arrays with the new field values, and then FIELDSTORE_FORM will build the + * new tuple value. + * + * Source record is in step's result variable. + */ +void +ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + TupleDesc tupDesc; + + /* Lookup tupdesc if first time through or after rescan */ + tupDesc = get_cached_rowtype(op->d.fieldstore.fstore->resulttype, -1, + op->d.fieldstore.argdesc, econtext); + + /* Check that current tupdesc doesn't have more fields than we allocated */ + if (unlikely(tupDesc->natts > op->d.fieldstore.ncolumns)) + elog(ERROR, "too many columns in composite type %u", + op->d.fieldstore.fstore->resulttype); + + if (*op->resnull) + { + /* Convert null input tuple into an all-nulls row */ + memset(op->d.fieldstore.nulls, true, + op->d.fieldstore.ncolumns * sizeof(bool)); + } + else + { + /* + * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We + * set all the fields in the struct just in case. + */ + Datum tupDatum = *op->resvalue; + HeapTupleHeader tuphdr; + HeapTupleData tmptup; + + tuphdr = DatumGetHeapTupleHeader(tupDatum); + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuphdr; + + heap_deform_tuple(&tmptup, tupDesc, + op->d.fieldstore.values, + op->d.fieldstore.nulls); + } +} + +/* + * Compute the new composite datum after each individual field value of a + * FieldStore expression has been evaluated. + */ +void +ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + HeapTuple tuple; + + /* argdesc should already be valid from the DeForm step */ + tuple = heap_form_tuple(*op->d.fieldstore.argdesc, + op->d.fieldstore.values, + op->d.fieldstore.nulls); + + *op->resvalue = HeapTupleGetDatum(tuple); + *op->resnull = false; +} + +/* + * Process a subscript in an ArrayRef expression. + * + * If subscript is NULL, throw error in assignment case, or in fetch case + * set result to NULL and return false (instructing caller to skip the rest + * of the ArrayRef sequence). + * + * Subscript expression result is in subscriptvalue/subscriptnull. + * On success, integer subscript value has been saved in upperindex[] or + * lowerindex[] for use later. + */ +bool +ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op) +{ + ArrayRefState *arefstate = op->d.arrayref_subscript.state; + int *indexes; + int off; + + /* If any index expr yields NULL, result is NULL or error */ + if (arefstate->subscriptnull) + { + if (arefstate->isassignment) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("array subscript in assignment must not be null"))); + *op->resnull = true; + return false; + } + + /* Convert datum to int, save in appropriate place */ + if (op->d.arrayref_subscript.isupper) + indexes = arefstate->upperindex; + else + indexes = arefstate->lowerindex; + off = op->d.arrayref_subscript.off; + + indexes[off] = DatumGetInt32(arefstate->subscriptvalue); + + return true; +} + +/* + * Evaluate ArrayRef fetch. + * + * Source array is in step's result variable. + */ +void +ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op) +{ + ArrayRefState *arefstate = op->d.arrayref.state; + + /* Should not get here if source array (or any subscript) is null */ + Assert(!(*op->resnull)); + + if (arefstate->numlower == 0) + { + /* Scalar case */ + *op->resvalue = array_get_element(*op->resvalue, + arefstate->numupper, + arefstate->upperindex, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign, + op->resnull); + } + else + { + /* Slice case */ + *op->resvalue = array_get_slice(*op->resvalue, + arefstate->numupper, + arefstate->upperindex, + arefstate->lowerindex, + arefstate->upperprovided, + arefstate->lowerprovided, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign); + } +} + +/* + * Compute old array element/slice value for an ArrayRef assignment + * expression. Will only be generated if the new-value subexpression + * contains ArrayRef or FieldStore. The value is stored into the + * ArrayRefState's prevvalue/prevnull fields. + */ +void +ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op) +{ + ArrayRefState *arefstate = op->d.arrayref.state; + + if (*op->resnull) + { + /* whole array is null, so any element or slice is too */ + arefstate->prevvalue = (Datum) 0; + arefstate->prevnull = true; + } + else if (arefstate->numlower == 0) + { + /* Scalar case */ + arefstate->prevvalue = array_get_element(*op->resvalue, + arefstate->numupper, + arefstate->upperindex, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign, + &arefstate->prevnull); + } + else + { + /* Slice case */ + /* this is currently unreachable */ + arefstate->prevvalue = array_get_slice(*op->resvalue, + arefstate->numupper, + arefstate->upperindex, + arefstate->lowerindex, + arefstate->upperprovided, + arefstate->lowerprovided, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign); + arefstate->prevnull = false; + } +} + +/* + * Evaluate ArrayRef assignment. + * + * Input array (possibly null) is in result area, replacement value is in + * ArrayRefState's replacevalue/replacenull. + */ +void +ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op) +{ + ArrayRefState *arefstate = op->d.arrayref.state; + + /* + * For an assignment to a fixed-length array type, both the original array + * and the value to be assigned into it must be non-NULL, else we punt and + * return the original array. + */ + if (arefstate->refattrlength > 0) /* fixed-length array? */ + { + if (*op->resnull || arefstate->replacenull) + return; + } + + /* + * For assignment to varlena arrays, we handle a NULL original array by + * substituting an empty (zero-dimensional) array; insertion of the new + * element will result in a singleton array value. It does not matter + * whether the new element is NULL. + */ + if (*op->resnull) + { + *op->resvalue = PointerGetDatum(construct_empty_array(arefstate->refelemtype)); + *op->resnull = false; + } + + if (arefstate->numlower == 0) + { + /* Scalar case */ + *op->resvalue = array_set_element(*op->resvalue, + arefstate->numupper, + arefstate->upperindex, + arefstate->replacevalue, + arefstate->replacenull, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign); + } + else + { + /* Slice case */ + *op->resvalue = array_set_slice(*op->resvalue, + arefstate->numupper, + arefstate->upperindex, + arefstate->lowerindex, + arefstate->upperprovided, + arefstate->lowerprovided, + arefstate->replacevalue, + arefstate->replacenull, + arefstate->refattrlength, + arefstate->refelemlength, + arefstate->refelembyval, + arefstate->refelemalign); + } +} + +/* + * Evaluate a rowtype coercion operation. + * This may require rearranging field positions. + * + * Source record is in step's result variable. + */ +void +ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + ConvertRowtypeExpr *convert = op->d.convert_rowtype.convert; + HeapTuple result; + Datum tupDatum; + HeapTupleHeader tuple; + HeapTupleData tmptup; + TupleDesc indesc, + outdesc; + + /* NULL in -> NULL out */ + if (*op->resnull) + return; + + tupDatum = *op->resvalue; + tuple = DatumGetHeapTupleHeader(tupDatum); + + /* Lookup tupdescs if first time through or after rescan */ + if (op->d.convert_rowtype.indesc == NULL) + { + get_cached_rowtype(exprType((Node *) convert->arg), -1, + &op->d.convert_rowtype.indesc, + econtext); + op->d.convert_rowtype.initialized = false; + } + if (op->d.convert_rowtype.outdesc == NULL) + { + get_cached_rowtype(convert->resulttype, -1, + &op->d.convert_rowtype.outdesc, + econtext); + op->d.convert_rowtype.initialized = false; + } + + indesc = op->d.convert_rowtype.indesc; + outdesc = op->d.convert_rowtype.outdesc; + + /* + * We used to be able to assert that incoming tuples are marked with + * exactly the rowtype of indesc. However, now that ExecEvalWholeRowVar + * might change the tuples' marking to plain RECORD due to inserting + * aliases, we can only make this weak test: + */ + Assert(HeapTupleHeaderGetTypeId(tuple) == indesc->tdtypeid || + HeapTupleHeaderGetTypeId(tuple) == RECORDOID); + + /* if first time through, initialize conversion map */ + if (!op->d.convert_rowtype.initialized) + { + MemoryContext old_cxt; + + /* allocate map in long-lived memory context */ + old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + + /* prepare map from old to new attribute numbers */ + op->d.convert_rowtype.map = + convert_tuples_by_name(indesc, outdesc, + gettext_noop("could not convert row type")); + op->d.convert_rowtype.initialized = true; + + MemoryContextSwitchTo(old_cxt); + } + + /* + * No-op if no conversion needed (not clear this can happen here). + */ + if (op->d.convert_rowtype.map == NULL) + return; + + /* + * do_convert_tuple needs a HeapTuple not a bare HeapTupleHeader. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + tmptup.t_data = tuple; + + result = do_convert_tuple(&tmptup, op->d.convert_rowtype.map); + + *op->resvalue = HeapTupleGetDatum(result); +} + +/* + * Evaluate "scalar op ANY/ALL (array)". + * + * Source array is in our result area, scalar arg is already evaluated into + * fcinfo->arg[0]/argnull[0]. + * + * The operator always yields boolean, and we combine the results across all + * array elements using OR and AND (for ANY and ALL respectively). Of course + * we short-circuit as soon as the result is known. + */ +void +ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op) +{ + FunctionCallInfo fcinfo = op->d.scalararrayop.fcinfo_data; + bool useOr = op->d.scalararrayop.useOr; + bool strictfunc = op->d.scalararrayop.finfo->fn_strict; + ArrayType *arr; + int nitems; + Datum result; + bool resultnull; + int i; + int16 typlen; + bool typbyval; + char typalign; + char *s; + bits8 *bitmap; + int bitmask; + + /* + * If the array is NULL then we return NULL --- it's not very meaningful + * to do anything else, even if the operator isn't strict. + */ + if (*op->resnull) + return; + + /* Else okay to fetch and detoast the array */ + arr = DatumGetArrayTypeP(*op->resvalue); + + /* + * If the array is empty, we return either FALSE or TRUE per the useOr + * flag. This is correct even if the scalar is NULL; since we would + * evaluate the operator zero times, it matters not whether it would want + * to return NULL. + */ + nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); + if (nitems <= 0) + { + *op->resvalue = BoolGetDatum(!useOr); + *op->resnull = false; + return; + } + + /* + * If the scalar is NULL, and the function is strict, return NULL; no + * point in iterating the loop. + */ + if (fcinfo->argnull[0] && strictfunc) + { + *op->resnull = true; + return; + } + + /* + * We arrange to look up info about the element type only once per series + * of calls, assuming the element type doesn't change underneath us. + */ + if (op->d.scalararrayop.element_type != ARR_ELEMTYPE(arr)) + { + get_typlenbyvalalign(ARR_ELEMTYPE(arr), + &op->d.scalararrayop.typlen, + &op->d.scalararrayop.typbyval, + &op->d.scalararrayop.typalign); + op->d.scalararrayop.element_type = ARR_ELEMTYPE(arr); + } + + typlen = op->d.scalararrayop.typlen; + typbyval = op->d.scalararrayop.typbyval; + typalign = op->d.scalararrayop.typalign; + + /* Initialize result appropriately depending on useOr */ + result = BoolGetDatum(!useOr); + resultnull = false; + + /* Loop over the array elements */ + s = (char *) ARR_DATA_PTR(arr); + bitmap = ARR_NULLBITMAP(arr); + bitmask = 1; + + for (i = 0; i < nitems; i++) + { + Datum elt; + Datum thisresult; + + /* Get array element, checking for NULL */ + if (bitmap && (*bitmap & bitmask) == 0) + { + fcinfo->arg[1] = (Datum) 0; + fcinfo->argnull[1] = true; + } + else + { + elt = fetch_att(s, typbyval, typlen); + s = att_addlength_pointer(s, typlen, s); + s = (char *) att_align_nominal(s, typalign); + fcinfo->arg[1] = elt; + fcinfo->argnull[1] = false; + } + + /* Call comparison function */ + if (fcinfo->argnull[1] && strictfunc) + { + fcinfo->isnull = true; + thisresult = (Datum) 0; + } + else + { + fcinfo->isnull = false; + thisresult = (op->d.scalararrayop.fn_addr) (fcinfo); + } + + /* Combine results per OR or AND semantics */ + if (fcinfo->isnull) + resultnull = true; + else if (useOr) + { + if (DatumGetBool(thisresult)) + { + result = BoolGetDatum(true); + resultnull = false; + break; /* needn't look at any more elements */ + } + } + else + { + if (!DatumGetBool(thisresult)) + { + result = BoolGetDatum(false); + resultnull = false; + break; /* needn't look at any more elements */ + } + } + + /* advance bitmap pointer if any */ + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100) + { + bitmap++; + bitmask = 1; + } + } + } + + *op->resvalue = result; + *op->resnull = resultnull; +} + +/* + * Evaluate a NOT NULL domain constraint. + */ +void +ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op) +{ + if (*op->resnull) + ereport(ERROR, + (errcode(ERRCODE_NOT_NULL_VIOLATION), + errmsg("domain %s does not allow null values", + format_type_be(op->d.domaincheck.resulttype)), + errdatatype(op->d.domaincheck.resulttype))); +} + +/* + * Evaluate a CHECK domain constraint. + */ +void +ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op) +{ + if (!*op->d.domaincheck.checknull && + !DatumGetBool(*op->d.domaincheck.checkvalue)) + ereport(ERROR, + (errcode(ERRCODE_CHECK_VIOLATION), + errmsg("value for domain %s violates check constraint \"%s\"", + format_type_be(op->d.domaincheck.resulttype), + op->d.domaincheck.constraintname), + errdomainconstraint(op->d.domaincheck.resulttype, + op->d.domaincheck.constraintname))); +} + +/* + * Evaluate the various forms of XmlExpr. + * + * Arguments have been evaluated into named_argvalue/named_argnull + * and/or argvalue/argnull arrays. + */ +void +ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op) +{ + XmlExpr *xexpr = op->d.xmlexpr.xexpr; + Datum value; + int i; + + *op->resnull = true; /* until we get a result */ + *op->resvalue = (Datum) 0; + + switch (xexpr->op) + { + case IS_XMLCONCAT: + { + Datum *argvalue = op->d.xmlexpr.argvalue; + bool *argnull = op->d.xmlexpr.argnull; + List *values = NIL; + + for (i = 0; i < list_length(xexpr->args); i++) + { + if (!argnull[i]) + values = lappend(values, DatumGetPointer(argvalue[i])); + } + + if (values != NIL) + { + *op->resvalue = PointerGetDatum(xmlconcat(values)); + *op->resnull = false; + } + } + break; + + case IS_XMLFOREST: + { + Datum *argvalue = op->d.xmlexpr.named_argvalue; + bool *argnull = op->d.xmlexpr.named_argnull; + StringInfoData buf; + ListCell *lc; + ListCell *lc2; + + initStringInfo(&buf); + + i = 0; + forboth(lc, xexpr->named_args, lc2, xexpr->arg_names) + { + Expr *e = (Expr *) lfirst(lc); + char *argname = strVal(lfirst(lc2)); + + if (!argnull[i]) + { + value = argvalue[i]; + appendStringInfo(&buf, "<%s>%s", + argname, + map_sql_value_to_xml_value(value, + exprType((Node *) e), true), + argname); + *op->resnull = false; + } + i++; + } + + if (!*op->resnull) + { + text *result; + + result = cstring_to_text_with_len(buf.data, buf.len); + *op->resvalue = PointerGetDatum(result); + } + + pfree(buf.data); + } + break; + + case IS_XMLELEMENT: + *op->resvalue = PointerGetDatum(xmlelement(xexpr, + op->d.xmlexpr.named_argvalue, + op->d.xmlexpr.named_argnull, + op->d.xmlexpr.argvalue, + op->d.xmlexpr.argnull)); + *op->resnull = false; + break; + + case IS_XMLPARSE: + { + Datum *argvalue = op->d.xmlexpr.argvalue; + bool *argnull = op->d.xmlexpr.argnull; + text *data; + bool preserve_whitespace; + + /* arguments are known to be text, bool */ + Assert(list_length(xexpr->args) == 2); + + if (argnull[0]) + return; + value = argvalue[0]; + data = DatumGetTextPP(value); + + if (argnull[1]) /* probably can't happen */ + return; + value = argvalue[1]; + preserve_whitespace = DatumGetBool(value); + + *op->resvalue = PointerGetDatum(xmlparse(data, + xexpr->xmloption, + preserve_whitespace)); + *op->resnull = false; + } + break; + + case IS_XMLPI: + { + text *arg; + bool isnull; + + /* optional argument is known to be text */ + Assert(list_length(xexpr->args) <= 1); + + if (xexpr->args) + { + isnull = op->d.xmlexpr.argnull[0]; + if (isnull) + arg = NULL; + else + arg = DatumGetTextPP(op->d.xmlexpr.argvalue[0]); + } + else + { + arg = NULL; + isnull = false; + } + + *op->resvalue = PointerGetDatum(xmlpi(xexpr->name, + arg, + isnull, + op->resnull)); + } + break; + + case IS_XMLROOT: + { + Datum *argvalue = op->d.xmlexpr.argvalue; + bool *argnull = op->d.xmlexpr.argnull; + xmltype *data; + text *version; + int standalone; + + /* arguments are known to be xml, text, int */ + Assert(list_length(xexpr->args) == 3); + + if (argnull[0]) + return; + data = DatumGetXmlP(argvalue[0]); + + if (argnull[1]) + version = NULL; + else + version = DatumGetTextPP(argvalue[1]); + + Assert(!argnull[2]); /* always present */ + standalone = DatumGetInt32(argvalue[2]); + + *op->resvalue = PointerGetDatum(xmlroot(data, + version, + standalone)); + *op->resnull = false; + } + break; + + case IS_XMLSERIALIZE: + { + Datum *argvalue = op->d.xmlexpr.argvalue; + bool *argnull = op->d.xmlexpr.argnull; + + /* argument type is known to be xml */ + Assert(list_length(xexpr->args) == 1); + + if (argnull[0]) + return; + value = argvalue[0]; + + *op->resvalue = PointerGetDatum( + xmltotext_with_xmloption(DatumGetXmlP(value), + xexpr->xmloption)); + *op->resnull = false; + } + break; + + case IS_DOCUMENT: + { + Datum *argvalue = op->d.xmlexpr.argvalue; + bool *argnull = op->d.xmlexpr.argnull; + + /* optional argument is known to be xml */ + Assert(list_length(xexpr->args) == 1); + + if (argnull[0]) + return; + value = argvalue[0]; + + *op->resvalue = + BoolGetDatum(xml_is_document(DatumGetXmlP(value))); + *op->resnull = false; + } + break; + + default: + elog(ERROR, "unrecognized XML operation"); + break; + } +} + +/* + * ExecEvalGroupingFunc + * + * Computes a bitmask with a bit for each (unevaluated) argument expression + * (rightmost arg is least significant bit). + * + * A bit is set if the corresponding expression is NOT part of the set of + * grouping expressions in the current grouping set. + */ +void +ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op) +{ + int result = 0; + Bitmapset *grouped_cols = op->d.grouping_func.parent->grouped_cols; + ListCell *lc; + + foreach(lc, op->d.grouping_func.clauses) + { + int attnum = lfirst_int(lc); + + result <<= 1; + + if (!bms_is_member(attnum, grouped_cols)) + result |= 1; + } + + *op->resvalue = Int32GetDatum(result); + *op->resnull = false; +} + +/* + * Hand off evaluation of a subplan to nodeSubplan.c + */ +void +ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + SubPlanState *sstate = op->d.subplan.sstate; + + /* could potentially be nested, so make sure there's enough stack */ + check_stack_depth(); + + *op->resvalue = ExecSubPlan(sstate, econtext, op->resnull); +} + +/* + * Hand off evaluation of an alternative subplan to nodeSubplan.c + */ +void +ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + AlternativeSubPlanState *asstate = op->d.alternative_subplan.asstate; + + /* could potentially be nested, so make sure there's enough stack */ + check_stack_depth(); + + *op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull); +} + +/* + * Evaluate a wholerow Var expression. + * + * Returns a Datum whose value is the value of a whole-row range variable + * with respect to given expression context. + */ +void +ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + Var *variable = op->d.wholerow.var; + TupleTableSlot *slot; + TupleDesc output_tupdesc; + MemoryContext oldcontext; + HeapTupleHeader dtuple; + HeapTuple tuple; + + /* This was checked by ExecInitExpr */ + Assert(variable->varattno == InvalidAttrNumber); + + /* Get the input slot we want */ + switch (variable->varno) + { + case INNER_VAR: + /* get the tuple from the inner node */ + slot = econtext->ecxt_innertuple; + break; + + case OUTER_VAR: + /* get the tuple from the outer node */ + slot = econtext->ecxt_outertuple; + break; + + /* INDEX_VAR is handled by default case */ + + default: + /* get the tuple from the relation being scanned */ + slot = econtext->ecxt_scantuple; + break; + } + + /* Apply the junkfilter if any */ + if (op->d.wholerow.junkFilter != NULL) + slot = ExecFilterJunk(op->d.wholerow.junkFilter, slot); + + /* + * If first time through, obtain tuple descriptor and check compatibility. + * + * XXX: It'd be great if this could be moved to the expression + * initialization phase, but due to using slots that's currently not + * feasible. + */ + if (op->d.wholerow.first) + { + /* optimistically assume we don't need slow path */ + op->d.wholerow.slow = false; + + /* + * If the Var identifies a named composite type, we must check that + * the actual tuple type is compatible with it. + */ + if (variable->vartype != RECORDOID) + { + TupleDesc var_tupdesc; + TupleDesc slot_tupdesc; + int i; + + /* + * We really only care about numbers of attributes and data types. + * Also, we can ignore type mismatch on columns that are dropped + * in the destination type, so long as (1) the physical storage + * matches or (2) the actual column value is NULL. Case (1) is + * helpful in some cases involving out-of-date cached plans, while + * case (2) is expected behavior in situations such as an INSERT + * into a table with dropped columns (the planner typically + * generates an INT4 NULL regardless of the dropped column type). + * If we find a dropped column and cannot verify that case (1) + * holds, we have to use the slow path to check (2) for each row. + */ + var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); + + slot_tupdesc = slot->tts_tupleDescriptor; + + if (var_tupdesc->natts != slot_tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail_plural("Table row contains %d attribute, but query expects %d.", + "Table row contains %d attributes, but query expects %d.", + slot_tupdesc->natts, + slot_tupdesc->natts, + var_tupdesc->natts))); + + for (i = 0; i < var_tupdesc->natts; i++) + { + Form_pg_attribute vattr = var_tupdesc->attrs[i]; + Form_pg_attribute sattr = slot_tupdesc->attrs[i]; + + if (vattr->atttypid == sattr->atttypid) + continue; /* no worries */ + if (!vattr->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Table has type %s at ordinal position %d, but query expects %s.", + format_type_be(sattr->atttypid), + i + 1, + format_type_be(vattr->atttypid)))); + + if (vattr->attlen != sattr->attlen || + vattr->attalign != sattr->attalign) + op->d.wholerow.slow = true; /* need to check for nulls */ + } + + /* + * Use the variable's declared rowtype as the descriptor for the + * output values, modulo possibly assigning new column names + * below. In particular, we *must* absorb any attisdropped + * markings. + */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + output_tupdesc = CreateTupleDescCopy(var_tupdesc); + MemoryContextSwitchTo(oldcontext); + + ReleaseTupleDesc(var_tupdesc); + } + else + { + /* + * In the RECORD case, we use the input slot's rowtype as the + * descriptor for the output values, modulo possibly assigning new + * column names below. + */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor); + MemoryContextSwitchTo(oldcontext); + } + + /* + * Construct a tuple descriptor for the composite values we'll + * produce, and make sure its record type is "blessed". The main + * reason to do this is to be sure that operations such as + * row_to_json() will see the desired column names when they look up + * the descriptor from the type information embedded in the composite + * values. + * + * We already got the correct physical datatype info above, but now we + * should try to find the source RTE and adopt its column aliases, in + * case they are different from the original rowtype's names. For + * example, in "SELECT foo(t) FROM tab t(x,y)", the first two columns + * in the composite output should be named "x" and "y" regardless of + * tab's column names. + * + * If we can't locate the RTE, assume the column names we've got are + * OK. (As of this writing, the only cases where we can't locate the + * RTE are in execution of trigger WHEN clauses, and then the Var will + * have the trigger's relation's rowtype, so its names are fine.) + * Also, if the creator of the RTE didn't bother to fill in an eref + * field, assume our column names are OK. (This happens in COPY, and + * perhaps other places.) + */ + if (econtext->ecxt_estate && + variable->varno <= list_length(econtext->ecxt_estate->es_range_table)) + { + RangeTblEntry *rte = rt_fetch(variable->varno, + econtext->ecxt_estate->es_range_table); + + if (rte->eref) + ExecTypeSetColNames(output_tupdesc, rte->eref->colnames); + } + + /* Bless the tupdesc if needed, and save it in the execution state */ + op->d.wholerow.tupdesc = BlessTupleDesc(output_tupdesc); + + op->d.wholerow.first = false; + } + + /* + * Make sure all columns of the slot are accessible in the slot's + * Datum/isnull arrays. + */ + slot_getallattrs(slot); + + if (op->d.wholerow.slow) + { + /* Check to see if any dropped attributes are non-null */ + TupleDesc tupleDesc = slot->tts_tupleDescriptor; + TupleDesc var_tupdesc = op->d.wholerow.tupdesc; + int i; + + Assert(var_tupdesc->natts == tupleDesc->natts); + + for (i = 0; i < var_tupdesc->natts; i++) + { + Form_pg_attribute vattr = var_tupdesc->attrs[i]; + Form_pg_attribute sattr = tupleDesc->attrs[i]; + + if (!vattr->attisdropped) + continue; /* already checked non-dropped cols */ + if (slot->tts_isnull[i]) + continue; /* null is always okay */ + if (vattr->attlen != sattr->attlen || + vattr->attalign != sattr->attalign) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("table row type and query-specified row type do not match"), + errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", + i + 1))); + } + } + + /* + * Copy the slot tuple and make sure any toasted fields get detoasted. + * + * (The intermediate copy is a tad annoying here, but we currently have no + * primitive that will do the right thing. Note it is critical that we + * not change the slot's state, so we can't use ExecFetchSlotTupleDatum.) + */ + tuple = ExecCopySlotTuple(slot); + dtuple = (HeapTupleHeader) + DatumGetPointer(heap_copy_tuple_as_datum(tuple, + slot->tts_tupleDescriptor)); + heap_freetuple(tuple); + + /* + * Label the datum with the composite type info we identified before. + */ + HeapTupleHeaderSetTypeId(dtuple, op->d.wholerow.tupdesc->tdtypeid); + HeapTupleHeaderSetTypMod(dtuple, op->d.wholerow.tupdesc->tdtypmod); + + *op->resnull = false; + *op->resvalue = PointerGetDatum(dtuple); +} diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index 5242dee006..108060ac0f 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -327,23 +327,21 @@ ExecInsertIndexTuples(TupleTableSlot *slot, /* Check for partial index */ if (indexInfo->ii_Predicate != NIL) { - List *predicate; + ExprState *predicate; /* * If predicate state not set up yet, create it (in the estate's * per-query context) */ predicate = indexInfo->ii_PredicateState; - if (predicate == NIL) + if (predicate == NULL) { - predicate = (List *) - ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, - estate); + predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); indexInfo->ii_PredicateState = predicate; } /* Skip this index-update if the predicate isn't satisfied */ - if (!ExecQual(predicate, econtext, false)) + if (!ExecQual(predicate, econtext)) continue; } @@ -551,23 +549,21 @@ ExecCheckIndexConstraints(TupleTableSlot *slot, /* Check for partial index */ if (indexInfo->ii_Predicate != NIL) { - List *predicate; + ExprState *predicate; /* * If predicate state not set up yet, create it (in the estate's * per-query context) */ predicate = indexInfo->ii_PredicateState; - if (predicate == NIL) + if (predicate == NULL) { - predicate = (List *) - ExecPrepareExpr((Expr *) indexInfo->ii_Predicate, - estate); + predicate = ExecPrepareQual(indexInfo->ii_Predicate, estate); indexInfo->ii_PredicateState = predicate; } /* Skip this index-update if the predicate isn't satisfied */ - if (!ExecQual(predicate, econtext, false)) + if (!ExecQual(predicate, econtext)) continue; } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index c28cf9c8ea..f2995f2e7b 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -600,8 +600,8 @@ ExecCheckRTEPerms(RangeTblEntry *rte) /* * Only plain-relation RTEs need to be checked here. Function RTEs are - * checked by init_fcache when the function is prepared for execution. - * Join, subquery, and special RTEs need no checks. + * checked when the function is prepared for execution. Join, subquery, + * and special RTEs need no checks. */ if (rte->rtekind != RTE_RELATION) return true; @@ -1275,8 +1275,8 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_TrigFunctions = (FmgrInfo *) palloc0(n * sizeof(FmgrInfo)); - resultRelInfo->ri_TrigWhenExprs = (List **) - palloc0(n * sizeof(List *)); + resultRelInfo->ri_TrigWhenExprs = (ExprState **) + palloc0(n * sizeof(ExprState *)); if (instrument_options) resultRelInfo->ri_TrigInstrument = InstrAlloc(n, instrument_options); } @@ -1723,7 +1723,6 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, ConstrCheck *check = rel->rd_att->constr->check; ExprContext *econtext; MemoryContext oldContext; - List *qual; int i; /* @@ -1735,13 +1734,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, { oldContext = MemoryContextSwitchTo(estate->es_query_cxt); resultRelInfo->ri_ConstraintExprs = - (List **) palloc(ncheck * sizeof(List *)); + (ExprState **) palloc(ncheck * sizeof(ExprState *)); for (i = 0; i < ncheck; i++) { - /* ExecQual wants implicit-AND form */ - qual = make_ands_implicit(stringToNode(check[i].ccbin)); - resultRelInfo->ri_ConstraintExprs[i] = (List *) - ExecPrepareExpr((Expr *) qual, estate); + Expr *checkconstr; + + checkconstr = stringToNode(check[i].ccbin); + resultRelInfo->ri_ConstraintExprs[i] = + ExecPrepareExpr(checkconstr, estate); } MemoryContextSwitchTo(oldContext); } @@ -1758,14 +1758,14 @@ ExecRelCheck(ResultRelInfo *resultRelInfo, /* And evaluate the constraints */ for (i = 0; i < ncheck; i++) { - qual = resultRelInfo->ri_ConstraintExprs[i]; + ExprState *checkconstr = resultRelInfo->ri_ConstraintExprs[i]; /* * NOTE: SQL specifies that a NULL result from a constraint expression - * is not to be treated as a failure. Therefore, tell ExecQual to - * return TRUE for NULL. + * is not to be treated as a failure. Therefore, use ExecCheck not + * ExecQual. */ - if (!ExecQual(qual, econtext, true)) + if (!ExecCheck(checkconstr, econtext)) return check[i].ccname; } @@ -1793,8 +1793,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, { List *qual = resultRelInfo->ri_PartitionCheck; - resultRelInfo->ri_PartitionCheckExpr = (List *) - ExecPrepareExpr((Expr *) qual, estate); + resultRelInfo->ri_PartitionCheckExpr = ExecPrepareCheck(qual, estate); } /* @@ -1810,7 +1809,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, * As in case of the catalogued constraints, we treat a NULL result as * success here, not a failure. */ - return ExecQual(resultRelInfo->ri_PartitionCheckExpr, econtext, true); + return ExecCheck(resultRelInfo->ri_PartitionCheckExpr, econtext); } /* @@ -1990,11 +1989,9 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo, * is visible (in the case of a view) or that it passes the * 'with-check' policy (in the case of row security). If the qual * evaluates to NULL or FALSE, then the new tuple won't be included in - * the view or doesn't pass the 'with-check' policy for the table. We - * need ExecQual to return FALSE for NULL to handle the view case (the - * opposite of what we do above for CHECK constraints). + * the view or doesn't pass the 'with-check' policy for the table. */ - if (!ExecQual((List *) wcoExpr, econtext, false)) + if (!ExecQual(wcoExpr, econtext)) { char *val_desc; Bitmapset *modifiedCols; diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 90bef6f01f..e69de29bb2 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -1,5313 +0,0 @@ -/*------------------------------------------------------------------------- - * - * execQual.c - * Routines to evaluate qualification and targetlist expressions - * - * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/backend/executor/execQual.c - * - *------------------------------------------------------------------------- - */ -/* - * INTERFACE ROUTINES - * ExecEvalExpr - (now a macro) evaluate an expression, return a datum - * ExecEvalExprSwitchContext - same, but switch into eval memory context - * ExecQual - return true/false if qualification is satisfied - * ExecProject - form a new tuple by projecting the given tuple - * - * NOTES - * The more heavily used ExecEvalExpr routines, such as ExecEvalScalarVar, - * are hotspots. Making these faster will speed up the entire system. - * - * ExecProject() is used to make tuple projections. Rather then - * trying to speed it up, the execution plan should be pre-processed - * to facilitate attribute sharing between nodes wherever possible, - * instead of doing needless copying. -cim 5/31/91 - * - * During expression evaluation, we check_stack_depth only in - * ExecMakeFunctionResultSet/ExecMakeFunctionResultNoSets rather than at - * every single node. This is a compromise that trades off precision of - * the stack limit setting to gain speed. - */ - -#include "postgres.h" - -#include "access/htup_details.h" -#include "access/nbtree.h" -#include "access/tupconvert.h" -#include "catalog/objectaccess.h" -#include "catalog/pg_type.h" -#include "executor/execdebug.h" -#include "executor/nodeSubplan.h" -#include "funcapi.h" -#include "miscadmin.h" -#include "nodes/makefuncs.h" -#include "nodes/nodeFuncs.h" -#include "optimizer/planner.h" -#include "parser/parse_coerce.h" -#include "parser/parsetree.h" -#include "pgstat.h" -#include "utils/acl.h" -#include "utils/builtins.h" -#include "utils/date.h" -#include "utils/lsyscache.h" -#include "utils/memutils.h" -#include "utils/timestamp.h" -#include "utils/typcache.h" -#include "utils/xml.h" - - -/* static function decls */ -static Datum ExecEvalArrayRef(ArrayRefExprState *astate, - ExprContext *econtext, - bool *isNull); -static bool isAssignmentIndirectionExpr(ExprState *exprstate); -static Datum ExecEvalAggref(AggrefExprState *aggref, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalWindowFunc(WindowFuncExprState *wfunc, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalConst(ExprState *exprstate, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext, - bool *isNull); -static void init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache, - MemoryContext fcacheCxt, bool allowSRF, bool needDescForSRF); -static void ShutdownFuncExpr(Datum arg); -static TupleDesc get_cached_rowtype(Oid type_id, int32 typmod, - TupleDesc *cache_field, ExprContext *econtext); -static void ShutdownTupleDescRef(Datum arg); -static void ExecEvalFuncArgs(FunctionCallInfo fcinfo, - List *argList, ExprContext *econtext); -static void ExecPrepareTuplestoreResult(FuncExprState *fcache, - ExprContext *econtext, - Tuplestorestate *resultStore, - TupleDesc resultDesc); -static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc); -static Datum ExecMakeFunctionResultNoSets(FuncExprState *fcache, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalFunc(FuncExprState *fcache, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalOper(FuncExprState *fcache, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalDistinct(FuncExprState *fcache, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalCaseTestExpr(ExprState *exprstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalArray(ArrayExprState *astate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalRow(RowExprState *rstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalRowCompare(RowCompareExprState *rstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalCoalesce(CoalesceExprState *coalesceExpr, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalMinMax(MinMaxExprState *minmaxExpr, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalSQLValueFunction(ExprState *svfExpr, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalNullIf(FuncExprState *nullIfExpr, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalNullTest(NullTestState *nstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalBooleanTest(GenericExprState *bstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalCoerceToDomain(CoerceToDomainState *cstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalCoerceToDomainValue(ExprState *exprstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalFieldSelect(FieldSelectState *fstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalFieldStore(FieldStoreState *fstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalRelabelType(GenericExprState *exprstate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalCoerceViaIO(CoerceViaIOState *iostate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate, - ExprContext *econtext, - bool *isNull); -static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, - bool *isNull); -static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate, - ExprContext *econtext, - bool *isNull); - - -/* ---------------------------------------------------------------- - * ExecEvalExpr routines - * - * Recursively evaluate a targetlist or qualification expression. - * - * Each of the following routines having the signature - * Datum ExecEvalFoo(ExprState *expression, - * ExprContext *econtext, - * bool *isNull); - * is responsible for evaluating one type or subtype of ExprState node. - * They are normally called via the ExecEvalExpr macro, which makes use of - * the function pointer set up when the ExprState node was built by - * ExecInitExpr. (In some cases, we change this pointer later to avoid - * re-executing one-time overhead.) - * - * Note: for notational simplicity we declare these functions as taking the - * specific type of ExprState that they work on. This requires casting when - * assigning the function pointer in ExecInitExpr. Be careful that the - * function signature is declared correctly, because the cast suppresses - * automatic checking! - * - * - * All these functions share this calling convention: - * - * Inputs: - * expression: the expression state tree to evaluate - * econtext: evaluation context information - * - * Outputs: - * return value: Datum value of result - * *isNull: set to TRUE if result is NULL (actual return value is - * meaningless if so); set to FALSE if non-null result - * - * The caller should already have switched into the temporary memory - * context econtext->ecxt_per_tuple_memory. The convenience entry point - * ExecEvalExprSwitchContext() is provided for callers who don't prefer to - * do the switch in an outer loop. We do not do the switch in these routines - * because it'd be a waste of cycles during nested expression evaluation. - * ---------------------------------------------------------------- - */ - - -/*---------- - * ExecEvalArrayRef - * - * This function takes an ArrayRef and returns the extracted Datum - * if it's a simple reference, or the modified array value if it's - * an array assignment (i.e., array element or slice insertion). - * - * NOTE: if we get a NULL result from a subscript expression, we return NULL - * when it's an array reference, or raise an error when it's an assignment. - *---------- - */ -static Datum -ExecEvalArrayRef(ArrayRefExprState *astate, - ExprContext *econtext, - bool *isNull) -{ - ArrayRef *arrayRef = (ArrayRef *) astate->xprstate.expr; - Datum array_source; - bool isAssignment = (arrayRef->refassgnexpr != NULL); - bool eisnull; - ListCell *l; - int i = 0, - j = 0; - IntArray upper, - lower; - bool upperProvided[MAXDIM], - lowerProvided[MAXDIM]; - int *lIndex; - - array_source = ExecEvalExpr(astate->refexpr, - econtext, - isNull); - - /* - * If refexpr yields NULL, and it's a fetch, then result is NULL. In the - * assignment case, we'll cons up something below. - */ - if (*isNull) - { - if (!isAssignment) - return (Datum) NULL; - } - - foreach(l, astate->refupperindexpr) - { - ExprState *eltstate = (ExprState *) lfirst(l); - - if (i >= MAXDIM) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", - i + 1, MAXDIM))); - - if (eltstate == NULL) - { - /* Slice bound is omitted, so use array's upper bound */ - Assert(astate->reflowerindexpr != NIL); - upperProvided[i++] = false; - continue; - } - upperProvided[i] = true; - - upper.indx[i++] = DatumGetInt32(ExecEvalExpr(eltstate, - econtext, - &eisnull)); - /* If any index expr yields NULL, result is NULL or error */ - if (eisnull) - { - if (isAssignment) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("array subscript in assignment must not be null"))); - *isNull = true; - return (Datum) NULL; - } - } - - if (astate->reflowerindexpr != NIL) - { - foreach(l, astate->reflowerindexpr) - { - ExprState *eltstate = (ExprState *) lfirst(l); - - if (j >= MAXDIM) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", - j + 1, MAXDIM))); - - if (eltstate == NULL) - { - /* Slice bound is omitted, so use array's lower bound */ - lowerProvided[j++] = false; - continue; - } - lowerProvided[j] = true; - - lower.indx[j++] = DatumGetInt32(ExecEvalExpr(eltstate, - econtext, - &eisnull)); - /* If any index expr yields NULL, result is NULL or error */ - if (eisnull) - { - if (isAssignment) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("array subscript in assignment must not be null"))); - *isNull = true; - return (Datum) NULL; - } - } - /* this can't happen unless parser messed up */ - if (i != j) - elog(ERROR, "upper and lower index lists are not same length"); - lIndex = lower.indx; - } - else - lIndex = NULL; - - if (isAssignment) - { - Datum sourceData; - Datum save_datum; - bool save_isNull; - - /* - * We might have a nested-assignment situation, in which the - * refassgnexpr is itself a FieldStore or ArrayRef that needs to - * obtain and modify the previous value of the array element or slice - * being replaced. If so, we have to extract that value from the - * array and pass it down via the econtext's caseValue. It's safe to - * reuse the CASE mechanism because there cannot be a CASE between - * here and where the value would be needed, and an array assignment - * can't be within a CASE either. (So saving and restoring the - * caseValue is just paranoia, but let's do it anyway.) - * - * Since fetching the old element might be a nontrivial expense, do it - * only if the argument appears to actually need it. - */ - save_datum = econtext->caseValue_datum; - save_isNull = econtext->caseValue_isNull; - - if (isAssignmentIndirectionExpr(astate->refassgnexpr)) - { - if (*isNull) - { - /* whole array is null, so any element or slice is too */ - econtext->caseValue_datum = (Datum) 0; - econtext->caseValue_isNull = true; - } - else if (lIndex == NULL) - { - econtext->caseValue_datum = - array_get_element(array_source, i, - upper.indx, - astate->refattrlength, - astate->refelemlength, - astate->refelembyval, - astate->refelemalign, - &econtext->caseValue_isNull); - } - else - { - /* this is currently unreachable */ - econtext->caseValue_datum = - array_get_slice(array_source, i, - upper.indx, lower.indx, - upperProvided, lowerProvided, - astate->refattrlength, - astate->refelemlength, - astate->refelembyval, - astate->refelemalign); - econtext->caseValue_isNull = false; - } - } - else - { - /* argument shouldn't need caseValue, but for safety set it null */ - econtext->caseValue_datum = (Datum) 0; - econtext->caseValue_isNull = true; - } - - /* - * Evaluate the value to be assigned into the array. - */ - sourceData = ExecEvalExpr(astate->refassgnexpr, - econtext, - &eisnull); - - econtext->caseValue_datum = save_datum; - econtext->caseValue_isNull = save_isNull; - - /* - * For an assignment to a fixed-length array type, both the original - * array and the value to be assigned into it must be non-NULL, else - * we punt and return the original array. - */ - if (astate->refattrlength > 0) /* fixed-length array? */ - if (eisnull || *isNull) - return array_source; - - /* - * For assignment to varlena arrays, we handle a NULL original array - * by substituting an empty (zero-dimensional) array; insertion of the - * new element will result in a singleton array value. It does not - * matter whether the new element is NULL. - */ - if (*isNull) - { - array_source = PointerGetDatum(construct_empty_array(arrayRef->refelemtype)); - *isNull = false; - } - - if (lIndex == NULL) - return array_set_element(array_source, i, - upper.indx, - sourceData, - eisnull, - astate->refattrlength, - astate->refelemlength, - astate->refelembyval, - astate->refelemalign); - else - return array_set_slice(array_source, i, - upper.indx, lower.indx, - upperProvided, lowerProvided, - sourceData, - eisnull, - astate->refattrlength, - astate->refelemlength, - astate->refelembyval, - astate->refelemalign); - } - - if (lIndex == NULL) - return array_get_element(array_source, i, - upper.indx, - astate->refattrlength, - astate->refelemlength, - astate->refelembyval, - astate->refelemalign, - isNull); - else - return array_get_slice(array_source, i, - upper.indx, lower.indx, - upperProvided, lowerProvided, - astate->refattrlength, - astate->refelemlength, - astate->refelembyval, - astate->refelemalign); -} - -/* - * Helper for ExecEvalArrayRef: is expr a nested FieldStore or ArrayRef - * that might need the old element value passed down? - * - * (We could use this in ExecEvalFieldStore too, but in that case passing - * the old value is so cheap there's no need.) - */ -static bool -isAssignmentIndirectionExpr(ExprState *exprstate) -{ - if (exprstate == NULL) - return false; /* just paranoia */ - if (IsA(exprstate, FieldStoreState)) - { - FieldStore *fstore = (FieldStore *) exprstate->expr; - - if (fstore->arg && IsA(fstore->arg, CaseTestExpr)) - return true; - } - else if (IsA(exprstate, ArrayRefExprState)) - { - ArrayRef *arrayRef = (ArrayRef *) exprstate->expr; - - if (arrayRef->refexpr && IsA(arrayRef->refexpr, CaseTestExpr)) - return true; - } - return false; -} - -/* ---------------------------------------------------------------- - * ExecEvalAggref - * - * Returns a Datum whose value is the value of the precomputed - * aggregate found in the given expression context. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalAggref(AggrefExprState *aggref, ExprContext *econtext, - bool *isNull) -{ - if (econtext->ecxt_aggvalues == NULL) /* safety check */ - elog(ERROR, "no aggregates in this expression context"); - - *isNull = econtext->ecxt_aggnulls[aggref->aggno]; - return econtext->ecxt_aggvalues[aggref->aggno]; -} - -/* ---------------------------------------------------------------- - * ExecEvalWindowFunc - * - * Returns a Datum whose value is the value of the precomputed - * window function found in the given expression context. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalWindowFunc(WindowFuncExprState *wfunc, ExprContext *econtext, - bool *isNull) -{ - if (econtext->ecxt_aggvalues == NULL) /* safety check */ - elog(ERROR, "no window functions in this expression context"); - - *isNull = econtext->ecxt_aggnulls[wfunc->wfuncno]; - return econtext->ecxt_aggvalues[wfunc->wfuncno]; -} - -/* ---------------------------------------------------------------- - * ExecEvalScalarVar - * - * Returns a Datum whose value is the value of a scalar (not whole-row) - * range variable with respect to given expression context. - * - * Note: ExecEvalScalarVar is executed only the first time through in a given - * plan; it changes the ExprState's function pointer to pass control directly - * to ExecEvalScalarVarFast after making one-time checks. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, - bool *isNull) -{ - Var *variable = (Var *) exprstate->expr; - TupleTableSlot *slot; - AttrNumber attnum; - - /* Get the input slot and attribute number we want */ - switch (variable->varno) - { - case INNER_VAR: /* get the tuple from the inner node */ - slot = econtext->ecxt_innertuple; - break; - - case OUTER_VAR: /* get the tuple from the outer node */ - slot = econtext->ecxt_outertuple; - break; - - /* INDEX_VAR is handled by default case */ - - default: /* get the tuple from the relation being - * scanned */ - slot = econtext->ecxt_scantuple; - break; - } - - attnum = variable->varattno; - - /* This was checked by ExecInitExpr */ - Assert(attnum != InvalidAttrNumber); - - /* - * If it's a user attribute, check validity (bogus system attnums will be - * caught inside slot_getattr). What we have to check for here is the - * possibility of an attribute having been changed in type since the plan - * tree was created. Ideally the plan will get invalidated and not - * re-used, but just in case, we keep these defenses. Fortunately it's - * sufficient to check once on the first time through. - * - * Note: we allow a reference to a dropped attribute. slot_getattr will - * force a NULL result in such cases. - * - * Note: ideally we'd check typmod as well as typid, but that seems - * impractical at the moment: in many cases the tupdesc will have been - * generated by ExecTypeFromTL(), and that can't guarantee to generate an - * accurate typmod in all cases, because some expression node types don't - * carry typmod. - */ - if (attnum > 0) - { - TupleDesc slot_tupdesc = slot->tts_tupleDescriptor; - Form_pg_attribute attr; - - if (attnum > slot_tupdesc->natts) /* should never happen */ - elog(ERROR, "attribute number %d exceeds number of columns %d", - attnum, slot_tupdesc->natts); - - attr = slot_tupdesc->attrs[attnum - 1]; - - /* can't check type if dropped, since atttypid is probably 0 */ - if (!attr->attisdropped) - { - if (variable->vartype != attr->atttypid) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("attribute %d has wrong type", attnum), - errdetail("Table has type %s, but query expects %s.", - format_type_be(attr->atttypid), - format_type_be(variable->vartype)))); - } - } - - /* Skip the checking on future executions of node */ - exprstate->evalfunc = ExecEvalScalarVarFast; - - /* Fetch the value from the slot */ - return slot_getattr(slot, attnum, isNull); -} - -/* ---------------------------------------------------------------- - * ExecEvalScalarVarFast - * - * Returns a Datum for a scalar variable. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext, - bool *isNull) -{ - Var *variable = (Var *) exprstate->expr; - TupleTableSlot *slot; - AttrNumber attnum; - - /* Get the input slot and attribute number we want */ - switch (variable->varno) - { - case INNER_VAR: /* get the tuple from the inner node */ - slot = econtext->ecxt_innertuple; - break; - - case OUTER_VAR: /* get the tuple from the outer node */ - slot = econtext->ecxt_outertuple; - break; - - /* INDEX_VAR is handled by default case */ - - default: /* get the tuple from the relation being - * scanned */ - slot = econtext->ecxt_scantuple; - break; - } - - attnum = variable->varattno; - - /* Fetch the value from the slot */ - return slot_getattr(slot, attnum, isNull); -} - -/* ---------------------------------------------------------------- - * ExecEvalWholeRowVar - * - * Returns a Datum whose value is the value of a whole-row range - * variable with respect to given expression context. - * - * Note: ExecEvalWholeRowVar is executed only the first time through in a - * given plan; it changes the ExprState's function pointer to pass control - * directly to ExecEvalWholeRowFast or ExecEvalWholeRowSlow after making - * one-time checks. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext, - bool *isNull) -{ - Var *variable = (Var *) wrvstate->xprstate.expr; - TupleTableSlot *slot; - TupleDesc output_tupdesc; - MemoryContext oldcontext; - bool needslow = false; - - /* This was checked by ExecInitExpr */ - Assert(variable->varattno == InvalidAttrNumber); - - /* Get the input slot we want */ - switch (variable->varno) - { - case INNER_VAR: /* get the tuple from the inner node */ - slot = econtext->ecxt_innertuple; - break; - - case OUTER_VAR: /* get the tuple from the outer node */ - slot = econtext->ecxt_outertuple; - break; - - /* INDEX_VAR is handled by default case */ - - default: /* get the tuple from the relation being - * scanned */ - slot = econtext->ecxt_scantuple; - break; - } - - /* - * If the input tuple came from a subquery, it might contain "resjunk" - * columns (such as GROUP BY or ORDER BY columns), which we don't want to - * keep in the whole-row result. We can get rid of such columns by - * passing the tuple through a JunkFilter --- but to make one, we have to - * lay our hands on the subquery's targetlist. Fortunately, there are not - * very many cases where this can happen, and we can identify all of them - * by examining our parent PlanState. We assume this is not an issue in - * standalone expressions that don't have parent plans. (Whole-row Vars - * can occur in such expressions, but they will always be referencing - * table rows.) - */ - if (wrvstate->parent) - { - PlanState *subplan = NULL; - - switch (nodeTag(wrvstate->parent)) - { - case T_SubqueryScanState: - subplan = ((SubqueryScanState *) wrvstate->parent)->subplan; - break; - case T_CteScanState: - subplan = ((CteScanState *) wrvstate->parent)->cteplanstate; - break; - default: - break; - } - - if (subplan) - { - bool junk_filter_needed = false; - ListCell *tlist; - - /* Detect whether subplan tlist actually has any junk columns */ - foreach(tlist, subplan->plan->targetlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tlist); - - if (tle->resjunk) - { - junk_filter_needed = true; - break; - } - } - - /* If so, build the junkfilter in the query memory context */ - if (junk_filter_needed) - { - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - wrvstate->wrv_junkFilter = - ExecInitJunkFilter(subplan->plan->targetlist, - ExecGetResultType(subplan)->tdhasoid, - ExecInitExtraTupleSlot(wrvstate->parent->state)); - MemoryContextSwitchTo(oldcontext); - } - } - } - - /* Apply the junkfilter if any */ - if (wrvstate->wrv_junkFilter != NULL) - slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); - - /* - * If the Var identifies a named composite type, we must check that the - * actual tuple type is compatible with it. - */ - if (variable->vartype != RECORDOID) - { - TupleDesc var_tupdesc; - TupleDesc slot_tupdesc; - int i; - - /* - * We really only care about numbers of attributes and data types. - * Also, we can ignore type mismatch on columns that are dropped in - * the destination type, so long as (1) the physical storage matches - * or (2) the actual column value is NULL. Case (1) is helpful in - * some cases involving out-of-date cached plans, while case (2) is - * expected behavior in situations such as an INSERT into a table with - * dropped columns (the planner typically generates an INT4 NULL - * regardless of the dropped column type). If we find a dropped - * column and cannot verify that case (1) holds, we have to use - * ExecEvalWholeRowSlow to check (2) for each row. - */ - var_tupdesc = lookup_rowtype_tupdesc(variable->vartype, -1); - - slot_tupdesc = slot->tts_tupleDescriptor; - - if (var_tupdesc->natts != slot_tupdesc->natts) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail_plural("Table row contains %d attribute, but query expects %d.", - "Table row contains %d attributes, but query expects %d.", - slot_tupdesc->natts, - slot_tupdesc->natts, - var_tupdesc->natts))); - - for (i = 0; i < var_tupdesc->natts; i++) - { - Form_pg_attribute vattr = var_tupdesc->attrs[i]; - Form_pg_attribute sattr = slot_tupdesc->attrs[i]; - - if (vattr->atttypid == sattr->atttypid) - continue; /* no worries */ - if (!vattr->attisdropped) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Table has type %s at ordinal position %d, but query expects %s.", - format_type_be(sattr->atttypid), - i + 1, - format_type_be(vattr->atttypid)))); - - if (vattr->attlen != sattr->attlen || - vattr->attalign != sattr->attalign) - needslow = true; /* need runtime check for null */ - } - - /* - * Use the variable's declared rowtype as the descriptor for the - * output values, modulo possibly assigning new column names below. In - * particular, we *must* absorb any attisdropped markings. - */ - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - output_tupdesc = CreateTupleDescCopy(var_tupdesc); - MemoryContextSwitchTo(oldcontext); - - ReleaseTupleDesc(var_tupdesc); - } - else - { - /* - * In the RECORD case, we use the input slot's rowtype as the - * descriptor for the output values, modulo possibly assigning new - * column names below. - */ - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor); - MemoryContextSwitchTo(oldcontext); - } - - /* - * Construct a tuple descriptor for the composite values we'll produce, - * and make sure its record type is "blessed". The main reason to do this - * is to be sure that operations such as row_to_json() will see the - * desired column names when they look up the descriptor from the type - * information embedded in the composite values. - * - * We already got the correct physical datatype info above, but now we - * should try to find the source RTE and adopt its column aliases, in case - * they are different from the original rowtype's names. For example, in - * "SELECT foo(t) FROM tab t(x,y)", the first two columns in the composite - * output should be named "x" and "y" regardless of tab's column names. - * - * If we can't locate the RTE, assume the column names we've got are OK. - * (As of this writing, the only cases where we can't locate the RTE are - * in execution of trigger WHEN clauses, and then the Var will have the - * trigger's relation's rowtype, so its names are fine.) Also, if the - * creator of the RTE didn't bother to fill in an eref field, assume our - * column names are OK. (This happens in COPY, and perhaps other places.) - */ - if (econtext->ecxt_estate && - variable->varno <= list_length(econtext->ecxt_estate->es_range_table)) - { - RangeTblEntry *rte = rt_fetch(variable->varno, - econtext->ecxt_estate->es_range_table); - - if (rte->eref) - ExecTypeSetColNames(output_tupdesc, rte->eref->colnames); - } - - /* Bless the tupdesc if needed, and save it in the execution state */ - wrvstate->wrv_tupdesc = BlessTupleDesc(output_tupdesc); - - /* Skip all the above on future executions of node */ - if (needslow) - wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowSlow; - else - wrvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowFast; - - /* Fetch the value */ - return (*wrvstate->xprstate.evalfunc) ((ExprState *) wrvstate, econtext, - isNull); -} - -/* ---------------------------------------------------------------- - * ExecEvalWholeRowFast - * - * Returns a Datum for a whole-row variable. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext, - bool *isNull) -{ - Var *variable = (Var *) wrvstate->xprstate.expr; - TupleTableSlot *slot; - HeapTupleHeader dtuple; - - *isNull = false; - - /* Get the input slot we want */ - switch (variable->varno) - { - case INNER_VAR: /* get the tuple from the inner node */ - slot = econtext->ecxt_innertuple; - break; - - case OUTER_VAR: /* get the tuple from the outer node */ - slot = econtext->ecxt_outertuple; - break; - - /* INDEX_VAR is handled by default case */ - - default: /* get the tuple from the relation being - * scanned */ - slot = econtext->ecxt_scantuple; - break; - } - - /* Apply the junkfilter if any */ - if (wrvstate->wrv_junkFilter != NULL) - slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); - - /* - * Copy the slot tuple and make sure any toasted fields get detoasted. - */ - dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot)); - - /* - * Label the datum with the composite type info we identified before. - */ - HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid); - HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod); - - return PointerGetDatum(dtuple); -} - -/* ---------------------------------------------------------------- - * ExecEvalWholeRowSlow - * - * Returns a Datum for a whole-row variable, in the "slow" case where - * we can't just copy the subplan's output. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext, - bool *isNull) -{ - Var *variable = (Var *) wrvstate->xprstate.expr; - TupleTableSlot *slot; - HeapTuple tuple; - TupleDesc tupleDesc; - TupleDesc var_tupdesc; - HeapTupleHeader dtuple; - int i; - - *isNull = false; - - /* Get the input slot we want */ - switch (variable->varno) - { - case INNER_VAR: /* get the tuple from the inner node */ - slot = econtext->ecxt_innertuple; - break; - - case OUTER_VAR: /* get the tuple from the outer node */ - slot = econtext->ecxt_outertuple; - break; - - /* INDEX_VAR is handled by default case */ - - default: /* get the tuple from the relation being - * scanned */ - slot = econtext->ecxt_scantuple; - break; - } - - /* Apply the junkfilter if any */ - if (wrvstate->wrv_junkFilter != NULL) - slot = ExecFilterJunk(wrvstate->wrv_junkFilter, slot); - - tuple = ExecFetchSlotTuple(slot); - tupleDesc = slot->tts_tupleDescriptor; - - /* wrv_tupdesc is a good enough representation of the Var's rowtype */ - Assert(variable->vartype != RECORDOID); - var_tupdesc = wrvstate->wrv_tupdesc; - - /* Check to see if any dropped attributes are non-null */ - for (i = 0; i < var_tupdesc->natts; i++) - { - Form_pg_attribute vattr = var_tupdesc->attrs[i]; - Form_pg_attribute sattr = tupleDesc->attrs[i]; - - if (!vattr->attisdropped) - continue; /* already checked non-dropped cols */ - if (heap_attisnull(tuple, i + 1)) - continue; /* null is always okay */ - if (vattr->attlen != sattr->attlen || - vattr->attalign != sattr->attalign) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("table row type and query-specified row type do not match"), - errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", - i + 1))); - } - - /* - * Copy the slot tuple and make sure any toasted fields get detoasted. - */ - dtuple = DatumGetHeapTupleHeader(ExecFetchSlotTupleDatum(slot)); - - /* - * Label the datum with the composite type info we identified before. - */ - HeapTupleHeaderSetTypeId(dtuple, wrvstate->wrv_tupdesc->tdtypeid); - HeapTupleHeaderSetTypMod(dtuple, wrvstate->wrv_tupdesc->tdtypmod); - - return PointerGetDatum(dtuple); -} - -/* ---------------------------------------------------------------- - * ExecEvalConst - * - * Returns the value of a constant. - * - * Note that for pass-by-ref datatypes, we return a pointer to the - * actual constant node. This is one of the reasons why functions - * must treat their input arguments as read-only. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalConst(ExprState *exprstate, ExprContext *econtext, - bool *isNull) -{ - Const *con = (Const *) exprstate->expr; - - *isNull = con->constisnull; - return con->constvalue; -} - -/* ---------------------------------------------------------------- - * ExecEvalParamExec - * - * Returns the value of a PARAM_EXEC parameter. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalParamExec(ExprState *exprstate, ExprContext *econtext, - bool *isNull) -{ - Param *expression = (Param *) exprstate->expr; - int thisParamId = expression->paramid; - ParamExecData *prm; - - /* - * PARAM_EXEC params (internal executor parameters) are stored in the - * ecxt_param_exec_vals array, and can be accessed by array index. - */ - prm = &(econtext->ecxt_param_exec_vals[thisParamId]); - if (prm->execPlan != NULL) - { - /* Parameter not evaluated yet, so go do it */ - ExecSetParamPlan(prm->execPlan, econtext); - /* ExecSetParamPlan should have processed this param... */ - Assert(prm->execPlan == NULL); - } - *isNull = prm->isnull; - return prm->value; -} - -/* ---------------------------------------------------------------- - * ExecEvalParamExtern - * - * Returns the value of a PARAM_EXTERN parameter. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext, - bool *isNull) -{ - Param *expression = (Param *) exprstate->expr; - int thisParamId = expression->paramid; - ParamListInfo paramInfo = econtext->ecxt_param_list_info; - - /* - * PARAM_EXTERN parameters must be sought in ecxt_param_list_info. - */ - if (paramInfo && - thisParamId > 0 && thisParamId <= paramInfo->numParams) - { - ParamExternData *prm = ¶mInfo->params[thisParamId - 1]; - - /* give hook a chance in case parameter is dynamic */ - if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) - (*paramInfo->paramFetch) (paramInfo, thisParamId); - - if (OidIsValid(prm->ptype)) - { - /* safety check in case hook did something unexpected */ - if (prm->ptype != expression->paramtype) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)", - thisParamId, - format_type_be(prm->ptype), - format_type_be(expression->paramtype)))); - - *isNull = prm->isnull; - return prm->value; - } - } - - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("no value found for parameter %d", thisParamId))); - return (Datum) 0; /* keep compiler quiet */ -} - - -/* ---------------------------------------------------------------- - * ExecEvalOper / ExecEvalFunc support routines - * ---------------------------------------------------------------- - */ - -/* - * GetAttributeByName - * GetAttributeByNum - * - * These functions return the value of the requested attribute - * out of the given tuple Datum. - * C functions which take a tuple as an argument are expected - * to use these. Ex: overpaid(EMP) might call GetAttributeByNum(). - * Note: these are actually rather slow because they do a typcache - * lookup on each call. - */ -Datum -GetAttributeByNum(HeapTupleHeader tuple, - AttrNumber attrno, - bool *isNull) -{ - Datum result; - Oid tupType; - int32 tupTypmod; - TupleDesc tupDesc; - HeapTupleData tmptup; - - if (!AttributeNumberIsValid(attrno)) - elog(ERROR, "invalid attribute number %d", attrno); - - if (isNull == NULL) - elog(ERROR, "a NULL isNull pointer was passed"); - - if (tuple == NULL) - { - /* Kinda bogus but compatible with old behavior... */ - *isNull = true; - return (Datum) 0; - } - - tupType = HeapTupleHeaderGetTypeId(tuple); - tupTypmod = HeapTupleHeaderGetTypMod(tuple); - tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - - /* - * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all - * the fields in the struct just in case user tries to inspect system - * columns. - */ - tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); - ItemPointerSetInvalid(&(tmptup.t_self)); - tmptup.t_tableOid = InvalidOid; - tmptup.t_data = tuple; - - result = heap_getattr(&tmptup, - attrno, - tupDesc, - isNull); - - ReleaseTupleDesc(tupDesc); - - return result; -} - -Datum -GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) -{ - AttrNumber attrno; - Datum result; - Oid tupType; - int32 tupTypmod; - TupleDesc tupDesc; - HeapTupleData tmptup; - int i; - - if (attname == NULL) - elog(ERROR, "invalid attribute name"); - - if (isNull == NULL) - elog(ERROR, "a NULL isNull pointer was passed"); - - if (tuple == NULL) - { - /* Kinda bogus but compatible with old behavior... */ - *isNull = true; - return (Datum) 0; - } - - tupType = HeapTupleHeaderGetTypeId(tuple); - tupTypmod = HeapTupleHeaderGetTypMod(tuple); - tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); - - attrno = InvalidAttrNumber; - for (i = 0; i < tupDesc->natts; i++) - { - if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0) - { - attrno = tupDesc->attrs[i]->attnum; - break; - } - } - - if (attrno == InvalidAttrNumber) - elog(ERROR, "attribute \"%s\" does not exist", attname); - - /* - * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all - * the fields in the struct just in case user tries to inspect system - * columns. - */ - tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); - ItemPointerSetInvalid(&(tmptup.t_self)); - tmptup.t_tableOid = InvalidOid; - tmptup.t_data = tuple; - - result = heap_getattr(&tmptup, - attrno, - tupDesc, - isNull); - - ReleaseTupleDesc(tupDesc); - - return result; -} - -/* - * init_fcache - initialize a FuncExprState node during first use - */ -static void -init_fcache(Oid foid, Oid input_collation, FuncExprState *fcache, - MemoryContext fcacheCxt, bool allowSRF, bool needDescForSRF) -{ - AclResult aclresult; - - /* Check permission to call function */ - aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid)); - InvokeFunctionExecuteHook(foid); - - /* - * Safety check on nargs. Under normal circumstances this should never - * fail, as parser should check sooner. But possibly it might fail if - * server has been compiled with FUNC_MAX_ARGS smaller than some functions - * declared in pg_proc? - */ - if (list_length(fcache->args) > FUNC_MAX_ARGS) - ereport(ERROR, - (errcode(ERRCODE_TOO_MANY_ARGUMENTS), - errmsg_plural("cannot pass more than %d argument to a function", - "cannot pass more than %d arguments to a function", - FUNC_MAX_ARGS, - FUNC_MAX_ARGS))); - - /* Set up the primary fmgr lookup information */ - fmgr_info_cxt(foid, &(fcache->func), fcacheCxt); - fmgr_info_set_expr((Node *) fcache->xprstate.expr, &(fcache->func)); - - /* Initialize the function call parameter struct as well */ - InitFunctionCallInfoData(fcache->fcinfo_data, &(fcache->func), - list_length(fcache->args), - input_collation, NULL, NULL); - - /* If function returns set, check if that's allowed by caller */ - if (fcache->func.fn_retset && !allowSRF) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - - /* Otherwise, ExecInitExpr should have marked the fcache correctly */ - Assert(fcache->func.fn_retset == fcache->funcReturnsSet); - - /* If function returns set, prepare expected tuple descriptor */ - if (fcache->func.fn_retset && needDescForSRF) - { - TypeFuncClass functypclass; - Oid funcrettype; - TupleDesc tupdesc; - MemoryContext oldcontext; - - functypclass = get_expr_result_type(fcache->func.fn_expr, - &funcrettype, - &tupdesc); - - /* Must save tupdesc in fcache's context */ - oldcontext = MemoryContextSwitchTo(fcacheCxt); - - if (functypclass == TYPEFUNC_COMPOSITE) - { - /* Composite data type, e.g. a table's row type */ - Assert(tupdesc); - /* Must copy it out of typcache for safety */ - fcache->funcResultDesc = CreateTupleDescCopy(tupdesc); - fcache->funcReturnsTuple = true; - } - else if (functypclass == TYPEFUNC_SCALAR) - { - /* Base data type, i.e. scalar */ - tupdesc = CreateTemplateTupleDesc(1, false); - TupleDescInitEntry(tupdesc, - (AttrNumber) 1, - NULL, - funcrettype, - -1, - 0); - fcache->funcResultDesc = tupdesc; - fcache->funcReturnsTuple = false; - } - else if (functypclass == TYPEFUNC_RECORD) - { - /* This will work if function doesn't need an expectedDesc */ - fcache->funcResultDesc = NULL; - fcache->funcReturnsTuple = true; - } - else - { - /* Else, we will fail if function needs an expectedDesc */ - fcache->funcResultDesc = NULL; - } - - MemoryContextSwitchTo(oldcontext); - } - else - fcache->funcResultDesc = NULL; - - /* Initialize additional state */ - fcache->funcResultStore = NULL; - fcache->funcResultSlot = NULL; - fcache->shutdown_reg = false; -} - -/* - * callback function in case a FuncExpr returning a set needs to be shut down - * before it has been run to completion - */ -static void -ShutdownFuncExpr(Datum arg) -{ - FuncExprState *fcache = (FuncExprState *) DatumGetPointer(arg); - - /* If we have a slot, make sure it's let go of any tuplestore pointer */ - if (fcache->funcResultSlot) - ExecClearTuple(fcache->funcResultSlot); - - /* Release any open tuplestore */ - if (fcache->funcResultStore) - tuplestore_end(fcache->funcResultStore); - fcache->funcResultStore = NULL; - - /* Clear any active set-argument state */ - fcache->setArgsValid = false; - - /* execUtils will deregister the callback... */ - fcache->shutdown_reg = false; -} - -/* - * get_cached_rowtype: utility function to lookup a rowtype tupdesc - * - * type_id, typmod: identity of the rowtype - * cache_field: where to cache the TupleDesc pointer in expression state node - * (field must be initialized to NULL) - * econtext: expression context we are executing in - * - * NOTE: because the shutdown callback will be called during plan rescan, - * must be prepared to re-do this during any node execution; cannot call - * just once during expression initialization - */ -static TupleDesc -get_cached_rowtype(Oid type_id, int32 typmod, - TupleDesc *cache_field, ExprContext *econtext) -{ - TupleDesc tupDesc = *cache_field; - - /* Do lookup if no cached value or if requested type changed */ - if (tupDesc == NULL || - type_id != tupDesc->tdtypeid || - typmod != tupDesc->tdtypmod) - { - tupDesc = lookup_rowtype_tupdesc(type_id, typmod); - - if (*cache_field) - { - /* Release old tupdesc; but callback is already registered */ - ReleaseTupleDesc(*cache_field); - } - else - { - /* Need to register shutdown callback to release tupdesc */ - RegisterExprContextCallback(econtext, - ShutdownTupleDescRef, - PointerGetDatum(cache_field)); - } - *cache_field = tupDesc; - } - return tupDesc; -} - -/* - * Callback function to release a tupdesc refcount at expression tree shutdown - */ -static void -ShutdownTupleDescRef(Datum arg) -{ - TupleDesc *cache_field = (TupleDesc *) DatumGetPointer(arg); - - if (*cache_field) - ReleaseTupleDesc(*cache_field); - *cache_field = NULL; -} - -/* - * Evaluate arguments for a function. - */ -static void -ExecEvalFuncArgs(FunctionCallInfo fcinfo, - List *argList, - ExprContext *econtext) -{ - int i; - ListCell *arg; - - i = 0; - foreach(arg, argList) - { - ExprState *argstate = (ExprState *) lfirst(arg); - - fcinfo->arg[i] = ExecEvalExpr(argstate, - econtext, - &fcinfo->argnull[i]); - i++; - } - - Assert(i == fcinfo->nargs); -} - -/* - * ExecPrepareTuplestoreResult - * - * Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a - * tuplestore function result. We must set up a funcResultSlot (unless - * already done in a previous call cycle) and verify that the function - * returned the expected tuple descriptor. - */ -static void -ExecPrepareTuplestoreResult(FuncExprState *fcache, - ExprContext *econtext, - Tuplestorestate *resultStore, - TupleDesc resultDesc) -{ - fcache->funcResultStore = resultStore; - - if (fcache->funcResultSlot == NULL) - { - /* Create a slot so we can read data out of the tuplestore */ - TupleDesc slotDesc; - MemoryContext oldcontext; - - oldcontext = MemoryContextSwitchTo(fcache->func.fn_mcxt); - - /* - * If we were not able to determine the result rowtype from context, - * and the function didn't return a tupdesc, we have to fail. - */ - if (fcache->funcResultDesc) - slotDesc = fcache->funcResultDesc; - else if (resultDesc) - { - /* don't assume resultDesc is long-lived */ - slotDesc = CreateTupleDescCopy(resultDesc); - } - else - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning setof record called in " - "context that cannot accept type record"))); - slotDesc = NULL; /* keep compiler quiet */ - } - - fcache->funcResultSlot = MakeSingleTupleTableSlot(slotDesc); - MemoryContextSwitchTo(oldcontext); - } - - /* - * If function provided a tupdesc, cross-check it. We only really need to - * do this for functions returning RECORD, but might as well do it always. - */ - if (resultDesc) - { - if (fcache->funcResultDesc) - tupledesc_match(fcache->funcResultDesc, resultDesc); - - /* - * If it is a dynamically-allocated TupleDesc, free it: it is - * typically allocated in a per-query context, so we must avoid - * leaking it across multiple usages. - */ - if (resultDesc->tdrefcount == -1) - FreeTupleDesc(resultDesc); - } - - /* Register cleanup callback if we didn't already */ - if (!fcache->shutdown_reg) - { - RegisterExprContextCallback(econtext, - ShutdownFuncExpr, - PointerGetDatum(fcache)); - fcache->shutdown_reg = true; - } -} - -/* - * Check that function result tuple type (src_tupdesc) matches or can - * be considered to match what the query expects (dst_tupdesc). If - * they don't match, ereport. - * - * We really only care about number of attributes and data type. - * Also, we can ignore type mismatch on columns that are dropped in the - * destination type, so long as the physical storage matches. This is - * helpful in some cases involving out-of-date cached plans. - */ -static void -tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) -{ - int i; - - if (dst_tupdesc->natts != src_tupdesc->natts) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("function return row and query-specified return row do not match"), - errdetail_plural("Returned row contains %d attribute, but query expects %d.", - "Returned row contains %d attributes, but query expects %d.", - src_tupdesc->natts, - src_tupdesc->natts, dst_tupdesc->natts))); - - for (i = 0; i < dst_tupdesc->natts; i++) - { - Form_pg_attribute dattr = dst_tupdesc->attrs[i]; - Form_pg_attribute sattr = src_tupdesc->attrs[i]; - - if (IsBinaryCoercible(sattr->atttypid, dattr->atttypid)) - continue; /* no worries */ - if (!dattr->attisdropped) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("function return row and query-specified return row do not match"), - errdetail("Returned type %s at ordinal position %d, but query expects %s.", - format_type_be(sattr->atttypid), - i + 1, - format_type_be(dattr->atttypid)))); - - if (dattr->attlen != sattr->attlen || - dattr->attalign != sattr->attalign) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("function return row and query-specified return row do not match"), - errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", - i + 1))); - } -} - -/* - * ExecMakeFunctionResultSet - * - * Evaluate the arguments to a set-returning function and then call the - * function itself. The argument expressions may not contain set-returning - * functions (the planner is supposed to have separated evaluation for those). - */ -Datum -ExecMakeFunctionResultSet(FuncExprState *fcache, - ExprContext *econtext, - bool *isNull, - ExprDoneCond *isDone) -{ - List *arguments; - Datum result; - FunctionCallInfo fcinfo; - PgStat_FunctionCallUsage fcusage; - ReturnSetInfo rsinfo; - bool callit; - int i; - -restart: - - /* Guard against stack overflow due to overly complex expressions */ - check_stack_depth(); - - /* - * Initialize function cache if first time through. The expression node - * could be either a FuncExpr or an OpExpr. - */ - if (fcache->func.fn_oid == InvalidOid) - { - if (IsA(fcache->xprstate.expr, FuncExpr)) - { - FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; - - init_fcache(func->funcid, func->inputcollid, fcache, - econtext->ecxt_per_query_memory, true, true); - } - else if (IsA(fcache->xprstate.expr, OpExpr)) - { - OpExpr *op = (OpExpr *) fcache->xprstate.expr; - - init_fcache(op->opfuncid, op->inputcollid, fcache, - econtext->ecxt_per_query_memory, true, true); - } - else - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(fcache->xprstate.expr)); - - /* shouldn't get here otherwise */ - Assert(fcache->func.fn_retset); - } - - /* - * If a previous call of the function returned a set result in the form of - * a tuplestore, continue reading rows from the tuplestore until it's - * empty. - */ - if (fcache->funcResultStore) - { - if (tuplestore_gettupleslot(fcache->funcResultStore, true, false, - fcache->funcResultSlot)) - { - *isDone = ExprMultipleResult; - if (fcache->funcReturnsTuple) - { - /* We must return the whole tuple as a Datum. */ - *isNull = false; - return ExecFetchSlotTupleDatum(fcache->funcResultSlot); - } - else - { - /* Extract the first column and return it as a scalar. */ - return slot_getattr(fcache->funcResultSlot, 1, isNull); - } - } - /* Exhausted the tuplestore, so clean up */ - tuplestore_end(fcache->funcResultStore); - fcache->funcResultStore = NULL; - *isDone = ExprEndResult; - *isNull = true; - return (Datum) 0; - } - - /* - * arguments is a list of expressions to evaluate before passing to the - * function manager. We skip the evaluation if it was already done in the - * previous call (ie, we are continuing the evaluation of a set-valued - * function). Otherwise, collect the current argument values into fcinfo. - */ - fcinfo = &fcache->fcinfo_data; - arguments = fcache->args; - if (!fcache->setArgsValid) - ExecEvalFuncArgs(fcinfo, arguments, econtext); - else - { - /* Reset flag (we may set it again below) */ - fcache->setArgsValid = false; - } - - /* - * Now call the function, passing the evaluated parameter values. - */ - - /* Prepare a resultinfo node for communication. */ - fcinfo->resultinfo = (Node *) &rsinfo; - rsinfo.type = T_ReturnSetInfo; - rsinfo.econtext = econtext; - rsinfo.expectedDesc = fcache->funcResultDesc; - rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); - /* note we do not set SFRM_Materialize_Random or _Preferred */ - rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ - rsinfo.setResult = NULL; - rsinfo.setDesc = NULL; - - /* - * If function is strict, and there are any NULL arguments, skip calling - * the function. - */ - callit = true; - if (fcache->func.fn_strict) - { - for (i = 0; i < fcinfo->nargs; i++) - { - if (fcinfo->argnull[i]) - { - callit = false; - break; - } - } - } - - if (callit) - { - pgstat_init_function_usage(fcinfo, &fcusage); - - fcinfo->isnull = false; - rsinfo.isDone = ExprSingleResult; - result = FunctionCallInvoke(fcinfo); - *isNull = fcinfo->isnull; - *isDone = rsinfo.isDone; - - pgstat_end_function_usage(&fcusage, - rsinfo.isDone != ExprMultipleResult); - } - else - { - /* for a strict SRF, result for NULL is an empty set */ - result = (Datum) 0; - *isNull = true; - *isDone = ExprEndResult; - } - - /* Which protocol does function want to use? */ - if (rsinfo.returnMode == SFRM_ValuePerCall) - { - if (*isDone != ExprEndResult) - { - /* - * Save the current argument values to re-use on the next call. - */ - if (*isDone == ExprMultipleResult) - { - fcache->setArgsValid = true; - /* Register cleanup callback if we didn't already */ - if (!fcache->shutdown_reg) - { - RegisterExprContextCallback(econtext, - ShutdownFuncExpr, - PointerGetDatum(fcache)); - fcache->shutdown_reg = true; - } - } - } - } - else if (rsinfo.returnMode == SFRM_Materialize) - { - /* check we're on the same page as the function author */ - if (rsinfo.isDone != ExprSingleResult) - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), - errmsg("table-function protocol for materialize mode was not followed"))); - if (rsinfo.setResult != NULL) - { - /* prepare to return values from the tuplestore */ - ExecPrepareTuplestoreResult(fcache, econtext, - rsinfo.setResult, - rsinfo.setDesc); - /* loop back to top to start returning from tuplestore */ - goto restart; - } - /* if setResult was left null, treat it as empty set */ - *isDone = ExprEndResult; - *isNull = true; - result = (Datum) 0; - } - else - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), - errmsg("unrecognized table-function returnMode: %d", - (int) rsinfo.returnMode))); - - return result; -} - -/* - * ExecMakeFunctionResultNoSets - * - * Evaluate a function or operator node with a non-set-returning function. - * Assumes init_fcache() already done. Hand-tuned for speed. - */ -static Datum -ExecMakeFunctionResultNoSets(FuncExprState *fcache, - ExprContext *econtext, - bool *isNull) -{ - ListCell *arg; - Datum result; - FunctionCallInfo fcinfo; - PgStat_FunctionCallUsage fcusage; - int i; - - /* Guard against stack overflow due to overly complex expressions */ - check_stack_depth(); - - /* inlined, simplified version of ExecEvalFuncArgs */ - fcinfo = &fcache->fcinfo_data; - i = 0; - foreach(arg, fcache->args) - { - ExprState *argstate = (ExprState *) lfirst(arg); - - fcinfo->arg[i] = ExecEvalExpr(argstate, - econtext, - &fcinfo->argnull[i]); - i++; - } - - /* - * If function is strict, and there are any NULL arguments, skip calling - * the function and return NULL. - */ - if (fcache->func.fn_strict) - { - while (--i >= 0) - { - if (fcinfo->argnull[i]) - { - *isNull = true; - return (Datum) 0; - } - } - } - - pgstat_init_function_usage(fcinfo, &fcusage); - - fcinfo->isnull = false; - result = FunctionCallInvoke(fcinfo); - *isNull = fcinfo->isnull; - - pgstat_end_function_usage(&fcusage, true); - - return result; -} - - -/* - * ExecMakeTableFunctionResult - * - * Evaluate a table function, producing a materialized result in a Tuplestore - * object. - */ -Tuplestorestate * -ExecMakeTableFunctionResult(ExprState *funcexpr, - ExprContext *econtext, - MemoryContext argContext, - TupleDesc expectedDesc, - bool randomAccess) -{ - Tuplestorestate *tupstore = NULL; - TupleDesc tupdesc = NULL; - Oid funcrettype; - bool returnsTuple; - bool returnsSet = false; - FunctionCallInfoData fcinfo; - PgStat_FunctionCallUsage fcusage; - ReturnSetInfo rsinfo; - HeapTupleData tmptup; - MemoryContext callerContext; - MemoryContext oldcontext; - bool direct_function_call; - bool first_time = true; - - callerContext = CurrentMemoryContext; - - funcrettype = exprType((Node *) funcexpr->expr); - - returnsTuple = type_is_rowtype(funcrettype); - - /* - * Prepare a resultinfo node for communication. We always do this even if - * not expecting a set result, so that we can pass expectedDesc. In the - * generic-expression case, the expression doesn't actually get to see the - * resultinfo, but set it up anyway because we use some of the fields as - * our own state variables. - */ - rsinfo.type = T_ReturnSetInfo; - rsinfo.econtext = econtext; - rsinfo.expectedDesc = expectedDesc; - rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred); - if (randomAccess) - rsinfo.allowedModes |= (int) SFRM_Materialize_Random; - rsinfo.returnMode = SFRM_ValuePerCall; - /* isDone is filled below */ - rsinfo.setResult = NULL; - rsinfo.setDesc = NULL; - - /* - * Normally the passed expression tree will be a FuncExprState, since the - * grammar only allows a function call at the top level of a table - * function reference. However, if the function doesn't return set then - * the planner might have replaced the function call via constant-folding - * or inlining. So if we see any other kind of expression node, execute - * it via the general ExecEvalExpr() code; the only difference is that we - * don't get a chance to pass a special ReturnSetInfo to any functions - * buried in the expression. - */ - if (funcexpr && IsA(funcexpr, FuncExprState) && - IsA(funcexpr->expr, FuncExpr)) - { - FuncExprState *fcache = (FuncExprState *) funcexpr; - - /* - * This path is similar to ExecMakeFunctionResultSet. - */ - direct_function_call = true; - - /* - * Initialize function cache if first time through - */ - if (fcache->func.fn_oid == InvalidOid) - { - FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; - - init_fcache(func->funcid, func->inputcollid, fcache, - econtext->ecxt_per_query_memory, true, false); - } - returnsSet = fcache->func.fn_retset; - InitFunctionCallInfoData(fcinfo, &(fcache->func), - list_length(fcache->args), - fcache->fcinfo_data.fncollation, - NULL, (Node *) &rsinfo); - - /* - * Evaluate the function's argument list. - * - * We can't do this in the per-tuple context: the argument values - * would disappear when we reset that context in the inner loop. And - * the caller's CurrentMemoryContext is typically a query-lifespan - * context, so we don't want to leak memory there. We require the - * caller to pass a separate memory context that can be used for this, - * and can be reset each time through to avoid bloat. - */ - MemoryContextReset(argContext); - oldcontext = MemoryContextSwitchTo(argContext); - ExecEvalFuncArgs(&fcinfo, fcache->args, econtext); - MemoryContextSwitchTo(oldcontext); - - /* - * If function is strict, and there are any NULL arguments, skip - * calling the function and act like it returned NULL (or an empty - * set, in the returns-set case). - */ - if (fcache->func.fn_strict) - { - int i; - - for (i = 0; i < fcinfo.nargs; i++) - { - if (fcinfo.argnull[i]) - goto no_function_result; - } - } - } - else - { - /* Treat funcexpr as a generic expression */ - direct_function_call = false; - InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); - } - - /* - * Switch to short-lived context for calling the function or expression. - */ - MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - - /* - * Loop to handle the ValuePerCall protocol (which is also the same - * behavior needed in the generic ExecEvalExpr path). - */ - for (;;) - { - Datum result; - - CHECK_FOR_INTERRUPTS(); - - /* - * reset per-tuple memory context before each call of the function or - * expression. This cleans up any local memory the function may leak - * when called. - */ - ResetExprContext(econtext); - - /* Call the function or expression one time */ - if (direct_function_call) - { - pgstat_init_function_usage(&fcinfo, &fcusage); - - fcinfo.isnull = false; - rsinfo.isDone = ExprSingleResult; - result = FunctionCallInvoke(&fcinfo); - - pgstat_end_function_usage(&fcusage, - rsinfo.isDone != ExprMultipleResult); - } - else - { - result = ExecEvalExpr(funcexpr, econtext, &fcinfo.isnull); - rsinfo.isDone = ExprSingleResult; - } - - /* Which protocol does function want to use? */ - if (rsinfo.returnMode == SFRM_ValuePerCall) - { - /* - * Check for end of result set. - */ - if (rsinfo.isDone == ExprEndResult) - break; - - /* - * If first time through, build tuplestore for result. For a - * scalar function result type, also make a suitable tupdesc. - */ - if (first_time) - { - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); - rsinfo.setResult = tupstore; - if (!returnsTuple) - { - tupdesc = CreateTemplateTupleDesc(1, false); - TupleDescInitEntry(tupdesc, - (AttrNumber) 1, - "column", - funcrettype, - -1, - 0); - rsinfo.setDesc = tupdesc; - } - MemoryContextSwitchTo(oldcontext); - } - - /* - * Store current resultset item. - */ - if (returnsTuple) - { - if (!fcinfo.isnull) - { - HeapTupleHeader td = DatumGetHeapTupleHeader(result); - - if (tupdesc == NULL) - { - /* - * This is the first non-NULL result from the - * function. Use the type info embedded in the - * rowtype Datum to look up the needed tupdesc. Make - * a copy for the query. - */ - oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), - HeapTupleHeaderGetTypMod(td)); - rsinfo.setDesc = tupdesc; - MemoryContextSwitchTo(oldcontext); - } - else - { - /* - * Verify all later returned rows have same subtype; - * necessary in case the type is RECORD. - */ - if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid || - HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("rows returned by function are not all of the same row type"))); - } - - /* - * tuplestore_puttuple needs a HeapTuple not a bare - * HeapTupleHeader, but it doesn't need all the fields. - */ - tmptup.t_len = HeapTupleHeaderGetDatumLength(td); - tmptup.t_data = td; - - tuplestore_puttuple(tupstore, &tmptup); - } - else - { - /* - * NULL result from a tuple-returning function; expand it - * to a row of all nulls. We rely on the expectedDesc to - * form such rows. (Note: this would be problematic if - * tuplestore_putvalues saved the tdtypeid/tdtypmod from - * the provided descriptor, since that might not match - * what we get from the function itself. But it doesn't.) - */ - int natts = expectedDesc->natts; - bool *nullflags; - - nullflags = (bool *) palloc(natts * sizeof(bool)); - memset(nullflags, true, natts * sizeof(bool)); - tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); - } - } - else - { - /* Scalar-type case: just store the function result */ - tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull); - } - - /* - * Are we done? - */ - if (rsinfo.isDone != ExprMultipleResult) - break; - } - else if (rsinfo.returnMode == SFRM_Materialize) - { - /* check we're on the same page as the function author */ - if (!first_time || rsinfo.isDone != ExprSingleResult) - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), - errmsg("table-function protocol for materialize mode was not followed"))); - /* Done evaluating the set result */ - break; - } - else - ereport(ERROR, - (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), - errmsg("unrecognized table-function returnMode: %d", - (int) rsinfo.returnMode))); - - first_time = false; - } - -no_function_result: - - /* - * If we got nothing from the function (ie, an empty-set or NULL result), - * we have to create the tuplestore to return, and if it's a - * non-set-returning function then insert a single all-nulls row. As - * above, we depend on the expectedDesc to manufacture the dummy row. - */ - if (rsinfo.setResult == NULL) - { - MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); - rsinfo.setResult = tupstore; - if (!returnsSet) - { - int natts = expectedDesc->natts; - bool *nullflags; - - MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - nullflags = (bool *) palloc(natts * sizeof(bool)); - memset(nullflags, true, natts * sizeof(bool)); - tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); - } - } - - /* - * If function provided a tupdesc, cross-check it. We only really need to - * do this for functions returning RECORD, but might as well do it always. - */ - if (rsinfo.setDesc) - { - tupledesc_match(expectedDesc, rsinfo.setDesc); - - /* - * If it is a dynamically-allocated TupleDesc, free it: it is - * typically allocated in a per-query context, so we must avoid - * leaking it across multiple usages. - */ - if (rsinfo.setDesc->tdrefcount == -1) - FreeTupleDesc(rsinfo.setDesc); - } - - MemoryContextSwitchTo(callerContext); - - /* All done, pass back the tuplestore */ - return rsinfo.setResult; -} - - -/* ---------------------------------------------------------------- - * ExecEvalFunc - * ExecEvalOper - * - * Evaluate the functional result of a list of arguments by calling the - * function manager. - * ---------------------------------------------------------------- - */ - -/* ---------------------------------------------------------------- - * ExecEvalFunc - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalFunc(FuncExprState *fcache, - ExprContext *econtext, - bool *isNull) -{ - /* This is called only the first time through */ - FuncExpr *func = (FuncExpr *) fcache->xprstate.expr; - - /* Initialize function lookup info */ - init_fcache(func->funcid, func->inputcollid, fcache, - econtext->ecxt_per_query_memory, false, false); - - /* Change the evalfunc pointer to save a few cycles in additional calls */ - fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets; - return ExecMakeFunctionResultNoSets(fcache, econtext, isNull); -} - -/* ---------------------------------------------------------------- - * ExecEvalOper - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalOper(FuncExprState *fcache, - ExprContext *econtext, - bool *isNull) -{ - /* This is called only the first time through */ - OpExpr *op = (OpExpr *) fcache->xprstate.expr; - - /* Initialize function lookup info */ - init_fcache(op->opfuncid, op->inputcollid, fcache, - econtext->ecxt_per_query_memory, false, false); - - /* Change the evalfunc pointer to save a few cycles in additional calls */ - fcache->xprstate.evalfunc = (ExprStateEvalFunc) ExecMakeFunctionResultNoSets; - return ExecMakeFunctionResultNoSets(fcache, econtext, isNull); -} - -/* ---------------------------------------------------------------- - * ExecEvalDistinct - * - * IS DISTINCT FROM must evaluate arguments to determine whether - * they are NULL; if either is NULL then the result is already - * known. If neither is NULL, then proceed to evaluate the - * function. Note that this is *always* derived from the equals - * operator, but since we need special processing of the arguments - * we can not simply reuse ExecEvalOper() or ExecEvalFunc(). - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalDistinct(FuncExprState *fcache, - ExprContext *econtext, - bool *isNull) -{ - Datum result; - FunctionCallInfo fcinfo; - - /* Set non-null as default */ - *isNull = false; - - /* - * Initialize function cache if first time through - */ - if (fcache->func.fn_oid == InvalidOid) - { - DistinctExpr *op = (DistinctExpr *) fcache->xprstate.expr; - - init_fcache(op->opfuncid, op->inputcollid, fcache, - econtext->ecxt_per_query_memory, false, false); - } - - /* - * Evaluate arguments - */ - fcinfo = &fcache->fcinfo_data; - ExecEvalFuncArgs(fcinfo, fcache->args, econtext); - Assert(fcinfo->nargs == 2); - - if (fcinfo->argnull[0] && fcinfo->argnull[1]) - { - /* Both NULL? Then is not distinct... */ - result = BoolGetDatum(FALSE); - } - else if (fcinfo->argnull[0] || fcinfo->argnull[1]) - { - /* Only one is NULL? Then is distinct... */ - result = BoolGetDatum(TRUE); - } - else - { - fcinfo->isnull = false; - result = FunctionCallInvoke(fcinfo); - *isNull = fcinfo->isnull; - /* Must invert result of "=" */ - result = BoolGetDatum(!DatumGetBool(result)); - } - - return result; -} - -/* - * ExecEvalScalarArrayOp - * - * Evaluate "scalar op ANY/ALL (array)". The operator always yields boolean, - * and we combine the results across all array elements using OR and AND - * (for ANY and ALL respectively). Of course we short-circuit as soon as - * the result is known. - */ -static Datum -ExecEvalScalarArrayOp(ScalarArrayOpExprState *sstate, - ExprContext *econtext, - bool *isNull) -{ - ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) sstate->fxprstate.xprstate.expr; - bool useOr = opexpr->useOr; - ArrayType *arr; - int nitems; - Datum result; - bool resultnull; - FunctionCallInfo fcinfo; - int i; - int16 typlen; - bool typbyval; - char typalign; - char *s; - bits8 *bitmap; - int bitmask; - - /* Set non-null as default */ - *isNull = false; - - /* - * Initialize function cache if first time through - */ - if (sstate->fxprstate.func.fn_oid == InvalidOid) - { - init_fcache(opexpr->opfuncid, opexpr->inputcollid, &sstate->fxprstate, - econtext->ecxt_per_query_memory, false, false); - } - - /* - * Evaluate arguments - */ - fcinfo = &sstate->fxprstate.fcinfo_data; - ExecEvalFuncArgs(fcinfo, sstate->fxprstate.args, econtext); - Assert(fcinfo->nargs == 2); - - /* - * If the array is NULL then we return NULL --- it's not very meaningful - * to do anything else, even if the operator isn't strict. - */ - if (fcinfo->argnull[1]) - { - *isNull = true; - return (Datum) 0; - } - /* Else okay to fetch and detoast the array */ - arr = DatumGetArrayTypeP(fcinfo->arg[1]); - - /* - * If the array is empty, we return either FALSE or TRUE per the useOr - * flag. This is correct even if the scalar is NULL; since we would - * evaluate the operator zero times, it matters not whether it would want - * to return NULL. - */ - nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); - if (nitems <= 0) - return BoolGetDatum(!useOr); - - /* - * If the scalar is NULL, and the function is strict, return NULL; no - * point in iterating the loop. - */ - if (fcinfo->argnull[0] && sstate->fxprstate.func.fn_strict) - { - *isNull = true; - return (Datum) 0; - } - - /* - * We arrange to look up info about the element type only once per series - * of calls, assuming the element type doesn't change underneath us. - */ - if (sstate->element_type != ARR_ELEMTYPE(arr)) - { - get_typlenbyvalalign(ARR_ELEMTYPE(arr), - &sstate->typlen, - &sstate->typbyval, - &sstate->typalign); - sstate->element_type = ARR_ELEMTYPE(arr); - } - typlen = sstate->typlen; - typbyval = sstate->typbyval; - typalign = sstate->typalign; - - result = BoolGetDatum(!useOr); - resultnull = false; - - /* Loop over the array elements */ - s = (char *) ARR_DATA_PTR(arr); - bitmap = ARR_NULLBITMAP(arr); - bitmask = 1; - - for (i = 0; i < nitems; i++) - { - Datum elt; - Datum thisresult; - - /* Get array element, checking for NULL */ - if (bitmap && (*bitmap & bitmask) == 0) - { - fcinfo->arg[1] = (Datum) 0; - fcinfo->argnull[1] = true; - } - else - { - elt = fetch_att(s, typbyval, typlen); - s = att_addlength_pointer(s, typlen, s); - s = (char *) att_align_nominal(s, typalign); - fcinfo->arg[1] = elt; - fcinfo->argnull[1] = false; - } - - /* Call comparison function */ - if (fcinfo->argnull[1] && sstate->fxprstate.func.fn_strict) - { - fcinfo->isnull = true; - thisresult = (Datum) 0; - } - else - { - fcinfo->isnull = false; - thisresult = FunctionCallInvoke(fcinfo); - } - - /* Combine results per OR or AND semantics */ - if (fcinfo->isnull) - resultnull = true; - else if (useOr) - { - if (DatumGetBool(thisresult)) - { - result = BoolGetDatum(true); - resultnull = false; - break; /* needn't look at any more elements */ - } - } - else - { - if (!DatumGetBool(thisresult)) - { - result = BoolGetDatum(false); - resultnull = false; - break; /* needn't look at any more elements */ - } - } - - /* advance bitmap pointer if any */ - if (bitmap) - { - bitmask <<= 1; - if (bitmask == 0x100) - { - bitmap++; - bitmask = 1; - } - } - } - - *isNull = resultnull; - return result; -} - -/* ---------------------------------------------------------------- - * ExecEvalNot - * ExecEvalOr - * ExecEvalAnd - * - * Evaluate boolean expressions, with appropriate short-circuiting. - * - * The query planner reformulates clause expressions in the - * qualification to conjunctive normal form. If we ever get - * an AND to evaluate, we can be sure that it's not a top-level - * clause in the qualification, but appears lower (as a function - * argument, for example), or in the target list. Not that you - * need to know this, mind you... - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalNot(BoolExprState *notclause, ExprContext *econtext, - bool *isNull) -{ - ExprState *clause = linitial(notclause->args); - Datum expr_value; - - expr_value = ExecEvalExpr(clause, econtext, isNull); - - /* - * if the expression evaluates to null, then we just cascade the null back - * to whoever called us. - */ - if (*isNull) - return expr_value; - - /* - * evaluation of 'not' is simple.. expr is false, then return 'true' and - * vice versa. - */ - return BoolGetDatum(!DatumGetBool(expr_value)); -} - -/* ---------------------------------------------------------------- - * ExecEvalOr - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalOr(BoolExprState *orExpr, ExprContext *econtext, - bool *isNull) -{ - List *clauses = orExpr->args; - ListCell *clause; - bool AnyNull; - - AnyNull = false; - - /* - * If any of the clauses is TRUE, the OR result is TRUE regardless of the - * states of the rest of the clauses, so we can stop evaluating and return - * TRUE immediately. If none are TRUE and one or more is NULL, we return - * NULL; otherwise we return FALSE. This makes sense when you interpret - * NULL as "don't know": if we have a TRUE then the OR is TRUE even if we - * aren't sure about some of the other inputs. If all the known inputs are - * FALSE, but we have one or more "don't knows", then we have to report - * that we "don't know" what the OR's result should be --- perhaps one of - * the "don't knows" would have been TRUE if we'd known its value. Only - * when all the inputs are known to be FALSE can we state confidently that - * the OR's result is FALSE. - */ - foreach(clause, clauses) - { - ExprState *clausestate = (ExprState *) lfirst(clause); - Datum clause_value; - - clause_value = ExecEvalExpr(clausestate, econtext, isNull); - - /* - * if we have a non-null true result, then return it. - */ - if (*isNull) - AnyNull = true; /* remember we got a null */ - else if (DatumGetBool(clause_value)) - return clause_value; - } - - /* AnyNull is true if at least one clause evaluated to NULL */ - *isNull = AnyNull; - return BoolGetDatum(false); -} - -/* ---------------------------------------------------------------- - * ExecEvalAnd - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalAnd(BoolExprState *andExpr, ExprContext *econtext, - bool *isNull) -{ - List *clauses = andExpr->args; - ListCell *clause; - bool AnyNull; - - AnyNull = false; - - /* - * If any of the clauses is FALSE, the AND result is FALSE regardless of - * the states of the rest of the clauses, so we can stop evaluating and - * return FALSE immediately. If none are FALSE and one or more is NULL, - * we return NULL; otherwise we return TRUE. This makes sense when you - * interpret NULL as "don't know", using the same sort of reasoning as for - * OR, above. - */ - - foreach(clause, clauses) - { - ExprState *clausestate = (ExprState *) lfirst(clause); - Datum clause_value; - - clause_value = ExecEvalExpr(clausestate, econtext, isNull); - - /* - * if we have a non-null false result, then return it. - */ - if (*isNull) - AnyNull = true; /* remember we got a null */ - else if (!DatumGetBool(clause_value)) - return clause_value; - } - - /* AnyNull is true if at least one clause evaluated to NULL */ - *isNull = AnyNull; - return BoolGetDatum(!AnyNull); -} - -/* ---------------------------------------------------------------- - * ExecEvalConvertRowtype - * - * Evaluate a rowtype coercion operation. This may require - * rearranging field positions. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate, - ExprContext *econtext, - bool *isNull) -{ - ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) cstate->xprstate.expr; - HeapTuple result; - Datum tupDatum; - HeapTupleHeader tuple; - HeapTupleData tmptup; - - tupDatum = ExecEvalExpr(cstate->arg, econtext, isNull); - - /* this test covers the isDone exception too: */ - if (*isNull) - return tupDatum; - - tuple = DatumGetHeapTupleHeader(tupDatum); - - /* Lookup tupdescs if first time through or after rescan */ - if (cstate->indesc == NULL) - { - get_cached_rowtype(exprType((Node *) convert->arg), -1, - &cstate->indesc, econtext); - cstate->initialized = false; - } - if (cstate->outdesc == NULL) - { - get_cached_rowtype(convert->resulttype, -1, - &cstate->outdesc, econtext); - cstate->initialized = false; - } - - /* - * We used to be able to assert that incoming tuples are marked with - * exactly the rowtype of cstate->indesc. However, now that - * ExecEvalWholeRowVar might change the tuples' marking to plain RECORD - * due to inserting aliases, we can only make this weak test: - */ - Assert(HeapTupleHeaderGetTypeId(tuple) == cstate->indesc->tdtypeid || - HeapTupleHeaderGetTypeId(tuple) == RECORDOID); - - /* if first time through, initialize conversion map */ - if (!cstate->initialized) - { - MemoryContext old_cxt; - - /* allocate map in long-lived memory context */ - old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); - - /* prepare map from old to new attribute numbers */ - cstate->map = convert_tuples_by_name(cstate->indesc, - cstate->outdesc, - gettext_noop("could not convert row type")); - cstate->initialized = true; - - MemoryContextSwitchTo(old_cxt); - } - - /* - * No-op if no conversion needed (not clear this can happen here). - */ - if (cstate->map == NULL) - return tupDatum; - - /* - * do_convert_tuple needs a HeapTuple not a bare HeapTupleHeader. - */ - tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); - tmptup.t_data = tuple; - - result = do_convert_tuple(&tmptup, cstate->map); - - return HeapTupleGetDatum(result); -} - -/* ---------------------------------------------------------------- - * ExecEvalCase - * - * Evaluate a CASE clause. Will have boolean expressions - * inside the WHEN clauses, and will have expressions - * for results. - * - thomas 1998-11-09 - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalCase(CaseExprState *caseExpr, ExprContext *econtext, - bool *isNull) -{ - List *clauses = caseExpr->args; - ListCell *clause; - Datum save_datum; - bool save_isNull; - - /* - * If there's a test expression, we have to evaluate it and save the value - * where the CaseTestExpr placeholders can find it. We must save and - * restore prior setting of econtext's caseValue fields, in case this node - * is itself within a larger CASE. Furthermore, don't assign to the - * econtext fields until after returning from evaluation of the test - * expression. We used to pass &econtext->caseValue_isNull to the - * recursive call, but that leads to aliasing that variable within said - * call, which can (and did) produce bugs when the test expression itself - * contains a CASE. - * - * If there's no test expression, we don't actually need to save and - * restore these fields; but it's less code to just do so unconditionally. - */ - save_datum = econtext->caseValue_datum; - save_isNull = econtext->caseValue_isNull; - - if (caseExpr->arg) - { - Datum arg_value; - bool arg_isNull; - - arg_value = ExecEvalExpr(caseExpr->arg, - econtext, - &arg_isNull); - /* Since caseValue_datum may be read multiple times, force to R/O */ - econtext->caseValue_datum = - MakeExpandedObjectReadOnly(arg_value, - arg_isNull, - caseExpr->argtyplen); - econtext->caseValue_isNull = arg_isNull; - } - - /* - * we evaluate each of the WHEN clauses in turn, as soon as one is true we - * return the corresponding result. If none are true then we return the - * value of the default clause, or NULL if there is none. - */ - foreach(clause, clauses) - { - CaseWhenState *wclause = lfirst(clause); - Datum clause_value; - bool clause_isNull; - - clause_value = ExecEvalExpr(wclause->expr, - econtext, - &clause_isNull); - - /* - * if we have a true test, then we return the result, since the case - * statement is satisfied. A NULL result from the test is not - * considered true. - */ - if (DatumGetBool(clause_value) && !clause_isNull) - { - econtext->caseValue_datum = save_datum; - econtext->caseValue_isNull = save_isNull; - return ExecEvalExpr(wclause->result, - econtext, - isNull); - } - } - - econtext->caseValue_datum = save_datum; - econtext->caseValue_isNull = save_isNull; - - if (caseExpr->defresult) - { - return ExecEvalExpr(caseExpr->defresult, - econtext, - isNull); - } - - *isNull = true; - return (Datum) 0; -} - -/* - * ExecEvalCaseTestExpr - * - * Return the value stored by CASE. - */ -static Datum -ExecEvalCaseTestExpr(ExprState *exprstate, - ExprContext *econtext, - bool *isNull) -{ - *isNull = econtext->caseValue_isNull; - return econtext->caseValue_datum; -} - -/* - * ExecEvalGroupingFuncExpr - * - * Return a bitmask with a bit for each (unevaluated) argument expression - * (rightmost arg is least significant bit). - * - * A bit is set if the corresponding expression is NOT part of the set of - * grouping expressions in the current grouping set. - */ -static Datum -ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate, - ExprContext *econtext, - bool *isNull) -{ - int result = 0; - int attnum = 0; - Bitmapset *grouped_cols = gstate->aggstate->grouped_cols; - ListCell *lc; - - *isNull = false; - - foreach(lc, (gstate->clauses)) - { - attnum = lfirst_int(lc); - - result = result << 1; - - if (!bms_is_member(attnum, grouped_cols)) - result = result | 1; - } - - return (Datum) result; -} - -/* ---------------------------------------------------------------- - * ExecEvalArray - ARRAY[] expressions - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalArray(ArrayExprState *astate, ExprContext *econtext, - bool *isNull) -{ - ArrayExpr *arrayExpr = (ArrayExpr *) astate->xprstate.expr; - ArrayType *result; - ListCell *element; - Oid element_type = arrayExpr->element_typeid; - int ndims = 0; - int dims[MAXDIM]; - int lbs[MAXDIM]; - - /* Set non-null as default */ - *isNull = false; - - if (!arrayExpr->multidims) - { - /* Elements are presumably of scalar type */ - int nelems; - Datum *dvalues; - bool *dnulls; - int i = 0; - - ndims = 1; - nelems = list_length(astate->elements); - - /* Shouldn't happen here, but if length is 0, return empty array */ - if (nelems == 0) - return PointerGetDatum(construct_empty_array(element_type)); - - dvalues = (Datum *) palloc(nelems * sizeof(Datum)); - dnulls = (bool *) palloc(nelems * sizeof(bool)); - - /* loop through and build array of datums */ - foreach(element, astate->elements) - { - ExprState *e = (ExprState *) lfirst(element); - - dvalues[i] = ExecEvalExpr(e, econtext, &dnulls[i]); - i++; - } - - /* setup for 1-D array of the given length */ - dims[0] = nelems; - lbs[0] = 1; - - result = construct_md_array(dvalues, dnulls, ndims, dims, lbs, - element_type, - astate->elemlength, - astate->elembyval, - astate->elemalign); - } - else - { - /* Must be nested array expressions */ - int nbytes = 0; - int nitems = 0; - int outer_nelems = 0; - int elem_ndims = 0; - int *elem_dims = NULL; - int *elem_lbs = NULL; - bool firstone = true; - bool havenulls = false; - bool haveempty = false; - char **subdata; - bits8 **subbitmaps; - int *subbytes; - int *subnitems; - int i; - int32 dataoffset; - char *dat; - int iitem; - - i = list_length(astate->elements); - subdata = (char **) palloc(i * sizeof(char *)); - subbitmaps = (bits8 **) palloc(i * sizeof(bits8 *)); - subbytes = (int *) palloc(i * sizeof(int)); - subnitems = (int *) palloc(i * sizeof(int)); - - /* loop through and get data area from each element */ - foreach(element, astate->elements) - { - ExprState *e = (ExprState *) lfirst(element); - bool eisnull; - Datum arraydatum; - ArrayType *array; - int this_ndims; - - arraydatum = ExecEvalExpr(e, econtext, &eisnull); - /* temporarily ignore null subarrays */ - if (eisnull) - { - haveempty = true; - continue; - } - - array = DatumGetArrayTypeP(arraydatum); - - /* run-time double-check on element type */ - if (element_type != ARR_ELEMTYPE(array)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("cannot merge incompatible arrays"), - errdetail("Array with element type %s cannot be " - "included in ARRAY construct with element type %s.", - format_type_be(ARR_ELEMTYPE(array)), - format_type_be(element_type)))); - - this_ndims = ARR_NDIM(array); - /* temporarily ignore zero-dimensional subarrays */ - if (this_ndims <= 0) - { - haveempty = true; - continue; - } - - if (firstone) - { - /* Get sub-array details from first member */ - elem_ndims = this_ndims; - ndims = elem_ndims + 1; - if (ndims <= 0 || ndims > MAXDIM) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("number of array dimensions (%d) exceeds " \ - "the maximum allowed (%d)", ndims, MAXDIM))); - - elem_dims = (int *) palloc(elem_ndims * sizeof(int)); - memcpy(elem_dims, ARR_DIMS(array), elem_ndims * sizeof(int)); - elem_lbs = (int *) palloc(elem_ndims * sizeof(int)); - memcpy(elem_lbs, ARR_LBOUND(array), elem_ndims * sizeof(int)); - - firstone = false; - } - else - { - /* Check other sub-arrays are compatible */ - if (elem_ndims != this_ndims || - memcmp(elem_dims, ARR_DIMS(array), - elem_ndims * sizeof(int)) != 0 || - memcmp(elem_lbs, ARR_LBOUND(array), - elem_ndims * sizeof(int)) != 0) - ereport(ERROR, - (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), - errmsg("multidimensional arrays must have array " - "expressions with matching dimensions"))); - } - - subdata[outer_nelems] = ARR_DATA_PTR(array); - subbitmaps[outer_nelems] = ARR_NULLBITMAP(array); - subbytes[outer_nelems] = ARR_SIZE(array) - ARR_DATA_OFFSET(array); - nbytes += subbytes[outer_nelems]; - subnitems[outer_nelems] = ArrayGetNItems(this_ndims, - ARR_DIMS(array)); - nitems += subnitems[outer_nelems]; - havenulls |= ARR_HASNULL(array); - outer_nelems++; - } - - /* - * If all items were null or empty arrays, return an empty array; - * otherwise, if some were and some weren't, raise error. (Note: we - * must special-case this somehow to avoid trying to generate a 1-D - * array formed from empty arrays. It's not ideal...) - */ - if (haveempty) - { - if (ndims == 0) /* didn't find any nonempty array */ - return PointerGetDatum(construct_empty_array(element_type)); - ereport(ERROR, - (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), - errmsg("multidimensional arrays must have array " - "expressions with matching dimensions"))); - } - - /* setup for multi-D array */ - dims[0] = outer_nelems; - lbs[0] = 1; - for (i = 1; i < ndims; i++) - { - dims[i] = elem_dims[i - 1]; - lbs[i] = elem_lbs[i - 1]; - } - - if (havenulls) - { - dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nitems); - nbytes += dataoffset; - } - else - { - dataoffset = 0; /* marker for no null bitmap */ - nbytes += ARR_OVERHEAD_NONULLS(ndims); - } - - result = (ArrayType *) palloc(nbytes); - SET_VARSIZE(result, nbytes); - result->ndim = ndims; - result->dataoffset = dataoffset; - result->elemtype = element_type; - memcpy(ARR_DIMS(result), dims, ndims * sizeof(int)); - memcpy(ARR_LBOUND(result), lbs, ndims * sizeof(int)); - - dat = ARR_DATA_PTR(result); - iitem = 0; - for (i = 0; i < outer_nelems; i++) - { - memcpy(dat, subdata[i], subbytes[i]); - dat += subbytes[i]; - if (havenulls) - array_bitmap_copy(ARR_NULLBITMAP(result), iitem, - subbitmaps[i], 0, - subnitems[i]); - iitem += subnitems[i]; - } - } - - return PointerGetDatum(result); -} - -/* ---------------------------------------------------------------- - * ExecEvalRow - ROW() expressions - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalRow(RowExprState *rstate, - ExprContext *econtext, - bool *isNull) -{ - HeapTuple tuple; - Datum *values; - bool *isnull; - int natts; - ListCell *arg; - int i; - - /* Set non-null as default */ - *isNull = false; - - /* Allocate workspace */ - natts = rstate->tupdesc->natts; - values = (Datum *) palloc0(natts * sizeof(Datum)); - isnull = (bool *) palloc(natts * sizeof(bool)); - - /* preset to nulls in case rowtype has some later-added columns */ - memset(isnull, true, natts * sizeof(bool)); - - /* Evaluate field values */ - i = 0; - foreach(arg, rstate->args) - { - ExprState *e = (ExprState *) lfirst(arg); - - values[i] = ExecEvalExpr(e, econtext, &isnull[i]); - i++; - } - - tuple = heap_form_tuple(rstate->tupdesc, values, isnull); - - pfree(values); - pfree(isnull); - - return HeapTupleGetDatum(tuple); -} - -/* ---------------------------------------------------------------- - * ExecEvalRowCompare - ROW() comparison-op ROW() - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalRowCompare(RowCompareExprState *rstate, - ExprContext *econtext, - bool *isNull) -{ - bool result; - RowCompareType rctype = ((RowCompareExpr *) rstate->xprstate.expr)->rctype; - int32 cmpresult = 0; - ListCell *l; - ListCell *r; - int i; - - *isNull = true; /* until we get a result */ - - i = 0; - forboth(l, rstate->largs, r, rstate->rargs) - { - ExprState *le = (ExprState *) lfirst(l); - ExprState *re = (ExprState *) lfirst(r); - FunctionCallInfoData locfcinfo; - - InitFunctionCallInfoData(locfcinfo, &(rstate->funcs[i]), 2, - rstate->collations[i], - NULL, NULL); - locfcinfo.arg[0] = ExecEvalExpr(le, econtext, - &locfcinfo.argnull[0]); - locfcinfo.arg[1] = ExecEvalExpr(re, econtext, - &locfcinfo.argnull[1]); - if (rstate->funcs[i].fn_strict && - (locfcinfo.argnull[0] || locfcinfo.argnull[1])) - return (Datum) 0; /* force NULL result */ - locfcinfo.isnull = false; - cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); - if (locfcinfo.isnull) - return (Datum) 0; /* force NULL result */ - if (cmpresult != 0) - break; /* no need to compare remaining columns */ - i++; - } - - switch (rctype) - { - /* EQ and NE cases aren't allowed here */ - case ROWCOMPARE_LT: - result = (cmpresult < 0); - break; - case ROWCOMPARE_LE: - result = (cmpresult <= 0); - break; - case ROWCOMPARE_GE: - result = (cmpresult >= 0); - break; - case ROWCOMPARE_GT: - result = (cmpresult > 0); - break; - default: - elog(ERROR, "unrecognized RowCompareType: %d", (int) rctype); - result = 0; /* keep compiler quiet */ - break; - } - - *isNull = false; - return BoolGetDatum(result); -} - -/* ---------------------------------------------------------------- - * ExecEvalCoalesce - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalCoalesce(CoalesceExprState *coalesceExpr, ExprContext *econtext, - bool *isNull) -{ - ListCell *arg; - - /* Simply loop through until something NOT NULL is found */ - foreach(arg, coalesceExpr->args) - { - ExprState *e = (ExprState *) lfirst(arg); - Datum value; - - value = ExecEvalExpr(e, econtext, isNull); - if (!*isNull) - return value; - } - - /* Else return NULL */ - *isNull = true; - return (Datum) 0; -} - -/* ---------------------------------------------------------------- - * ExecEvalMinMax - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalMinMax(MinMaxExprState *minmaxExpr, ExprContext *econtext, - bool *isNull) -{ - Datum result = (Datum) 0; - MinMaxExpr *minmax = (MinMaxExpr *) minmaxExpr->xprstate.expr; - Oid collation = minmax->inputcollid; - MinMaxOp op = minmax->op; - FunctionCallInfoData locfcinfo; - ListCell *arg; - - *isNull = true; /* until we get a result */ - - InitFunctionCallInfoData(locfcinfo, &minmaxExpr->cfunc, 2, - collation, NULL, NULL); - locfcinfo.argnull[0] = false; - locfcinfo.argnull[1] = false; - - foreach(arg, minmaxExpr->args) - { - ExprState *e = (ExprState *) lfirst(arg); - Datum value; - bool valueIsNull; - int32 cmpresult; - - value = ExecEvalExpr(e, econtext, &valueIsNull); - if (valueIsNull) - continue; /* ignore NULL inputs */ - - if (*isNull) - { - /* first nonnull input, adopt value */ - result = value; - *isNull = false; - } - else - { - /* apply comparison function */ - locfcinfo.arg[0] = result; - locfcinfo.arg[1] = value; - locfcinfo.isnull = false; - cmpresult = DatumGetInt32(FunctionCallInvoke(&locfcinfo)); - if (locfcinfo.isnull) /* probably should not happen */ - continue; - if (cmpresult > 0 && op == IS_LEAST) - result = value; - else if (cmpresult < 0 && op == IS_GREATEST) - result = value; - } - } - - return result; -} - -/* ---------------------------------------------------------------- - * ExecEvalSQLValueFunction - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalSQLValueFunction(ExprState *svfExpr, - ExprContext *econtext, - bool *isNull) -{ - Datum result = (Datum) 0; - SQLValueFunction *svf = (SQLValueFunction *) svfExpr->expr; - FunctionCallInfoData fcinfo; - - *isNull = false; - - /* - * Note: current_schema() can return NULL. current_user() etc currently - * cannot, but might as well code those cases the same way for safety. - */ - switch (svf->op) - { - case SVFOP_CURRENT_DATE: - result = DateADTGetDatum(GetSQLCurrentDate()); - break; - case SVFOP_CURRENT_TIME: - case SVFOP_CURRENT_TIME_N: - result = TimeTzADTPGetDatum(GetSQLCurrentTime(svf->typmod)); - break; - case SVFOP_CURRENT_TIMESTAMP: - case SVFOP_CURRENT_TIMESTAMP_N: - result = TimestampTzGetDatum(GetSQLCurrentTimestamp(svf->typmod)); - break; - case SVFOP_LOCALTIME: - case SVFOP_LOCALTIME_N: - result = TimeADTGetDatum(GetSQLLocalTime(svf->typmod)); - break; - case SVFOP_LOCALTIMESTAMP: - case SVFOP_LOCALTIMESTAMP_N: - result = TimestampGetDatum(GetSQLLocalTimestamp(svf->typmod)); - break; - case SVFOP_CURRENT_ROLE: - case SVFOP_CURRENT_USER: - case SVFOP_USER: - InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); - result = current_user(&fcinfo); - *isNull = fcinfo.isnull; - break; - case SVFOP_SESSION_USER: - InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); - result = session_user(&fcinfo); - *isNull = fcinfo.isnull; - break; - case SVFOP_CURRENT_CATALOG: - InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); - result = current_database(&fcinfo); - *isNull = fcinfo.isnull; - break; - case SVFOP_CURRENT_SCHEMA: - InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); - result = current_schema(&fcinfo); - *isNull = fcinfo.isnull; - break; - } - - return result; -} - -/* ---------------------------------------------------------------- - * ExecEvalXml - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalXml(XmlExprState *xmlExpr, ExprContext *econtext, - bool *isNull) -{ - XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr; - Datum value; - bool isnull; - ListCell *arg; - ListCell *narg; - - *isNull = true; /* until we get a result */ - - switch (xexpr->op) - { - case IS_XMLCONCAT: - { - List *values = NIL; - - foreach(arg, xmlExpr->args) - { - ExprState *e = (ExprState *) lfirst(arg); - - value = ExecEvalExpr(e, econtext, &isnull); - if (!isnull) - values = lappend(values, DatumGetPointer(value)); - } - - if (list_length(values) > 0) - { - *isNull = false; - return PointerGetDatum(xmlconcat(values)); - } - else - return (Datum) 0; - } - break; - - case IS_XMLFOREST: - { - StringInfoData buf; - - initStringInfo(&buf); - forboth(arg, xmlExpr->named_args, narg, xexpr->arg_names) - { - ExprState *e = (ExprState *) lfirst(arg); - char *argname = strVal(lfirst(narg)); - - value = ExecEvalExpr(e, econtext, &isnull); - if (!isnull) - { - appendStringInfo(&buf, "<%s>%s", - argname, - map_sql_value_to_xml_value(value, exprType((Node *) e->expr), true), - argname); - *isNull = false; - } - } - - if (*isNull) - { - pfree(buf.data); - return (Datum) 0; - } - else - { - text *result; - - result = cstring_to_text_with_len(buf.data, buf.len); - pfree(buf.data); - - return PointerGetDatum(result); - } - } - break; - - case IS_XMLELEMENT: - *isNull = false; - return PointerGetDatum(xmlelement(xmlExpr, econtext)); - break; - - case IS_XMLPARSE: - { - ExprState *e; - text *data; - bool preserve_whitespace; - - /* arguments are known to be text, bool */ - Assert(list_length(xmlExpr->args) == 2); - - e = (ExprState *) linitial(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull); - if (isnull) - return (Datum) 0; - data = DatumGetTextPP(value); - - e = (ExprState *) lsecond(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull); - if (isnull) /* probably can't happen */ - return (Datum) 0; - preserve_whitespace = DatumGetBool(value); - - *isNull = false; - - return PointerGetDatum(xmlparse(data, - xexpr->xmloption, - preserve_whitespace)); - } - break; - - case IS_XMLPI: - { - ExprState *e; - text *arg; - - /* optional argument is known to be text */ - Assert(list_length(xmlExpr->args) <= 1); - - if (xmlExpr->args) - { - e = (ExprState *) linitial(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull); - if (isnull) - arg = NULL; - else - arg = DatumGetTextPP(value); - } - else - { - arg = NULL; - isnull = false; - } - - return PointerGetDatum(xmlpi(xexpr->name, arg, isnull, isNull)); - } - break; - - case IS_XMLROOT: - { - ExprState *e; - xmltype *data; - text *version; - int standalone; - - /* arguments are known to be xml, text, int */ - Assert(list_length(xmlExpr->args) == 3); - - e = (ExprState *) linitial(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull); - if (isnull) - return (Datum) 0; - data = DatumGetXmlP(value); - - e = (ExprState *) lsecond(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull); - if (isnull) - version = NULL; - else - version = DatumGetTextPP(value); - - e = (ExprState *) lthird(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull); - standalone = DatumGetInt32(value); - - *isNull = false; - - return PointerGetDatum(xmlroot(data, - version, - standalone)); - } - break; - - case IS_XMLSERIALIZE: - { - ExprState *e; - - /* argument type is known to be xml */ - Assert(list_length(xmlExpr->args) == 1); - - e = (ExprState *) linitial(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull); - if (isnull) - return (Datum) 0; - - *isNull = false; - - return PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value), xexpr->xmloption)); - } - break; - - case IS_DOCUMENT: - { - ExprState *e; - - /* optional argument is known to be xml */ - Assert(list_length(xmlExpr->args) == 1); - - e = (ExprState *) linitial(xmlExpr->args); - value = ExecEvalExpr(e, econtext, &isnull); - if (isnull) - return (Datum) 0; - else - { - *isNull = false; - return BoolGetDatum(xml_is_document(DatumGetXmlP(value))); - } - } - break; - } - - elog(ERROR, "unrecognized XML operation"); - return (Datum) 0; -} - -/* ---------------------------------------------------------------- - * ExecEvalNullIf - * - * Note that this is *always* derived from the equals operator, - * but since we need special processing of the arguments - * we can not simply reuse ExecEvalOper() or ExecEvalFunc(). - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalNullIf(FuncExprState *nullIfExpr, - ExprContext *econtext, - bool *isNull) -{ - Datum result; - FunctionCallInfo fcinfo; - - /* - * Initialize function cache if first time through - */ - if (nullIfExpr->func.fn_oid == InvalidOid) - { - NullIfExpr *op = (NullIfExpr *) nullIfExpr->xprstate.expr; - - init_fcache(op->opfuncid, op->inputcollid, nullIfExpr, - econtext->ecxt_per_query_memory, false, false); - } - - /* - * Evaluate arguments - */ - fcinfo = &nullIfExpr->fcinfo_data; - ExecEvalFuncArgs(fcinfo, nullIfExpr->args, econtext); - Assert(fcinfo->nargs == 2); - - /* if either argument is NULL they can't be equal */ - if (!fcinfo->argnull[0] && !fcinfo->argnull[1]) - { - fcinfo->isnull = false; - result = FunctionCallInvoke(fcinfo); - /* if the arguments are equal return null */ - if (!fcinfo->isnull && DatumGetBool(result)) - { - *isNull = true; - return (Datum) 0; - } - } - - /* else return first argument */ - *isNull = fcinfo->argnull[0]; - return fcinfo->arg[0]; -} - -/* ---------------------------------------------------------------- - * ExecEvalNullTest - * - * Evaluate a NullTest node. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalNullTest(NullTestState *nstate, - ExprContext *econtext, - bool *isNull) -{ - NullTest *ntest = (NullTest *) nstate->xprstate.expr; - Datum result; - - result = ExecEvalExpr(nstate->arg, econtext, isNull); - - if (ntest->argisrow && !(*isNull)) - { - /* - * The SQL standard defines IS [NOT] NULL for a non-null rowtype - * argument as: - * - * "R IS NULL" is true if every field is the null value. - * - * "R IS NOT NULL" is true if no field is the null value. - * - * This definition is (apparently intentionally) not recursive; so our - * tests on the fields are primitive attisnull tests, not recursive - * checks to see if they are all-nulls or no-nulls rowtypes. - * - * The standard does not consider the possibility of zero-field rows, - * but here we consider them to vacuously satisfy both predicates. - */ - HeapTupleHeader tuple; - Oid tupType; - int32 tupTypmod; - TupleDesc tupDesc; - HeapTupleData tmptup; - int att; - - tuple = DatumGetHeapTupleHeader(result); - - tupType = HeapTupleHeaderGetTypeId(tuple); - tupTypmod = HeapTupleHeaderGetTypMod(tuple); - - /* Lookup tupdesc if first time through or if type changes */ - tupDesc = get_cached_rowtype(tupType, tupTypmod, - &nstate->argdesc, econtext); - - /* - * heap_attisnull needs a HeapTuple not a bare HeapTupleHeader. - */ - tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); - tmptup.t_data = tuple; - - for (att = 1; att <= tupDesc->natts; att++) - { - /* ignore dropped columns */ - if (tupDesc->attrs[att - 1]->attisdropped) - continue; - if (heap_attisnull(&tmptup, att)) - { - /* null field disproves IS NOT NULL */ - if (ntest->nulltesttype == IS_NOT_NULL) - return BoolGetDatum(false); - } - else - { - /* non-null field disproves IS NULL */ - if (ntest->nulltesttype == IS_NULL) - return BoolGetDatum(false); - } - } - - return BoolGetDatum(true); - } - else - { - /* Simple scalar-argument case, or a null rowtype datum */ - switch (ntest->nulltesttype) - { - case IS_NULL: - if (*isNull) - { - *isNull = false; - return BoolGetDatum(true); - } - else - return BoolGetDatum(false); - case IS_NOT_NULL: - if (*isNull) - { - *isNull = false; - return BoolGetDatum(false); - } - else - return BoolGetDatum(true); - default: - elog(ERROR, "unrecognized nulltesttype: %d", - (int) ntest->nulltesttype); - return (Datum) 0; /* keep compiler quiet */ - } - } -} - -/* ---------------------------------------------------------------- - * ExecEvalBooleanTest - * - * Evaluate a BooleanTest node. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalBooleanTest(GenericExprState *bstate, - ExprContext *econtext, - bool *isNull) -{ - BooleanTest *btest = (BooleanTest *) bstate->xprstate.expr; - Datum result; - - result = ExecEvalExpr(bstate->arg, econtext, isNull); - - switch (btest->booltesttype) - { - case IS_TRUE: - if (*isNull) - { - *isNull = false; - return BoolGetDatum(false); - } - else if (DatumGetBool(result)) - return BoolGetDatum(true); - else - return BoolGetDatum(false); - case IS_NOT_TRUE: - if (*isNull) - { - *isNull = false; - return BoolGetDatum(true); - } - else if (DatumGetBool(result)) - return BoolGetDatum(false); - else - return BoolGetDatum(true); - case IS_FALSE: - if (*isNull) - { - *isNull = false; - return BoolGetDatum(false); - } - else if (DatumGetBool(result)) - return BoolGetDatum(false); - else - return BoolGetDatum(true); - case IS_NOT_FALSE: - if (*isNull) - { - *isNull = false; - return BoolGetDatum(true); - } - else if (DatumGetBool(result)) - return BoolGetDatum(true); - else - return BoolGetDatum(false); - case IS_UNKNOWN: - if (*isNull) - { - *isNull = false; - return BoolGetDatum(true); - } - else - return BoolGetDatum(false); - case IS_NOT_UNKNOWN: - if (*isNull) - { - *isNull = false; - return BoolGetDatum(false); - } - else - return BoolGetDatum(true); - default: - elog(ERROR, "unrecognized booltesttype: %d", - (int) btest->booltesttype); - return (Datum) 0; /* keep compiler quiet */ - } -} - -/* - * ExecEvalCoerceToDomain - * - * Test the provided data against the domain constraint(s). If the data - * passes the constraint specifications, pass it through (return the - * datum) otherwise throw an error. - */ -static Datum -ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, - bool *isNull) -{ - CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr; - Datum result; - ListCell *l; - - result = ExecEvalExpr(cstate->arg, econtext, isNull); - - /* Make sure we have up-to-date constraints */ - UpdateDomainConstraintRef(cstate->constraint_ref); - - foreach(l, cstate->constraint_ref->constraints) - { - DomainConstraintState *con = (DomainConstraintState *) lfirst(l); - - switch (con->constrainttype) - { - case DOM_CONSTRAINT_NOTNULL: - if (*isNull) - ereport(ERROR, - (errcode(ERRCODE_NOT_NULL_VIOLATION), - errmsg("domain %s does not allow null values", - format_type_be(ctest->resulttype)), - errdatatype(ctest->resulttype))); - break; - case DOM_CONSTRAINT_CHECK: - { - Datum conResult; - bool conIsNull; - Datum save_datum; - bool save_isNull; - - /* - * Set up value to be returned by CoerceToDomainValue - * nodes. We must save and restore prior setting of - * econtext's domainValue fields, in case this node is - * itself within a check expression for another domain. - * - * Also, if we are working with a read-write expanded - * datum, be sure that what we pass to CHECK expressions - * is a read-only pointer; else called functions might - * modify or even delete the expanded object. - */ - save_datum = econtext->domainValue_datum; - save_isNull = econtext->domainValue_isNull; - - econtext->domainValue_datum = - MakeExpandedObjectReadOnly(result, *isNull, - cstate->constraint_ref->tcache->typlen); - econtext->domainValue_isNull = *isNull; - - conResult = ExecEvalExpr(con->check_expr, econtext, - &conIsNull); - - if (!conIsNull && - !DatumGetBool(conResult)) - ereport(ERROR, - (errcode(ERRCODE_CHECK_VIOLATION), - errmsg("value for domain %s violates check constraint \"%s\"", - format_type_be(ctest->resulttype), - con->name), - errdomainconstraint(ctest->resulttype, - con->name))); - econtext->domainValue_datum = save_datum; - econtext->domainValue_isNull = save_isNull; - - break; - } - default: - elog(ERROR, "unrecognized constraint type: %d", - (int) con->constrainttype); - break; - } - } - - /* If all has gone well (constraints did not fail) return the datum */ - return result; -} - -/* - * ExecEvalCoerceToDomainValue - * - * Return the value stored by CoerceToDomain. - */ -static Datum -ExecEvalCoerceToDomainValue(ExprState *exprstate, - ExprContext *econtext, - bool *isNull) -{ - *isNull = econtext->domainValue_isNull; - return econtext->domainValue_datum; -} - -/* ---------------------------------------------------------------- - * ExecEvalFieldSelect - * - * Evaluate a FieldSelect node. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalFieldSelect(FieldSelectState *fstate, - ExprContext *econtext, - bool *isNull) -{ - FieldSelect *fselect = (FieldSelect *) fstate->xprstate.expr; - AttrNumber fieldnum = fselect->fieldnum; - Datum result; - Datum tupDatum; - HeapTupleHeader tuple; - Oid tupType; - int32 tupTypmod; - TupleDesc tupDesc; - Form_pg_attribute attr; - HeapTupleData tmptup; - - tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull); - - if (*isNull) - return tupDatum; - - tuple = DatumGetHeapTupleHeader(tupDatum); - - tupType = HeapTupleHeaderGetTypeId(tuple); - tupTypmod = HeapTupleHeaderGetTypMod(tuple); - - /* Lookup tupdesc if first time through or if type changes */ - tupDesc = get_cached_rowtype(tupType, tupTypmod, - &fstate->argdesc, econtext); - - /* - * Find field's attr record. Note we don't support system columns here: a - * datum tuple doesn't have valid values for most of the interesting - * system columns anyway. - */ - if (fieldnum <= 0) /* should never happen */ - elog(ERROR, "unsupported reference to system column %d in FieldSelect", - fieldnum); - if (fieldnum > tupDesc->natts) /* should never happen */ - elog(ERROR, "attribute number %d exceeds number of columns %d", - fieldnum, tupDesc->natts); - attr = tupDesc->attrs[fieldnum - 1]; - - /* Check for dropped column, and force a NULL result if so */ - if (attr->attisdropped) - { - *isNull = true; - return (Datum) 0; - } - - /* Check for type mismatch --- possible after ALTER COLUMN TYPE? */ - /* As in ExecEvalScalarVar, we should but can't check typmod */ - if (fselect->resulttype != attr->atttypid) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("attribute %d has wrong type", fieldnum), - errdetail("Table has type %s, but query expects %s.", - format_type_be(attr->atttypid), - format_type_be(fselect->resulttype)))); - - /* heap_getattr needs a HeapTuple not a bare HeapTupleHeader */ - tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); - tmptup.t_data = tuple; - - result = heap_getattr(&tmptup, - fieldnum, - tupDesc, - isNull); - return result; -} - -/* ---------------------------------------------------------------- - * ExecEvalFieldStore - * - * Evaluate a FieldStore node. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalFieldStore(FieldStoreState *fstate, - ExprContext *econtext, - bool *isNull) -{ - FieldStore *fstore = (FieldStore *) fstate->xprstate.expr; - HeapTuple tuple; - Datum tupDatum; - TupleDesc tupDesc; - Datum *values; - bool *isnull; - Datum save_datum; - bool save_isNull; - ListCell *l1, - *l2; - - tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull); - - /* Lookup tupdesc if first time through or after rescan */ - tupDesc = get_cached_rowtype(fstore->resulttype, -1, - &fstate->argdesc, econtext); - - /* Allocate workspace */ - values = (Datum *) palloc(tupDesc->natts * sizeof(Datum)); - isnull = (bool *) palloc(tupDesc->natts * sizeof(bool)); - - if (!*isNull) - { - /* - * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader. We - * set all the fields in the struct just in case. - */ - HeapTupleHeader tuphdr; - HeapTupleData tmptup; - - tuphdr = DatumGetHeapTupleHeader(tupDatum); - tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr); - ItemPointerSetInvalid(&(tmptup.t_self)); - tmptup.t_tableOid = InvalidOid; - tmptup.t_data = tuphdr; - - heap_deform_tuple(&tmptup, tupDesc, values, isnull); - } - else - { - /* Convert null input tuple into an all-nulls row */ - memset(isnull, true, tupDesc->natts * sizeof(bool)); - } - - /* Result is never null */ - *isNull = false; - - save_datum = econtext->caseValue_datum; - save_isNull = econtext->caseValue_isNull; - - forboth(l1, fstate->newvals, l2, fstore->fieldnums) - { - ExprState *newval = (ExprState *) lfirst(l1); - AttrNumber fieldnum = lfirst_int(l2); - - Assert(fieldnum > 0 && fieldnum <= tupDesc->natts); - - /* - * Use the CaseTestExpr mechanism to pass down the old value of the - * field being replaced; this is needed in case the newval is itself a - * FieldStore or ArrayRef that has to obtain and modify the old value. - * It's safe to reuse the CASE mechanism because there cannot be a - * CASE between here and where the value would be needed, and a field - * assignment can't be within a CASE either. (So saving and restoring - * the caseValue is just paranoia, but let's do it anyway.) - */ - econtext->caseValue_datum = values[fieldnum - 1]; - econtext->caseValue_isNull = isnull[fieldnum - 1]; - - values[fieldnum - 1] = ExecEvalExpr(newval, - econtext, - &isnull[fieldnum - 1]); - } - - econtext->caseValue_datum = save_datum; - econtext->caseValue_isNull = save_isNull; - - tuple = heap_form_tuple(tupDesc, values, isnull); - - pfree(values); - pfree(isnull); - - return HeapTupleGetDatum(tuple); -} - -/* ---------------------------------------------------------------- - * ExecEvalRelabelType - * - * Evaluate a RelabelType node. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalRelabelType(GenericExprState *exprstate, - ExprContext *econtext, - bool *isNull) -{ - return ExecEvalExpr(exprstate->arg, econtext, isNull); -} - -/* ---------------------------------------------------------------- - * ExecEvalCoerceViaIO - * - * Evaluate a CoerceViaIO node. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalCoerceViaIO(CoerceViaIOState *iostate, - ExprContext *econtext, - bool *isNull) -{ - Datum result; - Datum inputval; - char *string; - - inputval = ExecEvalExpr(iostate->arg, econtext, isNull); - - if (*isNull) - string = NULL; /* output functions are not called on nulls */ - else - string = OutputFunctionCall(&iostate->outfunc, inputval); - - result = InputFunctionCall(&iostate->infunc, - string, - iostate->intypioparam, - -1); - - /* The input function cannot change the null/not-null status */ - return result; -} - -/* ---------------------------------------------------------------- - * ExecEvalArrayCoerceExpr - * - * Evaluate an ArrayCoerceExpr node. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalArrayCoerceExpr(ArrayCoerceExprState *astate, - ExprContext *econtext, - bool *isNull) -{ - ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) astate->xprstate.expr; - Datum result; - FunctionCallInfoData locfcinfo; - - result = ExecEvalExpr(astate->arg, econtext, isNull); - - if (*isNull) - return result; /* nothing to do */ - - /* - * If it's binary-compatible, modify the element type in the array header, - * but otherwise leave the array as we received it. - */ - if (!OidIsValid(acoerce->elemfuncid)) - { - /* Detoast input array if necessary, and copy in any case */ - ArrayType *array = DatumGetArrayTypePCopy(result); - - ARR_ELEMTYPE(array) = astate->resultelemtype; - PG_RETURN_ARRAYTYPE_P(array); - } - - /* Initialize function cache if first time through */ - if (astate->elemfunc.fn_oid == InvalidOid) - { - AclResult aclresult; - - /* Check permission to call function */ - aclresult = pg_proc_aclcheck(acoerce->elemfuncid, GetUserId(), - ACL_EXECUTE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_PROC, - get_func_name(acoerce->elemfuncid)); - InvokeFunctionExecuteHook(acoerce->elemfuncid); - - /* Set up the primary fmgr lookup information */ - fmgr_info_cxt(acoerce->elemfuncid, &(astate->elemfunc), - econtext->ecxt_per_query_memory); - fmgr_info_set_expr((Node *) acoerce, &(astate->elemfunc)); - } - - /* - * Use array_map to apply the function to each array element. - * - * We pass on the desttypmod and isExplicit flags whether or not the - * function wants them. - * - * Note: coercion functions are assumed to not use collation. - */ - InitFunctionCallInfoData(locfcinfo, &(astate->elemfunc), 3, - InvalidOid, NULL, NULL); - locfcinfo.arg[0] = result; - locfcinfo.arg[1] = Int32GetDatum(acoerce->resulttypmod); - locfcinfo.arg[2] = BoolGetDatum(acoerce->isExplicit); - locfcinfo.argnull[0] = false; - locfcinfo.argnull[1] = false; - locfcinfo.argnull[2] = false; - - return array_map(&locfcinfo, astate->resultelemtype, astate->amstate); -} - -/* ---------------------------------------------------------------- - * ExecEvalCurrentOfExpr - * - * The planner should convert CURRENT OF into a TidScan qualification, or some - * other special handling in a ForeignScan node. So we have to be able to do - * ExecInitExpr on a CurrentOfExpr, but we shouldn't ever actually execute it. - * If we get here, we suppose we must be dealing with CURRENT OF on a foreign - * table whose FDW doesn't handle it, and complain accordingly. - * ---------------------------------------------------------------- - */ -static Datum -ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, - bool *isNull) -{ - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("WHERE CURRENT OF is not supported for this table type"))); - return 0; /* keep compiler quiet */ -} - - -/* - * ExecEvalExprSwitchContext - * - * Same as ExecEvalExpr, but get into the right allocation context explicitly. - */ -Datum -ExecEvalExprSwitchContext(ExprState *expression, - ExprContext *econtext, - bool *isNull) -{ - Datum retDatum; - MemoryContext oldContext; - - oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - retDatum = ExecEvalExpr(expression, econtext, isNull); - MemoryContextSwitchTo(oldContext); - return retDatum; -} - - -/* - * ExecInitExpr: prepare an expression tree for execution - * - * This function builds and returns an ExprState tree paralleling the given - * Expr node tree. The ExprState tree can then be handed to ExecEvalExpr - * for execution. Because the Expr tree itself is read-only as far as - * ExecInitExpr and ExecEvalExpr are concerned, several different executions - * of the same plan tree can occur concurrently. - * - * This must be called in a memory context that will last as long as repeated - * executions of the expression are needed. Typically the context will be - * the same as the per-query context of the associated ExprContext. - * - * Any Aggref, WindowFunc, or SubPlan nodes found in the tree are added to the - * lists of such nodes held by the parent PlanState. Otherwise, we do very - * little initialization here other than building the state-node tree. Any - * nontrivial work associated with initializing runtime info for a node should - * happen during the first actual evaluation of that node. (This policy lets - * us avoid work if the node is never actually evaluated.) - * - * Note: there is no ExecEndExpr function; we assume that any resource - * cleanup needed will be handled by just releasing the memory context - * in which the state tree is built. Functions that require additional - * cleanup work can register a shutdown callback in the ExprContext. - * - * 'node' is the root of the expression tree to examine - * 'parent' is the PlanState node that owns the expression. - * - * 'parent' may be NULL if we are preparing an expression that is not - * associated with a plan tree. (If so, it can't have aggs or subplans.) - * This case should usually come through ExecPrepareExpr, not directly here. - */ -ExprState * -ExecInitExpr(Expr *node, PlanState *parent) -{ - ExprState *state; - - if (node == NULL) - return NULL; - - /* Guard against stack overflow due to overly complex expressions */ - check_stack_depth(); - - switch (nodeTag(node)) - { - case T_Var: - /* varattno == InvalidAttrNumber means it's a whole-row Var */ - if (((Var *) node)->varattno == InvalidAttrNumber) - { - WholeRowVarExprState *wstate = makeNode(WholeRowVarExprState); - - wstate->parent = parent; - wstate->wrv_tupdesc = NULL; - wstate->wrv_junkFilter = NULL; - state = (ExprState *) wstate; - state->evalfunc = (ExprStateEvalFunc) ExecEvalWholeRowVar; - } - else - { - state = makeNode(ExprState); - state->evalfunc = ExecEvalScalarVar; - } - break; - case T_Const: - state = makeNode(ExprState); - state->evalfunc = ExecEvalConst; - break; - case T_Param: - state = makeNode(ExprState); - switch (((Param *) node)->paramkind) - { - case PARAM_EXEC: - state->evalfunc = ExecEvalParamExec; - break; - case PARAM_EXTERN: - state->evalfunc = ExecEvalParamExtern; - break; - default: - elog(ERROR, "unrecognized paramkind: %d", - (int) ((Param *) node)->paramkind); - break; - } - break; - case T_CoerceToDomainValue: - state = makeNode(ExprState); - state->evalfunc = ExecEvalCoerceToDomainValue; - break; - case T_CaseTestExpr: - state = makeNode(ExprState); - state->evalfunc = ExecEvalCaseTestExpr; - break; - case T_Aggref: - { - AggrefExprState *astate = makeNode(AggrefExprState); - - astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAggref; - if (parent && IsA(parent, AggState)) - { - AggState *aggstate = (AggState *) parent; - - aggstate->aggs = lcons(astate, aggstate->aggs); - aggstate->numaggs++; - } - else - { - /* planner messed up */ - elog(ERROR, "Aggref found in non-Agg plan node"); - } - state = (ExprState *) astate; - } - break; - case T_GroupingFunc: - { - GroupingFunc *grp_node = (GroupingFunc *) node; - GroupingFuncExprState *grp_state = makeNode(GroupingFuncExprState); - Agg *agg = NULL; - - if (!parent || !IsA(parent, AggState) ||!IsA(parent->plan, Agg)) - elog(ERROR, "parent of GROUPING is not Agg node"); - - grp_state->aggstate = (AggState *) parent; - - agg = (Agg *) (parent->plan); - - if (agg->groupingSets) - grp_state->clauses = grp_node->cols; - else - grp_state->clauses = NIL; - - state = (ExprState *) grp_state; - state->evalfunc = (ExprStateEvalFunc) ExecEvalGroupingFuncExpr; - } - break; - case T_WindowFunc: - { - WindowFunc *wfunc = (WindowFunc *) node; - WindowFuncExprState *wfstate = makeNode(WindowFuncExprState); - - wfstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalWindowFunc; - if (parent && IsA(parent, WindowAggState)) - { - WindowAggState *winstate = (WindowAggState *) parent; - int nfuncs; - - winstate->funcs = lcons(wfstate, winstate->funcs); - nfuncs = ++winstate->numfuncs; - if (wfunc->winagg) - winstate->numaggs++; - - wfstate->args = (List *) ExecInitExpr((Expr *) wfunc->args, - parent); - wfstate->aggfilter = ExecInitExpr(wfunc->aggfilter, - parent); - - /* - * Complain if the windowfunc's arguments contain any - * windowfuncs; nested window functions are semantically - * nonsensical. (This should have been caught earlier, - * but we defend against it here anyway.) - */ - if (nfuncs != winstate->numfuncs) - ereport(ERROR, - (errcode(ERRCODE_WINDOWING_ERROR), - errmsg("window function calls cannot be nested"))); - } - else - { - /* planner messed up */ - elog(ERROR, "WindowFunc found in non-WindowAgg plan node"); - } - state = (ExprState *) wfstate; - } - break; - case T_ArrayRef: - { - ArrayRef *aref = (ArrayRef *) node; - ArrayRefExprState *astate = makeNode(ArrayRefExprState); - - astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayRef; - astate->refupperindexpr = (List *) - ExecInitExpr((Expr *) aref->refupperindexpr, parent); - astate->reflowerindexpr = (List *) - ExecInitExpr((Expr *) aref->reflowerindexpr, parent); - astate->refexpr = ExecInitExpr(aref->refexpr, parent); - astate->refassgnexpr = ExecInitExpr(aref->refassgnexpr, - parent); - /* do one-time catalog lookups for type info */ - astate->refattrlength = get_typlen(aref->refarraytype); - get_typlenbyvalalign(aref->refelemtype, - &astate->refelemlength, - &astate->refelembyval, - &astate->refelemalign); - state = (ExprState *) astate; - } - break; - case T_FuncExpr: - { - FuncExpr *funcexpr = (FuncExpr *) node; - FuncExprState *fstate = makeNode(FuncExprState); - - fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFunc; - fstate->args = (List *) - ExecInitExpr((Expr *) funcexpr->args, parent); - fstate->func.fn_oid = InvalidOid; /* not initialized */ - fstate->funcReturnsSet = funcexpr->funcretset; - state = (ExprState *) fstate; - } - break; - case T_OpExpr: - { - OpExpr *opexpr = (OpExpr *) node; - FuncExprState *fstate = makeNode(FuncExprState); - - fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOper; - fstate->args = (List *) - ExecInitExpr((Expr *) opexpr->args, parent); - fstate->func.fn_oid = InvalidOid; /* not initialized */ - fstate->funcReturnsSet = opexpr->opretset; - state = (ExprState *) fstate; - } - break; - case T_DistinctExpr: - { - DistinctExpr *distinctexpr = (DistinctExpr *) node; - FuncExprState *fstate = makeNode(FuncExprState); - - fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalDistinct; - fstate->args = (List *) - ExecInitExpr((Expr *) distinctexpr->args, parent); - fstate->func.fn_oid = InvalidOid; /* not initialized */ - fstate->funcReturnsSet = false; /* not supported */ - state = (ExprState *) fstate; - } - break; - case T_NullIfExpr: - { - NullIfExpr *nullifexpr = (NullIfExpr *) node; - FuncExprState *fstate = makeNode(FuncExprState); - - fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullIf; - fstate->args = (List *) - ExecInitExpr((Expr *) nullifexpr->args, parent); - fstate->func.fn_oid = InvalidOid; /* not initialized */ - fstate->funcReturnsSet = false; /* not supported */ - state = (ExprState *) fstate; - } - break; - case T_ScalarArrayOpExpr: - { - ScalarArrayOpExpr *opexpr = (ScalarArrayOpExpr *) node; - ScalarArrayOpExprState *sstate = makeNode(ScalarArrayOpExprState); - - sstate->fxprstate.xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalScalarArrayOp; - sstate->fxprstate.args = (List *) - ExecInitExpr((Expr *) opexpr->args, parent); - sstate->fxprstate.func.fn_oid = InvalidOid; /* not initialized */ - sstate->fxprstate.funcReturnsSet = false; /* not supported */ - sstate->element_type = InvalidOid; /* ditto */ - state = (ExprState *) sstate; - } - break; - case T_BoolExpr: - { - BoolExpr *boolexpr = (BoolExpr *) node; - BoolExprState *bstate = makeNode(BoolExprState); - - switch (boolexpr->boolop) - { - case AND_EXPR: - bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalAnd; - break; - case OR_EXPR: - bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalOr; - break; - case NOT_EXPR: - bstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNot; - break; - default: - elog(ERROR, "unrecognized boolop: %d", - (int) boolexpr->boolop); - break; - } - bstate->args = (List *) - ExecInitExpr((Expr *) boolexpr->args, parent); - state = (ExprState *) bstate; - } - break; - case T_SubPlan: - { - SubPlan *subplan = (SubPlan *) node; - SubPlanState *sstate; - - if (!parent) - elog(ERROR, "SubPlan found with no parent plan"); - - sstate = ExecInitSubPlan(subplan, parent); - - /* Add SubPlanState nodes to parent->subPlan */ - parent->subPlan = lappend(parent->subPlan, sstate); - - state = (ExprState *) sstate; - } - break; - case T_AlternativeSubPlan: - { - AlternativeSubPlan *asplan = (AlternativeSubPlan *) node; - AlternativeSubPlanState *asstate; - - if (!parent) - elog(ERROR, "AlternativeSubPlan found with no parent plan"); - - asstate = ExecInitAlternativeSubPlan(asplan, parent); - - state = (ExprState *) asstate; - } - break; - case T_FieldSelect: - { - FieldSelect *fselect = (FieldSelect *) node; - FieldSelectState *fstate = makeNode(FieldSelectState); - - fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldSelect; - fstate->arg = ExecInitExpr(fselect->arg, parent); - fstate->argdesc = NULL; - state = (ExprState *) fstate; - } - break; - case T_FieldStore: - { - FieldStore *fstore = (FieldStore *) node; - FieldStoreState *fstate = makeNode(FieldStoreState); - - fstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalFieldStore; - fstate->arg = ExecInitExpr(fstore->arg, parent); - fstate->newvals = (List *) ExecInitExpr((Expr *) fstore->newvals, parent); - fstate->argdesc = NULL; - state = (ExprState *) fstate; - } - break; - case T_RelabelType: - { - RelabelType *relabel = (RelabelType *) node; - GenericExprState *gstate = makeNode(GenericExprState); - - gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRelabelType; - gstate->arg = ExecInitExpr(relabel->arg, parent); - state = (ExprState *) gstate; - } - break; - case T_CoerceViaIO: - { - CoerceViaIO *iocoerce = (CoerceViaIO *) node; - CoerceViaIOState *iostate = makeNode(CoerceViaIOState); - Oid iofunc; - bool typisvarlena; - - iostate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceViaIO; - iostate->arg = ExecInitExpr(iocoerce->arg, parent); - /* lookup the result type's input function */ - getTypeInputInfo(iocoerce->resulttype, &iofunc, - &iostate->intypioparam); - fmgr_info(iofunc, &iostate->infunc); - /* lookup the input type's output function */ - getTypeOutputInfo(exprType((Node *) iocoerce->arg), - &iofunc, &typisvarlena); - fmgr_info(iofunc, &iostate->outfunc); - state = (ExprState *) iostate; - } - break; - case T_ArrayCoerceExpr: - { - ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node; - ArrayCoerceExprState *astate = makeNode(ArrayCoerceExprState); - - astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArrayCoerceExpr; - astate->arg = ExecInitExpr(acoerce->arg, parent); - astate->resultelemtype = get_element_type(acoerce->resulttype); - if (astate->resultelemtype == InvalidOid) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("target type is not an array"))); - /* Arrays over domains aren't supported yet */ - Assert(getBaseType(astate->resultelemtype) == - astate->resultelemtype); - astate->elemfunc.fn_oid = InvalidOid; /* not initialized */ - astate->amstate = (ArrayMapState *) palloc0(sizeof(ArrayMapState)); - state = (ExprState *) astate; - } - break; - case T_ConvertRowtypeExpr: - { - ConvertRowtypeExpr *convert = (ConvertRowtypeExpr *) node; - ConvertRowtypeExprState *cstate = makeNode(ConvertRowtypeExprState); - - cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalConvertRowtype; - cstate->arg = ExecInitExpr(convert->arg, parent); - state = (ExprState *) cstate; - } - break; - case T_CaseExpr: - { - CaseExpr *caseexpr = (CaseExpr *) node; - CaseExprState *cstate = makeNode(CaseExprState); - List *outlist = NIL; - ListCell *l; - - cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCase; - cstate->arg = ExecInitExpr(caseexpr->arg, parent); - foreach(l, caseexpr->args) - { - CaseWhen *when = castNode(CaseWhen, lfirst(l)); - CaseWhenState *wstate = makeNode(CaseWhenState); - - wstate->xprstate.evalfunc = NULL; /* not used */ - wstate->xprstate.expr = (Expr *) when; - wstate->expr = ExecInitExpr(when->expr, parent); - wstate->result = ExecInitExpr(when->result, parent); - outlist = lappend(outlist, wstate); - } - cstate->args = outlist; - cstate->defresult = ExecInitExpr(caseexpr->defresult, parent); - if (caseexpr->arg) - cstate->argtyplen = get_typlen(exprType((Node *) caseexpr->arg)); - state = (ExprState *) cstate; - } - break; - case T_ArrayExpr: - { - ArrayExpr *arrayexpr = (ArrayExpr *) node; - ArrayExprState *astate = makeNode(ArrayExprState); - List *outlist = NIL; - ListCell *l; - - astate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalArray; - foreach(l, arrayexpr->elements) - { - Expr *e = (Expr *) lfirst(l); - ExprState *estate; - - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - } - astate->elements = outlist; - /* do one-time catalog lookup for type info */ - get_typlenbyvalalign(arrayexpr->element_typeid, - &astate->elemlength, - &astate->elembyval, - &astate->elemalign); - state = (ExprState *) astate; - } - break; - case T_RowExpr: - { - RowExpr *rowexpr = (RowExpr *) node; - RowExprState *rstate = makeNode(RowExprState); - Form_pg_attribute *attrs; - List *outlist = NIL; - ListCell *l; - int i; - - rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRow; - /* Build tupdesc to describe result tuples */ - if (rowexpr->row_typeid == RECORDOID) - { - /* generic record, use types of given expressions */ - rstate->tupdesc = ExecTypeFromExprList(rowexpr->args); - } - else - { - /* it's been cast to a named type, use that */ - rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1); - } - /* In either case, adopt RowExpr's column aliases */ - ExecTypeSetColNames(rstate->tupdesc, rowexpr->colnames); - /* Bless the tupdesc in case it's now of type RECORD */ - BlessTupleDesc(rstate->tupdesc); - /* Set up evaluation, skipping any deleted columns */ - Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts); - attrs = rstate->tupdesc->attrs; - i = 0; - foreach(l, rowexpr->args) - { - Expr *e = (Expr *) lfirst(l); - ExprState *estate; - - if (!attrs[i]->attisdropped) - { - /* - * Guard against ALTER COLUMN TYPE on rowtype since - * the RowExpr was created. XXX should we check - * typmod too? Not sure we can be sure it'll be the - * same. - */ - if (exprType((Node *) e) != attrs[i]->atttypid) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("ROW() column has type %s instead of type %s", - format_type_be(exprType((Node *) e)), - format_type_be(attrs[i]->atttypid)))); - } - else - { - /* - * Ignore original expression and insert a NULL. We - * don't really care what type of NULL it is, so - * always make an int4 NULL. - */ - e = (Expr *) makeNullConst(INT4OID, -1, InvalidOid); - } - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - i++; - } - rstate->args = outlist; - state = (ExprState *) rstate; - } - break; - case T_RowCompareExpr: - { - RowCompareExpr *rcexpr = (RowCompareExpr *) node; - RowCompareExprState *rstate = makeNode(RowCompareExprState); - int nopers = list_length(rcexpr->opnos); - List *outlist; - ListCell *l; - ListCell *l2; - ListCell *l3; - int i; - - rstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalRowCompare; - Assert(list_length(rcexpr->largs) == nopers); - outlist = NIL; - foreach(l, rcexpr->largs) - { - Expr *e = (Expr *) lfirst(l); - ExprState *estate; - - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - } - rstate->largs = outlist; - Assert(list_length(rcexpr->rargs) == nopers); - outlist = NIL; - foreach(l, rcexpr->rargs) - { - Expr *e = (Expr *) lfirst(l); - ExprState *estate; - - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - } - rstate->rargs = outlist; - Assert(list_length(rcexpr->opfamilies) == nopers); - rstate->funcs = (FmgrInfo *) palloc(nopers * sizeof(FmgrInfo)); - rstate->collations = (Oid *) palloc(nopers * sizeof(Oid)); - i = 0; - forthree(l, rcexpr->opnos, l2, rcexpr->opfamilies, l3, rcexpr->inputcollids) - { - Oid opno = lfirst_oid(l); - Oid opfamily = lfirst_oid(l2); - Oid inputcollid = lfirst_oid(l3); - int strategy; - Oid lefttype; - Oid righttype; - Oid proc; - - get_op_opfamily_properties(opno, opfamily, false, - &strategy, - &lefttype, - &righttype); - proc = get_opfamily_proc(opfamily, - lefttype, - righttype, - BTORDER_PROC); - - /* - * If we enforced permissions checks on index support - * functions, we'd need to make a check here. But the - * index support machinery doesn't do that, and neither - * does this code. - */ - fmgr_info(proc, &(rstate->funcs[i])); - rstate->collations[i] = inputcollid; - i++; - } - state = (ExprState *) rstate; - } - break; - case T_CoalesceExpr: - { - CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; - CoalesceExprState *cstate = makeNode(CoalesceExprState); - List *outlist = NIL; - ListCell *l; - - cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoalesce; - foreach(l, coalesceexpr->args) - { - Expr *e = (Expr *) lfirst(l); - ExprState *estate; - - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - } - cstate->args = outlist; - state = (ExprState *) cstate; - } - break; - case T_MinMaxExpr: - { - MinMaxExpr *minmaxexpr = (MinMaxExpr *) node; - MinMaxExprState *mstate = makeNode(MinMaxExprState); - List *outlist = NIL; - ListCell *l; - TypeCacheEntry *typentry; - - mstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalMinMax; - foreach(l, minmaxexpr->args) - { - Expr *e = (Expr *) lfirst(l); - ExprState *estate; - - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - } - mstate->args = outlist; - /* Look up the btree comparison function for the datatype */ - typentry = lookup_type_cache(minmaxexpr->minmaxtype, - TYPECACHE_CMP_PROC); - if (!OidIsValid(typentry->cmp_proc)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("could not identify a comparison function for type %s", - format_type_be(minmaxexpr->minmaxtype)))); - - /* - * If we enforced permissions checks on index support - * functions, we'd need to make a check here. But the index - * support machinery doesn't do that, and neither does this - * code. - */ - fmgr_info(typentry->cmp_proc, &(mstate->cfunc)); - state = (ExprState *) mstate; - } - break; - case T_SQLValueFunction: - state = makeNode(ExprState); - state->evalfunc = ExecEvalSQLValueFunction; - break; - case T_XmlExpr: - { - XmlExpr *xexpr = (XmlExpr *) node; - XmlExprState *xstate = makeNode(XmlExprState); - List *outlist; - ListCell *arg; - - xstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalXml; - outlist = NIL; - foreach(arg, xexpr->named_args) - { - Expr *e = (Expr *) lfirst(arg); - ExprState *estate; - - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - } - xstate->named_args = outlist; - - outlist = NIL; - foreach(arg, xexpr->args) - { - Expr *e = (Expr *) lfirst(arg); - ExprState *estate; - - estate = ExecInitExpr(e, parent); - outlist = lappend(outlist, estate); - } - xstate->args = outlist; - - state = (ExprState *) xstate; - } - break; - case T_NullTest: - { - NullTest *ntest = (NullTest *) node; - NullTestState *nstate = makeNode(NullTestState); - - nstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalNullTest; - nstate->arg = ExecInitExpr(ntest->arg, parent); - nstate->argdesc = NULL; - state = (ExprState *) nstate; - } - break; - case T_BooleanTest: - { - BooleanTest *btest = (BooleanTest *) node; - GenericExprState *gstate = makeNode(GenericExprState); - - gstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalBooleanTest; - gstate->arg = ExecInitExpr(btest->arg, parent); - state = (ExprState *) gstate; - } - break; - case T_CoerceToDomain: - { - CoerceToDomain *ctest = (CoerceToDomain *) node; - CoerceToDomainState *cstate = makeNode(CoerceToDomainState); - - cstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalCoerceToDomain; - cstate->arg = ExecInitExpr(ctest->arg, parent); - /* We spend an extra palloc to reduce header inclusions */ - cstate->constraint_ref = (DomainConstraintRef *) - palloc(sizeof(DomainConstraintRef)); - InitDomainConstraintRef(ctest->resulttype, - cstate->constraint_ref, - CurrentMemoryContext); - state = (ExprState *) cstate; - } - break; - case T_CurrentOfExpr: - state = makeNode(ExprState); - state->evalfunc = ExecEvalCurrentOfExpr; - break; - case T_TargetEntry: - { - TargetEntry *tle = (TargetEntry *) node; - GenericExprState *gstate = makeNode(GenericExprState); - - gstate->xprstate.evalfunc = NULL; /* not used */ - gstate->arg = ExecInitExpr(tle->expr, parent); - state = (ExprState *) gstate; - } - break; - case T_List: - { - List *outlist = NIL; - ListCell *l; - - foreach(l, (List *) node) - { - outlist = lappend(outlist, - ExecInitExpr((Expr *) lfirst(l), - parent)); - } - /* Don't fall through to the "common" code below */ - return (ExprState *) outlist; - } - default: - elog(ERROR, "unrecognized node type: %d", - (int) nodeTag(node)); - state = NULL; /* keep compiler quiet */ - break; - } - - /* Common code for all state-node types */ - state->expr = node; - - return state; -} - -/* - * ExecPrepareExpr --- initialize for expression execution outside a normal - * Plan tree context. - * - * This differs from ExecInitExpr in that we don't assume the caller is - * already running in the EState's per-query context. Also, we run the - * passed expression tree through expression_planner() to prepare it for - * execution. (In ordinary Plan trees the regular planning process will have - * made the appropriate transformations on expressions, but for standalone - * expressions this won't have happened.) - */ -ExprState * -ExecPrepareExpr(Expr *node, EState *estate) -{ - ExprState *result; - MemoryContext oldcontext; - - oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); - - node = expression_planner(node); - - result = ExecInitExpr(node, NULL); - - MemoryContextSwitchTo(oldcontext); - - return result; -} - - -/* ---------------------------------------------------------------- - * ExecQual / ExecTargetList / ExecProject - * ---------------------------------------------------------------- - */ - -/* ---------------------------------------------------------------- - * ExecQual - * - * Evaluates a conjunctive boolean expression (qual list) and - * returns true iff none of the subexpressions are false. - * (We also return true if the list is empty.) - * - * If some of the subexpressions yield NULL but none yield FALSE, - * then the result of the conjunction is NULL (ie, unknown) - * according to three-valued boolean logic. In this case, - * we return the value specified by the "resultForNull" parameter. - * - * Callers evaluating WHERE clauses should pass resultForNull=FALSE, - * since SQL specifies that tuples with null WHERE results do not - * get selected. On the other hand, callers evaluating constraint - * conditions should pass resultForNull=TRUE, since SQL also specifies - * that NULL constraint conditions are not failures. - * - * NOTE: it would not be correct to use this routine to evaluate an - * AND subclause of a boolean expression; for that purpose, a NULL - * result must be returned as NULL so that it can be properly treated - * in the next higher operator (cf. ExecEvalAnd and ExecEvalOr). - * This routine is only used in contexts where a complete expression - * is being evaluated and we know that NULL can be treated the same - * as one boolean result or the other. - * - * ---------------------------------------------------------------- - */ -bool -ExecQual(List *qual, ExprContext *econtext, bool resultForNull) -{ - bool result; - MemoryContext oldContext; - ListCell *l; - - /* - * debugging stuff - */ - EV_printf("ExecQual: qual is "); - EV_nodeDisplay(qual); - EV_printf("\n"); - - /* - * Run in short-lived per-tuple context while computing expressions. - */ - oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - - /* - * Evaluate the qual conditions one at a time. If we find a FALSE result, - * we can stop evaluating and return FALSE --- the AND result must be - * FALSE. Also, if we find a NULL result when resultForNull is FALSE, we - * can stop and return FALSE --- the AND result must be FALSE or NULL in - * that case, and the caller doesn't care which. - * - * If we get to the end of the list, we can return TRUE. This will happen - * when the AND result is indeed TRUE, or when the AND result is NULL (one - * or more NULL subresult, with all the rest TRUE) and the caller has - * specified resultForNull = TRUE. - */ - result = true; - - foreach(l, qual) - { - ExprState *clause = (ExprState *) lfirst(l); - Datum expr_value; - bool isNull; - - expr_value = ExecEvalExpr(clause, econtext, &isNull); - - if (isNull) - { - if (resultForNull == false) - { - result = false; /* treat NULL as FALSE */ - break; - } - } - else - { - if (!DatumGetBool(expr_value)) - { - result = false; /* definitely FALSE */ - break; - } - } - } - - MemoryContextSwitchTo(oldContext); - - return result; -} - -/* - * Number of items in a tlist (including any resjunk items!) - */ -int -ExecTargetListLength(List *targetlist) -{ - /* This used to be more complex, but fjoins are dead */ - return list_length(targetlist); -} - -/* - * Number of items in a tlist, not including any resjunk items - */ -int -ExecCleanTargetListLength(List *targetlist) -{ - int len = 0; - ListCell *tl; - - foreach(tl, targetlist) - { - TargetEntry *curTle = castNode(TargetEntry, lfirst(tl)); - - if (!curTle->resjunk) - len++; - } - return len; -} - -/* - * ExecTargetList - * Evaluates a targetlist with respect to the given - * expression context. - * - * tupdesc must describe the rowtype of the expected result. - * - * Results are stored into the passed values and isnull arrays. - * - * Since fields of the result tuple might be multiply referenced in higher - * plan nodes, we have to force any read/write expanded values to read-only - * status. It's a bit annoying to have to do that for every projected - * expression; in the future, consider teaching the planner to detect - * actually-multiply-referenced Vars and insert an expression node that - * would do that only where really required. - */ -static void -ExecTargetList(List *targetlist, - TupleDesc tupdesc, - ExprContext *econtext, - Datum *values, - bool *isnull) -{ - Form_pg_attribute *att = tupdesc->attrs; - MemoryContext oldContext; - ListCell *tl; - - /* - * Run in short-lived per-tuple context while computing expressions. - */ - oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); - - /* - * evaluate all the expressions in the target list - */ - foreach(tl, targetlist) - { - GenericExprState *gstate = (GenericExprState *) lfirst(tl); - TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; - AttrNumber resind = tle->resno - 1; - - values[resind] = ExecEvalExpr(gstate->arg, - econtext, - &isnull[resind]); - - values[resind] = MakeExpandedObjectReadOnly(values[resind], - isnull[resind], - att[resind]->attlen); - } - - MemoryContextSwitchTo(oldContext); -} - -/* - * ExecProject - * - * projects a tuple based on projection info and stores - * it in the previously specified tuple table slot. - * - * Note: the result is always a virtual tuple; therefore it - * may reference the contents of the exprContext's scan tuples - * and/or temporary results constructed in the exprContext. - * If the caller wishes the result to be valid longer than that - * data will be valid, he must call ExecMaterializeSlot on the - * result slot. - */ -TupleTableSlot * -ExecProject(ProjectionInfo *projInfo) -{ - TupleTableSlot *slot; - ExprContext *econtext; - int numSimpleVars; - - /* - * sanity checks - */ - Assert(projInfo != NULL); - - /* - * get the projection info we want - */ - slot = projInfo->pi_slot; - econtext = projInfo->pi_exprContext; - - /* - * Clear any former contents of the result slot. This makes it safe for - * us to use the slot's Datum/isnull arrays as workspace. - */ - ExecClearTuple(slot); - - /* - * Force extraction of all input values that we'll need. The - * Var-extraction loops below depend on this, and we are also prefetching - * all attributes that will be referenced in the generic expressions. - */ - if (projInfo->pi_lastInnerVar > 0) - slot_getsomeattrs(econtext->ecxt_innertuple, - projInfo->pi_lastInnerVar); - if (projInfo->pi_lastOuterVar > 0) - slot_getsomeattrs(econtext->ecxt_outertuple, - projInfo->pi_lastOuterVar); - if (projInfo->pi_lastScanVar > 0) - slot_getsomeattrs(econtext->ecxt_scantuple, - projInfo->pi_lastScanVar); - - /* - * Assign simple Vars to result by direct extraction of fields from source - * slots ... a mite ugly, but fast ... - */ - numSimpleVars = projInfo->pi_numSimpleVars; - if (numSimpleVars > 0) - { - Datum *values = slot->tts_values; - bool *isnull = slot->tts_isnull; - int *varSlotOffsets = projInfo->pi_varSlotOffsets; - int *varNumbers = projInfo->pi_varNumbers; - int i; - - if (projInfo->pi_directMap) - { - /* especially simple case where vars go to output in order */ - for (i = 0; i < numSimpleVars; i++) - { - char *slotptr = ((char *) econtext) + varSlotOffsets[i]; - TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); - int varNumber = varNumbers[i] - 1; - - values[i] = varSlot->tts_values[varNumber]; - isnull[i] = varSlot->tts_isnull[varNumber]; - } - } - else - { - /* we have to pay attention to varOutputCols[] */ - int *varOutputCols = projInfo->pi_varOutputCols; - - for (i = 0; i < numSimpleVars; i++) - { - char *slotptr = ((char *) econtext) + varSlotOffsets[i]; - TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr); - int varNumber = varNumbers[i] - 1; - int varOutputCol = varOutputCols[i] - 1; - - values[varOutputCol] = varSlot->tts_values[varNumber]; - isnull[varOutputCol] = varSlot->tts_isnull[varNumber]; - } - } - } - - /* - * If there are any generic expressions, evaluate them. - */ - if (projInfo->pi_targetlist) - { - ExecTargetList(projInfo->pi_targetlist, - slot->tts_tupleDescriptor, - econtext, - slot->tts_values, - slot->tts_isnull); - } - - /* - * Mark the result slot as containing a valid virtual tuple. - */ - return ExecStoreVirtualTuple(slot); -} diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c new file mode 100644 index 0000000000..4badd5c576 --- /dev/null +++ b/src/backend/executor/execSRF.c @@ -0,0 +1,924 @@ +/*------------------------------------------------------------------------- + * + * execSRF.c + * Routines implementing the API for set-returning functions + * + * This file serves nodeFunctionscan.c and nodeProjectSet.c, providing + * common code for calling set-returning functions according to the + * ReturnSetInfo API. + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/execSRF.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/objectaccess.h" +#include "executor/execdebug.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_coerce.h" +#include "pgstat.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/typcache.h" + + +/* static function decls */ +static void init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr, + MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF); +static void ShutdownSetExpr(Datum arg); +static void ExecEvalFuncArgs(FunctionCallInfo fcinfo, + List *argList, ExprContext *econtext); +static void ExecPrepareTuplestoreResult(SetExprState *sexpr, + ExprContext *econtext, + Tuplestorestate *resultStore, + TupleDesc resultDesc); +static void tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc); + + +/* + * Prepare function call in FROM (ROWS FROM) for execution. + * + * This is used by nodeFunctionscan.c. + */ +SetExprState * +ExecInitTableFunctionResult(Expr *expr, + ExprContext *econtext, PlanState *parent) +{ + SetExprState *state = makeNode(SetExprState); + + state->funcReturnsSet = false; + state->expr = expr; + state->func.fn_oid = InvalidOid; + + /* + * Normally the passed expression tree will be a FuncExpr, since the + * grammar only allows a function call at the top level of a table + * function reference. However, if the function doesn't return set then + * the planner might have replaced the function call via constant-folding + * or inlining. So if we see any other kind of expression node, execute + * it via the general ExecEvalExpr() code. That code path will not + * support set-returning functions buried in the expression, though. + */ + if (IsA(expr, FuncExpr)) + { + FuncExpr *func = (FuncExpr *) expr; + + state->funcReturnsSet = func->funcretset; + state->args = ExecInitExprList(func->args, parent); + + init_sexpr(func->funcid, func->inputcollid, state, + econtext->ecxt_per_query_memory, func->funcretset, false); + } + else + { + state->elidedFuncState = ExecInitExpr(expr, parent); + } + + return state; +} + +/* + * ExecMakeTableFunctionResult + * + * Evaluate a table function, producing a materialized result in a Tuplestore + * object. + * + * This is used by nodeFunctionscan.c. + */ +Tuplestorestate * +ExecMakeTableFunctionResult(SetExprState *setexpr, + ExprContext *econtext, + MemoryContext argContext, + TupleDesc expectedDesc, + bool randomAccess) +{ + Tuplestorestate *tupstore = NULL; + TupleDesc tupdesc = NULL; + Oid funcrettype; + bool returnsTuple; + bool returnsSet = false; + FunctionCallInfoData fcinfo; + PgStat_FunctionCallUsage fcusage; + ReturnSetInfo rsinfo; + HeapTupleData tmptup; + MemoryContext callerContext; + MemoryContext oldcontext; + bool first_time = true; + + callerContext = CurrentMemoryContext; + + funcrettype = exprType((Node *) setexpr->expr); + + returnsTuple = type_is_rowtype(funcrettype); + + /* + * Prepare a resultinfo node for communication. We always do this even if + * not expecting a set result, so that we can pass expectedDesc. In the + * generic-expression case, the expression doesn't actually get to see the + * resultinfo, but set it up anyway because we use some of the fields as + * our own state variables. + */ + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.expectedDesc = expectedDesc; + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize | SFRM_Materialize_Preferred); + if (randomAccess) + rsinfo.allowedModes |= (int) SFRM_Materialize_Random; + rsinfo.returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; + + /* + * Normally the passed expression tree will be a SetExprState, since the + * grammar only allows a function call at the top level of a table + * function reference. However, if the function doesn't return set then + * the planner might have replaced the function call via constant-folding + * or inlining. So if we see any other kind of expression node, execute + * it via the general ExecEvalExpr() code; the only difference is that we + * don't get a chance to pass a special ReturnSetInfo to any functions + * buried in the expression. + */ + if (!setexpr->elidedFuncState) + { + /* + * This path is similar to ExecMakeFunctionResultSet. + */ + returnsSet = setexpr->funcReturnsSet; + InitFunctionCallInfoData(fcinfo, &(setexpr->func), + list_length(setexpr->args), + setexpr->fcinfo_data.fncollation, + NULL, (Node *) &rsinfo); + + /* + * Evaluate the function's argument list. + * + * We can't do this in the per-tuple context: the argument values + * would disappear when we reset that context in the inner loop. And + * the caller's CurrentMemoryContext is typically a query-lifespan + * context, so we don't want to leak memory there. We require the + * caller to pass a separate memory context that can be used for this, + * and can be reset each time through to avoid bloat. + */ + MemoryContextReset(argContext); + oldcontext = MemoryContextSwitchTo(argContext); + ExecEvalFuncArgs(&fcinfo, setexpr->args, econtext); + MemoryContextSwitchTo(oldcontext); + + /* + * If function is strict, and there are any NULL arguments, skip + * calling the function and act like it returned NULL (or an empty + * set, in the returns-set case). + */ + if (setexpr->func.fn_strict) + { + int i; + + for (i = 0; i < fcinfo.nargs; i++) + { + if (fcinfo.argnull[i]) + goto no_function_result; + } + } + } + else + { + /* Treat setexpr as a generic expression */ + InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); + } + + /* + * Switch to short-lived context for calling the function or expression. + */ + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + /* + * Loop to handle the ValuePerCall protocol (which is also the same + * behavior needed in the generic ExecEvalExpr path). + */ + for (;;) + { + Datum result; + + CHECK_FOR_INTERRUPTS(); + + /* + * reset per-tuple memory context before each call of the function or + * expression. This cleans up any local memory the function may leak + * when called. + */ + ResetExprContext(econtext); + + /* Call the function or expression one time */ + if (!setexpr->elidedFuncState) + { + pgstat_init_function_usage(&fcinfo, &fcusage); + + fcinfo.isnull = false; + rsinfo.isDone = ExprSingleResult; + result = FunctionCallInvoke(&fcinfo); + + pgstat_end_function_usage(&fcusage, + rsinfo.isDone != ExprMultipleResult); + } + else + { + result = + ExecEvalExpr(setexpr->elidedFuncState, econtext, &fcinfo.isnull); + rsinfo.isDone = ExprSingleResult; + } + + /* Which protocol does function want to use? */ + if (rsinfo.returnMode == SFRM_ValuePerCall) + { + /* + * Check for end of result set. + */ + if (rsinfo.isDone == ExprEndResult) + break; + + /* + * If first time through, build tuplestore for result. For a + * scalar function result type, also make a suitable tupdesc. + */ + if (first_time) + { + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + rsinfo.setResult = tupstore; + if (!returnsTuple) + { + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + "column", + funcrettype, + -1, + 0); + rsinfo.setDesc = tupdesc; + } + MemoryContextSwitchTo(oldcontext); + } + + /* + * Store current resultset item. + */ + if (returnsTuple) + { + if (!fcinfo.isnull) + { + HeapTupleHeader td = DatumGetHeapTupleHeader(result); + + if (tupdesc == NULL) + { + /* + * This is the first non-NULL result from the + * function. Use the type info embedded in the + * rowtype Datum to look up the needed tupdesc. Make + * a copy for the query. + */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupdesc = lookup_rowtype_tupdesc_copy(HeapTupleHeaderGetTypeId(td), + HeapTupleHeaderGetTypMod(td)); + rsinfo.setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + } + else + { + /* + * Verify all later returned rows have same subtype; + * necessary in case the type is RECORD. + */ + if (HeapTupleHeaderGetTypeId(td) != tupdesc->tdtypeid || + HeapTupleHeaderGetTypMod(td) != tupdesc->tdtypmod) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("rows returned by function are not all of the same row type"))); + } + + /* + * tuplestore_puttuple needs a HeapTuple not a bare + * HeapTupleHeader, but it doesn't need all the fields. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; + + tuplestore_puttuple(tupstore, &tmptup); + } + else + { + /* + * NULL result from a tuple-returning function; expand it + * to a row of all nulls. We rely on the expectedDesc to + * form such rows. (Note: this would be problematic if + * tuplestore_putvalues saved the tdtypeid/tdtypmod from + * the provided descriptor, since that might not match + * what we get from the function itself. But it doesn't.) + */ + int natts = expectedDesc->natts; + bool *nullflags; + + nullflags = (bool *) palloc(natts * sizeof(bool)); + memset(nullflags, true, natts * sizeof(bool)); + tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); + } + } + else + { + /* Scalar-type case: just store the function result */ + tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull); + } + + /* + * Are we done? + */ + if (rsinfo.isDone != ExprMultipleResult) + break; + } + else if (rsinfo.returnMode == SFRM_Materialize) + { + /* check we're on the same page as the function author */ + if (!first_time || rsinfo.isDone != ExprSingleResult) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("table-function protocol for materialize mode was not followed"))); + /* Done evaluating the set result */ + break; + } + else + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("unrecognized table-function returnMode: %d", + (int) rsinfo.returnMode))); + + first_time = false; + } + +no_function_result: + + /* + * If we got nothing from the function (ie, an empty-set or NULL result), + * we have to create the tuplestore to return, and if it's a + * non-set-returning function then insert a single all-nulls row. As + * above, we depend on the expectedDesc to manufacture the dummy row. + */ + if (rsinfo.setResult == NULL) + { + MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + rsinfo.setResult = tupstore; + if (!returnsSet) + { + int natts = expectedDesc->natts; + bool *nullflags; + + MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + nullflags = (bool *) palloc(natts * sizeof(bool)); + memset(nullflags, true, natts * sizeof(bool)); + tuplestore_putvalues(tupstore, expectedDesc, NULL, nullflags); + } + } + + /* + * If function provided a tupdesc, cross-check it. We only really need to + * do this for functions returning RECORD, but might as well do it always. + */ + if (rsinfo.setDesc) + { + tupledesc_match(expectedDesc, rsinfo.setDesc); + + /* + * If it is a dynamically-allocated TupleDesc, free it: it is + * typically allocated in a per-query context, so we must avoid + * leaking it across multiple usages. + */ + if (rsinfo.setDesc->tdrefcount == -1) + FreeTupleDesc(rsinfo.setDesc); + } + + MemoryContextSwitchTo(callerContext); + + /* All done, pass back the tuplestore */ + return rsinfo.setResult; +} + + +/* + * Prepare targetlist SRF function call for execution. + * + * This is used by nodeProjectSet.c. + */ +SetExprState * +ExecInitFunctionResultSet(Expr *expr, + ExprContext *econtext, PlanState *parent) +{ + SetExprState *state = makeNode(SetExprState); + + state->funcReturnsSet = true; + state->expr = expr; + state->func.fn_oid = InvalidOid; + + /* + * Initialize metadata. The expression node could be either a FuncExpr or + * an OpExpr. + */ + if (IsA(expr, FuncExpr)) + { + FuncExpr *func = (FuncExpr *) expr; + + state->args = ExecInitExprList(func->args, parent); + init_sexpr(func->funcid, func->inputcollid, state, + econtext->ecxt_per_query_memory, true, true); + } + else if (IsA(expr, OpExpr)) + { + OpExpr *op = (OpExpr *) expr; + + state->args = ExecInitExprList(op->args, parent); + init_sexpr(op->opfuncid, op->inputcollid, state, + econtext->ecxt_per_query_memory, true, true); + } + else + elog(ERROR, "unrecognized node type: %d", + (int) nodeTag(expr)); + + /* shouldn't get here unless the selected function returns set */ + Assert(state->func.fn_retset); + + return state; +} + +/* + * ExecMakeFunctionResultSet + * + * Evaluate the arguments to a set-returning function and then call the + * function itself. The argument expressions may not contain set-returning + * functions (the planner is supposed to have separated evaluation for those). + * + * This is used by nodeProjectSet.c. + */ +Datum +ExecMakeFunctionResultSet(SetExprState *fcache, + ExprContext *econtext, + bool *isNull, + ExprDoneCond *isDone) +{ + List *arguments; + Datum result; + FunctionCallInfo fcinfo; + PgStat_FunctionCallUsage fcusage; + ReturnSetInfo rsinfo; + bool callit; + int i; + +restart: + + /* Guard against stack overflow due to overly complex expressions */ + check_stack_depth(); + + /* + * If a previous call of the function returned a set result in the form of + * a tuplestore, continue reading rows from the tuplestore until it's + * empty. + */ + if (fcache->funcResultStore) + { + if (tuplestore_gettupleslot(fcache->funcResultStore, true, false, + fcache->funcResultSlot)) + { + *isDone = ExprMultipleResult; + if (fcache->funcReturnsTuple) + { + /* We must return the whole tuple as a Datum. */ + *isNull = false; + return ExecFetchSlotTupleDatum(fcache->funcResultSlot); + } + else + { + /* Extract the first column and return it as a scalar. */ + return slot_getattr(fcache->funcResultSlot, 1, isNull); + } + } + /* Exhausted the tuplestore, so clean up */ + tuplestore_end(fcache->funcResultStore); + fcache->funcResultStore = NULL; + *isDone = ExprEndResult; + *isNull = true; + return (Datum) 0; + } + + /* + * arguments is a list of expressions to evaluate before passing to the + * function manager. We skip the evaluation if it was already done in the + * previous call (ie, we are continuing the evaluation of a set-valued + * function). Otherwise, collect the current argument values into fcinfo. + */ + fcinfo = &fcache->fcinfo_data; + arguments = fcache->args; + if (!fcache->setArgsValid) + ExecEvalFuncArgs(fcinfo, arguments, econtext); + else + { + /* Reset flag (we may set it again below) */ + fcache->setArgsValid = false; + } + + /* + * Now call the function, passing the evaluated parameter values. + */ + + /* Prepare a resultinfo node for communication. */ + fcinfo->resultinfo = (Node *) &rsinfo; + rsinfo.type = T_ReturnSetInfo; + rsinfo.econtext = econtext; + rsinfo.expectedDesc = fcache->funcResultDesc; + rsinfo.allowedModes = (int) (SFRM_ValuePerCall | SFRM_Materialize); + /* note we do not set SFRM_Materialize_Random or _Preferred */ + rsinfo.returnMode = SFRM_ValuePerCall; + /* isDone is filled below */ + rsinfo.setResult = NULL; + rsinfo.setDesc = NULL; + + /* + * If function is strict, and there are any NULL arguments, skip calling + * the function. + */ + callit = true; + if (fcache->func.fn_strict) + { + for (i = 0; i < fcinfo->nargs; i++) + { + if (fcinfo->argnull[i]) + { + callit = false; + break; + } + } + } + + if (callit) + { + pgstat_init_function_usage(fcinfo, &fcusage); + + fcinfo->isnull = false; + rsinfo.isDone = ExprSingleResult; + result = FunctionCallInvoke(fcinfo); + *isNull = fcinfo->isnull; + *isDone = rsinfo.isDone; + + pgstat_end_function_usage(&fcusage, + rsinfo.isDone != ExprMultipleResult); + } + else + { + /* for a strict SRF, result for NULL is an empty set */ + result = (Datum) 0; + *isNull = true; + *isDone = ExprEndResult; + } + + /* Which protocol does function want to use? */ + if (rsinfo.returnMode == SFRM_ValuePerCall) + { + if (*isDone != ExprEndResult) + { + /* + * Save the current argument values to re-use on the next call. + */ + if (*isDone == ExprMultipleResult) + { + fcache->setArgsValid = true; + /* Register cleanup callback if we didn't already */ + if (!fcache->shutdown_reg) + { + RegisterExprContextCallback(econtext, + ShutdownSetExpr, + PointerGetDatum(fcache)); + fcache->shutdown_reg = true; + } + } + } + } + else if (rsinfo.returnMode == SFRM_Materialize) + { + /* check we're on the same page as the function author */ + if (rsinfo.isDone != ExprSingleResult) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("table-function protocol for materialize mode was not followed"))); + if (rsinfo.setResult != NULL) + { + /* prepare to return values from the tuplestore */ + ExecPrepareTuplestoreResult(fcache, econtext, + rsinfo.setResult, + rsinfo.setDesc); + /* loop back to top to start returning from tuplestore */ + goto restart; + } + /* if setResult was left null, treat it as empty set */ + *isDone = ExprEndResult; + *isNull = true; + result = (Datum) 0; + } + else + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("unrecognized table-function returnMode: %d", + (int) rsinfo.returnMode))); + + return result; +} + + +/* + * init_sexpr - initialize a SetExprState node during first use + */ +static void +init_sexpr(Oid foid, Oid input_collation, SetExprState *sexpr, + MemoryContext sexprCxt, bool allowSRF, bool needDescForSRF) +{ + AclResult aclresult; + + /* Check permission to call function */ + aclresult = pg_proc_aclcheck(foid, GetUserId(), ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_PROC, get_func_name(foid)); + InvokeFunctionExecuteHook(foid); + + /* + * Safety check on nargs. Under normal circumstances this should never + * fail, as parser should check sooner. But possibly it might fail if + * server has been compiled with FUNC_MAX_ARGS smaller than some functions + * declared in pg_proc? + */ + if (list_length(sexpr->args) > FUNC_MAX_ARGS) + ereport(ERROR, + (errcode(ERRCODE_TOO_MANY_ARGUMENTS), + errmsg_plural("cannot pass more than %d argument to a function", + "cannot pass more than %d arguments to a function", + FUNC_MAX_ARGS, + FUNC_MAX_ARGS))); + + /* Set up the primary fmgr lookup information */ + fmgr_info_cxt(foid, &(sexpr->func), sexprCxt); + fmgr_info_set_expr((Node *) sexpr->expr, &(sexpr->func)); + + /* Initialize the function call parameter struct as well */ + InitFunctionCallInfoData(sexpr->fcinfo_data, &(sexpr->func), + list_length(sexpr->args), + input_collation, NULL, NULL); + + /* If function returns set, check if that's allowed by caller */ + if (sexpr->func.fn_retset && !allowSRF) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + /* Otherwise, caller should have marked the sexpr correctly */ + Assert(sexpr->func.fn_retset == sexpr->funcReturnsSet); + + /* If function returns set, prepare expected tuple descriptor */ + if (sexpr->func.fn_retset && needDescForSRF) + { + TypeFuncClass functypclass; + Oid funcrettype; + TupleDesc tupdesc; + MemoryContext oldcontext; + + functypclass = get_expr_result_type(sexpr->func.fn_expr, + &funcrettype, + &tupdesc); + + /* Must save tupdesc in sexpr's context */ + oldcontext = MemoryContextSwitchTo(sexprCxt); + + if (functypclass == TYPEFUNC_COMPOSITE) + { + /* Composite data type, e.g. a table's row type */ + Assert(tupdesc); + /* Must copy it out of typcache for safety */ + sexpr->funcResultDesc = CreateTupleDescCopy(tupdesc); + sexpr->funcReturnsTuple = true; + } + else if (functypclass == TYPEFUNC_SCALAR) + { + /* Base data type, i.e. scalar */ + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, + (AttrNumber) 1, + NULL, + funcrettype, + -1, + 0); + sexpr->funcResultDesc = tupdesc; + sexpr->funcReturnsTuple = false; + } + else if (functypclass == TYPEFUNC_RECORD) + { + /* This will work if function doesn't need an expectedDesc */ + sexpr->funcResultDesc = NULL; + sexpr->funcReturnsTuple = true; + } + else + { + /* Else, we will fail if function needs an expectedDesc */ + sexpr->funcResultDesc = NULL; + } + + MemoryContextSwitchTo(oldcontext); + } + else + sexpr->funcResultDesc = NULL; + + /* Initialize additional state */ + sexpr->funcResultStore = NULL; + sexpr->funcResultSlot = NULL; + sexpr->shutdown_reg = false; +} + +/* + * callback function in case a SetExprState needs to be shut down before it + * has been run to completion + */ +static void +ShutdownSetExpr(Datum arg) +{ + SetExprState *sexpr = castNode(SetExprState, DatumGetPointer(arg)); + + /* If we have a slot, make sure it's let go of any tuplestore pointer */ + if (sexpr->funcResultSlot) + ExecClearTuple(sexpr->funcResultSlot); + + /* Release any open tuplestore */ + if (sexpr->funcResultStore) + tuplestore_end(sexpr->funcResultStore); + sexpr->funcResultStore = NULL; + + /* Clear any active set-argument state */ + sexpr->setArgsValid = false; + + /* execUtils will deregister the callback... */ + sexpr->shutdown_reg = false; +} + +/* + * Evaluate arguments for a function. + */ +static void +ExecEvalFuncArgs(FunctionCallInfo fcinfo, + List *argList, + ExprContext *econtext) +{ + int i; + ListCell *arg; + + i = 0; + foreach(arg, argList) + { + ExprState *argstate = (ExprState *) lfirst(arg); + + fcinfo->arg[i] = ExecEvalExpr(argstate, + econtext, + &fcinfo->argnull[i]); + i++; + } + + Assert(i == fcinfo->nargs); +} + +/* + * ExecPrepareTuplestoreResult + * + * Subroutine for ExecMakeFunctionResultSet: prepare to extract rows from a + * tuplestore function result. We must set up a funcResultSlot (unless + * already done in a previous call cycle) and verify that the function + * returned the expected tuple descriptor. + */ +static void +ExecPrepareTuplestoreResult(SetExprState *sexpr, + ExprContext *econtext, + Tuplestorestate *resultStore, + TupleDesc resultDesc) +{ + sexpr->funcResultStore = resultStore; + + if (sexpr->funcResultSlot == NULL) + { + /* Create a slot so we can read data out of the tuplestore */ + TupleDesc slotDesc; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(sexpr->func.fn_mcxt); + + /* + * If we were not able to determine the result rowtype from context, + * and the function didn't return a tupdesc, we have to fail. + */ + if (sexpr->funcResultDesc) + slotDesc = sexpr->funcResultDesc; + else if (resultDesc) + { + /* don't assume resultDesc is long-lived */ + slotDesc = CreateTupleDescCopy(resultDesc); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning setof record called in " + "context that cannot accept type record"))); + slotDesc = NULL; /* keep compiler quiet */ + } + + sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc); + MemoryContextSwitchTo(oldcontext); + } + + /* + * If function provided a tupdesc, cross-check it. We only really need to + * do this for functions returning RECORD, but might as well do it always. + */ + if (resultDesc) + { + if (sexpr->funcResultDesc) + tupledesc_match(sexpr->funcResultDesc, resultDesc); + + /* + * If it is a dynamically-allocated TupleDesc, free it: it is + * typically allocated in a per-query context, so we must avoid + * leaking it across multiple usages. + */ + if (resultDesc->tdrefcount == -1) + FreeTupleDesc(resultDesc); + } + + /* Register cleanup callback if we didn't already */ + if (!sexpr->shutdown_reg) + { + RegisterExprContextCallback(econtext, + ShutdownSetExpr, + PointerGetDatum(sexpr)); + sexpr->shutdown_reg = true; + } +} + +/* + * Check that function result tuple type (src_tupdesc) matches or can + * be considered to match what the query expects (dst_tupdesc). If + * they don't match, ereport. + * + * We really only care about number of attributes and data type. + * Also, we can ignore type mismatch on columns that are dropped in the + * destination type, so long as the physical storage matches. This is + * helpful in some cases involving out-of-date cached plans. + */ +static void +tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc) +{ + int i; + + if (dst_tupdesc->natts != src_tupdesc->natts) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail_plural("Returned row contains %d attribute, but query expects %d.", + "Returned row contains %d attributes, but query expects %d.", + src_tupdesc->natts, + src_tupdesc->natts, dst_tupdesc->natts))); + + for (i = 0; i < dst_tupdesc->natts; i++) + { + Form_pg_attribute dattr = dst_tupdesc->attrs[i]; + Form_pg_attribute sattr = src_tupdesc->attrs[i]; + + if (IsBinaryCoercible(sattr->atttypid, dattr->atttypid)) + continue; /* no worries */ + if (!dattr->attisdropped) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail("Returned type %s at ordinal position %d, but query expects %s.", + format_type_be(sattr->atttypid), + i + 1, + format_type_be(dattr->atttypid)))); + + if (dattr->attlen != sattr->attlen || + dattr->attalign != sattr->attalign) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("function return row and query-specified return row do not match"), + errdetail("Physical storage mismatch on dropped attribute at ordinal position %d.", + i + 1))); + } +} diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 65196795d7..4f131b3ee0 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -123,7 +123,7 @@ ExecScan(ScanState *node, ExecScanRecheckMtd recheckMtd) { ExprContext *econtext; - List *qual; + ExprState *qual; ProjectionInfo *projInfo; /* @@ -170,7 +170,7 @@ ExecScan(ScanState *node, if (TupIsNull(slot)) { if (projInfo) - return ExecClearTuple(projInfo->pi_slot); + return ExecClearTuple(projInfo->pi_state.resultslot); else return slot; } @@ -183,11 +183,11 @@ ExecScan(ScanState *node, /* * check that the current tuple satisfies the qual-clause * - * check for non-nil qual here to avoid a function call to ExecQual() - * when the qual is nil ... saves only a few cycles, but they add up + * check for non-null qual here to avoid a function call to ExecQual() + * when the qual is null ... saves only a few cycles, but they add up * ... */ - if (!qual || ExecQual(qual, econtext, false)) + if (qual == NULL || ExecQual(qual, econtext)) { /* * Found a satisfactory scan tuple. diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index a72cffeb6e..2613ffbb71 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -31,6 +31,9 @@ * RegisterExprContextCallback Register function shutdown callback * UnregisterExprContextCallback Deregister function shutdown callback * + * GetAttributeByName Runtime extraction of columns from tuples. + * GetAttributeByNum + * * NOTES * This file has traditionally been the place to stick misc. * executor support stuff that doesn't really go anyplace else. @@ -44,11 +47,12 @@ #include "nodes/nodeFuncs.h" #include "parser/parsetree.h" #include "storage/lmgr.h" +#include "utils/builtins.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "utils/typcache.h" -static bool get_last_attnums(Node *node, ProjectionInfo *projInfo); static void ShutdownExprContext(ExprContext *econtext, bool isCommit); @@ -464,186 +468,6 @@ ExecGetResultType(PlanState *planstate) return slot->tts_tupleDescriptor; } -/* ---------------- - * ExecBuildProjectionInfo - * - * Build a ProjectionInfo node for evaluating the given tlist in the given - * econtext, and storing the result into the tuple slot. (Caller must have - * ensured that tuple slot has a descriptor matching the tlist!) Note that - * the given tlist should be a list of ExprState nodes, not Expr nodes. - * - * inputDesc can be NULL, but if it is not, we check to see whether simple - * Vars in the tlist match the descriptor. It is important to provide - * inputDesc for relation-scan plan nodes, as a cross check that the relation - * hasn't been changed since the plan was made. At higher levels of a plan, - * there is no need to recheck. - * ---------------- - */ -ProjectionInfo * -ExecBuildProjectionInfo(List *targetList, - ExprContext *econtext, - TupleTableSlot *slot, - TupleDesc inputDesc) -{ - ProjectionInfo *projInfo = makeNode(ProjectionInfo); - int len = ExecTargetListLength(targetList); - int *workspace; - int *varSlotOffsets; - int *varNumbers; - int *varOutputCols; - List *exprlist; - int numSimpleVars; - bool directMap; - ListCell *tl; - - projInfo->pi_exprContext = econtext; - projInfo->pi_slot = slot; - /* since these are all int arrays, we need do just one palloc */ - workspace = (int *) palloc(len * 3 * sizeof(int)); - projInfo->pi_varSlotOffsets = varSlotOffsets = workspace; - projInfo->pi_varNumbers = varNumbers = workspace + len; - projInfo->pi_varOutputCols = varOutputCols = workspace + len * 2; - projInfo->pi_lastInnerVar = 0; - projInfo->pi_lastOuterVar = 0; - projInfo->pi_lastScanVar = 0; - - /* - * We separate the target list elements into simple Var references and - * expressions which require the full ExecTargetList machinery. To be a - * simple Var, a Var has to be a user attribute and not mismatch the - * inputDesc. (Note: if there is a type mismatch then ExecEvalScalarVar - * will probably throw an error at runtime, but we leave that to it.) - */ - exprlist = NIL; - numSimpleVars = 0; - directMap = true; - foreach(tl, targetList) - { - GenericExprState *gstate = (GenericExprState *) lfirst(tl); - Var *variable = (Var *) gstate->arg->expr; - bool isSimpleVar = false; - - if (variable != NULL && - IsA(variable, Var) && - variable->varattno > 0) - { - if (!inputDesc) - isSimpleVar = true; /* can't check type, assume OK */ - else if (variable->varattno <= inputDesc->natts) - { - Form_pg_attribute attr; - - attr = inputDesc->attrs[variable->varattno - 1]; - if (!attr->attisdropped && variable->vartype == attr->atttypid) - isSimpleVar = true; - } - } - - if (isSimpleVar) - { - TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr; - AttrNumber attnum = variable->varattno; - - varNumbers[numSimpleVars] = attnum; - varOutputCols[numSimpleVars] = tle->resno; - if (tle->resno != numSimpleVars + 1) - directMap = false; - - switch (variable->varno) - { - case INNER_VAR: - varSlotOffsets[numSimpleVars] = offsetof(ExprContext, - ecxt_innertuple); - if (projInfo->pi_lastInnerVar < attnum) - projInfo->pi_lastInnerVar = attnum; - break; - - case OUTER_VAR: - varSlotOffsets[numSimpleVars] = offsetof(ExprContext, - ecxt_outertuple); - if (projInfo->pi_lastOuterVar < attnum) - projInfo->pi_lastOuterVar = attnum; - break; - - /* INDEX_VAR is handled by default case */ - - default: - varSlotOffsets[numSimpleVars] = offsetof(ExprContext, - ecxt_scantuple); - if (projInfo->pi_lastScanVar < attnum) - projInfo->pi_lastScanVar = attnum; - break; - } - numSimpleVars++; - } - else - { - /* Not a simple variable, add it to generic targetlist */ - exprlist = lappend(exprlist, gstate); - /* Examine expr to include contained Vars in lastXXXVar counts */ - get_last_attnums((Node *) variable, projInfo); - } - } - projInfo->pi_targetlist = exprlist; - projInfo->pi_numSimpleVars = numSimpleVars; - projInfo->pi_directMap = directMap; - - return projInfo; -} - -/* - * get_last_attnums: expression walker for ExecBuildProjectionInfo - * - * Update the lastXXXVar counts to be at least as large as the largest - * attribute numbers found in the expression - */ -static bool -get_last_attnums(Node *node, ProjectionInfo *projInfo) -{ - if (node == NULL) - return false; - if (IsA(node, Var)) - { - Var *variable = (Var *) node; - AttrNumber attnum = variable->varattno; - - switch (variable->varno) - { - case INNER_VAR: - if (projInfo->pi_lastInnerVar < attnum) - projInfo->pi_lastInnerVar = attnum; - break; - - case OUTER_VAR: - if (projInfo->pi_lastOuterVar < attnum) - projInfo->pi_lastOuterVar = attnum; - break; - - /* INDEX_VAR is handled by default case */ - - default: - if (projInfo->pi_lastScanVar < attnum) - projInfo->pi_lastScanVar = attnum; - break; - } - return false; - } - - /* - * Don't examine the arguments or filters of Aggrefs or WindowFuncs, - * because those do not represent expressions to be evaluated within the - * overall targetlist's econtext. GroupingFunc arguments are never - * evaluated at all. - */ - if (IsA(node, Aggref)) - return false; - if (IsA(node, WindowFunc)) - return false; - if (IsA(node, GroupingFunc)) - return false; - return expression_tree_walker(node, get_last_attnums, - (void *) projInfo); -} /* ---------------- * ExecAssignProjectionInfo @@ -659,9 +483,10 @@ ExecAssignProjectionInfo(PlanState *planstate, TupleDesc inputDesc) { planstate->ps_ProjInfo = - ExecBuildProjectionInfo(planstate->targetlist, + ExecBuildProjectionInfo(planstate->plan->targetlist, planstate->ps_ExprContext, planstate->ps_ResultTupleSlot, + planstate, inputDesc); } @@ -1009,3 +834,152 @@ ExecLockNonLeafAppendTables(List *partitioned_rels, EState *estate) } } } + +/* + * GetAttributeByName + * GetAttributeByNum + * + * These functions return the value of the requested attribute + * out of the given tuple Datum. + * C functions which take a tuple as an argument are expected + * to use these. Ex: overpaid(EMP) might call GetAttributeByNum(). + * Note: these are actually rather slow because they do a typcache + * lookup on each call. + */ +Datum +GetAttributeByName(HeapTupleHeader tuple, const char *attname, bool *isNull) +{ + AttrNumber attrno; + Datum result; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; + int i; + + if (attname == NULL) + elog(ERROR, "invalid attribute name"); + + if (isNull == NULL) + elog(ERROR, "a NULL isNull pointer was passed"); + + if (tuple == NULL) + { + /* Kinda bogus but compatible with old behavior... */ + *isNull = true; + return (Datum) 0; + } + + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + attrno = InvalidAttrNumber; + for (i = 0; i < tupDesc->natts; i++) + { + if (namestrcmp(&(tupDesc->attrs[i]->attname), attname) == 0) + { + attrno = tupDesc->attrs[i]->attnum; + break; + } + } + + if (attrno == InvalidAttrNumber) + elog(ERROR, "attribute \"%s\" does not exist", attname); + + /* + * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all + * the fields in the struct just in case user tries to inspect system + * columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, + attrno, + tupDesc, + isNull); + + ReleaseTupleDesc(tupDesc); + + return result; +} + +Datum +GetAttributeByNum(HeapTupleHeader tuple, + AttrNumber attrno, + bool *isNull) +{ + Datum result; + Oid tupType; + int32 tupTypmod; + TupleDesc tupDesc; + HeapTupleData tmptup; + + if (!AttributeNumberIsValid(attrno)) + elog(ERROR, "invalid attribute number %d", attrno); + + if (isNull == NULL) + elog(ERROR, "a NULL isNull pointer was passed"); + + if (tuple == NULL) + { + /* Kinda bogus but compatible with old behavior... */ + *isNull = true; + return (Datum) 0; + } + + tupType = HeapTupleHeaderGetTypeId(tuple); + tupTypmod = HeapTupleHeaderGetTypMod(tuple); + tupDesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + /* + * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all + * the fields in the struct just in case user tries to inspect system + * columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, + attrno, + tupDesc, + isNull); + + ReleaseTupleDesc(tupDesc); + + return result; +} + +/* + * Number of items in a tlist (including any resjunk items!) + */ +int +ExecTargetListLength(List *targetlist) +{ + /* This used to be more complex, but fjoins are dead */ + return list_length(targetlist); +} + +/* + * Number of items in a tlist, not including any resjunk items + */ +int +ExecCleanTargetListLength(List *targetlist) +{ + int len = 0; + ListCell *tl; + + foreach(tl, targetlist) + { + TargetEntry *curTle = castNode(TargetEntry, lfirst(tl)); + + if (!curTle->resjunk) + len++; + } + return len; +} diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 65e52cca63..3e4b0191c7 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -1279,7 +1279,7 @@ fmgr_sql(PG_FUNCTION_ARGS) rsi->returnMode = SFRM_Materialize; rsi->setResult = fcache->tstore; fcache->tstore = NULL; - /* must copy desc because execQual will free it */ + /* must copy desc because execSRF.c will free it */ if (fcache->junkFilter) rsi->setDesc = CreateTupleDescCopy(fcache->junkFilter->jf_cleanTupType); diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 3207ee460c..471acc4b3e 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -1639,7 +1639,7 @@ project_aggregates(AggState *aggstate) /* * Check the qual (HAVING clause); if the group does not match, ignore it. */ - if (ExecQual(aggstate->ss.ps.qual, econtext, false)) + if (ExecQual(aggstate->ss.ps.qual, econtext)) { /* * Form and return projection tuple using the aggregate results and @@ -2501,18 +2501,17 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* * initialize child expressions * - * Note: ExecInitExpr finds Aggrefs for us, and also checks that no aggs - * contain other agg calls in their arguments. This would make no sense - * under SQL semantics anyway (and it's forbidden by the spec). Because - * that is true, we don't need to worry about evaluating the aggs in any - * particular order. + * We rely on the parser to have checked that no aggs contain other agg + * calls in their arguments. This would make no sense under SQL semantics + * (and it's forbidden by the spec). Because it is true, we don't need to + * worry about evaluating the aggs in any particular order. + * + * Note: execExpr.c finds Aggrefs for us, and adds their AggrefExprState + * nodes to aggstate->aggs. Aggrefs in the qual are found here; Aggrefs + * in the targetlist are found during ExecAssignProjectionInfo, below. */ - aggstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->plan.targetlist, - (PlanState *) aggstate); - aggstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->plan.qual, - (PlanState *) aggstate); + aggstate->ss.ps.qual = + ExecInitQual(node->plan.qual, (PlanState *) aggstate); /* * Initialize child nodes. @@ -2540,7 +2539,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) ExecAssignProjectionInfo(&aggstate->ss.ps, NULL); /* - * get the count of aggregates in targetlist and quals + * We should now have found all Aggrefs in the targetlist and quals. */ numaggs = aggstate->numaggs; Assert(numaggs == list_length(aggstate->aggs)); @@ -2724,7 +2723,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) foreach(l, aggstate->aggs) { AggrefExprState *aggrefstate = (AggrefExprState *) lfirst(l); - Aggref *aggref = (Aggref *) aggrefstate->xprstate.expr; + Aggref *aggref = aggrefstate->aggref; AggStatePerAgg peragg; AggStatePerTrans pertrans; int existing_aggno; @@ -3024,11 +3023,10 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* and then create a projection for that targetlist */ aggstate->evaldesc = ExecTypeFromTL(combined_inputeval, false); aggstate->evalslot = ExecInitExtraTupleSlot(estate); - combined_inputeval = (List *) ExecInitExpr((Expr *) combined_inputeval, - (PlanState *) aggstate); aggstate->evalproj = ExecBuildProjectionInfo(combined_inputeval, aggstate->tmpcontext, aggstate->evalslot, + &aggstate->ss.ps, NULL); ExecSetSlotDescriptor(aggstate->evalslot, aggstate->evaldesc); @@ -3206,8 +3204,8 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans, naggs = aggstate->numaggs; pertrans->aggfilter = ExecInitExpr(aggref->aggfilter, (PlanState *) aggstate); - pertrans->aggdirectargs = (List *) ExecInitExpr((Expr *) aggref->aggdirectargs, - (PlanState *) aggstate); + pertrans->aggdirectargs = ExecInitExprList(aggref->aggdirectargs, + (PlanState *) aggstate); /* * Complain if the aggregate's arguments contain any aggregates; nested diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c index 2e9ff7d1b9..19eb1755be 100644 --- a/src/backend/executor/nodeBitmapHeapscan.c +++ b/src/backend/executor/nodeBitmapHeapscan.c @@ -319,7 +319,7 @@ BitmapHeapNext(BitmapHeapScanState *node) econtext->ecxt_scantuple = slot; ResetExprContext(econtext); - if (!ExecQual(node->bitmapqualorig, econtext, false)) + if (!ExecQual(node->bitmapqualorig, econtext)) { /* Fails recheck, so drop it and loop back for another */ InstrCountFiltered2(node, 1); @@ -654,7 +654,7 @@ BitmapHeapRecheck(BitmapHeapScanState *node, TupleTableSlot *slot) ResetExprContext(econtext); - return ExecQual(node->bitmapqualorig, econtext, false); + return ExecQual(node->bitmapqualorig, econtext); } /* ---------------------------------------------------------------- @@ -837,15 +837,10 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags) /* * initialize child expressions */ - scanstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) scanstate); - scanstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) scanstate); - scanstate->bitmapqualorig = (List *) - ExecInitExpr((Expr *) node->bitmapqualorig, - (PlanState *) scanstate); + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); + scanstate->bitmapqualorig = + ExecInitQual(node->bitmapqualorig, (PlanState *) scanstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c index 8f4e0f527e..bed7949c5a 100644 --- a/src/backend/executor/nodeCtescan.c +++ b/src/backend/executor/nodeCtescan.c @@ -242,12 +242,8 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags) /* * initialize child expressions */ - scanstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) scanstate); - scanstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) scanstate); + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c index d464748290..5d309828ef 100644 --- a/src/backend/executor/nodeCustom.c +++ b/src/backend/executor/nodeCustom.c @@ -49,12 +49,8 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags) ExecAssignExprContext(estate, &css->ss.ps); /* initialize child expressions */ - css->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) cscan->scan.plan.targetlist, - (PlanState *) css); - css->ss.ps.qual = (List *) - ExecInitExpr((Expr *) cscan->scan.plan.qual, - (PlanState *) css); + css->ss.ps.qual = + ExecInitQual(cscan->scan.plan.qual, (PlanState *) css); /* tuple table initialization */ ExecInitScanTupleSlot(estate, &css->ss); diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c index 3b6d1390eb..9ae1561404 100644 --- a/src/backend/executor/nodeForeignscan.c +++ b/src/backend/executor/nodeForeignscan.c @@ -101,7 +101,7 @@ ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot) !fdwroutine->RecheckForeignScan(node, slot)) return false; - return ExecQual(node->fdw_recheck_quals, econtext, false); + return ExecQual(node->fdw_recheck_quals, econtext); } /* ---------------------------------------------------------------- @@ -155,15 +155,10 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) /* * initialize child expressions */ - scanstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) scanstate); - scanstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) scanstate); - scanstate->fdw_recheck_quals = (List *) - ExecInitExpr((Expr *) node->fdw_recheck_quals, - (PlanState *) scanstate); + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); + scanstate->fdw_recheck_quals = + ExecInitQual(node->fdw_recheck_quals, (PlanState *) scanstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index 972022784d..426527d2a2 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -35,7 +35,7 @@ */ typedef struct FunctionScanPerFuncState { - ExprState *funcexpr; /* state of the expression being evaluated */ + SetExprState *setexpr; /* state of the expression being evaluated */ TupleDesc tupdesc; /* desc of the function result type */ int colcount; /* expected number of result columns */ Tuplestorestate *tstore; /* holds the function result set */ @@ -92,7 +92,7 @@ FunctionNext(FunctionScanState *node) if (tstore == NULL) { node->funcstates[0].tstore = tstore = - ExecMakeTableFunctionResult(node->funcstates[0].funcexpr, + ExecMakeTableFunctionResult(node->funcstates[0].setexpr, node->ss.ps.ps_ExprContext, node->argcontext, node->funcstates[0].tupdesc, @@ -151,7 +151,7 @@ FunctionNext(FunctionScanState *node) if (fs->tstore == NULL) { fs->tstore = - ExecMakeTableFunctionResult(fs->funcexpr, + ExecMakeTableFunctionResult(fs->setexpr, node->ss.ps.ps_ExprContext, node->argcontext, fs->tupdesc, @@ -340,12 +340,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) /* * initialize child expressions */ - scanstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) scanstate); - scanstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) scanstate); + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); scanstate->funcstates = palloc(nfuncs * sizeof(FunctionScanPerFuncState)); @@ -361,7 +357,10 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags) Oid funcrettype; TupleDesc tupdesc; - fs->funcexpr = ExecInitExpr((Expr *) funcexpr, (PlanState *) scanstate); + fs->setexpr = + ExecInitTableFunctionResult((Expr *) funcexpr, + scanstate->ss.ps.ps_ExprContext, + &scanstate->ss.ps); /* * Don't allocate the tuplestores; the actual calls to the functions diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c index 32c97d390e..1e5b1b7675 100644 --- a/src/backend/executor/nodeGather.c +++ b/src/backend/executor/nodeGather.c @@ -81,12 +81,8 @@ ExecInitGather(Gather *node, EState *estate, int eflags) /* * initialize child expressions */ - gatherstate->ps.targetlist = (List *) - ExecInitExpr((Expr *) node->plan.targetlist, - (PlanState *) gatherstate); - gatherstate->ps.qual = (List *) - ExecInitExpr((Expr *) node->plan.qual, - (PlanState *) gatherstate); + gatherstate->ps.qual = + ExecInitQual(node->plan.qual, (PlanState *) gatherstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c index 72f30ab4e6..3f0c3ee4d1 100644 --- a/src/backend/executor/nodeGatherMerge.c +++ b/src/backend/executor/nodeGatherMerge.c @@ -86,12 +86,8 @@ ExecInitGatherMerge(GatherMerge *node, EState *estate, int eflags) /* * initialize child expressions */ - gm_state->ps.targetlist = (List *) - ExecInitExpr((Expr *) node->plan.targetlist, - (PlanState *) gm_state); - gm_state->ps.qual = (List *) - ExecInitExpr((Expr *) node->plan.qual, - (PlanState *) gm_state); + gm_state->ps.qual = + ExecInitQual(node->plan.qual, &gm_state->ps); /* * tuple table initialization diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c index 66c095bc72..af9ba4905e 100644 --- a/src/backend/executor/nodeGroup.c +++ b/src/backend/executor/nodeGroup.c @@ -85,7 +85,7 @@ ExecGroup(GroupState *node) * Check the qual (HAVING clause); if the group does not match, ignore * it and fall into scan loop. */ - if (ExecQual(node->ss.ps.qual, econtext, false)) + if (ExecQual(node->ss.ps.qual, econtext)) { /* * Form and return a projection tuple using the first input tuple. @@ -139,7 +139,7 @@ ExecGroup(GroupState *node) * Check the qual (HAVING clause); if the group does not match, ignore * it and loop back to scan the rest of the group. */ - if (ExecQual(node->ss.ps.qual, econtext, false)) + if (ExecQual(node->ss.ps.qual, econtext)) { /* * Form and return a projection tuple using the first input tuple. @@ -188,12 +188,8 @@ ExecInitGroup(Group *node, EState *estate, int eflags) /* * initialize child expressions */ - grpstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->plan.targetlist, - (PlanState *) grpstate); - grpstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->plan.qual, - (PlanState *) grpstate); + grpstate->ss.ps.qual = + ExecInitQual(node->plan.qual, (PlanState *) grpstate); /* * initialize child nodes diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index e695d8834b..cfc6b96093 100644 --- a/src/backend/executor/nodeHash.c +++ b/src/backend/executor/nodeHash.c @@ -190,12 +190,8 @@ ExecInitHash(Hash *node, EState *estate, int eflags) /* * initialize child expressions */ - hashstate->ps.targetlist = (List *) - ExecInitExpr((Expr *) node->plan.targetlist, - (PlanState *) hashstate); - hashstate->ps.qual = (List *) - ExecInitExpr((Expr *) node->plan.qual, - (PlanState *) hashstate); + hashstate->ps.qual = + ExecInitQual(node->plan.qual, (PlanState *) hashstate); /* * initialize child nodes @@ -1063,7 +1059,7 @@ bool ExecScanHashBucket(HashJoinState *hjstate, ExprContext *econtext) { - List *hjclauses = hjstate->hashclauses; + ExprState *hjclauses = hjstate->hashclauses; HashJoinTable hashtable = hjstate->hj_HashTable; HashJoinTuple hashTuple = hjstate->hj_CurTuple; uint32 hashvalue = hjstate->hj_CurHashValue; @@ -1097,7 +1093,7 @@ ExecScanHashBucket(HashJoinState *hjstate, /* reset temp memory each time to avoid leaks from qual expr */ ResetExprContext(econtext); - if (ExecQual(hjclauses, econtext, false)) + if (ExecQual(hjclauses, econtext)) { hjstate->hj_CurTuple = hashTuple; return true; diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index c50d93f43d..f2c885afbe 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -63,8 +63,8 @@ ExecHashJoin(HashJoinState *node) { PlanState *outerNode; HashState *hashNode; - List *joinqual; - List *otherqual; + ExprState *joinqual; + ExprState *otherqual; ExprContext *econtext; HashJoinTable hashtable; TupleTableSlot *outerTupleSlot; @@ -275,7 +275,7 @@ ExecHashJoin(HashJoinState *node) * Only the joinquals determine tuple match status, but all * quals must pass to actually return the tuple. */ - if (joinqual == NIL || ExecQual(joinqual, econtext, false)) + if (joinqual == NULL || ExecQual(joinqual, econtext)) { node->hj_MatchedOuter = true; HeapTupleHeaderSetMatch(HJTUPLE_MINTUPLE(node->hj_CurTuple)); @@ -294,8 +294,7 @@ ExecHashJoin(HashJoinState *node) if (node->js.jointype == JOIN_SEMI) node->hj_JoinState = HJ_NEED_NEW_OUTER; - if (otherqual == NIL || - ExecQual(otherqual, econtext, false)) + if (otherqual == NULL || ExecQual(otherqual, econtext)) return ExecProject(node->js.ps.ps_ProjInfo); else InstrCountFiltered2(node, 1); @@ -322,8 +321,7 @@ ExecHashJoin(HashJoinState *node) */ econtext->ecxt_innertuple = node->hj_NullInnerTupleSlot; - if (otherqual == NIL || - ExecQual(otherqual, econtext, false)) + if (otherqual == NULL || ExecQual(otherqual, econtext)) return ExecProject(node->js.ps.ps_ProjInfo); else InstrCountFiltered2(node, 1); @@ -350,8 +348,7 @@ ExecHashJoin(HashJoinState *node) */ econtext->ecxt_outertuple = node->hj_NullOuterTupleSlot; - if (otherqual == NIL || - ExecQual(otherqual, econtext, false)) + if (otherqual == NULL || ExecQual(otherqual, econtext)) return ExecProject(node->js.ps.ps_ProjInfo); else InstrCountFiltered2(node, 1); @@ -411,19 +408,13 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) /* * initialize child expressions */ - hjstate->js.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->join.plan.targetlist, - (PlanState *) hjstate); - hjstate->js.ps.qual = (List *) - ExecInitExpr((Expr *) node->join.plan.qual, - (PlanState *) hjstate); + hjstate->js.ps.qual = + ExecInitQual(node->join.plan.qual, (PlanState *) hjstate); hjstate->js.jointype = node->join.jointype; - hjstate->js.joinqual = (List *) - ExecInitExpr((Expr *) node->join.joinqual, - (PlanState *) hjstate); - hjstate->hashclauses = (List *) - ExecInitExpr((Expr *) node->hashclauses, - (PlanState *) hjstate); + hjstate->js.joinqual = + ExecInitQual(node->join.joinqual, (PlanState *) hjstate); + hjstate->hashclauses = + ExecInitQual(node->hashclauses, (PlanState *) hjstate); /* * initialize child nodes @@ -517,13 +508,14 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags) lclauses = NIL; rclauses = NIL; hoperators = NIL; - foreach(l, hjstate->hashclauses) + foreach(l, node->hashclauses) { - FuncExprState *fstate = castNode(FuncExprState, lfirst(l)); - OpExpr *hclause = castNode(OpExpr, fstate->xprstate.expr); + OpExpr *hclause = castNode(OpExpr, lfirst(l)); - lclauses = lappend(lclauses, linitial(fstate->args)); - rclauses = lappend(rclauses, lsecond(fstate->args)); + lclauses = lappend(lclauses, ExecInitExpr(linitial(hclause->args), + (PlanState *) hjstate)); + rclauses = lappend(rclauses, ExecInitExpr(lsecond(hclause->args), + (PlanState *) hjstate)); hoperators = lappend_oid(hoperators, hclause->opno); } hjstate->hj_OuterHashKeys = lclauses; diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c index db7f2e120e..5550f6c0a4 100644 --- a/src/backend/executor/nodeIndexonlyscan.c +++ b/src/backend/executor/nodeIndexonlyscan.c @@ -211,7 +211,7 @@ IndexOnlyNext(IndexOnlyScanState *node) { econtext->ecxt_scantuple = slot; ResetExprContext(econtext); - if (!ExecQual(node->indexqual, econtext, false)) + if (!ExecQual(node->indexqual, econtext)) { /* Fails recheck, so drop it and loop back for another */ InstrCountFiltered2(node, 1); @@ -488,15 +488,10 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags) * Note: we don't initialize all of the indexorderby expression, only the * sub-parts corresponding to runtime keys (see below). */ - indexstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) indexstate); - indexstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) indexstate); - indexstate->indexqual = (List *) - ExecInitExpr((Expr *) node->indexqual, - (PlanState *) indexstate); + indexstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate); + indexstate->indexqual = + ExecInitQual(node->indexqual, (PlanState *) indexstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c index cb6aff9137..5afd02e09d 100644 --- a/src/backend/executor/nodeIndexscan.c +++ b/src/backend/executor/nodeIndexscan.c @@ -149,7 +149,7 @@ IndexNext(IndexScanState *node) { econtext->ecxt_scantuple = slot; ResetExprContext(econtext); - if (!ExecQual(node->indexqualorig, econtext, false)) + if (!ExecQual(node->indexqualorig, econtext)) { /* Fails recheck, so drop it and loop back for another */ InstrCountFiltered2(node, 1); @@ -295,7 +295,7 @@ next_indextuple: { econtext->ecxt_scantuple = slot; ResetExprContext(econtext); - if (!ExecQual(node->indexqualorig, econtext, false)) + if (!ExecQual(node->indexqualorig, econtext)) { /* Fails recheck, so drop it and loop back for another */ InstrCountFiltered2(node, 1); @@ -415,7 +415,7 @@ IndexRecheck(IndexScanState *node, TupleTableSlot *slot) ResetExprContext(econtext); - return ExecQual(node->indexqualorig, econtext, false); + return ExecQual(node->indexqualorig, econtext); } @@ -921,18 +921,12 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags) * would be nice to improve that. (Problem is that any SubPlans present * in the expression must be found now...) */ - indexstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) indexstate); - indexstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) indexstate); - indexstate->indexqualorig = (List *) - ExecInitExpr((Expr *) node->indexqualorig, - (PlanState *) indexstate); - indexstate->indexorderbyorig = (List *) - ExecInitExpr((Expr *) node->indexorderbyorig, - (PlanState *) indexstate); + indexstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) indexstate); + indexstate->indexqualorig = + ExecInitQual(node->indexqualorig, (PlanState *) indexstate); + indexstate->indexorderbyorig = + ExecInitExprList(node->indexorderbyorig, (PlanState *) indexstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c index 105e2dcedb..62784af304 100644 --- a/src/backend/executor/nodeMergejoin.c +++ b/src/backend/executor/nodeMergejoin.c @@ -452,14 +452,14 @@ static TupleTableSlot * MJFillOuter(MergeJoinState *node) { ExprContext *econtext = node->js.ps.ps_ExprContext; - List *otherqual = node->js.ps.qual; + ExprState *otherqual = node->js.ps.qual; ResetExprContext(econtext); econtext->ecxt_outertuple = node->mj_OuterTupleSlot; econtext->ecxt_innertuple = node->mj_NullInnerTupleSlot; - if (ExecQual(otherqual, econtext, false)) + if (ExecQual(otherqual, econtext)) { /* * qualification succeeded. now form the desired projection tuple and @@ -483,14 +483,14 @@ static TupleTableSlot * MJFillInner(MergeJoinState *node) { ExprContext *econtext = node->js.ps.ps_ExprContext; - List *otherqual = node->js.ps.qual; + ExprState *otherqual = node->js.ps.qual; ResetExprContext(econtext); econtext->ecxt_outertuple = node->mj_NullOuterTupleSlot; econtext->ecxt_innertuple = node->mj_InnerTupleSlot; - if (ExecQual(otherqual, econtext, false)) + if (ExecQual(otherqual, econtext)) { /* * qualification succeeded. now form the desired projection tuple and @@ -598,8 +598,8 @@ ExecMergeTupleDump(MergeJoinState *mergestate) TupleTableSlot * ExecMergeJoin(MergeJoinState *node) { - List *joinqual; - List *otherqual; + ExprState *joinqual; + ExprState *otherqual; bool qualResult; int compareResult; PlanState *innerPlan; @@ -785,8 +785,8 @@ ExecMergeJoin(MergeJoinState *node) innerTupleSlot = node->mj_InnerTupleSlot; econtext->ecxt_innertuple = innerTupleSlot; - qualResult = (joinqual == NIL || - ExecQual(joinqual, econtext, false)); + qualResult = (joinqual == NULL || + ExecQual(joinqual, econtext)); MJ_DEBUG_QUAL(joinqual, qualResult); if (qualResult) @@ -808,8 +808,8 @@ ExecMergeJoin(MergeJoinState *node) if (node->js.jointype == JOIN_SEMI) node->mj_JoinState = EXEC_MJ_NEXTOUTER; - qualResult = (otherqual == NIL || - ExecQual(otherqual, econtext, false)); + qualResult = (otherqual == NULL || + ExecQual(otherqual, econtext)); MJ_DEBUG_QUAL(otherqual, qualResult); if (qualResult) @@ -1455,16 +1455,11 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags) /* * initialize child expressions */ - mergestate->js.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->join.plan.targetlist, - (PlanState *) mergestate); - mergestate->js.ps.qual = (List *) - ExecInitExpr((Expr *) node->join.plan.qual, - (PlanState *) mergestate); + mergestate->js.ps.qual = + ExecInitQual(node->join.plan.qual, (PlanState *) mergestate); mergestate->js.jointype = node->join.jointype; - mergestate->js.joinqual = (List *) - ExecInitExpr((Expr *) node->join.joinqual, - (PlanState *) mergestate); + mergestate->js.joinqual = + ExecInitQual(node->join.joinqual, (PlanState *) mergestate); mergestate->mj_ConstFalseJoin = false; /* mergeclauses are handled below */ diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 29c6a6e1d8..0b524e0b7c 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -1152,7 +1152,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, { ExprContext *econtext = mtstate->ps.ps_ExprContext; Relation relation = resultRelInfo->ri_RelationDesc; - List *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere; + ExprState *onConflictSetWhere = resultRelInfo->ri_onConflictSetWhere; HeapTupleData tuple; HeapUpdateFailureData hufd; LockTupleMode lockmode; @@ -1271,7 +1271,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, econtext->ecxt_innertuple = excludedSlot; econtext->ecxt_outertuple = NULL; - if (!ExecQual(onConflictSetWhere, econtext, false)) + if (!ExecQual(onConflictSetWhere, econtext)) { ReleaseBuffer(buffer); InstrCountFiltered1(&mtstate->ps, 1); @@ -1646,7 +1646,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate = makeNode(ModifyTableState); mtstate->ps.plan = (Plan *) node; mtstate->ps.state = estate; - mtstate->ps.targetlist = NIL; /* not actually used */ mtstate->operation = operation; mtstate->canSetTag = node->canSetTag; @@ -1778,7 +1777,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) foreach(ll, wcoList) { WithCheckOption *wco = (WithCheckOption *) lfirst(ll); - ExprState *wcoExpr = ExecInitExpr((Expr *) wco->qual, + ExprState *wcoExpr = ExecInitQual((List *) wco->qual, mtstate->mt_plans[i]); wcoExprs = lappend(wcoExprs, wcoExpr); @@ -1818,8 +1817,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) foreach(ll, mapped_wcoList) { WithCheckOption *wco = (WithCheckOption *) lfirst(ll); - ExprState *wcoExpr = ExecInitExpr((Expr *) wco->qual, - mtstate->mt_plans[i]); + ExprState *wcoExpr = ExecInitQual((List *) wco->qual, + mtstate->mt_plans[i]); wcoExprs = lappend(wcoExprs, wcoExpr); } @@ -1852,8 +1851,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) slot = mtstate->ps.ps_ResultTupleSlot; /* Need an econtext too */ - econtext = CreateExprContext(estate); - mtstate->ps.ps_ExprContext = econtext; + if (mtstate->ps.ps_ExprContext == NULL) + ExecAssignExprContext(estate, &mtstate->ps); + econtext = mtstate->ps.ps_ExprContext; /* * Build a projection for each result rel. @@ -1862,11 +1862,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) foreach(l, node->returningLists) { List *rlist = (List *) lfirst(l); - List *rliststate; - rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps); resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rliststate, econtext, slot, + ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps, resultRelInfo->ri_RelationDesc->rd_att); resultRelInfo++; } @@ -1883,16 +1881,14 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) for (i = 0; i < mtstate->mt_num_partitions; i++) { Relation partrel = resultRelInfo->ri_RelationDesc; - List *rlist, - *rliststate; + List *rlist; /* varno = node->nominalRelation */ rlist = map_partition_varattnos(returningList, node->nominalRelation, partrel, rel); - rliststate = (List *) ExecInitExpr((Expr *) rlist, &mtstate->ps); resultRelInfo->ri_projectReturning = - ExecBuildProjectionInfo(rliststate, econtext, slot, + ExecBuildProjectionInfo(rlist, econtext, slot, &mtstate->ps, resultRelInfo->ri_RelationDesc->rd_att); resultRelInfo++; } @@ -1922,7 +1918,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (node->onConflictAction == ONCONFLICT_UPDATE) { ExprContext *econtext; - ExprState *setexpr; TupleDesc tupDesc; /* insert may only have one plan, inheritance is not expanded */ @@ -1948,11 +1943,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_conflproj = ExecInitExtraTupleSlot(mtstate->ps.state); ExecSetSlotDescriptor(mtstate->mt_conflproj, tupDesc); - /* build UPDATE SET expression and projection state */ - setexpr = ExecInitExpr((Expr *) node->onConflictSet, &mtstate->ps); + /* build UPDATE SET projection state */ resultRelInfo->ri_onConflictSetProj = - ExecBuildProjectionInfo((List *) setexpr, econtext, - mtstate->mt_conflproj, + ExecBuildProjectionInfo(node->onConflictSet, econtext, + mtstate->mt_conflproj, &mtstate->ps, resultRelInfo->ri_RelationDesc->rd_att); /* build DO UPDATE WHERE clause expression */ @@ -1960,10 +1954,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) { ExprState *qualexpr; - qualexpr = ExecInitExpr((Expr *) node->onConflictWhere, + qualexpr = ExecInitQual((List *) node->onConflictWhere, &mtstate->ps); - resultRelInfo->ri_onConflictSetWhere = (List *) qualexpr; + resultRelInfo->ri_onConflictSetWhere = qualexpr; } } diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c index cac7ba1b9b..53977e0b32 100644 --- a/src/backend/executor/nodeNestloop.c +++ b/src/backend/executor/nodeNestloop.c @@ -64,8 +64,8 @@ ExecNestLoop(NestLoopState *node) PlanState *outerPlan; TupleTableSlot *outerTupleSlot; TupleTableSlot *innerTupleSlot; - List *joinqual; - List *otherqual; + ExprState *joinqual; + ExprState *otherqual; ExprContext *econtext; ListCell *lc; @@ -176,7 +176,7 @@ ExecNestLoop(NestLoopState *node) ENL1_printf("testing qualification for outer-join tuple"); - if (otherqual == NIL || ExecQual(otherqual, econtext, false)) + if (otherqual == NULL || ExecQual(otherqual, econtext)) { /* * qualification was satisfied so we project and return @@ -207,7 +207,7 @@ ExecNestLoop(NestLoopState *node) */ ENL1_printf("testing qualification"); - if (ExecQual(joinqual, econtext, false)) + if (ExecQual(joinqual, econtext)) { node->nl_MatchedOuter = true; @@ -225,7 +225,7 @@ ExecNestLoop(NestLoopState *node) if (node->js.jointype == JOIN_SEMI) node->nl_NeedNewOuter = true; - if (otherqual == NIL || ExecQual(otherqual, econtext, false)) + if (otherqual == NULL || ExecQual(otherqual, econtext)) { /* * qualification was satisfied so we project and return the @@ -282,16 +282,11 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags) /* * initialize child expressions */ - nlstate->js.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->join.plan.targetlist, - (PlanState *) nlstate); - nlstate->js.ps.qual = (List *) - ExecInitExpr((Expr *) node->join.plan.qual, - (PlanState *) nlstate); + nlstate->js.ps.qual = + ExecInitQual(node->join.plan.qual, (PlanState *) nlstate); nlstate->js.jointype = node->join.jointype; - nlstate->js.joinqual = (List *) - ExecInitExpr((Expr *) node->join.joinqual, - (PlanState *) nlstate); + nlstate->js.joinqual = + ExecInitQual(node->join.joinqual, (PlanState *) nlstate); /* * initialize child nodes diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c index eae0f1dad9..01048cc826 100644 --- a/src/backend/executor/nodeProjectSet.c +++ b/src/backend/executor/nodeProjectSet.c @@ -24,6 +24,7 @@ #include "executor/executor.h" #include "executor/nodeProjectSet.h" +#include "nodes/nodeFuncs.h" #include "utils/memutils.h" @@ -119,10 +120,9 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) { TupleTableSlot *resultSlot = node->ps.ps_ResultTupleSlot; ExprContext *econtext = node->ps.ps_ExprContext; - bool hassrf PG_USED_FOR_ASSERTS_ONLY = false; + bool hassrf PG_USED_FOR_ASSERTS_ONLY; bool hasresult; int argno; - ListCell *lc; ExecClearTuple(resultSlot); @@ -132,11 +132,10 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) */ node->pending_srf_tuples = false; - hasresult = false; - argno = 0; - foreach(lc, node->ps.targetlist) + hassrf = hasresult = false; + for (argno = 0; argno < node->nelems; argno++) { - GenericExprState *gstate = (GenericExprState *) lfirst(lc); + Node *elem = node->elems[argno]; ExprDoneCond *isdone = &node->elemdone[argno]; Datum *result = &resultSlot->tts_values[argno]; bool *isnull = &resultSlot->tts_isnull[argno]; @@ -151,13 +150,12 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) *isnull = true; hassrf = true; } - else if (IsA(gstate->arg, FuncExprState) && - ((FuncExprState *) gstate->arg)->funcReturnsSet) + else if (IsA(elem, SetExprState)) { /* * Evaluate SRF - possibly continuing previously started output. */ - *result = ExecMakeFunctionResultSet((FuncExprState *) gstate->arg, + *result = ExecMakeFunctionResultSet((SetExprState *) elem, econtext, isnull, isdone); if (*isdone != ExprEndResult) @@ -169,11 +167,9 @@ ExecProjectSRF(ProjectSetState *node, bool continuing) else { /* Non-SRF tlist expression, just evaluate normally. */ - *result = ExecEvalExpr(gstate->arg, econtext, isnull); + *result = ExecEvalExpr((ExprState *) elem, econtext, isnull); *isdone = ExprSingleResult; } - - argno++; } /* ProjectSet should not be used if there's no SRFs */ @@ -204,6 +200,8 @@ ProjectSetState * ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags) { ProjectSetState *state; + ListCell *lc; + int off; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_MARK | EXEC_FLAG_BACKWARD))); @@ -229,12 +227,7 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags) */ ExecInitResultTupleSlot(estate, &state->ps); - /* - * initialize child expressions - */ - state->ps.targetlist = (List *) - ExecInitExpr((Expr *) node->plan.targetlist, - (PlanState *) state); + /* We don't support any qual on ProjectSet nodes */ Assert(node->plan.qual == NIL); /* @@ -252,11 +245,41 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags) */ ExecAssignResultTypeFromTL(&state->ps); - /* Create workspace for per-SRF is-done state */ + /* Create workspace for per-tlist-entry expr state & SRF-is-done state */ state->nelems = list_length(node->plan.targetlist); + state->elems = (Node **) + palloc(sizeof(Node *) * state->nelems); state->elemdone = (ExprDoneCond *) palloc(sizeof(ExprDoneCond) * state->nelems); + /* + * Build expressions to evaluate targetlist. We can't use + * ExecBuildProjectionInfo here, since that doesn't deal with SRFs. + * Instead compile each expression separately, using + * ExecInitFunctionResultSet where applicable. + */ + off = 0; + foreach(lc, node->plan.targetlist) + { + TargetEntry *te = (TargetEntry *) lfirst(lc); + Expr *expr = te->expr; + + if ((IsA(expr, FuncExpr) &&((FuncExpr *) expr)->funcretset) || + (IsA(expr, OpExpr) &&((OpExpr *) expr)->opretset)) + { + state->elems[off] = (Node *) + ExecInitFunctionResultSet(expr, state->ps.ps_ExprContext, + &state->ps); + } + else + { + Assert(!expression_returns_set((Node *) expr)); + state->elems[off] = (Node *) ExecInitExpr(expr, &state->ps); + } + + off++; + } + return state; } diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c index b5b50b21e9..a753a53419 100644 --- a/src/backend/executor/nodeResult.c +++ b/src/backend/executor/nodeResult.c @@ -77,9 +77,7 @@ ExecResult(ResultState *node) */ if (node->rs_checkqual) { - bool qualResult = ExecQual((List *) node->resconstantqual, - econtext, - false); + bool qualResult = ExecQual(node->resconstantqual, econtext); node->rs_checkqual = false; if (!qualResult) @@ -209,14 +207,10 @@ ExecInitResult(Result *node, EState *estate, int eflags) /* * initialize child expressions */ - resstate->ps.targetlist = (List *) - ExecInitExpr((Expr *) node->plan.targetlist, - (PlanState *) resstate); - resstate->ps.qual = (List *) - ExecInitExpr((Expr *) node->plan.qual, - (PlanState *) resstate); - resstate->resconstantqual = ExecInitExpr((Expr *) node->resconstantqual, - (PlanState *) resstate); + resstate->ps.qual = + ExecInitQual(node->plan.qual, (PlanState *) resstate); + resstate->resconstantqual = + ExecInitQual((List *) node->resconstantqual, (PlanState *) resstate); /* * initialize child nodes diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c index d38265e810..0247bd2347 100644 --- a/src/backend/executor/nodeSamplescan.c +++ b/src/backend/executor/nodeSamplescan.c @@ -164,19 +164,12 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags) /* * initialize child expressions */ - scanstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) scanstate); - scanstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) scanstate); - - scanstate->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..b3a025879a 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -39,12 +39,6 @@ #include "utils/memutils.h" -static Datum ExecSubPlan(SubPlanState *node, - ExprContext *econtext, - bool *isNull); -static Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node, - ExprContext *econtext, - bool *isNull); static Datum ExecHashSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull); @@ -64,12 +58,12 @@ static bool slotNoNulls(TupleTableSlot *slot); * This is the main entry point for execution of a regular SubPlan. * ---------------------------------------------------------------- */ -static Datum +Datum ExecSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull) { - SubPlan *subplan = (SubPlan *) node->xprstate.expr; + SubPlan *subplan = node->subplan; /* Set non-null as default */ *isNull = false; @@ -95,7 +89,7 @@ ExecHashSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull) { - SubPlan *subplan = (SubPlan *) node->xprstate.expr; + SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; TupleTableSlot *slot; @@ -217,7 +211,7 @@ ExecScanSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull) { - SubPlan *subplan = (SubPlan *) node->xprstate.expr; + SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; SubLinkType subLinkType = subplan->subLinkType; MemoryContext oldcontext; @@ -462,7 +456,7 @@ ExecScanSubPlan(SubPlanState *node, static void buildSubPlanHash(SubPlanState *node, ExprContext *econtext) { - SubPlan *subplan = (SubPlan *) node->xprstate.expr; + SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; int ncols = list_length(subplan->paramIds); ExprContext *innerecontext = node->innerecontext; @@ -596,7 +590,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) * potential for a double free attempt. (XXX possibly no longer needed, * but can't hurt.) */ - ExecClearTuple(node->projRight->pi_slot); + ExecClearTuple(node->projRight->pi_state.resultslot); MemoryContextSwitchTo(oldcontext); } @@ -694,8 +688,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) SubPlanState *sstate = makeNode(SubPlanState); EState *estate = parent->state; - sstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecSubPlan; - sstate->xprstate.expr = (Expr *) subplan; + sstate->subplan = subplan; /* Link the SubPlanState to already-initialized subplan */ sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates, @@ -706,7 +699,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) /* Initialize subexpressions */ sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent); - sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent); + sstate->args = ExecInitExprList(subplan->args, parent); /* * initialize my state @@ -763,9 +756,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) TupleTableSlot *slot; List *oplist, *lefttlist, - *righttlist, - *leftptlist, - *rightptlist; + *righttlist; ListCell *l; /* We need a memory context to hold the hash table(s) */ @@ -792,35 +783,33 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) * use the sub-select's output tuples directly, but that is not the * case if we had to insert any run-time coercions of the sub-select's * output datatypes; anyway this avoids storing any resjunk columns - * that might be in the sub-select's output.) Run through the + * that might be in the sub-select's output.) Run through the * combining expressions to build tlists for the lefthand and - * righthand sides. We need both the ExprState list (for ExecProject) - * and the underlying parse Exprs (for ExecTypeFromTL). + * righthand sides. * * We also extract the combining operators themselves to initialize * the equality and hashing functions for the hash tables. */ - if (IsA(sstate->testexpr->expr, OpExpr)) + if (IsA(subplan->testexpr, OpExpr)) { /* single combining operator */ - oplist = list_make1(sstate->testexpr); + oplist = list_make1(subplan->testexpr); } - else if (and_clause((Node *) sstate->testexpr->expr)) + else if (and_clause((Node *) subplan->testexpr)) { /* multiple combining operators */ - oplist = castNode(BoolExprState, sstate->testexpr)->args; + oplist = castNode(BoolExpr, subplan->testexpr)->args; } else { /* shouldn't see anything else in a hashable subplan */ elog(ERROR, "unrecognized testexpr type: %d", - (int) nodeTag(sstate->testexpr->expr)); + (int) nodeTag(subplan->testexpr)); oplist = NIL; /* keep compiler quiet */ } Assert(list_length(oplist) == ncols); lefttlist = righttlist = NIL; - leftptlist = rightptlist = NIL; sstate->tab_hash_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo)); sstate->tab_eq_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo)); sstate->lhs_hash_funcs = (FmgrInfo *) palloc(ncols * sizeof(FmgrInfo)); @@ -828,45 +817,30 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) i = 1; foreach(l, oplist) { - FuncExprState *fstate = castNode(FuncExprState, lfirst(l)); - OpExpr *opexpr = castNode(OpExpr, fstate->xprstate.expr); - ExprState *exstate; + OpExpr *opexpr = castNode(OpExpr, lfirst(l)); Expr *expr; TargetEntry *tle; - GenericExprState *tlestate; Oid rhs_eq_oper; Oid left_hashfn; Oid right_hashfn; - Assert(list_length(fstate->args) == 2); + Assert(list_length(opexpr->args) == 2); /* Process lefthand argument */ - exstate = (ExprState *) linitial(fstate->args); - expr = exstate->expr; + expr = (Expr *) linitial(opexpr->args); tle = makeTargetEntry(expr, i, NULL, false); - tlestate = makeNode(GenericExprState); - tlestate->xprstate.expr = (Expr *) tle; - tlestate->xprstate.evalfunc = NULL; - tlestate->arg = exstate; - lefttlist = lappend(lefttlist, tlestate); - leftptlist = lappend(leftptlist, tle); + lefttlist = lappend(lefttlist, tle); /* Process righthand argument */ - exstate = (ExprState *) lsecond(fstate->args); - expr = exstate->expr; + expr = (Expr *) lsecond(opexpr->args); tle = makeTargetEntry(expr, i, NULL, false); - tlestate = makeNode(GenericExprState); - tlestate->xprstate.expr = (Expr *) tle; - tlestate->xprstate.evalfunc = NULL; - tlestate->arg = exstate; - righttlist = lappend(righttlist, tlestate); - rightptlist = lappend(rightptlist, tle); + righttlist = lappend(righttlist, tle); /* Lookup the equality function (potentially cross-type) */ fmgr_info(opexpr->opfuncid, &sstate->cur_eq_funcs[i - 1]); @@ -898,20 +872,22 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) * (hack alert!). The righthand expressions will be evaluated in our * own innerecontext. */ - tupDesc = ExecTypeFromTL(leftptlist, false); + tupDesc = ExecTypeFromTL(lefttlist, false); slot = ExecInitExtraTupleSlot(estate); ExecSetSlotDescriptor(slot, tupDesc); sstate->projLeft = ExecBuildProjectionInfo(lefttlist, NULL, slot, + parent, NULL); - tupDesc = ExecTypeFromTL(rightptlist, false); + tupDesc = ExecTypeFromTL(righttlist, false); slot = ExecInitExtraTupleSlot(estate); ExecSetSlotDescriptor(slot, tupDesc); sstate->projRight = ExecBuildProjectionInfo(righttlist, sstate->innerecontext, slot, + sstate->planstate, NULL); } @@ -934,7 +910,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) { - SubPlan *subplan = (SubPlan *) node->xprstate.expr; + SubPlan *subplan = node->subplan; PlanState *planstate = node->planstate; SubLinkType subLinkType = subplan->subLinkType; MemoryContext oldcontext; @@ -1111,7 +1087,7 @@ void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent) { PlanState *planstate = node->planstate; - SubPlan *subplan = (SubPlan *) node->xprstate.expr; + SubPlan *subplan = node->subplan; EState *estate = parent->state; ListCell *l; @@ -1162,16 +1138,22 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent) SubPlan *subplan2; Cost cost1; Cost cost2; + ListCell *lc; - asstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecAlternativeSubPlan; - asstate->xprstate.expr = (Expr *) asplan; + asstate->subplan = asplan; /* * Initialize subplans. (Can we get away with only initializing the one * we're going to use?) */ - asstate->subplans = (List *) ExecInitExpr((Expr *) asplan->subplans, - parent); + foreach(lc, asplan->subplans) + { + SubPlan *sp = castNode(SubPlan, lfirst(lc)); + SubPlanState *sps = ExecInitSubPlan(sp, parent); + + asstate->subplans = lappend(asstate->subplans, sps); + parent->subPlan = lappend(parent->subPlan, sps); + } /* * Select the one to be used. For this, we need an estimate of the number @@ -1209,7 +1191,7 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent) * Note: in future we might consider changing to different subplans on the * fly, in case the original rowcount estimate turns out to be way off. */ -static Datum +Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node, ExprContext *econtext, bool *isNull) diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c index 230a96f9d2..ae184700a6 100644 --- a/src/backend/executor/nodeSubqueryscan.c +++ b/src/backend/executor/nodeSubqueryscan.c @@ -120,12 +120,8 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags) /* * initialize child expressions */ - subquerystate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) subquerystate); - subquerystate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) subquerystate); + subquerystate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) subquerystate); /* * tuple table initialization diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c index 628f1ba074..e9df48044e 100644 --- a/src/backend/executor/nodeTableFuncscan.c +++ b/src/backend/executor/nodeTableFuncscan.c @@ -139,12 +139,8 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) /* * initialize child expressions */ - scanstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) scanstate); - scanstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) scanstate); + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, &scanstate->ss.ps); /* * tuple table initialization @@ -179,16 +175,16 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags) scanstate->ns_names = tf->ns_names; - scanstate->ns_uris = (List *) - ExecInitExpr((Expr *) tf->ns_uris, (PlanState *) scanstate); + scanstate->ns_uris = + ExecInitExprList(tf->ns_uris, (PlanState *) scanstate); scanstate->docexpr = ExecInitExpr((Expr *) tf->docexpr, (PlanState *) scanstate); scanstate->rowexpr = ExecInitExpr((Expr *) tf->rowexpr, (PlanState *) scanstate); - scanstate->colexprs = (List *) - ExecInitExpr((Expr *) tf->colexprs, (PlanState *) scanstate); - scanstate->coldefexprs = (List *) - ExecInitExpr((Expr *) tf->coldefexprs, (PlanState *) scanstate); + scanstate->colexprs = + ExecInitExprList(tf->colexprs, (PlanState *) scanstate); + scanstate->coldefexprs = + ExecInitExprList(tf->coldefexprs, (PlanState *) scanstate); scanstate->notnulls = tf->notnulls; diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c index 13ed886577..4860ec0f4d 100644 --- a/src/backend/executor/nodeTidscan.c +++ b/src/backend/executor/nodeTidscan.c @@ -38,11 +38,85 @@ ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \ ((Var *) (node))->varlevelsup == 0) -static void TidListCreate(TidScanState *tidstate); +/* one element in tss_tidexprs */ +typedef struct TidExpr +{ + ExprState *exprstate; /* ExprState for a TID-yielding subexpr */ + bool isarray; /* if true, it yields tid[] not just tid */ + CurrentOfExpr *cexpr; /* alternatively, we can have CURRENT OF */ +} TidExpr; + +static void TidExprListCreate(TidScanState *tidstate); +static void TidListEval(TidScanState *tidstate); static int itemptr_comparator(const void *a, const void *b); static TupleTableSlot *TidNext(TidScanState *node); +/* + * Extract the qual subexpressions that yield TIDs to search for, + * and compile them into ExprStates if they're ordinary expressions. + * + * CURRENT OF is a special case that we can't compile usefully; + * just drop it into the TidExpr list as-is. + */ +static void +TidExprListCreate(TidScanState *tidstate) +{ + TidScan *node = (TidScan *) tidstate->ss.ps.plan; + ListCell *l; + + tidstate->tss_tidexprs = NIL; + tidstate->tss_isCurrentOf = false; + + foreach(l, node->tidquals) + { + Expr *expr = (Expr *) lfirst(l); + TidExpr *tidexpr = (TidExpr *) palloc0(sizeof(TidExpr)); + + if (is_opclause(expr)) + { + Node *arg1; + Node *arg2; + + arg1 = get_leftop(expr); + arg2 = get_rightop(expr); + if (IsCTIDVar(arg1)) + tidexpr->exprstate = ExecInitExpr((Expr *) arg2, + &tidstate->ss.ps); + else if (IsCTIDVar(arg2)) + tidexpr->exprstate = ExecInitExpr((Expr *) arg1, + &tidstate->ss.ps); + else + elog(ERROR, "could not identify CTID variable"); + tidexpr->isarray = false; + } + else if (expr && IsA(expr, ScalarArrayOpExpr)) + { + ScalarArrayOpExpr *saex = (ScalarArrayOpExpr *) expr; + + Assert(IsCTIDVar(linitial(saex->args))); + tidexpr->exprstate = ExecInitExpr(lsecond(saex->args), + &tidstate->ss.ps); + tidexpr->isarray = true; + } + else if (expr && IsA(expr, CurrentOfExpr)) + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) expr; + + tidexpr->cexpr = cexpr; + tidstate->tss_isCurrentOf = true; + } + else + elog(ERROR, "could not identify CTID expression"); + + tidstate->tss_tidexprs = lappend(tidstate->tss_tidexprs, tidexpr); + } + + /* CurrentOfExpr could never appear OR'd with something else */ + Assert(list_length(tidstate->tss_tidexprs) == 1 || + !tidstate->tss_isCurrentOf); +} + /* * Compute the list of TIDs to be visited, by evaluating the expressions * for them. @@ -50,9 +124,8 @@ static TupleTableSlot *TidNext(TidScanState *node); * (The result is actually an array, not a list.) */ static void -TidListCreate(TidScanState *tidstate) +TidListEval(TidScanState *tidstate) { - List *evalList = tidstate->tss_tidquals; ExprContext *econtext = tidstate->ss.ps.ps_ExprContext; BlockNumber nblocks; ItemPointerData *tidList; @@ -73,36 +146,21 @@ TidListCreate(TidScanState *tidstate) * are simple OpExprs or CurrentOfExprs. If there are any * ScalarArrayOpExprs, we may have to enlarge the array. */ - numAllocTids = list_length(evalList); + numAllocTids = list_length(tidstate->tss_tidexprs); tidList = (ItemPointerData *) palloc(numAllocTids * sizeof(ItemPointerData)); numTids = 0; - tidstate->tss_isCurrentOf = false; - foreach(l, evalList) + foreach(l, tidstate->tss_tidexprs) { - ExprState *exstate = (ExprState *) lfirst(l); - Expr *expr = exstate->expr; + TidExpr *tidexpr = (TidExpr *) lfirst(l); ItemPointer itemptr; bool isNull; - if (is_opclause(expr)) + if (tidexpr->exprstate && !tidexpr->isarray) { - FuncExprState *fexstate = (FuncExprState *) exstate; - Node *arg1; - Node *arg2; - - arg1 = get_leftop(expr); - arg2 = get_rightop(expr); - if (IsCTIDVar(arg1)) - exstate = (ExprState *) lsecond(fexstate->args); - else if (IsCTIDVar(arg2)) - exstate = (ExprState *) linitial(fexstate->args); - else - elog(ERROR, "could not identify CTID variable"); - itemptr = (ItemPointer) - DatumGetPointer(ExecEvalExprSwitchContext(exstate, + DatumGetPointer(ExecEvalExprSwitchContext(tidexpr->exprstate, econtext, &isNull)); if (!isNull && @@ -119,9 +177,8 @@ TidListCreate(TidScanState *tidstate) tidList[numTids++] = *itemptr; } } - else if (expr && IsA(expr, ScalarArrayOpExpr)) + else if (tidexpr->exprstate && tidexpr->isarray) { - ScalarArrayOpExprState *saexstate = (ScalarArrayOpExprState *) exstate; Datum arraydatum; ArrayType *itemarray; Datum *ipdatums; @@ -129,8 +186,7 @@ TidListCreate(TidScanState *tidstate) int ndatums; int i; - exstate = (ExprState *) lsecond(saexstate->fxprstate.args); - arraydatum = ExecEvalExprSwitchContext(exstate, + arraydatum = ExecEvalExprSwitchContext(tidexpr->exprstate, econtext, &isNull); if (isNull) @@ -159,12 +215,12 @@ TidListCreate(TidScanState *tidstate) pfree(ipdatums); pfree(ipnulls); } - else if (expr && IsA(expr, CurrentOfExpr)) + else { - CurrentOfExpr *cexpr = (CurrentOfExpr *) expr; ItemPointerData cursor_tid; - if (execCurrentOf(cexpr, econtext, + Assert(tidexpr->cexpr); + if (execCurrentOf(tidexpr->cexpr, econtext, RelationGetRelid(tidstate->ss.ss_currentRelation), &cursor_tid)) { @@ -176,11 +232,8 @@ TidListCreate(TidScanState *tidstate) numAllocTids * sizeof(ItemPointerData)); } tidList[numTids++] = cursor_tid; - tidstate->tss_isCurrentOf = true; } } - else - elog(ERROR, "could not identify CTID expression"); } /* @@ -272,11 +325,15 @@ TidNext(TidScanState *node) * First time through, compute the list of TIDs to be visited */ if (node->tss_TidList == NULL) - TidListCreate(node); + TidListEval(node); tidList = node->tss_TidList; numTids = node->tss_NumTids; + /* + * We use node->tss_htup as the tuple pointer; note this can't just be a + * local variable here, as the scan tuple slot will keep a pointer to it. + */ tuple = &(node->tss_htup); /* @@ -470,16 +527,10 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags) /* * initialize child expressions */ - tidstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) tidstate); - tidstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) tidstate); - - tidstate->tss_tidquals = (List *) - ExecInitExpr((Expr *) node->tidquals, - (PlanState *) tidstate); + tidstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) tidstate); + + TidExprListCreate(tidstate); /* * tuple table initialization diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c index 9883a8b130..9ee776c4c3 100644 --- a/src/backend/executor/nodeValuesscan.c +++ b/src/backend/executor/nodeValuesscan.c @@ -120,7 +120,7 @@ ValuesNext(ValuesScanState *node) * is a SubPlan, and there shouldn't be any (any subselects in the * VALUES list should be InitPlans). */ - exprstatelist = (List *) ExecInitExpr((Expr *) exprlist, NULL); + exprstatelist = ExecInitExprList(exprlist, NULL); /* parser should have checked all sublists are the same length */ Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts); @@ -242,12 +242,8 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags) /* * initialize child expressions */ - scanstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) scanstate); - scanstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) scanstate); + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); /* * get info about values list diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 2a123e8452..628bc9f00b 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -1826,16 +1826,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate); winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate); - winstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->plan.targetlist, - (PlanState *) winstate); - /* * WindowAgg nodes never have quals, since they can only occur at the * logical top level of a query (ie, after any WHERE or HAVING filters) */ Assert(node->plan.qual == NIL); - winstate->ss.ps.qual = NIL; + winstate->ss.ps.qual = NULL; /* * initialize child nodes @@ -1894,7 +1890,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) foreach(l, winstate->funcs) { WindowFuncExprState *wfuncstate = (WindowFuncExprState *) lfirst(l); - WindowFunc *wfunc = (WindowFunc *) wfuncstate->xprstate.expr; + WindowFunc *wfunc = wfuncstate->wfunc; WindowStatePerFunc perfuncstate; AclResult aclresult; int i; diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c index 23b5b94985..d7616be065 100644 --- a/src/backend/executor/nodeWorktablescan.c +++ b/src/backend/executor/nodeWorktablescan.c @@ -156,12 +156,8 @@ ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags) /* * initialize child expressions */ - scanstate->ss.ps.targetlist = (List *) - ExecInitExpr((Expr *) node->scan.plan.targetlist, - (PlanState *) scanstate); - scanstate->ss.ps.qual = (List *) - ExecInitExpr((Expr *) node->scan.plan.qual, - (PlanState *) scanstate); + scanstate->ss.ps.qual = + ExecInitQual(node->scan.plan.qual, (PlanState *) scanstate); /* * tuple table initialization diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index a129d1ecb3..57229059bd 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -3501,7 +3501,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) /* * Aggref and WindowFunc nodes are (and should be) treated like Vars, * ie, zero execution cost in the current model, because they behave - * essentially like Vars in execQual.c. We disregard the costs of + * essentially like Vars at execution. We disregard the costs of * their input expressions for the same reason. The actual execution * costs of the aggregate/window functions and their arguments have to * be factored into plan-node-specific costing of the Agg or WindowAgg diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 68d74cb432..90619509a2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -5013,7 +5013,7 @@ make_pathkeys_for_window(PlannerInfo *root, WindowClause *wc, * bloat the sort dataset, and because it might cause unexpected output order * if the sort isn't stable. However there's a constraint on that: all SRFs * in the tlist should be evaluated at the same plan step, so that they can - * run in sync in ExecTargetList. So if any SRFs are in sort columns, we + * run in sync in nodeProjectSet. So if any SRFs are in sort columns, we * mustn't postpone any SRFs. (Note that in principle that policy should * probably get applied to the group/window input targetlists too, but we * have not done that historically.) Lastly, expensive expressions are diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index b19380e1b1..42bba543e9 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -3395,7 +3395,7 @@ eval_const_expressions_mutator(Node *node, * Else, make a scalar (argisrow == false) NullTest * for this field. Scalar semantics are required * because IS [NOT] NULL doesn't recurse; see comments - * in ExecEvalNullTest(). + * in ExecEvalRowNullInt(). */ newntest = makeNode(NullTest); newntest->arg = (Expr *) relem; @@ -3539,8 +3539,8 @@ eval_const_expressions_mutator(Node *node, * FALSE: drop (does not affect result) * TRUE: force result to TRUE * NULL: keep only one - * We must keep one NULL input because ExecEvalOr returns NULL when no input - * is TRUE and at least one is NULL. We don't actually include the NULL + * We must keep one NULL input because OR expressions evaluate to NULL when no + * input is TRUE and at least one is NULL. We don't actually include the NULL * here, that's supposed to be done by the caller. * * The output arguments *haveNull and *forceTrue must be initialized FALSE @@ -3651,9 +3651,9 @@ simplify_or_arguments(List *args, * TRUE: drop (does not affect result) * FALSE: force result to FALSE * NULL: keep only one - * We must keep one NULL input because ExecEvalAnd returns NULL when no input - * is FALSE and at least one is NULL. We don't actually include the NULL - * here, that's supposed to be done by the caller. + * We must keep one NULL input because AND expressions evaluate to NULL when + * no input is FALSE and at least one is NULL. We don't actually include the + * NULL here, that's supposed to be done by the caller. * * The output arguments *haveNull and *forceFalse must be initialized FALSE * by the caller. They will be set TRUE if a null constant or false constant, diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c index c2ad440013..73deaa7e1c 100644 --- a/src/backend/utils/adt/domains.c +++ b/src/backend/utils/adt/domains.c @@ -107,7 +107,7 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) fmgr_info_cxt(my_extra->typiofunc, &my_extra->proc, mcxt); /* Look up constraints for domain */ - InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt); + InitDomainConstraintRef(domainType, &my_extra->constraint_ref, mcxt, true); /* We don't make an ExprContext until needed */ my_extra->econtext = NULL; @@ -122,7 +122,9 @@ domain_state_setup(Oid domainType, bool binary, MemoryContext mcxt) /* * domain_check_input - apply the cached checks. * - * This is extremely similar to ExecEvalCoerceToDomain in execQual.c. + * This is roughly similar to the handling of CoerceToDomain nodes in + * execExpr*.c, but we execute each constraint separately, rather than + * compiling them in-line within a larger expression. */ static void domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) @@ -149,9 +151,6 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) break; case DOM_CONSTRAINT_CHECK: { - Datum conResult; - bool conIsNull; - /* Make the econtext if we didn't already */ if (econtext == NULL) { @@ -165,24 +164,20 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) /* * Set up value to be returned by CoerceToDomainValue - * nodes. Unlike ExecEvalCoerceToDomain, this econtext - * couldn't be shared with anything else, so no need to - * save and restore fields. But we do need to protect the - * passed-in value against being changed by called - * functions. (It couldn't be a R/W expanded object for - * most uses, but that seems possible for domain_check().) + * nodes. Unlike in the generic expression case, this + * econtext couldn't be shared with anything else, so no + * need to save and restore fields. But we do need to + * protect the passed-in value against being changed by + * called functions. (It couldn't be a R/W expanded + * object for most uses, but that seems possible for + * domain_check().) */ econtext->domainValue_datum = MakeExpandedObjectReadOnly(value, isnull, my_extra->constraint_ref.tcache->typlen); econtext->domainValue_isNull = isnull; - conResult = ExecEvalExprSwitchContext(con->check_expr, - econtext, - &conIsNull); - - if (!conIsNull && - !DatumGetBool(conResult)) + if (!ExecCheck(con->check_exprstate, econtext)) ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("value for domain %s violates check constraint \"%s\"", diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 81c91039e4..d57d5568b2 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -7000,7 +7000,7 @@ find_param_referent(Param *param, deparse_context *context, foreach(lc2, ps->subPlan) { SubPlanState *sstate = (SubPlanState *) lfirst(lc2); - SubPlan *subplan = (SubPlan *) sstate->xprstate.expr; + SubPlan *subplan = sstate->subplan; ListCell *lc3; ListCell *lc4; @@ -7041,7 +7041,7 @@ find_param_referent(Param *param, deparse_context *context, continue; /* No parameters to be had here. */ - Assert(((SubPlan *) sstate->xprstate.expr)->parParam == NIL); + Assert(sstate->subplan->parParam == NIL); /* Keep looking, but we are emerging from an initplan. */ in_same_plan_level = false; diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 1908b13db5..2f87151bec 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -72,7 +72,6 @@ #include "catalog/pg_class.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" -#include "executor/executor.h" #include "executor/spi.h" #include "executor/tablefunc.h" #include "fmgr.h" @@ -620,10 +619,11 @@ xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg) xmltype * -xmlelement(XmlExprState *xmlExpr, ExprContext *econtext) +xmlelement(XmlExpr *xexpr, + Datum *named_argvalue, bool *named_argnull, + Datum *argvalue, bool *argnull) { #ifdef USE_LIBXML - XmlExpr *xexpr = (XmlExpr *) xmlExpr->xprstate.expr; xmltype *result; List *named_arg_strings; List *arg_strings; @@ -635,48 +635,47 @@ xmlelement(XmlExprState *xmlExpr, ExprContext *econtext) volatile xmlTextWriterPtr writer = NULL; /* - * We first evaluate all the arguments, then start up libxml and create - * the result. This avoids issues if one of the arguments involves a call - * to some other function or subsystem that wants to use libxml on its own - * terms. + * All arguments are already evaluated, and their values are passed in the + * named_argvalue/named_argnull or argvalue/argnull arrays. This avoids + * issues if one of the arguments involves a call to some other function + * or subsystem that wants to use libxml on its own terms. We examine the + * original XmlExpr to identify the numbers and types of the arguments. */ named_arg_strings = NIL; i = 0; - foreach(arg, xmlExpr->named_args) + foreach(arg, xexpr->named_args) { - ExprState *e = (ExprState *) lfirst(arg); - Datum value; - bool isnull; + Expr *e = (Expr *) lfirst(arg); char *str; - value = ExecEvalExpr(e, econtext, &isnull); - if (isnull) + if (named_argnull[i]) str = NULL; else - str = map_sql_value_to_xml_value(value, exprType((Node *) e->expr), false); + str = map_sql_value_to_xml_value(named_argvalue[i], + exprType((Node *) e), + false); named_arg_strings = lappend(named_arg_strings, str); i++; } arg_strings = NIL; - foreach(arg, xmlExpr->args) + i = 0; + foreach(arg, xexpr->args) { - ExprState *e = (ExprState *) lfirst(arg); - Datum value; - bool isnull; + Expr *e = (Expr *) lfirst(arg); char *str; - value = ExecEvalExpr(e, econtext, &isnull); /* here we can just forget NULL elements immediately */ - if (!isnull) + if (!argnull[i]) { - str = map_sql_value_to_xml_value(value, - exprType((Node *) e->expr), true); + str = map_sql_value_to_xml_value(argvalue[i], + exprType((Node *) e), + true); arg_strings = lappend(arg_strings, str); } + i++; } - /* now safe to run libxml */ xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL); PG_TRY(); diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 6992634c39..0cf5001a75 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -96,11 +96,11 @@ static TypeCacheEntry *firstDomainTypeEntry = NULL; * this struct for the common case of a constraint-less domain; we just set * domainData to NULL to indicate that. * - * Within a DomainConstraintCache, we abuse the DomainConstraintState node - * type a bit: check_expr fields point to expression plan trees, not plan - * state trees. When needed, expression state trees are built by flat-copying - * the DomainConstraintState nodes and applying ExecInitExpr to check_expr. - * Such a state tree is not part of the DomainConstraintCache, but is + * Within a DomainConstraintCache, we store expression plan trees, but the + * check_exprstate fields of the DomainConstraintState nodes are just NULL. + * When needed, expression evaluation nodes are built by flat-copying the + * DomainConstraintState nodes and applying ExecInitExpr to check_expr. + * Such a node tree is not part of the DomainConstraintCache, but is * considered to belong to a DomainConstraintRef. */ struct DomainConstraintCache @@ -779,8 +779,8 @@ load_domaintype_info(TypeCacheEntry *typentry) r = makeNode(DomainConstraintState); r->constrainttype = DOM_CONSTRAINT_CHECK; r->name = pstrdup(NameStr(c->conname)); - /* Must cast here because we're not storing an expr state node */ - r->check_expr = (ExprState *) check_expr; + r->check_expr = check_expr; + r->check_exprstate = NULL; MemoryContextSwitchTo(oldcxt); @@ -859,6 +859,7 @@ load_domaintype_info(TypeCacheEntry *typentry) r->constrainttype = DOM_CONSTRAINT_NOTNULL; r->name = pstrdup("NOT NULL"); r->check_expr = NULL; + r->check_exprstate = NULL; /* lcons to apply the nullness check FIRST */ dcc->constraints = lcons(r, dcc->constraints); @@ -946,8 +947,8 @@ prep_domain_constraints(List *constraints, MemoryContext execctx) newr = makeNode(DomainConstraintState); newr->constrainttype = r->constrainttype; newr->name = r->name; - /* Must cast here because cache items contain expr plan trees */ - newr->check_expr = ExecInitExpr((Expr *) r->check_expr, NULL); + newr->check_expr = r->check_expr; + newr->check_exprstate = ExecInitExpr(r->check_expr, NULL); result = lappend(result, newr); } @@ -962,13 +963,18 @@ prep_domain_constraints(List *constraints, MemoryContext execctx) * * Caller must tell us the MemoryContext in which the DomainConstraintRef * lives. The ref will be cleaned up when that context is reset/deleted. + * + * Caller must also tell us whether it wants check_exprstate fields to be + * computed in the DomainConstraintState nodes attached to this ref. + * If it doesn't, we need not make a copy of the DomainConstraintState list. */ void InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref, - MemoryContext refctx) + MemoryContext refctx, bool need_exprstate) { /* Look up the typcache entry --- we assume it survives indefinitely */ ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO); + ref->need_exprstate = need_exprstate; /* For safety, establish the callback before acquiring a refcount */ ref->refctx = refctx; ref->dcc = NULL; @@ -980,8 +986,11 @@ InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref, { ref->dcc = ref->tcache->domainData; ref->dcc->dccRefCount++; - ref->constraints = prep_domain_constraints(ref->dcc->constraints, - ref->refctx); + if (ref->need_exprstate) + ref->constraints = prep_domain_constraints(ref->dcc->constraints, + ref->refctx); + else + ref->constraints = ref->dcc->constraints; } else ref->constraints = NIL; @@ -1032,8 +1041,11 @@ UpdateDomainConstraintRef(DomainConstraintRef *ref) { ref->dcc = dcc; dcc->dccRefCount++; - ref->constraints = prep_domain_constraints(dcc->constraints, - ref->refctx); + if (ref->need_exprstate) + ref->constraints = prep_domain_constraints(dcc->constraints, + ref->refctx); + else + ref->constraints = dcc->constraints; } } } diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h new file mode 100644 index 0000000000..a665388232 --- /dev/null +++ b/src/include/executor/execExpr.h @@ -0,0 +1,642 @@ +/*------------------------------------------------------------------------- + * + * execExpr.h + * Low level infrastructure related to expression evaluation + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/execExpr.h + * + *------------------------------------------------------------------------- + */ +#ifndef EXEC_EXPR_H +#define EXEC_EXPR_H + +#include "nodes/execnodes.h" + +/* forward reference to avoid circularity */ +struct ArrayRefState; + +/* Bits in ExprState->flags (see also execnodes.h for public flag bits): */ +/* expression's interpreter has been initialized */ +#define EEO_FLAG_INTERPRETER_INITIALIZED (1 << 1) +/* jump-threading is in use */ +#define EEO_FLAG_DIRECT_THREADED (1 << 2) + +/* + * Discriminator for ExprEvalSteps. + * + * Identifies the operation to be executed and which member in the + * ExprEvalStep->d union is valid. + * + * The order of entries needs to be kept in sync with the dispatch_table[] + * array in execExprInterp.c:ExecInterpExpr(). + */ +typedef enum ExprEvalOp +{ + /* entire expression has been evaluated completely, return */ + EEOP_DONE, + + /* apply slot_getsomeattrs on corresponding tuple slot */ + EEOP_INNER_FETCHSOME, + EEOP_OUTER_FETCHSOME, + EEOP_SCAN_FETCHSOME, + + /* compute non-system Var value */ + /* "FIRST" variants are used only the first time through */ + EEOP_INNER_VAR_FIRST, + EEOP_INNER_VAR, + EEOP_OUTER_VAR_FIRST, + EEOP_OUTER_VAR, + EEOP_SCAN_VAR_FIRST, + EEOP_SCAN_VAR, + + /* compute system Var value */ + EEOP_INNER_SYSVAR, + EEOP_OUTER_SYSVAR, + EEOP_SCAN_SYSVAR, + + /* compute wholerow Var */ + EEOP_WHOLEROW, + + /* compute non-system Var value, assign it into ExprState's resultslot */ + /* (these are not used if _FIRST checks would be needed) */ + EEOP_ASSIGN_INNER_VAR, + EEOP_ASSIGN_OUTER_VAR, + EEOP_ASSIGN_SCAN_VAR, + + /* assign ExprState's resvalue/resnull to a column of its resultslot */ + EEOP_ASSIGN_TMP, + /* ditto, applying MakeExpandedObjectReadOnly() */ + EEOP_ASSIGN_TMP_MAKE_RO, + + /* evaluate Const value */ + EEOP_CONST, + + /* + * Evaluate function call (including OpExprs etc). For speed, we + * distinguish in the opcode whether the function is strict and/or + * requires usage stats tracking. + */ + EEOP_FUNCEXPR, + EEOP_FUNCEXPR_STRICT, + EEOP_FUNCEXPR_FUSAGE, + EEOP_FUNCEXPR_STRICT_FUSAGE, + + /* + * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST + * subexpressions are special-cased for performance. Since AND always has + * at least two subexpressions, FIRST and LAST never apply to the same + * subexpression. + */ + EEOP_BOOL_AND_STEP_FIRST, + EEOP_BOOL_AND_STEP, + EEOP_BOOL_AND_STEP_LAST, + + /* similarly for boolean OR expression */ + EEOP_BOOL_OR_STEP_FIRST, + EEOP_BOOL_OR_STEP, + EEOP_BOOL_OR_STEP_LAST, + + /* evaluate boolean NOT expression */ + EEOP_BOOL_NOT_STEP, + + /* simplified version of BOOL_AND_STEP for use by ExecQual() */ + EEOP_QUAL, + + /* unconditional jump to another step */ + EEOP_JUMP, + + /* conditional jumps based on current result value */ + EEOP_JUMP_IF_NULL, + EEOP_JUMP_IF_NOT_NULL, + EEOP_JUMP_IF_NOT_TRUE, + + /* perform NULL tests for scalar values */ + EEOP_NULLTEST_ISNULL, + EEOP_NULLTEST_ISNOTNULL, + + /* perform NULL tests for row values */ + EEOP_NULLTEST_ROWISNULL, + EEOP_NULLTEST_ROWISNOTNULL, + + /* evaluate a BooleanTest expression */ + EEOP_BOOLTEST_IS_TRUE, + EEOP_BOOLTEST_IS_NOT_TRUE, + EEOP_BOOLTEST_IS_FALSE, + EEOP_BOOLTEST_IS_NOT_FALSE, + + /* evaluate PARAM_EXEC/EXTERN parameters */ + EEOP_PARAM_EXEC, + EEOP_PARAM_EXTERN, + + /* return CaseTestExpr value */ + EEOP_CASE_TESTVAL, + + /* apply MakeExpandedObjectReadOnly() to target value */ + EEOP_MAKE_READONLY, + + /* evaluate assorted special-purpose expression types */ + EEOP_IOCOERCE, + EEOP_DISTINCT, + EEOP_NULLIF, + EEOP_SQLVALUEFUNCTION, + EEOP_CURRENTOFEXPR, + EEOP_ARRAYEXPR, + EEOP_ARRAYCOERCE, + EEOP_ROW, + + /* + * Compare two individual elements of each of two compared ROW() + * expressions. Skip to ROWCOMPARE_FINAL if elements are not equal. + */ + EEOP_ROWCOMPARE_STEP, + + /* evaluate boolean value based on previous ROWCOMPARE_STEP operations */ + EEOP_ROWCOMPARE_FINAL, + + /* evaluate GREATEST() or LEAST() */ + EEOP_MINMAX, + + /* evaluate FieldSelect expression */ + EEOP_FIELDSELECT, + + /* + * Deform tuple before evaluating new values for individual fields in a + * FieldStore expression. + */ + EEOP_FIELDSTORE_DEFORM, + + /* + * Form the new tuple for a FieldStore expression. Individual fields will + * have been evaluated into columns of the tuple deformed by the preceding + * DEFORM step. + */ + EEOP_FIELDSTORE_FORM, + + /* Process an array subscript; short-circuit expression to NULL if NULL */ + EEOP_ARRAYREF_SUBSCRIPT, + + /* + * Compute old array element/slice when an ArrayRef assignment expression + * contains ArrayRef/FieldStore subexpressions. Value is accessed using + * the CaseTest mechanism. + */ + EEOP_ARRAYREF_OLD, + + /* compute new value for ArrayRef assignment expression */ + EEOP_ARRAYREF_ASSIGN, + + /* compute element/slice for ArrayRef fetch expression */ + EEOP_ARRAYREF_FETCH, + + /* evaluate value for CoerceToDomainValue */ + EEOP_DOMAIN_TESTVAL, + + /* evaluate a domain's NOT NULL constraint */ + EEOP_DOMAIN_NOTNULL, + + /* evaluate a single domain CHECK constraint */ + EEOP_DOMAIN_CHECK, + + /* evaluate assorted special-purpose expression types */ + EEOP_CONVERT_ROWTYPE, + EEOP_SCALARARRAYOP, + EEOP_XMLEXPR, + EEOP_AGGREF, + EEOP_GROUPING_FUNC, + EEOP_WINDOW_FUNC, + EEOP_SUBPLAN, + EEOP_ALTERNATIVE_SUBPLAN, + + /* non-existent operation, used e.g. to check array lengths */ + EEOP_LAST +} ExprEvalOp; + + +typedef struct ExprEvalStep +{ + /* + * Instruction to be executed. During instruction preparation this is an + * enum ExprEvalOp, but later it can be changed to some other type, e.g. a + * pointer for computed goto (that's why it's an intptr_t). + */ + intptr_t opcode; + + /* where to store the result of this step */ + Datum *resvalue; + bool *resnull; + + /* + * Inline data for the operation. Inline data is faster to access, but + * also bloats the size of all instructions. The union should be kept to + * no more than 40 bytes on 64-bit systems (so that the entire struct is + * no more than 64 bytes, a single cacheline on common systems). + */ + union + { + /* for EEOP_INNER/OUTER/SCAN_FETCHSOME */ + struct + { + /* attribute number up to which to fetch (inclusive) */ + int last_var; + } fetch; + + /* for EEOP_INNER/OUTER/SCAN_[SYS]VAR[_FIRST] */ + struct + { + /* attnum is attr number - 1 for regular VAR ... */ + /* but it's just the normal (negative) attr number for SYSVAR */ + int attnum; + Oid vartype; /* type OID of variable */ + } var; + + /* for EEOP_WHOLEROW */ + struct + { + Var *var; /* original Var node in plan tree */ + bool first; /* first time through, need to initialize? */ + bool slow; /* need runtime check for nulls? */ + TupleDesc tupdesc; /* descriptor for resulting tuples */ + JunkFilter *junkFilter; /* JunkFilter to remove resjunk cols */ + } wholerow; + + /* for EEOP_ASSIGN_*_VAR */ + struct + { + /* target index in ExprState->resultslot->tts_values/nulls */ + int resultnum; + /* source attribute number - 1 */ + int attnum; + } assign_var; + + /* for EEOP_ASSIGN_TMP[_MAKE_RO] */ + struct + { + /* target index in ExprState->resultslot->tts_values/nulls */ + int resultnum; + } assign_tmp; + + /* for EEOP_CONST */ + struct + { + /* constant's value */ + Datum value; + bool isnull; + } constval; + + /* for EEOP_FUNCEXPR_* / NULLIF / DISTINCT */ + struct + { + FmgrInfo *finfo; /* function's lookup data */ + FunctionCallInfo fcinfo_data; /* arguments etc */ + /* faster to access without additional indirection: */ + PGFunction fn_addr; /* actual call address */ + int nargs; /* number of arguments */ + } func; + + /* for EEOP_BOOL_*_STEP */ + struct + { + bool *anynull; /* track if any input was NULL */ + int jumpdone; /* jump here if result determined */ + } boolexpr; + + /* for EEOP_QUAL */ + struct + { + int jumpdone; /* jump here on false or null */ + } qualexpr; + + /* for EEOP_JUMP[_CONDITION] */ + struct + { + int jumpdone; /* target instruction's index */ + } jump; + + /* for EEOP_NULLTEST_ROWIS[NOT]NULL */ + struct + { + /* cached tupdesc pointer - filled at runtime */ + TupleDesc argdesc; + } nulltest_row; + + /* for EEOP_PARAM_EXEC/EXTERN */ + struct + { + int paramid; /* numeric ID for parameter */ + Oid paramtype; /* OID of parameter's datatype */ + } param; + + /* for EEOP_CASE_TESTVAL/DOMAIN_TESTVAL */ + struct + { + Datum *value; /* value to return */ + bool *isnull; + } casetest; + + /* for EEOP_MAKE_READONLY */ + struct + { + Datum *value; /* value to coerce to read-only */ + bool *isnull; + } make_readonly; + + /* for EEOP_IOCOERCE */ + struct + { + /* lookup and call info for source type's output function */ + FmgrInfo *finfo_out; + FunctionCallInfo fcinfo_data_out; + /* lookup and call info for result type's input function */ + FmgrInfo *finfo_in; + FunctionCallInfo fcinfo_data_in; + } iocoerce; + + /* for EEOP_SQLVALUEFUNCTION */ + struct + { + SQLValueFunction *svf; + } sqlvaluefunction; + + /* for EEOP_ARRAYEXPR */ + struct + { + Datum *elemvalues; /* element values get stored here */ + bool *elemnulls; + int nelems; /* length of the above arrays */ + Oid elemtype; /* array element type */ + int16 elemlength; /* typlen of the array element type */ + bool elembyval; /* is the element type pass-by-value? */ + char elemalign; /* typalign of the element type */ + bool multidims; /* is array expression multi-D? */ + } arrayexpr; + + /* for EEOP_ARRAYCOERCE */ + struct + { + ArrayCoerceExpr *coerceexpr; + Oid resultelemtype; /* element type of result array */ + FmgrInfo *elemfunc; /* lookup info for element coercion + * function */ + struct ArrayMapState *amstate; /* workspace for array_map */ + } arraycoerce; + + /* for EEOP_ROW */ + struct + { + TupleDesc tupdesc; /* descriptor for result tuples */ + /* workspace for the values constituting the row: */ + Datum *elemvalues; + bool *elemnulls; + } row; + + /* for EEOP_ROWCOMPARE_STEP */ + struct + { + /* lookup and call data for column comparison function */ + FmgrInfo *finfo; + FunctionCallInfo fcinfo_data; + PGFunction fn_addr; + /* target for comparison resulting in NULL */ + int jumpnull; + /* target for comparison yielding inequality */ + int jumpdone; + } rowcompare_step; + + /* for EEOP_ROWCOMPARE_FINAL */ + struct + { + RowCompareType rctype; + } rowcompare_final; + + /* for EEOP_MINMAX */ + struct + { + /* workspace for argument values */ + Datum *values; + bool *nulls; + int nelems; + /* is it GREATEST or LEAST? */ + MinMaxOp op; + /* lookup and call data for comparison function */ + FmgrInfo *finfo; + FunctionCallInfo fcinfo_data; + } minmax; + + /* for EEOP_FIELDSELECT */ + struct + { + AttrNumber fieldnum; /* field number to extract */ + Oid resulttype; /* field's type */ + /* cached tupdesc pointer - filled at runtime */ + TupleDesc argdesc; + } fieldselect; + + /* for EEOP_FIELDSTORE_DEFORM / FIELDSTORE_FORM */ + struct + { + /* original expression node */ + FieldStore *fstore; + + /* cached tupdesc pointer - filled at runtime */ + /* note that a DEFORM and FORM pair share the same tupdesc */ + TupleDesc *argdesc; + + /* workspace for column values */ + Datum *values; + bool *nulls; + int ncolumns; + } fieldstore; + + /* for EEOP_ARRAYREF_SUBSCRIPT */ + struct + { + /* too big to have inline */ + struct ArrayRefState *state; + int off; /* 0-based index of this subscript */ + bool isupper; /* is it upper or lower subscript? */ + int jumpdone; /* jump here on null */ + } arrayref_subscript; + + /* for EEOP_ARRAYREF_OLD / ASSIGN / FETCH */ + struct + { + /* too big to have inline */ + struct ArrayRefState *state; + } arrayref; + + /* for EEOP_DOMAIN_NOTNULL / DOMAIN_CHECK */ + struct + { + /* name of constraint */ + char *constraintname; + /* where the result of a CHECK constraint will be stored */ + Datum *checkvalue; + bool *checknull; + /* OID of domain type */ + Oid resulttype; + } domaincheck; + + /* for EEOP_CONVERT_ROWTYPE */ + struct + { + ConvertRowtypeExpr *convert; /* original expression */ + /* these three fields are filled at runtime: */ + TupleDesc indesc; /* tupdesc for input type */ + TupleDesc outdesc; /* tupdesc for output type */ + TupleConversionMap *map; /* column mapping */ + bool initialized; /* initialized for current types? */ + } convert_rowtype; + + /* for EEOP_SCALARARRAYOP */ + struct + { + /* element_type/typlen/typbyval/typalign are filled at runtime */ + Oid element_type; /* InvalidOid if not yet filled */ + bool useOr; /* use OR or AND semantics? */ + int16 typlen; /* array element type storage info */ + bool typbyval; + char typalign; + FmgrInfo *finfo; /* function's lookup data */ + FunctionCallInfo fcinfo_data; /* arguments etc */ + /* faster to access without additional indirection: */ + PGFunction fn_addr; /* actual call address */ + } scalararrayop; + + /* for EEOP_XMLEXPR */ + struct + { + XmlExpr *xexpr; /* original expression node */ + /* workspace for evaluating named args, if any */ + Datum *named_argvalue; + bool *named_argnull; + /* workspace for evaluating unnamed args, if any */ + Datum *argvalue; + bool *argnull; + } xmlexpr; + + /* for EEOP_AGGREF */ + struct + { + /* out-of-line state, modified by nodeAgg.c */ + AggrefExprState *astate; + } aggref; + + /* for EEOP_GROUPING_FUNC */ + struct + { + AggState *parent; /* parent Agg */ + List *clauses; /* integer list of column numbers */ + } grouping_func; + + /* for EEOP_WINDOW_FUNC */ + struct + { + /* out-of-line state, modified by nodeWindowFunc.c */ + WindowFuncExprState *wfstate; + } window_func; + + /* for EEOP_SUBPLAN */ + struct + { + /* out-of-line state, created by nodeSubplan.c */ + SubPlanState *sstate; + } subplan; + + /* for EEOP_ALTERNATIVE_SUBPLAN */ + struct + { + /* out-of-line state, created by nodeSubplan.c */ + AlternativeSubPlanState *asstate; + } alternative_subplan; + } d; +} ExprEvalStep; + + +/* Non-inline data for array operations */ +typedef struct ArrayRefState +{ + bool isassignment; /* is it assignment, or just fetch? */ + + Oid refelemtype; /* OID of the array element type */ + int16 refattrlength; /* typlen of array type */ + int16 refelemlength; /* typlen of the array element type */ + bool refelembyval; /* is the element type pass-by-value? */ + char refelemalign; /* typalign of the element type */ + + /* numupper and upperprovided[] are filled at compile time */ + /* at runtime, extracted subscript datums get stored in upperindex[] */ + int numupper; + bool upperprovided[MAXDIM]; + int upperindex[MAXDIM]; + + /* similarly for lower indexes, if any */ + int numlower; + bool lowerprovided[MAXDIM]; + int lowerindex[MAXDIM]; + + /* subscript expressions get evaluated into here */ + Datum subscriptvalue; + bool subscriptnull; + + /* for assignment, new value to assign is evaluated into here */ + Datum replacevalue; + bool replacenull; + + /* if we have a nested assignment, ARRAYREF_OLD puts old value here */ + Datum prevvalue; + bool prevnull; +} ArrayRefState; + + +extern void ExecReadyInterpretedExpr(ExprState *state); + +extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op); + +/* + * Non fast-path execution functions. These are externs instead of statics in + * execExprInterp.c, because that allows them to be used by other methods of + * expression evaluation, reducing code duplication. + */ +extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op); +extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op); +extern void ExecEvalRowNull(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalRowNotNull(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalArrayExpr(ExprState *state, ExprEvalStep *op); +extern void ExecEvalArrayCoerce(ExprState *state, ExprEvalStep *op); +extern void ExecEvalRow(ExprState *state, ExprEvalStep *op); +extern void ExecEvalMinMax(ExprState *state, ExprEvalStep *op); +extern void ExecEvalFieldSelect(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalFieldStoreDeForm(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern bool ExecEvalArrayRefSubscript(ExprState *state, ExprEvalStep *op); +extern void ExecEvalArrayRefFetch(ExprState *state, ExprEvalStep *op); +extern void ExecEvalArrayRefOld(ExprState *state, ExprEvalStep *op); +extern void ExecEvalArrayRefAssign(ExprState *state, ExprEvalStep *op); +extern void ExecEvalConvertRowtype(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op); +extern void ExecEvalConstraintNotNull(ExprState *state, ExprEvalStep *op); +extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op); +extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op); +extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op); +extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); +extern void ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); + +#endif /* EXEC_EXPR_H */ diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h index cf44c3edbb..8b61520e18 100644 --- a/src/include/executor/execdebug.h +++ b/src/include/executor/execdebug.h @@ -37,13 +37,6 @@ #undef EXEC_NESTLOOPDEBUG */ -/* ---------------- - * EXEC_EVALDEBUG is a flag which turns on debugging of - * ExecEval and ExecTargetList() stuff by EV_printf() in execQual.c - * ---------------- -#undef EXEC_EVALDEBUG - */ - /* ---------------- * EXEC_SORTDEBUG is a flag which turns on debugging of * the ExecSort() stuff by SO_printf() in nodeSort.c @@ -85,20 +78,6 @@ #define ENL1_printf(message) #endif /* EXEC_NESTLOOPDEBUG */ -/* ---------------- - * exec eval / target list debugging defines - * ---------------- - */ -#ifdef EXEC_EVALDEBUG -#define EV_nodeDisplay(l) nodeDisplay(l) -#define EV_printf(s) printf(s) -#define EV1_printf(s, a) printf(s, a) -#else -#define EV_nodeDisplay(l) -#define EV_printf(s) -#define EV1_printf(s, a) -#endif /* EXEC_EVALDEBUG */ - /* ---------------- * sort node debugging defines * ---------------- @@ -146,4 +125,4 @@ #define MJ_DEBUG_PROC_NODE(slot) #endif /* EXEC_MERGEJOINDEBUG */ -#endif /* ExecDebugIncluded */ +#endif /* EXECDEBUG_H */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index a5c75e771f..d3849b93eb 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -65,15 +65,6 @@ #define EXEC_FLAG_WITH_NO_DATA 0x0080 /* rel scannability doesn't matter */ -/* - * ExecEvalExpr was formerly a function containing a switch statement; - * now it's just a macro invoking the function pointed to by an ExprState - * node. Beware of double evaluation of the ExprState argument! - */ -#define ExecEvalExpr(expr, econtext, isNull) \ - ((*(expr)->evalfunc) (expr, econtext, isNull)) - - /* Hook for plugins to get control in ExecutorStart() */ typedef void (*ExecutorStart_hook_type) (QueryDesc *queryDesc, int eflags); extern PGDLLIMPORT ExecutorStart_hook_type ExecutorStart_hook; @@ -242,29 +233,155 @@ extern void ExecEndNode(PlanState *node); extern bool ExecShutdownNode(PlanState *node); /* - * prototypes from functions in execQual.c + * prototypes from functions in execExpr.c */ -extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno, - bool *isNull); -extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname, - bool *isNull); -extern Tuplestorestate *ExecMakeTableFunctionResult(ExprState *funcexpr, +extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); +extern ExprState *ExecInitQual(List *qual, PlanState *parent); +extern ExprState *ExecInitCheck(List *qual, PlanState *parent); +extern List *ExecInitExprList(List *nodes, PlanState *parent); +extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList, + ExprContext *econtext, + TupleTableSlot *slot, + PlanState *parent, + TupleDesc inputDesc); +extern ExprState *ExecPrepareExpr(Expr *node, EState *estate); +extern ExprState *ExecPrepareQual(List *qual, EState *estate); +extern ExprState *ExecPrepareCheck(List *qual, EState *estate); +extern List *ExecPrepareExprList(List *nodes, EState *estate); + +/* + * ExecEvalExpr + * + * Evaluate expression identified by "state" in the execution context + * given by "econtext". *isNull is set to the is-null flag for the result, + * and the Datum value is the function result. + * + * The caller should already have switched into the temporary memory + * context econtext->ecxt_per_tuple_memory. The convenience entry point + * ExecEvalExprSwitchContext() is provided for callers who don't prefer to + * do the switch in an outer loop. + */ +#ifndef FRONTEND +static inline Datum +ExecEvalExpr(ExprState *state, + ExprContext *econtext, + bool *isNull) +{ + return (*state->evalfunc) (state, econtext, isNull); +} +#endif + +/* + * ExecEvalExprSwitchContext + * + * Same as ExecEvalExpr, but get into the right allocation context explicitly. + */ +#ifndef FRONTEND +static inline Datum +ExecEvalExprSwitchContext(ExprState *state, + ExprContext *econtext, + bool *isNull) +{ + Datum retDatum; + MemoryContext oldContext; + + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + retDatum = (*state->evalfunc) (state, econtext, isNull); + MemoryContextSwitchTo(oldContext); + return retDatum; +} +#endif + +/* + * ExecProject + * + * Projects a tuple based on projection info and stores it in the slot passed + * to ExecBuildProjectInfo(). + * + * Note: the result is always a virtual tuple; therefore it may reference + * the contents of the exprContext's scan tuples and/or temporary results + * constructed in the exprContext. If the caller wishes the result to be + * valid longer than that data will be valid, he must call ExecMaterializeSlot + * on the result slot. + */ +#ifndef FRONTEND +static inline TupleTableSlot * +ExecProject(ProjectionInfo *projInfo) +{ + ExprContext *econtext = projInfo->pi_exprContext; + ExprState *state = &projInfo->pi_state; + TupleTableSlot *slot = state->resultslot; + bool isnull; + + /* + * Clear any former contents of the result slot. This makes it safe for + * us to use the slot's Datum/isnull arrays as workspace. + */ + ExecClearTuple(slot); + + /* Run the expression, discarding scalar result from the last column. */ + (void) ExecEvalExprSwitchContext(state, econtext, &isnull); + + /* + * Successfully formed a result row. Mark the result slot as containing a + * valid virtual tuple (inlined version of ExecStoreVirtualTuple()). + */ + slot->tts_isempty = false; + slot->tts_nvalid = slot->tts_tupleDescriptor->natts; + + return slot; +} +#endif + +/* + * ExecQual - evaluate a qual prepared with ExecInitQual (possibly via + * ExecPrepareQual). Returns true if qual is satisfied, else false. + * + * Note: ExecQual used to have a third argument "resultForNull". The + * behavior of this function now corresponds to resultForNull == false. + * If you want the resultForNull == true behavior, see ExecCheck. + */ +#ifndef FRONTEND +static inline bool +ExecQual(ExprState *state, ExprContext *econtext) +{ + Datum ret; + bool isnull; + + /* short-circuit (here and in ExecInitQual) for empty restriction list */ + if (state == NULL) + return true; + + /* verify that expression was compiled using ExecInitQual */ + Assert(state->flags & EEO_FLAG_IS_QUAL); + + ret = ExecEvalExprSwitchContext(state, econtext, &isnull); + + /* EEOP_QUAL should never return NULL */ + Assert(!isnull); + + return DatumGetBool(ret); +} +#endif + +extern bool ExecCheck(ExprState *state, ExprContext *context); + +/* + * prototypes from functions in execSRF.c + */ +extern SetExprState *ExecInitTableFunctionResult(Expr *expr, + ExprContext *econtext, PlanState *parent); +extern Tuplestorestate *ExecMakeTableFunctionResult(SetExprState *setexpr, ExprContext *econtext, MemoryContext argContext, TupleDesc expectedDesc, bool randomAccess); -extern Datum ExecMakeFunctionResultSet(FuncExprState *fcache, +extern SetExprState *ExecInitFunctionResultSet(Expr *expr, + ExprContext *econtext, PlanState *parent); +extern Datum ExecMakeFunctionResultSet(SetExprState *fcache, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); -extern Datum ExecEvalExprSwitchContext(ExprState *expression, ExprContext *econtext, - bool *isNull); -extern ExprState *ExecInitExpr(Expr *node, PlanState *parent); -extern ExprState *ExecPrepareExpr(Expr *node, EState *estate); -extern bool ExecQual(List *qual, ExprContext *econtext, bool resultForNull); -extern int ExecTargetListLength(List *targetlist); -extern int ExecCleanTargetListLength(List *targetlist); -extern TupleTableSlot *ExecProject(ProjectionInfo *projInfo); /* * prototypes from functions in execScan.c @@ -355,10 +472,6 @@ extern void ExecAssignExprContext(EState *estate, PlanState *planstate); extern void ExecAssignResultType(PlanState *planstate, TupleDesc tupDesc); extern void ExecAssignResultTypeFromTL(PlanState *planstate); extern TupleDesc ExecGetResultType(PlanState *planstate); -extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList, - ExprContext *econtext, - TupleTableSlot *slot, - TupleDesc inputDesc); extern void ExecAssignProjectionInfo(PlanState *planstate, TupleDesc inputDesc); extern void ExecFreeExprContext(PlanState *planstate); @@ -376,8 +489,17 @@ extern void RegisterExprContextCallback(ExprContext *econtext, extern void UnregisterExprContextCallback(ExprContext *econtext, ExprContextCallbackFunction function, Datum arg); + extern void ExecLockNonLeafAppendTables(List *partitioned_rels, EState *estate); +extern Datum GetAttributeByName(HeapTupleHeader tuple, const char *attname, + bool *isNull); +extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno, + bool *isNull); + +extern int ExecTargetListLength(List *targetlist); +extern int ExecCleanTargetListLength(List *targetlist); + /* * prototypes from functions in execIndexing.c */ diff --git a/src/include/executor/nodeSubplan.h b/src/include/executor/nodeSubplan.h index 0f821dc8f6..0d3f52118b 100644 --- a/src/include/executor/nodeSubplan.h +++ b/src/include/executor/nodeSubplan.h @@ -20,6 +20,10 @@ extern SubPlanState *ExecInitSubPlan(SubPlan *subplan, PlanState *parent); extern AlternativeSubPlanState *ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent); +extern Datum ExecSubPlan(SubPlanState *node, ExprContext *econtext, bool *isNull); + +extern Datum ExecAlternativeSubPlan(AlternativeSubPlanState *node, ExprContext *econtext, bool *isNull); + extern void ExecReScanSetParamPlan(SubPlanState *node, PlanState *parent); extern void ExecSetParamPlan(SubPlanState *node, ExprContext *econtext); diff --git a/src/include/fmgr.h b/src/include/fmgr.h index 0a155acee6..6128752ab1 100644 --- a/src/include/fmgr.h +++ b/src/include/fmgr.h @@ -49,6 +49,9 @@ typedef Datum (*PGFunction) (FunctionCallInfo fcinfo); * arguments, rather than about the function itself. But it's convenient * to store it here rather than in FunctionCallInfoData, where it might more * logically belong. + * + * fn_extra is available for use by the called function; all other fields + * should be treated as read-only after the struct is created. */ typedef struct FmgrInfo { @@ -65,6 +68,11 @@ typedef struct FmgrInfo /* * This struct is the data actually passed to an fmgr-called function. + * + * The called function is expected to set isnull, and possibly resultinfo or + * fields in whatever resultinfo points to. It should not change any other + * fields. (In particular, scribbling on the argument arrays is a bad idea, + * since some callers assume they can re-call with the same arguments.) */ typedef struct FunctionCallInfoData { diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index f856f6036f..ff42895118 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -30,6 +30,72 @@ #include "storage/condition_variable.h" +/* ---------------- + * ExprState node + * + * ExprState is the top-level node for expression evaluation. + * It contains instructions (in ->steps) to evaluate the expression. + * ---------------- + */ +struct ExprState; /* forward references in this file */ +struct ExprContext; +struct ExprEvalStep; /* avoid including execExpr.h everywhere */ + +typedef Datum (*ExprStateEvalFunc) (struct ExprState *expression, + struct ExprContext *econtext, + bool *isNull); + +/* Bits in ExprState->flags (see also execExpr.h for private flag bits): */ +/* expression is for use with ExecQual() */ +#define EEO_FLAG_IS_QUAL (1 << 0) + +typedef struct ExprState +{ + Node tag; + + uint8 flags; /* bitmask of EEO_FLAG_* bits, see above */ + + /* + * Storage for result value of a scalar expression, or for individual + * column results within expressions built by ExecBuildProjectionInfo(). + */ + bool resnull; + Datum resvalue; + + /* + * If projecting a tuple result, this slot holds the result; else NULL. + */ + TupleTableSlot *resultslot; + + /* + * Instructions to compute expression's return value. + */ + struct ExprEvalStep *steps; + + /* + * Function that actually evaluates the expression. This can be set to + * different values depending on the complexity of the expression. + */ + ExprStateEvalFunc evalfunc; + + /* original expression tree, for debugging only */ + Expr *expr; + + /* + * XXX: following only needed during "compilation", could be thrown away. + */ + + int steps_len; /* number of steps currently */ + int steps_alloc; /* allocated length of steps array */ + + Datum *innermost_caseval; + bool *innermost_casenull; + + Datum *innermost_domainval; + bool *innermost_domainnull; +} ExprState; + + /* ---------------- * IndexInfo information * @@ -69,7 +135,7 @@ typedef struct IndexInfo List *ii_Expressions; /* list of Expr */ List *ii_ExpressionsState; /* list of ExprState */ List *ii_Predicate; /* list of Expr */ - List *ii_PredicateState; /* list of ExprState */ + ExprState *ii_PredicateState; Oid *ii_ExclusionOps; /* array with one entry per column */ Oid *ii_ExclusionProcs; /* array with one entry per column */ uint16 *ii_ExclusionStrats; /* array with one entry per column */ @@ -214,51 +280,21 @@ typedef struct ReturnSetInfo * that is, form new tuples by evaluation of targetlist expressions. * Nodes which need to do projections create one of these. * + * The target tuple slot is kept in ProjectionInfo->pi_state.resultslot. * ExecProject() evaluates the tlist, forms a tuple, and stores it * in the given slot. Note that the result will be a "virtual" tuple * unless ExecMaterializeSlot() is then called to force it to be * converted to a physical tuple. The slot must have a tupledesc * that matches the output of the tlist! - * - * The planner very often produces tlists that consist entirely of - * simple Var references (lower levels of a plan tree almost always - * look like that). And top-level tlists are often mostly Vars too. - * We therefore optimize execution of simple-Var tlist entries. - * The pi_targetlist list actually contains only the tlist entries that - * aren't simple Vars, while those that are Vars are processed using the - * varSlotOffsets/varNumbers/varOutputCols arrays. - * - * The lastXXXVar fields are used to optimize fetching of fields from - * input tuples: they let us do a slot_getsomeattrs() call to ensure - * that all needed attributes are extracted in one pass. - * - * targetlist target list for projection (non-Var expressions only) - * exprContext expression context in which to evaluate targetlist - * slot slot to place projection result in - * directMap true if varOutputCols[] is an identity map - * numSimpleVars number of simple Vars found in original tlist - * varSlotOffsets array indicating which slot each simple Var is from - * varNumbers array containing input attr numbers of simple Vars - * varOutputCols array containing output attr numbers of simple Vars - * lastInnerVar highest attnum from inner tuple slot (0 if none) - * lastOuterVar highest attnum from outer tuple slot (0 if none) - * lastScanVar highest attnum from scan tuple slot (0 if none) * ---------------- */ typedef struct ProjectionInfo { NodeTag type; - List *pi_targetlist; + /* instructions to evaluate projection */ + ExprState pi_state; + /* expression context in which to evaluate expression */ ExprContext *pi_exprContext; - TupleTableSlot *pi_slot; - bool pi_directMap; - int pi_numSimpleVars; - int *pi_varSlotOffsets; - int *pi_varNumbers; - int *pi_varOutputCols; - int pi_lastInnerVar; - int pi_lastOuterVar; - int pi_lastScanVar; } ProjectionInfo; /* ---------------- @@ -340,20 +376,20 @@ typedef struct ResultRelInfo IndexInfo **ri_IndexRelationInfo; TriggerDesc *ri_TrigDesc; FmgrInfo *ri_TrigFunctions; - List **ri_TrigWhenExprs; + ExprState **ri_TrigWhenExprs; Instrumentation *ri_TrigInstrument; struct FdwRoutine *ri_FdwRoutine; void *ri_FdwState; bool ri_usesFdwDirectModify; List *ri_WithCheckOptions; List *ri_WithCheckOptionExprs; - List **ri_ConstraintExprs; + ExprState **ri_ConstraintExprs; JunkFilter *ri_junkFilter; ProjectionInfo *ri_projectReturning; ProjectionInfo *ri_onConflictSetProj; - List *ri_onConflictSetWhere; + ExprState *ri_onConflictSetWhere; List *ri_PartitionCheck; - List *ri_PartitionCheckExpr; + ExprState *ri_PartitionCheckExpr; Relation ri_PartitionRoot; } ResultRelInfo; @@ -564,139 +600,63 @@ typedef tuplehash_iterator TupleHashIterator; /* ---------------------------------------------------------------- - * Expression State Trees - * - * Each executable expression tree has a parallel ExprState tree. - * - * Unlike PlanState, there is not an exact one-for-one correspondence between - * ExprState node types and Expr node types. Many Expr node types have no - * need for node-type-specific run-time state, and so they can use plain - * ExprState or GenericExprState as their associated ExprState node type. + * Expression State Nodes + * + * Formerly, there was a separate executor expression state node corresponding + * to each node in a planned expression tree. That's no longer the case; for + * common expression node types, all the execution info is embedded into + * step(s) in a single ExprState node. But we still have a few executor state + * node types for selected expression node types, mostly those in which info + * has to be shared with other parts of the execution state tree. * ---------------------------------------------------------------- */ -/* ---------------- - * ExprState node - * - * ExprState is the common superclass for all ExprState-type nodes. - * - * It can also be instantiated directly for leaf Expr nodes that need no - * local run-time state (such as Var, Const, or Param). - * - * To save on dispatch overhead, each ExprState node contains a function - * pointer to the routine to execute to evaluate the node. - * ---------------- - */ - -typedef struct ExprState ExprState; - -typedef Datum (*ExprStateEvalFunc) (ExprState *expression, - ExprContext *econtext, - bool *isNull); - -struct ExprState -{ - NodeTag type; - Expr *expr; /* associated Expr node */ - ExprStateEvalFunc evalfunc; /* routine to run to execute node */ -}; - -/* ---------------- - * GenericExprState node - * - * This is used for Expr node types that need no local run-time state, - * but have one child Expr node. - * ---------------- - */ -typedef struct GenericExprState -{ - ExprState xprstate; - ExprState *arg; /* state of my child node */ -} GenericExprState; - -/* ---------------- - * WholeRowVarExprState node - * ---------------- - */ -typedef struct WholeRowVarExprState -{ - ExprState xprstate; - struct PlanState *parent; /* parent PlanState, or NULL if none */ - TupleDesc wrv_tupdesc; /* descriptor for resulting tuples */ - JunkFilter *wrv_junkFilter; /* JunkFilter to remove resjunk cols */ -} WholeRowVarExprState; - /* ---------------- * AggrefExprState node * ---------------- */ typedef struct AggrefExprState { - ExprState xprstate; + NodeTag type; + Aggref *aggref; /* expression plan node */ int aggno; /* ID number for agg within its plan node */ } AggrefExprState; -/* ---------------- - * GroupingFuncExprState node - * - * The list of column numbers refers to the input tuples of the Agg node to - * which the GroupingFunc belongs, and may contain 0 for references to columns - * that are only present in grouping sets processed by different Agg nodes (and - * which are therefore always considered "grouping" here). - * ---------------- - */ -typedef struct GroupingFuncExprState -{ - ExprState xprstate; - struct AggState *aggstate; - List *clauses; /* integer list of column numbers */ -} GroupingFuncExprState; - /* ---------------- * WindowFuncExprState node * ---------------- */ typedef struct WindowFuncExprState { - ExprState xprstate; - List *args; /* states of argument expressions */ + NodeTag type; + WindowFunc *wfunc; /* expression plan node */ + List *args; /* ExprStates for argument expressions */ ExprState *aggfilter; /* FILTER expression */ int wfuncno; /* ID number for wfunc within its plan node */ } WindowFuncExprState; -/* ---------------- - * ArrayRefExprState node - * - * Note: array types can be fixed-length (typlen > 0), but only when the - * element type is itself fixed-length. Otherwise they are varlena structures - * and have typlen = -1. In any case, an array type is never pass-by-value. - * ---------------- - */ -typedef struct ArrayRefExprState -{ - ExprState xprstate; - List *refupperindexpr; /* states for child nodes */ - List *reflowerindexpr; - ExprState *refexpr; - ExprState *refassgnexpr; - int16 refattrlength; /* typlen of array type */ - int16 refelemlength; /* typlen of the array element type */ - bool refelembyval; /* is the element type pass-by-value? */ - char refelemalign; /* typalign of the element type */ -} ArrayRefExprState; /* ---------------- - * FuncExprState node + * SetExprState node * - * Although named for FuncExpr, this is also used for OpExpr, DistinctExpr, - * and NullIf nodes; be careful to check what xprstate.expr is actually - * pointing at! + * State for evaluating a potentially set-returning expression (like FuncExpr + * or OpExpr). In some cases, like some of the expressions in ROWS FROM(...) + * the expression might not be a SRF, but nonetheless it uses the same + * machinery as SRFs; it will be treated as a SRF returning a single row. * ---------------- */ -typedef struct FuncExprState +typedef struct SetExprState { - ExprState xprstate; - List *args; /* states of argument expressions */ + NodeTag type; + Expr *expr; /* expression plan node */ + List *args; /* ExprStates for argument expressions */ + + /* + * In ROWS FROM, functions can be inlined, removing the FuncExpr normally + * inside. In such a case this is the compiled expression (which cannot + * return a set), which'll be evaluated using regular ExecEvalExpr(). + */ + ExprState *elidedFuncState; /* * Function manager's lookup info for the target function. If func.fn_oid @@ -738,7 +698,7 @@ typedef struct FuncExprState /* * Flag to remember whether we have registered a shutdown callback for - * this FuncExprState. We do so only if funcResultStore or setArgsValid + * this SetExprState. We do so only if funcResultStore or setArgsValid * has been set at least once (since all the callback is for is to release * the tuplestore or clear setArgsValid). */ @@ -750,33 +710,7 @@ typedef struct FuncExprState * argument values between calls, when setArgsValid is true. */ FunctionCallInfoData fcinfo_data; -} FuncExprState; - -/* ---------------- - * ScalarArrayOpExprState node - * - * This is a FuncExprState plus some additional data. - * ---------------- - */ -typedef struct ScalarArrayOpExprState -{ - FuncExprState fxprstate; - /* Cached info about array element type */ - Oid element_type; - int16 typlen; - bool typbyval; - char typalign; -} ScalarArrayOpExprState; - -/* ---------------- - * BoolExprState node - * ---------------- - */ -typedef struct BoolExprState -{ - ExprState xprstate; - List *args; /* states of argument expression(s) */ -} BoolExprState; +} SetExprState; /* ---------------- * SubPlanState node @@ -784,7 +718,8 @@ typedef struct BoolExprState */ typedef struct SubPlanState { - ExprState xprstate; + NodeTag type; + SubPlan *subplan; /* expression plan node */ struct PlanState *planstate; /* subselect plan's state tree */ struct PlanState *parent; /* parent plan node's state tree */ ExprState *testexpr; /* state of combining expression */ @@ -814,203 +749,18 @@ typedef struct SubPlanState */ typedef struct AlternativeSubPlanState { - ExprState xprstate; - List *subplans; /* states of alternative subplans */ + NodeTag type; + AlternativeSubPlan *subplan; /* expression plan node */ + List *subplans; /* SubPlanStates of alternative subplans */ int active; /* list index of the one we're using */ } AlternativeSubPlanState; -/* ---------------- - * FieldSelectState node - * ---------------- - */ -typedef struct FieldSelectState -{ - ExprState xprstate; - ExprState *arg; /* input expression */ - TupleDesc argdesc; /* tupdesc for most recent input */ -} FieldSelectState; - -/* ---------------- - * FieldStoreState node - * ---------------- - */ -typedef struct FieldStoreState -{ - ExprState xprstate; - ExprState *arg; /* input tuple value */ - List *newvals; /* new value(s) for field(s) */ - TupleDesc argdesc; /* tupdesc for most recent input */ -} FieldStoreState; - -/* ---------------- - * CoerceViaIOState node - * ---------------- - */ -typedef struct CoerceViaIOState -{ - ExprState xprstate; - ExprState *arg; /* input expression */ - FmgrInfo outfunc; /* lookup info for source output function */ - FmgrInfo infunc; /* lookup info for result input function */ - Oid intypioparam; /* argument needed for input function */ -} CoerceViaIOState; - -/* ---------------- - * ArrayCoerceExprState node - * ---------------- - */ -typedef struct ArrayCoerceExprState -{ - ExprState xprstate; - ExprState *arg; /* input array value */ - Oid resultelemtype; /* element type of result array */ - FmgrInfo elemfunc; /* lookup info for element coercion function */ - /* use struct pointer to avoid including array.h here */ - struct ArrayMapState *amstate; /* workspace for array_map */ -} ArrayCoerceExprState; - -/* ---------------- - * ConvertRowtypeExprState node - * ---------------- - */ -typedef struct ConvertRowtypeExprState -{ - ExprState xprstate; - ExprState *arg; /* input tuple value */ - TupleDesc indesc; /* tupdesc for source rowtype */ - TupleDesc outdesc; /* tupdesc for result rowtype */ - /* use "struct" so we needn't include tupconvert.h here */ - struct TupleConversionMap *map; - bool initialized; -} ConvertRowtypeExprState; - -/* ---------------- - * CaseExprState node - * ---------------- - */ -typedef struct CaseExprState -{ - ExprState xprstate; - ExprState *arg; /* implicit equality comparison argument */ - List *args; /* the arguments (list of WHEN clauses) */ - ExprState *defresult; /* the default result (ELSE clause) */ - int16 argtyplen; /* if arg is provided, its typlen */ -} CaseExprState; - -/* ---------------- - * CaseWhenState node - * ---------------- - */ -typedef struct CaseWhenState -{ - ExprState xprstate; - ExprState *expr; /* condition expression */ - ExprState *result; /* substitution result */ -} CaseWhenState; - -/* ---------------- - * ArrayExprState node - * - * Note: ARRAY[] expressions always produce varlena arrays, never fixed-length - * arrays. - * ---------------- - */ -typedef struct ArrayExprState -{ - ExprState xprstate; - List *elements; /* states for child nodes */ - int16 elemlength; /* typlen of the array element type */ - bool elembyval; /* is the element type pass-by-value? */ - char elemalign; /* typalign of the element type */ -} ArrayExprState; - -/* ---------------- - * RowExprState node - * ---------------- - */ -typedef struct RowExprState -{ - ExprState xprstate; - List *args; /* the arguments */ - TupleDesc tupdesc; /* descriptor for result tuples */ -} RowExprState; - -/* ---------------- - * RowCompareExprState node - * ---------------- - */ -typedef struct RowCompareExprState -{ - ExprState xprstate; - List *largs; /* the left-hand input arguments */ - List *rargs; /* the right-hand input arguments */ - FmgrInfo *funcs; /* array of comparison function info */ - Oid *collations; /* array of collations to use */ -} RowCompareExprState; - -/* ---------------- - * CoalesceExprState node - * ---------------- - */ -typedef struct CoalesceExprState -{ - ExprState xprstate; - List *args; /* the arguments */ -} CoalesceExprState; - -/* ---------------- - * MinMaxExprState node - * ---------------- - */ -typedef struct MinMaxExprState -{ - ExprState xprstate; - List *args; /* the arguments */ - FmgrInfo cfunc; /* lookup info for comparison func */ -} MinMaxExprState; - -/* ---------------- - * XmlExprState node - * ---------------- - */ -typedef struct XmlExprState -{ - ExprState xprstate; - List *named_args; /* ExprStates for named arguments */ - List *args; /* ExprStates for other arguments */ -} XmlExprState; - -/* ---------------- - * NullTestState node - * ---------------- - */ -typedef struct NullTestState -{ - ExprState xprstate; - ExprState *arg; /* input expression */ - /* used only if input is of composite type: */ - TupleDesc argdesc; /* tupdesc for most recent input */ -} NullTestState; - -/* ---------------- - * CoerceToDomainState node - * ---------------- - */ -typedef struct CoerceToDomainState -{ - ExprState xprstate; - ExprState *arg; /* input expression */ - /* Cached set of constraints that need to be checked */ - /* use struct pointer to avoid including typcache.h here */ - struct DomainConstraintRef *constraint_ref; -} CoerceToDomainState; - /* * DomainConstraintState - one item to check during CoerceToDomain * - * Note: this is just a Node, and not an ExprState, because it has no - * corresponding Expr to link to. Nonetheless it is part of an ExprState - * tree, so we give it a name following the xxxState convention. + * Note: we consider this to be part of an ExprState tree, so we give it + * a name following the xxxState convention. But there's no directly + * associated plan-tree node. */ typedef enum DomainConstraintType { @@ -1023,7 +773,8 @@ typedef struct DomainConstraintState NodeTag type; DomainConstraintType constrainttype; /* constraint type */ char *name; /* name of constraint (for error msgs) */ - ExprState *check_expr; /* for CHECK, a boolean expression */ + Expr *check_expr; /* for CHECK, a boolean expression */ + ExprState *check_exprstate; /* check_expr's eval state, or NULL */ } DomainConstraintState; @@ -1060,8 +811,7 @@ typedef struct PlanState * state trees parallel links in the associated plan tree (except for the * subPlan list, which does not exist in the plan tree). */ - List *targetlist; /* target list to be computed at this node */ - List *qual; /* implicitly-ANDed qual conditions */ + ExprState *qual; /* boolean qual condition */ struct PlanState *lefttree; /* input plan tree(s) */ struct PlanState *righttree; List *initPlan; /* Init SubPlanState nodes (un-correlated expr @@ -1133,11 +883,15 @@ typedef struct ResultState /* ---------------- * ProjectSetState information + * + * Note: at least one of the "elems" will be a SetExprState; the rest are + * regular ExprStates. * ---------------- */ typedef struct ProjectSetState { PlanState ps; /* its first field is NodeTag */ + Node **elems; /* array of expression states */ ExprDoneCond *elemdone; /* array of per-SRF is-done states */ int nelems; /* length of elemdone[] array */ bool pending_srf_tuples; /* still evaluating srfs in tlist? */ @@ -1372,7 +1126,7 @@ typedef struct typedef struct IndexScanState { ScanState ss; /* its first field is NodeTag */ - List *indexqualorig; + ExprState *indexqualorig; List *indexorderbyorig; ScanKey iss_ScanKeys; int iss_NumScanKeys; @@ -1418,7 +1172,7 @@ typedef struct IndexScanState typedef struct IndexOnlyScanState { ScanState ss; /* its first field is NodeTag */ - List *indexqual; + ExprState *indexqual; ScanKey ioss_ScanKeys; int ioss_NumScanKeys; ScanKey ioss_OrderByKeys; @@ -1534,7 +1288,7 @@ typedef struct ParallelBitmapHeapState typedef struct BitmapHeapScanState { ScanState ss; /* its first field is NodeTag */ - List *bitmapqualorig; + ExprState *bitmapqualorig; TIDBitmap *tbm; TBMIterator *tbmiterator; TBMIterateResult *tbmres; @@ -1554,16 +1308,18 @@ typedef struct BitmapHeapScanState /* ---------------- * TidScanState information * + * tidexprs list of TidExpr structs (see nodeTidscan.c) * isCurrentOf scan has a CurrentOfExpr qual * NumTids number of tids in this scan * TidPtr index of currently fetched tid * TidList evaluated item pointers (array of size NumTids) + * htup currently-fetched tuple, if any * ---------------- */ typedef struct TidScanState { ScanState ss; /* its first field is NodeTag */ - List *tss_tidquals; /* list of ExprState nodes */ + List *tss_tidexprs; bool tss_isCurrentOf; int tss_NumTids; int tss_TidPtr; @@ -1712,7 +1468,7 @@ typedef struct WorkTableScanState typedef struct ForeignScanState { ScanState ss; /* its first field is NodeTag */ - List *fdw_recheck_quals; /* original quals not in ss.ps.qual */ + ExprState *fdw_recheck_quals; /* original quals not in ss.ps.qual */ Size pscan_len; /* size of parallel coordination information */ /* use struct pointer to avoid including fdwapi.h here */ struct FdwRoutine *fdwroutine; @@ -1759,7 +1515,7 @@ typedef struct JoinState { PlanState ps; JoinType jointype; - List *joinqual; /* JOIN quals (in addition to ps.qual) */ + ExprState *joinqual; /* JOIN quals (in addition to ps.qual) */ } JoinState; /* ---------------- @@ -1857,7 +1613,7 @@ typedef struct HashJoinTableData *HashJoinTable; typedef struct HashJoinState { JoinState js; /* its first field is NodeTag */ - List *hashclauses; /* list of ExprState nodes */ + ExprState *hashclauses; List *hj_OuterHashKeys; /* list of ExprState nodes */ List *hj_InnerHashKeys; /* list of ExprState nodes */ List *hj_HashOperators; /* list of operator OIDs */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index fc883a6f3e..c83216943c 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -192,36 +192,18 @@ typedef enum NodeTag /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) * - * These correspond (not always one-for-one) to primitive nodes derived - * from Expr. + * ExprState represents the evaluation state for a whole expression tree. + * Most Expr-based plan nodes do not have a corresponding expression state + * node, they're fully handled within execExpr* - but sometimes the state + * needs to be shared with other parts of the executor, as for example + * with AggrefExprState, which nodeAgg.c has to modify. */ T_ExprState, - T_GenericExprState, - T_WholeRowVarExprState, T_AggrefExprState, - T_GroupingFuncExprState, T_WindowFuncExprState, - T_ArrayRefExprState, - T_FuncExprState, - T_ScalarArrayOpExprState, - T_BoolExprState, + T_SetExprState, T_SubPlanState, T_AlternativeSubPlanState, - T_FieldSelectState, - T_FieldStoreState, - T_CoerceViaIOState, - T_ArrayCoerceExprState, - T_ConvertRowtypeExprState, - T_CaseExprState, - T_CaseWhenState, - T_ArrayExprState, - T_RowExprState, - T_RowCompareExprState, - T_CoalesceExprState, - T_MinMaxExprState, - T_XmlExprState, - T_NullTestState, - T_CoerceToDomainState, T_DomainConstraintState, /* diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h index 90a1f6347a..1bf94e2548 100644 --- a/src/include/utils/typcache.h +++ b/src/include/utils/typcache.h @@ -132,6 +132,7 @@ typedef struct DomainConstraintRef List *constraints; /* list of DomainConstraintState nodes */ MemoryContext refctx; /* context holding DomainConstraintRef */ TypeCacheEntry *tcache; /* typcache entry for domain type */ + bool need_exprstate; /* does caller need check_exprstate? */ /* Management data --- treat these fields as private to typcache.c */ DomainConstraintCache *dcc; /* current constraints, or NULL if none */ @@ -142,7 +143,7 @@ typedef struct DomainConstraintRef extern TypeCacheEntry *lookup_type_cache(Oid type_id, int flags); extern void InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref, - MemoryContext refctx); + MemoryContext refctx, bool need_exprstate); extern void UpdateDomainConstraintRef(DomainConstraintRef *ref); diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index e570b71c04..195b9b3a97 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -61,7 +61,9 @@ extern void xml_ereport(PgXmlErrorContext *errcxt, int level, int sqlcode, const char *msg); extern xmltype *xmlconcat(List *args); -extern xmltype *xmlelement(XmlExprState *xmlExpr, ExprContext *econtext); +extern xmltype *xmlelement(XmlExpr *xexpr, + Datum *named_argvalue, bool *named_argnull, + Datum *argvalue, bool *argnull); extern xmltype *xmlparse(text *data, XmlOptionType xmloption, bool preserve_whitespace); extern xmltype *xmlpi(char *target, text *arg, bool arg_is_null, bool *result_is_null); extern xmltype *xmlroot(xmltype *data, text *version, int standalone); diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 664fc42514..84af815537 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -145,7 +145,7 @@ typedef struct /* cast_hash table entry */ { plpgsql_CastHashKey key; /* hash key --- MUST BE FIRST */ Expr *cast_expr; /* cast expression, or NULL if no-op cast */ - /* The ExprState tree is valid only when cast_lxid matches current LXID */ + /* ExprState is valid only when cast_lxid matches current LXID */ ExprState *cast_exprstate; /* expression's eval tree */ bool cast_in_use; /* true while we're executing eval tree */ LocalTransactionId cast_lxid; @@ -4710,7 +4710,8 @@ exec_assign_value(PLpgSQL_execstate *estate, /* * Evaluate the subscripts, switch into left-to-right order. - * Like ExecEvalArrayRef(), complain if any subscript is null. + * Like the expression built by ExecInitArrayRef(), complain + * if any subscript is null. */ for (i = 0; i < nsubscripts; i++) { diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out index 4cc4851475..36bf15c4ac 100644 --- a/src/test/regress/expected/case.out +++ b/src/test/regress/expected/case.out @@ -308,7 +308,7 @@ SELECT * FROM CASE_TBL; -- Nested CASE expressions -- -- This test exercises a bug caused by aliasing econtext->caseValue_isNull --- with the isNull argument of the inner CASE's ExecEvalCase() call. After +-- with the isNull argument of the inner CASE's CaseExpr evaluation. After -- evaluating the vol(null) expression in the inner CASE's second WHEN-clause, -- the isNull flag for the case test value incorrectly became true, causing -- the third WHEN-clause not to match. The volatile function calls are needed diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 720675032a..f349980759 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -586,14 +586,8 @@ ERROR: must be owner of function testfunc1 DROP FUNCTION testfunc1(int); -- ok -- restore to sanity GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC; --- verify privilege checks on coercions +-- verify privilege checks on array-element coercions BEGIN; -SELECT NULL::int4[]::int8[]; - int8 ------- - -(1 row) - SELECT '{1}'::int4[]::int8[]; int8 ------ @@ -601,12 +595,6 @@ SELECT '{1}'::int4[]::int8[]; (1 row) REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC; -SELECT NULL::int4[]::int8[]; - int8 ------- - -(1 row) - SELECT '{1}'::int4[]::int8[]; --superuser, suceed int8 ------ @@ -614,12 +602,6 @@ SELECT '{1}'::int4[]::int8[]; --superuser, suceed (1 row) SET SESSION AUTHORIZATION regress_user4; -SELECT NULL::int4[]::int8[]; --other user, no elements to convert - int8 ------- - -(1 row) - SELECT '{1}'::int4[]::int8[]; --other user, fail ERROR: permission denied for function int8 ROLLBACK; diff --git a/src/test/regress/sql/case.sql b/src/test/regress/sql/case.sql index 59268f8cdf..66b6e98fb1 100644 --- a/src/test/regress/sql/case.sql +++ b/src/test/regress/sql/case.sql @@ -166,7 +166,7 @@ SELECT * FROM CASE_TBL; -- -- This test exercises a bug caused by aliasing econtext->caseValue_isNull --- with the isNull argument of the inner CASE's ExecEvalCase() call. After +-- with the isNull argument of the inner CASE's CaseExpr evaluation. After -- evaluating the vol(null) expression in the inner CASE's second WHEN-clause, -- the isNull flag for the case test value incorrectly became true, causing -- the third WHEN-clause not to match. The volatile function calls are needed diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index e3275febea..166e903012 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -398,15 +398,12 @@ DROP FUNCTION testfunc1(int); -- ok -- restore to sanity GRANT ALL PRIVILEGES ON LANGUAGE sql TO PUBLIC; --- verify privilege checks on coercions +-- verify privilege checks on array-element coercions BEGIN; -SELECT NULL::int4[]::int8[]; SELECT '{1}'::int4[]::int8[]; REVOKE ALL ON FUNCTION int8(integer) FROM PUBLIC; -SELECT NULL::int4[]::int8[]; SELECT '{1}'::int4[]::int8[]; --superuser, suceed SET SESSION AUTHORIZATION regress_user4; -SELECT NULL::int4[]::int8[]; --other user, no elements to convert SELECT '{1}'::int4[]::int8[]; --other user, fail ROLLBACK; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 3487f7becb..15c72f5765 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -120,7 +120,7 @@ ArrayMapState ArrayMetaState ArrayParseState ArrayRef -ArrayRefExprState +ArrayRefState ArrayRemapInfo ArrayType AsyncQueueControl @@ -578,6 +578,8 @@ ExprContext_CB ExprDoneCond ExprState ExprStateEvalFunc +ExprEvalOp +ExprEvalStep ExtensibleNode ExtensibleNodeEntry ExtensibleNodeMethods @@ -1073,6 +1075,7 @@ LWLockPadded LWLockTranche LabelProvider LargeObjectDesc +LastAttnumInfo Latch LerpFunc LexDescr @@ -1908,6 +1911,7 @@ Session SetConstraintState SetConstraintStateData SetConstraintTriggerData +SetExprState SetFunctionReturnMode SetOp SetOpCmd -- 2.40.0