Speedup of PL/pgSQL by calling ExecEvalExpr() directly
authorJan Wieck
Wed, 27 Jan 1999 16:15:22 +0000 (16:15 +0000)
committerJan Wieck
Wed, 27 Jan 1999 16:15:22 +0000 (16:15 +0000)
instead of SPI_execp() for simple expressions.

Jan

src/backend/executor/spi.c
src/include/executor/spi.h
src/include/executor/spi_priv.h [new file with mode: 0644]
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/plpgsql.h

index a7358425b47582587b5411624e6b1305ff5e4379..7ca2a6c21e507972d953bbe821171e7d5c9dbfe6 100644 (file)
@@ -3,25 +3,16 @@
  * spi.c--
  *             Server Programming Interface
  *
- * $Id: spi.c,v 1.31 1999/01/27 00:36:21 tgl Exp $
+ * $Id: spi.c,v 1.32 1999/01/27 16:15:20 wieck Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "executor/spi.h"
+#include "executor/spi_priv.h"
 #include "catalog/pg_type.h"
 #include "access/printtup.h"
 #include "fmgr.h"
 
-typedef struct
-{
-   QueryTreeList *qtlist;      /* malloced */
-   uint32      processed;      /* by Executor */
-   SPITupleTable *tuptable;
-   Portal      portal;         /* portal per procedure */
-   MemoryContext savedcxt;
-   CommandId   savedId;
-} _SPI_connection;
-
 static Portal _SPI_portal = (Portal) NULL;
 static _SPI_connection *_SPI_stack = NULL;
 static _SPI_connection *_SPI_current = NULL;
@@ -32,24 +23,12 @@ uint32      SPI_processed = 0;
 SPITupleTable *SPI_tuptable;
 int            SPI_result;
 
-typedef struct
-{
-   QueryTreeList *qtlist;
-   List       *ptlist;
-   int         nargs;
-   Oid        *argtypes;
-} _SPI_plan;
-
 static int _SPI_execute(char *src, int tcount, _SPI_plan *plan);
 static int _SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount);
 
 static int _SPI_execute_plan(_SPI_plan *plan,
                  Datum *Values, char *Nulls, int tcount);
 
-#define _SPI_CPLAN_CURCXT  0
-#define _SPI_CPLAN_PROCXT  1
-#define _SPI_CPLAN_TOPCXT  2
-
 static _SPI_plan *_SPI_copy_plan(_SPI_plan *plan, int location);
 
 static int _SPI_begin_call(bool execmem);
@@ -178,6 +157,18 @@ SPI_finish()
 
 }
 
+void
+SPI_push(void)
+{
+   _SPI_curid++;
+}
+
+void
+SPI_pop(void)
+{
+   _SPI_curid--;
+}
+
 int
 SPI_exec(char *src, int tcount)
 {
index 72b6fe0f1fa1c4cbebc80c097c138e39ad0a336f..cc0ac6c4b7d0c43b6aca3db385401c48928a87c5 100644 (file)
@@ -72,6 +72,8 @@ extern int    SPI_result;
 
 extern int SPI_connect(void);
 extern int SPI_finish(void);
+extern void    SPI_push(void);
+extern void    SPI_pop(void);
 extern int SPI_exec(char *src, int tcount);
 extern int SPI_execp(void *plan, Datum *values, char *Nulls, int tcount);
 extern void *SPI_prepare(char *src, int nargs, Oid *argtypes);
diff --git a/src/include/executor/spi_priv.h b/src/include/executor/spi_priv.h
new file mode 100644 (file)
index 0000000..c0e4492
--- /dev/null
@@ -0,0 +1,38 @@
+/*-------------------------------------------------------------------------
+ *
+ * spi.c--
+ *             Server Programming Interface private declarations
+ *
+ * $Header: /cvsroot/pgsql/src/include/executor/spi_priv.h,v 1.1 1999/01/27 16:15:21 wieck Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SPI_PRIV_H
+#define SPI_PRIV_H
+
+#include "catalog/pg_type.h"
+#include "access/printtup.h"
+
+typedef struct
+{
+   QueryTreeList *qtlist;      /* malloced */
+   uint32      processed;      /* by Executor */
+   SPITupleTable *tuptable;
+   Portal      portal;         /* portal per procedure */
+   MemoryContext savedcxt;
+   CommandId   savedId;
+} _SPI_connection;
+
+typedef struct
+{
+   QueryTreeList *qtlist;
+   List       *ptlist;
+   int         nargs;
+   Oid        *argtypes;
+} _SPI_plan;
+
+#define _SPI_CPLAN_CURCXT  0
+#define _SPI_CPLAN_PROCXT  1
+#define _SPI_CPLAN_TOPCXT  2
+
+#endif /* SPI_PRIV_H */
index 89470729e8ba61414d33ff506e3e6b63712b51e7..30155f7b082d0205233e7540b43afa6d47cb28d2 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.5 1999/01/17 21:53:32 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.6 1999/01/27 16:15:22 wieck Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -48,6 +48,7 @@
 #include "pl.tab.h"
 
 #include "executor/spi.h"
+#include "executor/spi_priv.h"
 #include "commands/trigger.h"
 #include "utils/elog.h"
 #include "utils/builtins.h"
@@ -116,6 +117,16 @@ static int exec_stmt_raise(PLpgSQL_execstate * estate,
 static int exec_stmt_execsql(PLpgSQL_execstate * estate,
                  PLpgSQL_stmt_execsql * stmt);
 
+static void exec_prepare_plan(PLpgSQL_execstate * estate,
+               PLpgSQL_expr * expr);
+static bool exec_simple_check_node(Node * node);
+static void exec_simple_check_plan(PLpgSQL_expr * expr);
+static void exec_eval_clear_fcache(Node *node);
+static Datum exec_eval_simple_expr(PLpgSQL_execstate * estate, 
+               PLpgSQL_expr * expr, 
+               bool *isNull, 
+               Oid *rettype);
+
 static void exec_assign_expr(PLpgSQL_execstate * estate,
                 PLpgSQL_datum * target,
                 PLpgSQL_expr * expr);
@@ -1655,6 +1666,72 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt)
 }
 
 
+/* ----------
+ * Generate a prepared plan
+ * ----------
+ */
+static void
+exec_prepare_plan(PLpgSQL_execstate * estate,
+               PLpgSQL_expr * expr)
+{
+   PLpgSQL_var *var;
+   PLpgSQL_rec *rec;
+   PLpgSQL_recfield *recfield;
+   int         i;
+   int         fno;
+   void       *plan;
+   Oid        *argtypes;
+
+   /* ----------
+    * Setup the argtypes array
+    * ----------
+    */
+   argtypes = malloc(sizeof(Oid *) * (expr->nparams + 1));
+
+   for (i = 0; i < expr->nparams; i++)
+   {
+       switch (estate->datums[expr->params[i]]->dtype)
+       {
+           case PLPGSQL_DTYPE_VAR:
+               var = (PLpgSQL_var *) (estate->datums[expr->params[i]]);
+               argtypes[i] = var->datatype->typoid;
+               break;
+
+           case PLPGSQL_DTYPE_RECFIELD:
+               recfield = (PLpgSQL_recfield *) (estate->datums[expr->params[i]]);
+               rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);
+
+               if (!HeapTupleIsValid(rec->tup))
+                   elog(ERROR, "record %s is unassigned yet", rec->refname);
+               fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+               if (fno == SPI_ERROR_NOATTRIBUTE)
+                   elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+               argtypes[i] = SPI_gettypeid(rec->tupdesc, fno);
+               break;
+
+           case PLPGSQL_DTYPE_TRIGARG:
+               argtypes[i] = (Oid) TEXTOID;
+               break;
+
+           default:
+               elog(ERROR, "unknown parameter dtype %d in exec_run_select()", estate->datums[expr->params[i]]);
+       }
+   }
+
+   /* ----------
+    * Generate and save the plan
+    * ----------
+    */
+   plan = SPI_prepare(expr->query, expr->nparams, argtypes);
+   if (plan == NULL)
+       elog(ERROR, "SPI_prepare() failed on \"%s\"", expr->query);
+   expr->plan = SPI_saveplan(plan);
+   expr->plan_argtypes = argtypes;
+   expr->plan_simple_expr = NULL;
+   exec_simple_check_plan(expr);
+}
+
+
 /* ----------
  * exec_stmt_execsql           Execute an SQL statement not
  *                 returning any data.
@@ -1683,48 +1760,7 @@ exec_stmt_execsql(PLpgSQL_execstate * estate,
     * ----------
     */
    if (expr->plan == NULL)
-   {
-       void       *plan;
-       Oid        *argtypes;
-
-       argtypes = malloc(sizeof(Oid *) * (expr->nparams + 1));
-
-       for (i = 0; i < expr->nparams; i++)
-       {
-           switch (estate->datums[expr->params[i]]->dtype)
-           {
-               case PLPGSQL_DTYPE_VAR:
-                   var = (PLpgSQL_var *) (estate->datums[expr->params[i]]);
-                   argtypes[i] = var->datatype->typoid;
-                   break;
-
-               case PLPGSQL_DTYPE_RECFIELD:
-                   recfield = (PLpgSQL_recfield *) (estate->datums[expr->params[i]]);
-                   rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);
-
-                   if (!HeapTupleIsValid(rec->tup))
-                       elog(ERROR, "record %s is unassigned yet", rec->refname);
-                   fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
-                   if (fno == SPI_ERROR_NOATTRIBUTE)
-                       elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
-                   argtypes[i] = SPI_gettypeid(rec->tupdesc, fno);
-                   break;
-
-               case PLPGSQL_DTYPE_TRIGARG:
-                   argtypes[i] = (Oid) TEXTOID;
-                   break;
-
-               default:
-                   elog(ERROR, "unknown parameter dtype %d in exec_stmt_execsql()", estate->datums[expr->params[i]]->dtype);
-           }
-       }
-
-       plan = SPI_prepare(expr->query, expr->nparams, argtypes);
-       if (plan == NULL)
-           elog(ERROR, "SPI_prepare() failed on \"%s\"", expr->query);
-       expr->plan = SPI_saveplan(plan);
-       expr->plan_argtypes = argtypes;
-   }
+       exec_prepare_plan(estate, expr);
 
    /* ----------
     * Now build up the values and nulls arguments for SPI_execp()
@@ -1987,6 +2023,21 @@ exec_eval_expr(PLpgSQL_execstate * estate,
 {
    int         rc;
 
+   /* ----------
+    * If not already done create a plan for this expression
+    * ----------
+    */
+   if (expr->plan == NULL)
+       exec_prepare_plan(estate, expr);
+
+   /* ----------
+    * If this is a simple expression, bypass SPI and use the
+    * executor directly
+    * ----------
+    */
+   if (expr->plan_simple_expr != NULL)
+       return exec_eval_simple_expr(estate, expr, isNull, rettype);
+
    rc = exec_run_select(estate, expr, 2);
    if (rc != SPI_OK_SELECT)
        elog(ERROR, "query \"%s\" didn't return data", expr->query);
@@ -2045,56 +2096,7 @@ exec_run_select(PLpgSQL_execstate * estate,
     * ----------
     */
    if (expr->plan == NULL)
-   {
-       void       *plan;
-       Oid        *argtypes;
-
-       /* ----------
-        * Setup the argtypes array
-        * ----------
-        */
-       argtypes = malloc(sizeof(Oid *) * (expr->nparams + 1));
-
-       for (i = 0; i < expr->nparams; i++)
-       {
-           switch (estate->datums[expr->params[i]]->dtype)
-           {
-               case PLPGSQL_DTYPE_VAR:
-                   var = (PLpgSQL_var *) (estate->datums[expr->params[i]]);
-                   argtypes[i] = var->datatype->typoid;
-                   break;
-
-               case PLPGSQL_DTYPE_RECFIELD:
-                   recfield = (PLpgSQL_recfield *) (estate->datums[expr->params[i]]);
-                   rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);
-
-                   if (!HeapTupleIsValid(rec->tup))
-                       elog(ERROR, "record %s is unassigned yet", rec->refname);
-                   fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
-                   if (fno == SPI_ERROR_NOATTRIBUTE)
-                       elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
-                   argtypes[i] = SPI_gettypeid(rec->tupdesc, fno);
-                   break;
-
-               case PLPGSQL_DTYPE_TRIGARG:
-                   argtypes[i] = (Oid) TEXTOID;
-                   break;
-
-               default:
-                   elog(ERROR, "unknown parameter dtype %d in exec_run_select()", estate->datums[expr->params[i]]);
-           }
-       }
-
-       /* ----------
-        * Generate and save the plan
-        * ----------
-        */
-       plan = SPI_prepare(expr->query, expr->nparams, argtypes);
-       if (plan == NULL)
-           elog(ERROR, "SPI_prepare() failed on \"%s\"", expr->query);
-       expr->plan = SPI_saveplan(plan);
-       expr->plan_argtypes = argtypes;
-   }
+       exec_prepare_plan(estate, expr);
 
    /* ----------
     * Now build up the values and nulls arguments for SPI_execp()
@@ -2172,6 +2174,130 @@ exec_run_select(PLpgSQL_execstate * estate,
 }
 
 
+/* ----------
+ * exec_eval_simple_expr -     Evaluate a simple expression returning
+ *                             a Datum by directly calling ExecEvalExpr().
+ * ----------
+ */
+static Datum
+exec_eval_simple_expr(PLpgSQL_execstate * estate, 
+                   PLpgSQL_expr * expr, 
+                   bool *isNull, 
+                   Oid *rettype)
+{
+   Datum       retval;
+   PLpgSQL_var *var;
+   PLpgSQL_rec *rec;
+   PLpgSQL_recfield *recfield;
+   PLpgSQL_trigarg *trigarg;
+   int         tgargno;
+   Oid         tgargoid;
+   int         fno;
+   int         i;
+   bool        isnull;
+   bool        isdone;
+   ExprContext *econtext;
+   ParamListInfo paramLI;
+
+   /* ----------
+    * Create a simple expression context to hold the arguments
+    * ----------
+    */
+   econtext = makeNode(ExprContext);
+   paramLI = (ParamListInfo) palloc((expr->nparams + 1) *
+                           sizeof(ParamListInfoData));
+   econtext->ecxt_param_list_info = paramLI;
+
+   /* ----------
+    * Put the parameter values into the parameter list info of
+    * the expression context.
+    * ----------
+    */
+   for (i = 0; i < expr->nparams; i++, paramLI++)
+   {
+       paramLI->kind = PARAM_NUM;
+       paramLI->id   = i + 1;
+
+       switch (estate->datums[expr->params[i]]->dtype)
+       {
+           case PLPGSQL_DTYPE_VAR:
+               var = (PLpgSQL_var *) (estate->datums[expr->params[i]]);
+               paramLI->isnull = var->isnull;
+               paramLI->value = var->value;
+               break;
+
+           case PLPGSQL_DTYPE_RECFIELD:
+               recfield = (PLpgSQL_recfield *) (estate->datums[expr->params[i]]);
+               rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]);
+
+               if (!HeapTupleIsValid(rec->tup))
+                   elog(ERROR, "record %s is unassigned yet", rec->refname);
+               fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
+               if (fno == SPI_ERROR_NOATTRIBUTE)
+                   elog(ERROR, "record %s has no field %s", rec->refname, recfield->fieldname);
+
+               if (expr->plan_argtypes[i] != SPI_gettypeid(rec->tupdesc, fno))
+                   elog(ERROR, "type of %s.%s doesn't match that when preparing the plan", rec->refname, recfield->fieldname);
+
+               paramLI->value = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull);
+               paramLI->isnull = isnull;
+               break;
+
+           case PLPGSQL_DTYPE_TRIGARG:
+               trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]);
+               tgargno = (int) exec_eval_expr(estate, trigarg->argnum,
+                                              &isnull, &tgargoid);
+               if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
+               {
+                   paramLI->value = 0;
+                   paramLI->isnull = TRUE;
+               }
+               else
+               {
+                   paramLI->value = estate->trig_argv[tgargno];
+                   paramLI->isnull = FALSE;
+               }
+               break;
+
+           default:
+               elog(ERROR, "unknown parameter dtype %d in exec_eval_simple_expr()", estate->datums[expr->params[i]]->dtype);
+       }
+   }
+   paramLI->kind = PARAM_INVALID;
+
+   /* ----------
+    * Initialize things
+    * ----------
+    */
+   *isNull = FALSE;
+   *rettype = expr->plan_simple_type;
+   isdone = FALSE;
+
+   /* ----------
+    * Clear the function cache
+    * ----------
+    */
+   exec_eval_clear_fcache(expr->plan_simple_expr);
+
+   /* ----------
+    * Now call the executor to evaluate the expression
+    * ----------
+    */
+   SPI_push();
+   retval = ExecEvalExpr(expr->plan_simple_expr,
+                           econtext,
+                           isNull,
+                           &isdone);
+   SPI_pop();
+
+   /* ----------
+    * That's it.
+    * ----------
+    */
+   return retval;
+}
+
+
 /* ----------
  * exec_move_row           Move one tuples values into a
  *                 record or row
@@ -2296,6 +2422,169 @@ exec_cast_value(Datum value, Oid valtype,
 }
 
 
+/* ----------
+ * exec_simple_check_node -        Recursively check if an expression
+ *                             is made only of simple things we can
+ *                             hand out directly to ExecEvalExpr()
+ *                             instead of calling SPI.
+ * ----------
+ */
+static bool
+exec_simple_check_node(Node * node)
+{
+   switch (nodeTag(node))
+   {
+       case T_Expr:    {
+                           Expr    *expr = (Expr *)node;
+                           List    *l;
+
+                           switch (expr->opType)
+                           {
+                               case OP_EXPR:
+                               case FUNC_EXPR:
+                               case OR_EXPR:
+                               case AND_EXPR:
+                               case NOT_EXPR:  break;
+
+                               default:        return FALSE;
+                           }
+
+                           foreach (l, expr->args)
+                           {
+                               if (!exec_simple_check_node(lfirst(l)))
+                                   return FALSE;
+                           }
+
+                           return TRUE;
+                       }
+
+       case T_Param:   return TRUE;
+
+       case T_Const:   return TRUE;
+
+       default:        return FALSE;
+   }
+}
+
+
+/* ----------
+ * exec_simple_check_plan -        Check if a plan is simple enough to
+ *                             be evaluated by ExecEvalExpr() instead
+ *                             of SPI.
+ * ----------
+ */
+static void
+exec_simple_check_plan(PLpgSQL_expr * expr)
+{
+   _SPI_plan   *spi_plan = (_SPI_plan *)expr->plan;
+   Plan        *plan;
+   TargetEntry *tle;
+
+   expr->plan_simple_expr = NULL;
+
+   /* ----------
+    * 1. We can only evaluate queries that resulted in one single
+    *    execution plan
+    * ----------
+    */
+   if (spi_plan->ptlist == NULL || length(spi_plan->ptlist) != 1)
+       return;
+
+   plan = (Plan *)lfirst(spi_plan->ptlist);
+
+   /* ----------
+    * 2. It must be a RESULT plan --> no scan's required
+    * ----------
+    */
+   if (nodeTag(plan) != T_Result)
+       return;
+
+   /* ----------
+    * 3. The plan must have a single attribute as result
+    * ----------
+    */
+   if (length(plan->targetlist) != 1)
+       return;
+
+   /* ----------
+    * 4. Don't know if all these can break us, so let SPI handle
+    *    those plans
+    * ----------
+    */
+   if (plan->qual != NULL || plan->lefttree != NULL || plan->righttree != NULL)
+       return;
+
+   /* ----------
+    * 5. Check that all the nodes in the expression are one of
+    *    Expr, Param or Const.
+    * ----------
+    */
+   tle = (TargetEntry *)lfirst(plan->targetlist);
+   if (!exec_simple_check_node(tle->expr))
+       return;
+
+   /* ----------
+    * Yes - this is a simple expression. Remember the expression
+    * and the return type
+    * ----------
+    */
+   expr->plan_simple_expr = tle->expr;
+
+   switch (nodeTag(tle->expr))
+   {
+       case T_Expr:    expr->plan_simple_type = 
+                                   ((Expr *)(tle->expr))->typeOid;
+                       break;
+
+       case T_Param:   expr->plan_simple_type = 
+                                   ((Param *)(tle->expr))->paramtype;
+                       break;
+
+       case T_Const:   expr->plan_simple_type =
+                                   ((Const *)(tle->expr))->consttype;
+                       break;
+
+       default:        expr->plan_simple_type = InvalidOid;
+   }
+
+   return;
+}
+
+
+/* ----------
+ * exec_eval_clear_fcache -        The function cache is palloc()'d by
+ *                             the executor, and contains call specific
+ *                             data based on the arguments. This has
+ *                             to be recalculated.
+ * ----------
+ */
+static void
+exec_eval_clear_fcache(Node *node)
+{
+   Expr    *expr;
+   List    *l;
+
+   if (nodeTag(node) != T_Expr)
+       return;
+
+   expr = (Expr *)node;
+
+   switch(expr->opType)
+   {
+       case OP_EXPR:   ((Oper *)(expr->oper))->op_fcache = NULL;
+                       break;
+
+       case FUNC_EXPR: ((Func *)(expr->oper))->func_fcache = NULL;
+                       break;
+
+       default:        break;
+   }
+
+   foreach (l, expr->args)
+       exec_eval_clear_fcache(lfirst(l));
+}
+
+
 /* ----------
  * exec_set_found          Set the global found variable
  *                 to true/false
index 281cdc64a1c8efd0e601cdcc4771ee41695f001f..2d0a57cd2d04324d6f7c7e35339a651b21775d8c 100644 (file)
@@ -3,7 +3,7 @@
  *           procedural language
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.2 1998/09/01 04:40:27 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.3 1999/01/27 16:15:22 wieck Exp $
  *
  *   This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -143,6 +143,8 @@ typedef struct
    int         exprno;
    char       *query;
    void       *plan;
+   Node       *plan_simple_expr;
+   Oid         plan_simple_type;
    Oid        *plan_argtypes;
    int         nparams;
    int         params[1];