Faster expression evaluation and targetlist projection.
authorAndres Freund <andres@anarazel.de>
Tue, 14 Mar 2017 22:45:36 +0000 (15:45 -0700)
committerAndres Freund <andres@anarazel.de>
Sat, 25 Mar 2017 21:52:06 +0000 (14:52 -0700)
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

71 files changed:
contrib/postgres_fdw/postgres_fdw.c
src/backend/bootstrap/bootstrap.c
src/backend/catalog/index.c
src/backend/catalog/partition.c
src/backend/catalog/toasting.c
src/backend/commands/analyze.c
src/backend/commands/explain.c
src/backend/commands/indexcmds.c
src/backend/commands/prepare.c
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/executor/Makefile
src/backend/executor/README
src/backend/executor/execExpr.c [new file with mode: 0644]
src/backend/executor/execExprInterp.c [new file with mode: 0644]
src/backend/executor/execIndexing.c
src/backend/executor/execMain.c
src/backend/executor/execQual.c
src/backend/executor/execSRF.c [new file with mode: 0644]
src/backend/executor/execScan.c
src/backend/executor/execUtils.c
src/backend/executor/functions.c
src/backend/executor/nodeAgg.c
src/backend/executor/nodeBitmapHeapscan.c
src/backend/executor/nodeCtescan.c
src/backend/executor/nodeCustom.c
src/backend/executor/nodeForeignscan.c
src/backend/executor/nodeFunctionscan.c
src/backend/executor/nodeGather.c
src/backend/executor/nodeGatherMerge.c
src/backend/executor/nodeGroup.c
src/backend/executor/nodeHash.c
src/backend/executor/nodeHashjoin.c
src/backend/executor/nodeIndexonlyscan.c
src/backend/executor/nodeIndexscan.c
src/backend/executor/nodeMergejoin.c
src/backend/executor/nodeModifyTable.c
src/backend/executor/nodeNestloop.c
src/backend/executor/nodeProjectSet.c
src/backend/executor/nodeResult.c
src/backend/executor/nodeSamplescan.c
src/backend/executor/nodeSeqscan.c
src/backend/executor/nodeSubplan.c
src/backend/executor/nodeSubqueryscan.c
src/backend/executor/nodeTableFuncscan.c
src/backend/executor/nodeTidscan.c
src/backend/executor/nodeValuesscan.c
src/backend/executor/nodeWindowAgg.c
src/backend/executor/nodeWorktablescan.c
src/backend/optimizer/path/costsize.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/util/clauses.c
src/backend/utils/adt/domains.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/adt/xml.c
src/backend/utils/cache/typcache.c
src/include/executor/execExpr.h [new file with mode: 0644]
src/include/executor/execdebug.h
src/include/executor/executor.h
src/include/executor/nodeSubplan.h
src/include/fmgr.h
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/utils/typcache.h
src/include/utils/xml.h
src/pl/plpgsql/src/pl_exec.c
src/test/regress/expected/case.out
src/test/regress/expected/privileges.out
src/test/regress/sql/case.sql
src/test/regress/sql/privileges.sql
src/tools/pgindent/typedefs.list

index e8cb2d0b4da69cff407cda4a95501dab95acc1be..03f14800b0e375cb83e1260fb1c05c4aad8a7831 100644 (file)
@@ -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 *));
index 6511c6064b8f6df3d5e2b21fc3c66a3a4f5bdd13..6cfce4f8ddb0f897e1b211f9f95e2525cc55a282 100644 (file)
@@ -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);
index 7924c30369ffadb1ff358862f612712e863ceeda..1eb163f5392e9805682f73285d9cd3ef22f5ba4a 100644 (file)
@@ -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;
 }
 
 
index e01ef864f00f5f02258752d060ac8b077bc5c623..2b5b8e89bbc75e8dabe62ed56b539a53de757bc8 100644 (file)
@@ -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);
index 0e4231668d4e69a9d5b119b3e95b7c162b4aa449..29756eb14eb43f2a4f15ad8a7eda00bbdbd3158d 100644 (file)
@@ -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;
index c5b5c54babf0994567ad6bf729f49bf6ba888d52..404acb2debb83e1411a9c085bb28ce0be0c2746b 100644 (file)
@@ -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++;
index b4c7466666b1d529fb338ab5d19f5d0904617d00..1036b96aaea00f686c18ebb4b6a102ad4b487069 100644 (file)
@@ -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
index 9618032356af50a000d546702ee4903a9d4a0d18..486179938c3e8537dc2cf631f4693a488168a53d 100644 (file)
@@ -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;
index 992ba1c9a2e2f95ae56e76c6844414dc2f449979..a92461097748f5ce5957eee5e1945b579344a8aa 100644 (file)
@@ -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 = &paramLI->params[i];
 
        prm->ptype = param_types[i];
index 3b28e8c34f633965920118a73fb5b2f5a026c45b..96cf42a7f8266d58217004a8bef7ccda241c6544 100644 (file)
@@ -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",
index a1bb3e958c768e8e0d30a3f82b395cf0bf1671df..f3b1a5268267b10b0ce3307017dda205c6fe059b 100644 (file)
@@ -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;
    }
 
index d281906cd5cf496449f61cd358a1566c8ad9b5ef..d1c1324399a05e9020df6c6b1fd927a402d06726 100644 (file)
@@ -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 \
index f1d1e4c76ce9d78cfc70bc6da7348e2a03dae52d..a0045067fb84ac27593ce400ff25f5a8450e290e 100644 (file)
@@ -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 (file)
index 0000000..766dbbb
--- /dev/null
@@ -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 (file)
index 0000000..de7fe89
--- /dev/null
@@ -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&