From 28d8b42ca5de929fc6099dff2ac5ef7129a1d722 Mon Sep 17 00:00:00 2001 From: Jan Wieck Date: Wed, 27 Jan 1999 16:15:22 +0000 Subject: [PATCH] Speedup of PL/pgSQL by calling ExecEvalExpr() directly instead of SPI_execp() for simple expressions. Jan --- src/backend/executor/spi.c | 37 +-- src/include/executor/spi.h | 2 + src/include/executor/spi_priv.h | 38 +++ src/pl/plpgsql/src/pl_exec.c | 475 +++++++++++++++++++++++++------- src/pl/plpgsql/src/plpgsql.h | 4 +- 5 files changed, 439 insertions(+), 117 deletions(-) create mode 100644 src/include/executor/spi_priv.h diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index a7358425b4..7ca2a6c21e 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -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) { diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 72b6fe0f1f..cc0ac6c4b7 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -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 index 0000000000..c0e44921f1 --- /dev/null +++ b/src/include/executor/spi_priv.h @@ -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 */ diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 89470729e8..30155f7b08 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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 diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 281cdc64a1..2d0a57cd2d 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -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]; -- GitLab