]> granicus.if.org Git - postgresql/commitdiff
Perform slot validity checks in a separate pass over expression.
authorAndres Freund <andres@anarazel.de>
Fri, 29 Dec 2017 20:38:15 +0000 (12:38 -0800)
committerAndres Freund <andres@anarazel.de>
Fri, 29 Dec 2017 20:45:25 +0000 (12:45 -0800)
This reduces code duplication a bit, but the primary benefit that it
makes JITing expression evaluation easier. When doing so we can't, as
previously done in the interpreted case, really change opcode without
recompiling. Nor dow we just carry around unnecessary branches to
avoid re-checking over and over.

As a minor side-effect this makes ExecEvalStepOp() O(log(N)) rather
than O(N).

Author: Andres Freund
Discussion: https://postgr.es/m/20170901064131.tazjxwus3k2w3ybh@alap3.anarazel.de

src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/include/executor/execExpr.h
src/include/nodes/execnodes.h

index 55bb9251917aab478c53ad090d6c588e5885a475..2642b404ff6bf850b98d8fe314bd0cc09e925c4d 100644 (file)
@@ -680,20 +680,19 @@ ExecInitExprRec(Expr *node, ExprState *state,
                                        /* 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;
+                                                       scratch.opcode = EEOP_INNER_VAR;
                                                        break;
                                                case OUTER_VAR:
-                                                       scratch.opcode = EEOP_OUTER_VAR_FIRST;
+                                                       scratch.opcode = EEOP_OUTER_VAR;
                                                        break;
 
                                                        /* INDEX_VAR is handled by default case */
 
                                                default:
-                                                       scratch.opcode = EEOP_SCAN_VAR_FIRST;
+                                                       scratch.opcode = EEOP_SCAN_VAR;
                                                        break;
                                        }
                                }
index 0c3f66803f1615c1b3b3b8e8b913d39ebfeca418..fa4ab30e99773185e5afd9e2f8dae836da63b090 100644 (file)
  */
 #if defined(EEO_USE_COMPUTED_GOTO)
 
+/* struct for jump target -> opcode lookup table */
+typedef struct ExprEvalOpLookup
+{
+       const void *opcode;
+       ExprEvalOp op;
+} ExprEvalOpLookup;
+
 /* to make dispatch_table accessible outside ExecInterpExpr() */
 static const void **dispatch_table = NULL;
+/* jump target -> opcode lookup table */
+static ExprEvalOpLookup reverse_dispatch_table[EEOP_LAST];
 
 #define EEO_SWITCH()
 #define EEO_CASE(name)         CASE_##name:
@@ -137,11 +146,8 @@ 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);
@@ -171,6 +177,14 @@ ExecReadyInterpretedExpr(ExprState *state)
        if (state->flags & EEO_FLAG_INTERPRETER_INITIALIZED)
                return;
 
+       /*
+        * First time through, check whether attribute matches Var.  Might not be
+        * ok anymore, due to schema changes. We do that by setting up a callback
+        * that does checking on the first call, which then sets the evalfunc
+        * callback to the actual method of execution.
+        */
+       state->evalfunc = ExecInterpExprStillValid;
+
        /* DIRECT_THREADED should not already be set */
        Assert((state->flags & EEO_FLAG_DIRECT_THREADED) == 0);
 
@@ -192,53 +206,53 @@ ExecReadyInterpretedExpr(ExprState *state)
                ExprEvalOp      step1 = state->steps[1].opcode;
 
                if (step0 == EEOP_INNER_FETCHSOME &&
-                       step1 == EEOP_INNER_VAR_FIRST)
+                       step1 == EEOP_INNER_VAR)
                {
-                       state->evalfunc = ExecJustInnerVarFirst;
+                       state->evalfunc_private = (void *) ExecJustInnerVar;
                        return;
                }
                else if (step0 == EEOP_OUTER_FETCHSOME &&
-                                step1 == EEOP_OUTER_VAR_FIRST)
+                                step1 == EEOP_OUTER_VAR)
                {
-                       state->evalfunc = ExecJustOuterVarFirst;
+                       state->evalfunc_private = (void *) ExecJustOuterVar;
                        return;
                }
                else if (step0 == EEOP_SCAN_FETCHSOME &&
-                                step1 == EEOP_SCAN_VAR_FIRST)
+                                step1 == EEOP_SCAN_VAR)
                {
-                       state->evalfunc = ExecJustScanVarFirst;
+                       state->evalfunc_private = (void *) ExecJustScanVar;
                        return;
                }
                else if (step0 == EEOP_INNER_FETCHSOME &&
                                 step1 == EEOP_ASSIGN_INNER_VAR)
                {
-                       state->evalfunc = ExecJustAssignInnerVar;
+                       state->evalfunc_private = (void *) ExecJustAssignInnerVar;
                        return;
                }
                else if (step0 == EEOP_OUTER_FETCHSOME &&
                                 step1 == EEOP_ASSIGN_OUTER_VAR)
                {
-                       state->evalfunc = ExecJustAssignOuterVar;
+                       state->evalfunc_private = (void *) ExecJustAssignOuterVar;
                        return;
                }
                else if (step0 == EEOP_SCAN_FETCHSOME &&
                                 step1 == EEOP_ASSIGN_SCAN_VAR)
                {
-                       state->evalfunc = ExecJustAssignScanVar;
+                       state->evalfunc_private = (void *) ExecJustAssignScanVar;
                        return;
                }
                else if (step0 == EEOP_CASE_TESTVAL &&
                                 step1 == EEOP_FUNCEXPR_STRICT &&
                                 state->steps[0].d.casetest.value)
                {
-                       state->evalfunc = ExecJustApplyFuncToCase;
+                       state->evalfunc_private = (void *) ExecJustApplyFuncToCase;
                        return;
                }
        }
        else if (state->steps_len == 2 &&
                         state->steps[0].opcode == EEOP_CONST)
        {
-               state->evalfunc = ExecJustConst;
+               state->evalfunc_private = (void *) ExecJustConst;
                return;
        }
 
@@ -262,7 +276,7 @@ ExecReadyInterpretedExpr(ExprState *state)
        }
 #endif                                                 /* EEO_USE_COMPUTED_GOTO */
 
-       state->evalfunc = ExecInterpExpr;
+       state->evalfunc_private = (void *) ExecInterpExpr;
 }
 
 
@@ -293,11 +307,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                &&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,
@@ -420,22 +431,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        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;
@@ -453,18 +448,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        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;
@@ -478,18 +461,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
                        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;
@@ -1556,6 +1527,78 @@ out:
        return state->resvalue;
 }
 
+/*
+ * Expression evaluation callback that performs extra checks before executing
+ * the expression. Declared extern so other methods of execution can use it
+ * too.
+ */
+Datum
+ExecInterpExprStillValid(ExprState *state, ExprContext *econtext, bool *isNull)
+{
+       /*
+        * First time through, check whether attribute matches Var.  Might
+        * not be ok anymore, due to schema changes.
+        */
+       CheckExprStillValid(state, econtext);
+
+       /* skip the check during further executions */
+       state->evalfunc = (ExprStateEvalFunc) state->evalfunc_private;
+
+       /* and actually execute */
+       return state->evalfunc(state, econtext, isNull);
+}
+
+/*
+ * Check that an expression is still valid in the face of potential schema
+ * changes since the plan has been created.
+ */
+void
+CheckExprStillValid(ExprState *state, ExprContext *econtext)
+{
+       int i = 0;
+       TupleTableSlot *innerslot;
+       TupleTableSlot *outerslot;
+       TupleTableSlot *scanslot;
+
+       innerslot = econtext->ecxt_innertuple;
+       outerslot = econtext->ecxt_outertuple;
+       scanslot = econtext->ecxt_scantuple;
+
+       for (i = 0; i < state->steps_len;i++)
+       {
+               ExprEvalStep   *op = &state->steps[i];
+
+               switch (ExecEvalStepOp(state, op))
+               {
+                       case EEOP_INNER_VAR:
+                               {
+                                       int                     attnum = op->d.var.attnum;
+
+                                       CheckVarSlotCompatibility(innerslot, attnum + 1, op->d.var.vartype);
+                                       break;
+                               }
+
+                       case EEOP_OUTER_VAR:
+                               {
+                                       int                     attnum = op->d.var.attnum;
+
+                                       CheckVarSlotCompatibility(outerslot, attnum + 1, op->d.var.vartype);
+                                       break;
+                               }
+
+                       case EEOP_SCAN_VAR:
+                               {
+                                       int                     attnum = op->d.var.attnum;
+
+                                       CheckVarSlotCompatibility(scanslot, attnum + 1, op->d.var.vartype);
+                                       break;
+                               }
+                       default:
+                               break;
+               }
+       }
+}
+
 /*
  * Check whether a user attribute in a slot can be referenced by a Var
  * expression.  This should succeed unless there have been schema changes
@@ -1668,20 +1711,14 @@ ShutdownTupleDescRef(Datum arg)
  * Fast-path functions, for very simple expressions
  */
 
-/* Simple reference to inner Var, first time through */
+/* Simple reference to inner Var */
 static Datum
-ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
+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 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
@@ -1690,34 +1727,6 @@ ExecJustInnerVarFirst(ExprState *state, ExprContext *econtext, bool *isnull)
        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)
@@ -1726,23 +1735,7 @@ ExecJustOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
        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 */
+       /* See comments in ExecJustInnerVar */
        return slot_getattr(slot, attnum, isnull);
 }
 
@@ -1754,7 +1747,7 @@ ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
        int                     attnum = op->d.var.attnum + 1;
        TupleTableSlot *slot = econtext->ecxt_scantuple;
 
-       /* See comments in ExecJustInnerVarFirst */
+       /* See comments in ExecJustInnerVar */
        return slot_getattr(slot, attnum, isnull);
 }
 
@@ -1860,6 +1853,24 @@ ExecJustApplyFuncToCase(ExprState *state, ExprContext *econtext, bool *isnull)
        return d;
 }
 
+#if defined(EEO_USE_COMPUTED_GOTO)
+/*
+ * Comparator used when building address->opcode lookup table for
+ * ExecEvalStepOp() in the threaded dispatch case.
+ */
+static int
+dispatch_compare_ptr(const void* a, const void *b)
+{
+       const ExprEvalOpLookup *la = (const ExprEvalOpLookup *) a;
+       const ExprEvalOpLookup *lb = (const ExprEvalOpLookup *) b;
+
+       if (la->opcode < lb->opcode)
+               return -1;
+       else if (la->opcode > lb->opcode)
+               return 1;
+       return 0;
+}
+#endif
 
 /*
  * Do one-time initialization of interpretation machinery.
@@ -1870,8 +1881,25 @@ ExecInitInterpreter(void)
 #if defined(EEO_USE_COMPUTED_GOTO)
        /* Set up externally-visible pointer to dispatch table */
        if (dispatch_table == NULL)
+       {
+               int                     i;
+
                dispatch_table = (const void **)
                        DatumGetPointer(ExecInterpExpr(NULL, NULL, NULL));
+
+               /* build reverse lookup table */
+               for (i = 0; i < EEOP_LAST; i++)
+               {
+                       reverse_dispatch_table[i].opcode = dispatch_table[i];
+                       reverse_dispatch_table[i].op = (ExprEvalOp) i;
+               }
+
+               /* make it bsearch()able */
+               qsort(reverse_dispatch_table,
+                         EEOP_LAST /* nmembers */,
+                         sizeof(ExprEvalOpLookup),
+                         dispatch_compare_ptr);
+       }
 #endif
 }
 
@@ -1880,10 +1908,6 @@ ExecInitInterpreter(void)
  *
  * When direct-threading is in use, ExprState->opcode isn't easily
  * decipherable. This function returns the appropriate enum member.
- *
- * This currently is only supposed to be used in paths that aren't critical
- * performance-wise.  If that changes, we could add an inverse dispatch_table
- * that's sorted on the address, so a binary search can be performed.
  */
 ExprEvalOp
 ExecEvalStepOp(ExprState *state, ExprEvalStep *op)
@@ -1891,16 +1915,17 @@ ExecEvalStepOp(ExprState *state, ExprEvalStep *op)
 #if defined(EEO_USE_COMPUTED_GOTO)
        if (state->flags & EEO_FLAG_DIRECT_THREADED)
        {
-               int                     i;
-
-               for (i = 0; i < EEOP_LAST; i++)
-               {
-                       if ((void *) op->opcode == dispatch_table[i])
-                       {
-                               return (ExprEvalOp) i;
-                       }
-               }
-               elog(ERROR, "unknown opcode");
+               ExprEvalOpLookup key;
+               ExprEvalOpLookup *res;
+
+               key.opcode =  (void *) op->opcode;
+               res = bsearch(&key,
+                                         reverse_dispatch_table,
+                                         EEOP_LAST /* nmembers */,
+                                         sizeof(ExprEvalOpLookup),
+                                         dispatch_compare_ptr);
+               Assert(res); /* unknown ops shouldn't get looked up */
+               return res->op;
        }
 #endif
        return (ExprEvalOp) op->opcode;
index 080252fad6072dbba168ddcbdb460d23e52a6ac6..511205b5acc0497982e4cda0e3756cb5af7d0143 100644 (file)
@@ -51,12 +51,8 @@ typedef enum ExprEvalOp
        EEOP_SCAN_FETCHSOME,
 
        /* compute non-system Var value */
-       /* "FIRST" variants are used only the first time through */
-       EEOP_INNER_VAR_FIRST,
        EEOP_INNER_VAR,
-       EEOP_OUTER_VAR_FIRST,
        EEOP_OUTER_VAR,
-       EEOP_SCAN_VAR_FIRST,
        EEOP_SCAN_VAR,
 
        /* compute system Var value */
@@ -67,8 +63,11 @@ typedef enum ExprEvalOp
        /* compute wholerow Var */
        EEOP_WHOLEROW,
 
-       /* compute non-system Var value, assign it into ExprState's resultslot */
-       /* (these are not used if _FIRST checks would be needed) */
+       /*
+        * Compute non-system Var value, assign it into ExprState's
+        * resultslot. These are not used if a CheckVarSlotCompatibility() check
+        * would be needed.
+        */
        EEOP_ASSIGN_INNER_VAR,
        EEOP_ASSIGN_OUTER_VAR,
        EEOP_ASSIGN_SCAN_VAR,
@@ -621,6 +620,9 @@ extern void ExprEvalPushStep(ExprState *es, const ExprEvalStep *s);
 extern void ExecReadyInterpretedExpr(ExprState *state);
 extern ExprEvalOp ExecEvalStepOp(ExprState *state, ExprEvalStep *op);
 
+extern Datum ExecInterpExprStillValid(ExprState *state, ExprContext *econtext, bool *isNull);
+extern void CheckExprStillValid(ExprState *state, ExprContext *econtext);
+
 /*
  * Non fast-path execution functions. These are externs instead of statics in
  * execExprInterp.c, because that allows them to be used by other methods of
index c9a5279dc58bcae6986099f607ef89f4786aff97..94351eafaddd7cfa19d6a689a5b5e371b5267669 100644 (file)
@@ -87,6 +87,9 @@ typedef struct ExprState
        /* original expression tree, for debugging only */
        Expr       *expr;
 
+       /* private state for an evalfunc */
+       void       *evalfunc_private;
+
        /*
         * XXX: following fields only needed during "compilation" (ExecInitExpr);
         * could be thrown away afterwards.