/********************************************************************** * pl_exec.c - Executor for the PL/pgSQL * procedural language * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.76 2002/12/17 15:45:01 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * * The author hereby grants permission to use, copy, modify, * distribute, and license this software and its documentation * for any purpose, provided that existing copyright notices are * retained in all copies and that this notice is included * verbatim in any distributions. No written agreement, license, * or royalty fee is required for any of the authorized uses. * Modifications to this software may be copyrighted by their * author and need not follow the licensing terms described * here, provided that the new terms are clearly indicated on * the first page of each file where they apply. * * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN * IF THE AUTHOR HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON * AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE NO * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, * ENHANCEMENTS, OR MODIFICATIONS. * **********************************************************************/ #include "plpgsql.h" #include "pl.tab.h" #include #include #include "access/heapam.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/spi_priv.h" #include "funcapi.h" #include "optimizer/clauses.h" #include "parser/parse_expr.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" static PLpgSQL_function *error_info_func = NULL; static PLpgSQL_stmt *error_info_stmt = NULL; static char *error_info_text = NULL; /************************************************************ * Local function forward declarations ************************************************************/ static PLpgSQL_var *copy_var(PLpgSQL_var * var); static PLpgSQL_rec *copy_rec(PLpgSQL_rec * rec); static int exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block); static int exec_stmts(PLpgSQL_execstate * estate, PLpgSQL_stmts * stmts); static int exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt); static int exec_stmt_assign(PLpgSQL_execstate * estate, PLpgSQL_stmt_assign * stmt); static int exec_stmt_perform(PLpgSQL_execstate * estate, PLpgSQL_stmt_perform * stmt); static int exec_stmt_getdiag(PLpgSQL_execstate * estate, PLpgSQL_stmt_getdiag * stmt); static int exec_stmt_if(PLpgSQL_execstate * estate, PLpgSQL_stmt_if * stmt); static int exec_stmt_loop(PLpgSQL_execstate * estate, PLpgSQL_stmt_loop * stmt); static int exec_stmt_while(PLpgSQL_execstate * estate, PLpgSQL_stmt_while * stmt); static int exec_stmt_fori(PLpgSQL_execstate * estate, PLpgSQL_stmt_fori * stmt); static int exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt); static int exec_stmt_select(PLpgSQL_execstate * estate, PLpgSQL_stmt_select * stmt); static int exec_stmt_open(PLpgSQL_execstate * estate, PLpgSQL_stmt_open * stmt); static int exec_stmt_fetch(PLpgSQL_execstate * estate, PLpgSQL_stmt_fetch * stmt); static int exec_stmt_close(PLpgSQL_execstate * estate, PLpgSQL_stmt_close * stmt); static int exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt); static int exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt); static int exec_stmt_return_next(PLpgSQL_execstate * estate, PLpgSQL_stmt_return_next * stmt); static int exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt); static int exec_stmt_execsql(PLpgSQL_execstate * estate, PLpgSQL_stmt_execsql * stmt); static int exec_stmt_dynexecute(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynexecute * stmt); static int exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt); static void plpgsql_estate_setup(PLpgSQL_execstate * estate, PLpgSQL_function * func, ReturnSetInfo *rsi); static void exec_eval_cleanup(PLpgSQL_execstate * estate); 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 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); static void exec_assign_value(PLpgSQL_execstate * estate, PLpgSQL_datum * target, Datum value, Oid valtype, bool *isNull); static Datum exec_eval_expr(PLpgSQL_execstate * estate, PLpgSQL_expr * expr, bool *isNull, Oid *rettype); static int exec_run_select(PLpgSQL_execstate * estate, PLpgSQL_expr * expr, int maxtuples, Portal *portalP); static void exec_move_row(PLpgSQL_execstate * estate, PLpgSQL_rec * rec, PLpgSQL_row * row, HeapTuple tup, TupleDesc tupdesc); static Datum exec_cast_value(Datum value, Oid valtype, Oid reqtype, FmgrInfo *reqinput, Oid reqtypelem, int32 reqtypmod, bool *isnull); static void exec_init_tuple_store(PLpgSQL_execstate * estate); static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2); static void exec_set_found(PLpgSQL_execstate * estate, bool state); /* ---------- * plpgsql_exec_function Called by the call handler for * function execution. * ---------- */ Datum plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) { PLpgSQL_execstate estate; int i; sigjmp_buf save_restart; PLpgSQL_function *save_efunc; PLpgSQL_stmt *save_estmt; char *save_etext; /* * Setup debug error info and catch elog() */ save_efunc = error_info_func; save_estmt = error_info_stmt; save_etext = error_info_text; error_info_func = func; error_info_stmt = NULL; error_info_text = "while initialization of execution state"; memcpy(&save_restart, &Warn_restart, sizeof(save_restart)); if (sigsetjmp(Warn_restart, 1) != 0) { memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); /* * If we are the first of cascaded error catchings, print where * this happened */ if (error_info_func != NULL) { elog(WARNING, "Error occurred while executing PL/pgSQL function %s", error_info_func->fn_name); if (error_info_stmt != NULL) elog(WARNING, "line %d at %s", error_info_stmt->lineno, plpgsql_stmt_typename(error_info_stmt)); else if (error_info_text != NULL) elog(WARNING, "%s", error_info_text); else elog(WARNING, "no more error information available"); error_info_func = NULL; error_info_stmt = NULL; error_info_text = NULL; } siglongjmp(Warn_restart, 1); } /* * Setup the execution state */ plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo); /* * Make local execution copies of all the datums */ for (i = 0; i < func->ndatums; i++) { switch (func->datums[i]->dtype) { case PLPGSQL_DTYPE_VAR: estate.datums[i] = (PLpgSQL_datum *) copy_var((PLpgSQL_var *) (func->datums[i])); break; case PLPGSQL_DTYPE_REC: estate.datums[i] = (PLpgSQL_datum *) copy_rec((PLpgSQL_rec *) (func->datums[i])); break; case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_RECFIELD: estate.datums[i] = func->datums[i]; break; default: elog(ERROR, "unknown dtype %d in plpgsql_exec_function()", func->datums[i]->dtype); } } /* * Put the actual call argument values into the variables */ error_info_text = "while putting call arguments to local variables"; for (i = 0; i < func->fn_nargs; i++) { int n = func->fn_argvarnos[i]; switch (estate.datums[n]->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[n]; var->value = fcinfo->arg[i]; var->isnull = fcinfo->argnull[i]; var->freeval = false; } break; case PLPGSQL_DTYPE_ROW: { PLpgSQL_row *row = (PLpgSQL_row *) estate.datums[n]; TupleTableSlot *slot = (TupleTableSlot *) fcinfo->arg[i]; HeapTuple tup; TupleDesc tupdesc; Assert(slot != NULL && !fcinfo->argnull[i]); tup = slot->val; tupdesc = slot->ttc_tupleDescriptor; exec_move_row(&estate, NULL, row, tup, tupdesc); } break; default: elog(ERROR, "unknown dtype %d in plpgsql_exec_function()", func->datums[i]->dtype); } } /* * Initialize the other variables to NULL values for now. The default * values are set when the blocks are entered. */ error_info_text = "while initializing local variables to NULL"; for (i = estate.found_varno; i < estate.ndatums; i++) { switch (estate.datums[i]->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i]; var->value = 0; var->isnull = true; var->freeval = false; } break; case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_RECFIELD: break; default: elog(ERROR, "unknown dtype %d in plpgsql_exec_function()", func->datums[i]->dtype); } } /* * Set the magic variable FOUND to false */ exec_set_found(&estate, false); /* * Now call the toplevel block of statements */ error_info_text = NULL; error_info_stmt = (PLpgSQL_stmt *) (func->action); if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN) { error_info_stmt = NULL; error_info_text = "at END of toplevel PL block"; elog(ERROR, "control reaches end of function without RETURN"); } /* * We got a return value - process it */ error_info_stmt = NULL; error_info_text = "while casting return value to function's return type"; fcinfo->isnull = estate.retisnull; if (estate.retisset) { ReturnSetInfo *rsi = estate.rsi; /* Check caller can handle a set result */ if (!rsi || !IsA(rsi, ReturnSetInfo) || (rsi->allowedModes & SFRM_Materialize) == 0) elog(ERROR, "Set-valued function called in context that cannot accept a set"); rsi->returnMode = SFRM_Materialize; /* If we produced any tuples, send back the result */ if (estate.tuple_store) { MemoryContext oldcxt; oldcxt = MemoryContextSwitchTo(estate.tuple_store_cxt); tuplestore_donestoring(estate.tuple_store); rsi->setResult = estate.tuple_store; if (estate.rettupdesc) rsi->setDesc = CreateTupleDescCopy(estate.rettupdesc); MemoryContextSwitchTo(oldcxt); } estate.retval = (Datum) 0; fcinfo->isnull = true; } else if (!estate.retisnull) { if (estate.retistuple) { /* Copy tuple to upper executor memory */ /* Here we need to return a TupleTableSlot not just a tuple */ estate.retval = (Datum) SPI_copytupleintoslot((HeapTuple) (estate.retval), estate.rettupdesc); } else { /* Cast value to proper type */ estate.retval = exec_cast_value(estate.retval, estate.rettype, func->fn_rettype, &(func->fn_retinput), func->fn_rettypelem, -1, &fcinfo->isnull); /* * If the functions return type isn't by value, copy the value * into upper executor memory context. */ if (!fcinfo->isnull && !func->fn_retbyval) { Size len; void *tmp; len = datumGetSize(estate.retval, false, func->fn_rettyplen); tmp = (void *) SPI_palloc(len); memcpy(tmp, DatumGetPointer(estate.retval), len); estate.retval = PointerGetDatum(tmp); } } } /* Clean up any leftover temporary memory */ exec_eval_cleanup(&estate); /* * Restore the previous error info and elog() jump target */ error_info_func = save_efunc; error_info_stmt = save_estmt; error_info_text = save_etext; memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); /* * Return the functions result */ return estate.retval; } /* ---------- * plpgsql_exec_trigger Called by the call handler for * trigger execution. * ---------- */ HeapTuple plpgsql_exec_trigger(PLpgSQL_function * func, TriggerData *trigdata) { PLpgSQL_execstate estate; int i; sigjmp_buf save_restart; PLpgSQL_function *save_efunc; PLpgSQL_stmt *save_estmt; char *save_etext; PLpgSQL_var *var; PLpgSQL_rec *rec_new, *rec_old; HeapTuple rettup; /* * Setup debug error info and catch elog() */ save_efunc = error_info_func; save_estmt = error_info_stmt; save_etext = error_info_text; error_info_func = func; error_info_stmt = NULL; error_info_text = "while initialization of execution state"; memcpy(&save_restart, &Warn_restart, sizeof(save_restart)); if (sigsetjmp(Warn_restart, 1) != 0) { memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); /* * If we are the first of cascaded error catchings, print where * this happened */ if (error_info_func != NULL) { elog(WARNING, "Error occurred while executing PL/pgSQL function %s", error_info_func->fn_name); if (error_info_stmt != NULL) elog(WARNING, "line %d at %s", error_info_stmt->lineno, plpgsql_stmt_typename(error_info_stmt)); else if (error_info_text != NULL) elog(WARNING, "%s", error_info_text); else elog(WARNING, "no more error information available"); error_info_func = NULL; error_info_stmt = NULL; error_info_text = NULL; } siglongjmp(Warn_restart, 1); } /* * Setup the execution state */ plpgsql_estate_setup(&estate, func, NULL); /* * Make local execution copies of all the datums */ for (i = 0; i < func->ndatums; i++) { switch (func->datums[i]->dtype) { case PLPGSQL_DTYPE_VAR: estate.datums[i] = (PLpgSQL_datum *) copy_var((PLpgSQL_var *) (func->datums[i])); break; case PLPGSQL_DTYPE_REC: estate.datums[i] = (PLpgSQL_datum *) copy_rec((PLpgSQL_rec *) (func->datums[i])); break; case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_TRIGARG: estate.datums[i] = func->datums[i]; break; default: elog(ERROR, "unknown dtype %d in plpgsql_exec_function()", func->datums[i]->dtype); } } /* * Put the OLD and NEW tuples into record variables */ rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]); rec_new->freetup = false; rec_new->freetupdesc = false; rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]); rec_old->freetup = false; rec_old->freetupdesc = false; if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) { /* * Per-statement triggers don't use OLD/NEW variables */ rec_new->tup = NULL; rec_new->tupdesc = NULL; rec_old->tup = NULL; rec_old->tupdesc = NULL; } else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) { rec_new->tup = trigdata->tg_trigtuple; rec_new->tupdesc = trigdata->tg_relation->rd_att; rec_old->tup = NULL; rec_old->tupdesc = NULL; } else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) { rec_new->tup = trigdata->tg_newtuple; rec_new->tupdesc = trigdata->tg_relation->rd_att; rec_old->tup = trigdata->tg_trigtuple; rec_old->tupdesc = trigdata->tg_relation->rd_att; } else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) { rec_new->tup = NULL; rec_new->tupdesc = NULL; rec_old->tup = trigdata->tg_trigtuple; rec_old->tupdesc = trigdata->tg_relation->rd_att; } else elog(ERROR, "Unknown trigger action: not INSERT, DELETE, or UPDATE"); /* * Assign the special tg_ variables */ var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]); var->isnull = false; var->freeval = false; if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT")); else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE")); else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE")); else elog(ERROR, "Unknown trigger action: not INSERT, DELETE, or UPDATE"); var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]); var->isnull = false; var->freeval = true; var->value = DirectFunctionCall1(namein, CStringGetDatum(trigdata->tg_trigger->tgname)); var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]); var->isnull = false; var->freeval = true; if (TRIGGER_FIRED_BEFORE(trigdata->tg_event)) var->value = DirectFunctionCall1(textin, CStringGetDatum("BEFORE")); else if (TRIGGER_FIRED_AFTER(trigdata->tg_event)) var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER")); else elog(ERROR, "Unknown trigger execution time: not BEFORE or AFTER"); var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]); var->isnull = false; var->freeval = true; if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) var->value = DirectFunctionCall1(textin, CStringGetDatum("ROW")); else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT")); else elog(ERROR, "Unknown trigger event type: not ROW or STATEMENT"); var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]); var->isnull = false; var->freeval = false; var->value = ObjectIdGetDatum(trigdata->tg_relation->rd_id); var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]); var->isnull = false; var->freeval = true; var->value = DirectFunctionCall1(namein, CStringGetDatum(RelationGetRelationName(trigdata->tg_relation))); var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]); var->isnull = false; var->freeval = false; var->value = Int16GetDatum(trigdata->tg_trigger->tgnargs); /* * Put the actual call argument values into the special execution * state variables */ error_info_text = "while putting call arguments to local variables"; estate.trig_nargs = trigdata->tg_trigger->tgnargs; if (estate.trig_nargs == 0) estate.trig_argv = NULL; else { estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs); for (i = 0; i < trigdata->tg_trigger->tgnargs; i++) estate.trig_argv[i] = DirectFunctionCall1(textin, CStringGetDatum(trigdata->tg_trigger->tgargs[i])); } /* * Initialize the other variables to NULL values for now. The default * values are set when the blocks are entered. */ error_info_text = "while initializing local variables to NULL"; for (i = estate.found_varno; i < estate.ndatums; i++) { switch (estate.datums[i]->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i]; var->value = 0; var->isnull = true; var->freeval = false; } break; case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_TRIGARG: break; default: elog(ERROR, "unknown dtype %d in plpgsql_exec_trigger()", func->datums[i]->dtype); } } /* * Set the magic variable FOUND to false */ exec_set_found(&estate, false); /* * Now call the toplevel block of statements */ error_info_text = NULL; error_info_stmt = (PLpgSQL_stmt *) (func->action); if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN) { error_info_stmt = NULL; error_info_text = "at END of toplevel PL block"; elog(ERROR, "control reaches end of trigger procedure without RETURN"); } if (estate.retisset) elog(ERROR, "trigger procedure cannot return a set"); /* * Check that the returned tuple structure has the same attributes, * the relation that fired the trigger has. A per-statement trigger * always needs to return NULL, so we ignore any return value the * function itself produces (XXX: is this a good idea?) * * XXX This way it is possible, that the trigger returns a tuple where * attributes don't have the correct atttypmod's length. It's up to * the trigger's programmer to ensure that this doesn't happen. Jan */ if (estate.retisnull || TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) rettup = NULL; else { if (!compatible_tupdesc(estate.rettupdesc, trigdata->tg_relation->rd_att)) elog(ERROR, "returned tuple structure doesn't match table of trigger event"); /* Copy tuple to upper executor memory */ rettup = SPI_copytuple((HeapTuple) (estate.retval)); } /* Clean up any leftover temporary memory */ exec_eval_cleanup(&estate); /* * Restore the previous error info and elog() jump target */ error_info_func = save_efunc; error_info_stmt = save_estmt; error_info_text = save_etext; memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); /* * Return the triggers result */ return rettup; } /* ---------- * Support functions for copying local execution variables * ---------- */ static PLpgSQL_var * copy_var(PLpgSQL_var * var) { PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var)); memcpy(new, var, sizeof(PLpgSQL_var)); new->freeval = false; return new; } static PLpgSQL_rec * copy_rec(PLpgSQL_rec * rec) { PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec)); memcpy(new, rec, sizeof(PLpgSQL_rec)); new->tup = NULL; new->tupdesc = NULL; new->freetup = false; new->freetupdesc = false; return new; } /* ---------- * exec_stmt_block Execute a block of statements * ---------- */ static int exec_stmt_block(PLpgSQL_execstate * estate, PLpgSQL_stmt_block * block) { int rc; int i; int n; /* * First initialize all variables declared in this block */ for (i = 0; i < block->n_initvars; i++) { n = block->initvarnos[i]; switch (estate->datums[n]->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]); if (var->freeval) { pfree((void *) (var->value)); var->freeval = false; } if (!var->isconst || var->isnull) { if (var->default_val == NULL) { var->value = (Datum) 0; var->isnull = true; if (var->notnull) elog(ERROR, "variable '%s' declared NOT NULL cannot default to NULL", var->refname); } else { exec_assign_expr(estate, (PLpgSQL_datum *) var, var->default_val); } } } break; case PLPGSQL_DTYPE_REC: { PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[n]); if (rec->freetup) { heap_freetuple(rec->tup); FreeTupleDesc(rec->tupdesc); rec->freetup = false; } rec->tup = NULL; rec->tupdesc = NULL; } break; case PLPGSQL_DTYPE_RECFIELD: break; default: elog(ERROR, "unknown dtype %d in exec_stmt_block()", estate->datums[n]->dtype); } } /* * Execute the statements in the block's body */ rc = exec_stmts(estate, block->body); /* * Handle the return code. */ switch (rc) { case PLPGSQL_RC_OK: return PLPGSQL_RC_OK; case PLPGSQL_RC_EXIT: if (estate->exitlabel == NULL) return PLPGSQL_RC_OK; if (block->label == NULL) return PLPGSQL_RC_EXIT; if (strcmp(block->label, estate->exitlabel)) return PLPGSQL_RC_EXIT; estate->exitlabel = NULL; return PLPGSQL_RC_OK; case PLPGSQL_RC_RETURN: return PLPGSQL_RC_RETURN; default: elog(ERROR, "unknown rc %d from exec_stmt()", rc); } return PLPGSQL_RC_OK; } /* ---------- * exec_stmts Iterate over a list of statements * as long as their return code is OK * ---------- */ static int exec_stmts(PLpgSQL_execstate * estate, PLpgSQL_stmts * stmts) { int rc; int i; for (i = 0; i < stmts->stmts_used; i++) { rc = exec_stmt(estate, (PLpgSQL_stmt *) (stmts->stmts[i])); if (rc != PLPGSQL_RC_OK) return rc; } return PLPGSQL_RC_OK; } /* ---------- * exec_stmt Distribute one statement to the statements * type specific execution function. * ---------- */ static int exec_stmt(PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt) { PLpgSQL_stmt *save_estmt; int rc = -1; save_estmt = error_info_stmt; error_info_stmt = stmt; CHECK_FOR_INTERRUPTS(); switch (stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt); break; case PLPGSQL_STMT_ASSIGN: rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *) stmt); break; case PLPGSQL_STMT_PERFORM: rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt); break; case PLPGSQL_STMT_GETDIAG: rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt); break; case PLPGSQL_STMT_IF: rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt); break; case PLPGSQL_STMT_LOOP: rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt); break; case PLPGSQL_STMT_WHILE: rc = exec_stmt_while(estate, (PLpgSQL_stmt_while *) stmt); break; case PLPGSQL_STMT_FORI: rc = exec_stmt_fori(estate, (PLpgSQL_stmt_fori *) stmt); break; case PLPGSQL_STMT_FORS: rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt); break; case PLPGSQL_STMT_SELECT: rc = exec_stmt_select(estate, (PLpgSQL_stmt_select *) stmt); break; case PLPGSQL_STMT_EXIT: rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt); break; case PLPGSQL_STMT_RETURN: rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt); break; case PLPGSQL_STMT_RETURN_NEXT: rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt); break; case PLPGSQL_STMT_RAISE: rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt); break; case PLPGSQL_STMT_EXECSQL: rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt); break; case PLPGSQL_STMT_DYNEXECUTE: rc = exec_stmt_dynexecute(estate, (PLpgSQL_stmt_dynexecute *) stmt); break; case PLPGSQL_STMT_DYNFORS: rc = exec_stmt_dynfors(estate, (PLpgSQL_stmt_dynfors *) stmt); break; case PLPGSQL_STMT_OPEN: rc = exec_stmt_open(estate, (PLpgSQL_stmt_open *) stmt); break; case PLPGSQL_STMT_FETCH: rc = exec_stmt_fetch(estate, (PLpgSQL_stmt_fetch *) stmt); break; case PLPGSQL_STMT_CLOSE: rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt); break; default: error_info_stmt = save_estmt; elog(ERROR, "unknown cmdtype %d in exec_stmt", stmt->cmd_type); } error_info_stmt = save_estmt; return rc; } /* ---------- * exec_stmt_assign Evaluate an expression and * put the result into a variable. * ---------- */ static int exec_stmt_assign(PLpgSQL_execstate * estate, PLpgSQL_stmt_assign * stmt) { Assert(stmt->varno >= 0); exec_assign_expr(estate, estate->datums[stmt->varno], stmt->expr); return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_perform Evaluate query and discard result (but set * FOUND depending on whether at least one row * was returned). * ---------- */ static int exec_stmt_perform(PLpgSQL_execstate * estate, PLpgSQL_stmt_perform * stmt) { PLpgSQL_expr *expr = stmt->expr; int rc; /* * If not already done create a plan for this expression */ if (expr->plan == NULL) exec_prepare_plan(estate, expr); rc = exec_run_select(estate, expr, 0, NULL); if (rc != SPI_OK_SELECT) elog(ERROR, "query \"%s\" didn't return data", expr->query); exec_set_found(estate, (estate->eval_processed != 0)); exec_eval_cleanup(estate); return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_getdiag Put internal PG information into * specified variables. * ---------- */ static int exec_stmt_getdiag(PLpgSQL_execstate * estate, PLpgSQL_stmt_getdiag * stmt) { int i; PLpgSQL_datum *var; bool isnull = false; for (i = 0; i < stmt->ndtitems; i++) { PLpgSQL_diag_item *dtitem = &stmt->dtitems[i]; if (dtitem->target <= 0) continue; var = (estate->datums[dtitem->target]); if (var == NULL) continue; switch (dtitem->item) { case PLPGSQL_GETDIAG_ROW_COUNT: exec_assign_value(estate, var, UInt32GetDatum(estate->eval_processed), INT4OID, &isnull); break; case PLPGSQL_GETDIAG_RESULT_OID: exec_assign_value(estate, var, ObjectIdGetDatum(estate->eval_lastoid), OIDOID, &isnull); break; default: elog(ERROR, "unknown attribute request %d in get_diagnostic", dtitem->item); } } return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_if Evaluate a bool expression and * execute the true or false body * conditionally. * ---------- */ static int exec_stmt_if(PLpgSQL_execstate * estate, PLpgSQL_stmt_if * stmt) { Datum value; Oid valtype; bool isnull = false; value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype); exec_eval_cleanup(estate); if (!isnull && DatumGetBool(value)) { if (stmt->true_body != NULL) return exec_stmts(estate, stmt->true_body); } else { if (stmt->false_body != NULL) return exec_stmts(estate, stmt->false_body); } return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_loop Loop over statements until * an exit occurs. * ---------- */ static int exec_stmt_loop(PLpgSQL_execstate * estate, PLpgSQL_stmt_loop * stmt) { int rc; for (;;) { rc = exec_stmts(estate, stmt->body); switch (rc) { case PLPGSQL_RC_OK: break; case PLPGSQL_RC_EXIT: if (estate->exitlabel == NULL) return PLPGSQL_RC_OK; if (stmt->label == NULL) return PLPGSQL_RC_EXIT; if (strcmp(stmt->label, estate->exitlabel)) return PLPGSQL_RC_EXIT; estate->exitlabel = NULL; return PLPGSQL_RC_OK; case PLPGSQL_RC_RETURN: return PLPGSQL_RC_RETURN; default: elog(ERROR, "unknown rc %d from exec_stmts()", rc); } } return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_while Loop over statements as long * as an expression evaluates to * true or an exit occurs. * ---------- */ static int exec_stmt_while(PLpgSQL_execstate * estate, PLpgSQL_stmt_while * stmt) { Datum value; Oid valtype; bool isnull = false; int rc; for (;;) { value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype); exec_eval_cleanup(estate); if (isnull || !DatumGetBool(value)) break; rc = exec_stmts(estate, stmt->body); switch (rc) { case PLPGSQL_RC_OK: break; case PLPGSQL_RC_EXIT: if (estate->exitlabel == NULL) return PLPGSQL_RC_OK; if (stmt->label == NULL) return PLPGSQL_RC_EXIT; if (strcmp(stmt->label, estate->exitlabel)) return PLPGSQL_RC_EXIT; estate->exitlabel = NULL; return PLPGSQL_RC_OK; case PLPGSQL_RC_RETURN: return PLPGSQL_RC_RETURN; default: elog(ERROR, "unknown rc %d from exec_stmts()", rc); } } return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_fori Iterate an integer variable * from a lower to an upper value. * Loop can be left with exit. * ---------- */ static int exec_stmt_fori(PLpgSQL_execstate * estate, PLpgSQL_stmt_fori * stmt) { PLpgSQL_var *var; Datum value; Oid valtype; bool isnull = false; bool found = false; int rc = PLPGSQL_RC_OK; var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]); /* * Get the value of the lower bound into the loop var */ value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype); value = exec_cast_value(value, valtype, var->datatype->typoid, &(var->datatype->typinput), var->datatype->typelem, var->datatype->atttypmod, &isnull); if (isnull) elog(ERROR, "lower bound of FOR loop cannot be NULL"); var->value = value; var->isnull = false; exec_eval_cleanup(estate); /* * Get the value of the upper bound */ value = exec_eval_expr(estate, stmt->upper, &isnull, &valtype); value = exec_cast_value(value, valtype, var->datatype->typoid, &(var->datatype->typinput), var->datatype->typelem, var->datatype->atttypmod, &isnull); if (isnull) elog(ERROR, "upper bound of FOR loop cannot be NULL"); exec_eval_cleanup(estate); /* * Now do the loop */ for (;;) { /* * Check bounds */ if (stmt->reverse) { if ((int4) (var->value) < (int4) value) break; } else { if ((int4) (var->value) > (int4) value) break; } found = true; /* looped at least once */ /* * Execute the statements */ rc = exec_stmts(estate, stmt->body); if (rc == PLPGSQL_RC_RETURN) break; /* return from function */ else if (rc == PLPGSQL_RC_EXIT) { if (estate->exitlabel == NULL) /* unlabelled exit, finish the current loop */ rc = PLPGSQL_RC_OK; else if (stmt->label != NULL && strcmp(stmt->label, estate->exitlabel) == 0) { /* labelled exit, matches the current stmt's label */ estate->exitlabel = NULL; rc = PLPGSQL_RC_OK; } /* * otherwise, we processed a labelled exit that does not match * the current statement's label, if any: return RC_EXIT so * that the EXIT continues to recurse upward. */ break; } /* * Increase/decrease loop var */ if (stmt->reverse) var->value--; else var->value++; } /* * Set the FOUND variable to indicate the result of executing the loop * (namely, whether we looped one or more times). This must be set * here so that it does not interfere with the value of the FOUND * variable inside the loop processing itself. */ exec_set_found(estate, found); return rc; } /* ---------- * exec_stmt_fors Execute a query, assign each * tuple to a record or row and * execute a group of statements * for it. * ---------- */ static int exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt) { PLpgSQL_rec *rec = NULL; PLpgSQL_row *row = NULL; SPITupleTable *tuptab; Portal portal; bool found = false; int rc = PLPGSQL_RC_OK; int i; int n; /* * Determine if we assign to a record or a row */ if (stmt->rec != NULL) rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); else if (stmt->row != NULL) row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); else elog(ERROR, "unsupported target in exec_stmt_fors()"); /* * Open the implicit cursor for the statement and fetch the initial 10 * rows. */ exec_run_select(estate, stmt->query, 0, &portal); SPI_cursor_fetch(portal, true, 10); n = SPI_processed; tuptab = SPI_tuptable; /* * If the query didn't return any rows, set the target to NULL and * return with FOUND = false. */ if (n == 0) exec_move_row(estate, rec, row, NULL, NULL); else found = true; /* processed at least one tuple */ /* * Now do the loop */ while (n > 0) { for (i = 0; i < n; i++) { /* * Assign the tuple to the target */ exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc); /* * Execute the statements */ rc = exec_stmts(estate, stmt->body); if (rc != PLPGSQL_RC_OK) { /* * We're aborting the loop, so cleanup and set FOUND */ exec_set_found(estate, found); SPI_freetuptable(tuptab); SPI_cursor_close(portal); if (rc == PLPGSQL_RC_EXIT) { if (estate->exitlabel == NULL) /* unlabelled exit, finish the current loop */ rc = PLPGSQL_RC_OK; else if (stmt->label != NULL && strcmp(stmt->label, estate->exitlabel) == 0) { /* labelled exit, matches the current stmt's label */ estate->exitlabel = NULL; rc = PLPGSQL_RC_OK; } /* * otherwise, we processed a labelled exit that does * not match the current statement's label, if any: * return RC_EXIT so that the EXIT continues to * recurse upward. */ } return rc; } } SPI_freetuptable(tuptab); /* * Fetch the next 50 tuples */ SPI_cursor_fetch(portal, true, 50); n = SPI_processed; tuptab = SPI_tuptable; } /* * Close the implicit cursor */ SPI_cursor_close(portal); /* * Set the FOUND variable to indicate the result of executing the loop * (namely, whether we looped one or more times). This must be set * here so that it does not interfere with the value of the FOUND * variable inside the loop processing itself. */ exec_set_found(estate, found); return rc; } /* ---------- * exec_stmt_select Run a query and assign the first * row to a record or rowtype. * ---------- */ static int exec_stmt_select(PLpgSQL_execstate * estate, PLpgSQL_stmt_select * stmt) { PLpgSQL_rec *rec = NULL; PLpgSQL_row *row = NULL; SPITupleTable *tuptab; uint32 n; /* * Initialize the global found variable to false */ exec_set_found(estate, false); /* * Determine if we assign to a record or a row */ if (stmt->rec != NULL) rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); else if (stmt->row != NULL) row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); else elog(ERROR, "unsupported target in exec_stmt_select()"); /* * Run the query */ exec_run_select(estate, stmt->query, 1, NULL); n = estate->eval_processed; /* * If the query didn't return any row, set the target to NULL and * return. */ if (n == 0) { exec_move_row(estate, rec, row, NULL, NULL); exec_eval_cleanup(estate); return PLPGSQL_RC_OK; } /* * Put the result into the target and set found to true */ tuptab = estate->eval_tuptable; exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); exec_set_found(estate, true); exec_eval_cleanup(estate); return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_exit Start exiting loop(s) or blocks * ---------- */ static int exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt) { Datum value; Oid valtype; bool isnull = false; /* * If the exit has a condition, check that it's true */ if (stmt->cond != NULL) { value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype); exec_eval_cleanup(estate); if (isnull || !DatumGetBool(value)) return PLPGSQL_RC_OK; } estate->exitlabel = stmt->label; return PLPGSQL_RC_EXIT; } /* ---------- * exec_stmt_return Evaluate an expression and start * returning from the function. * ---------- */ static int exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt) { /* * If processing a set-returning PL/PgSQL function, the final RETURN * indicates that the function is finished producing tuples. The rest * of the work will be done at the top level. */ if (estate->retisset) return PLPGSQL_RC_RETURN; if (estate->retistuple) { /* initialize for null result tuple */ estate->retval = (Datum) 0; estate->rettupdesc = NULL; estate->retisnull = true; if (stmt->retrecno >= 0) { PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->retrecno]); if (HeapTupleIsValid(rec->tup)) { estate->retval = (Datum) rec->tup; estate->rettupdesc = rec->tupdesc; estate->retisnull = false; } return PLPGSQL_RC_RETURN; } if (stmt->expr != NULL) { exec_run_select(estate, stmt->expr, 1, NULL); if (estate->eval_processed > 0) { estate->retval = (Datum) estate->eval_tuptable->vals[0]; estate->rettupdesc = estate->eval_tuptable->tupdesc; estate->retisnull = false; } } return PLPGSQL_RC_RETURN; } if (estate->fn_rettype == VOIDOID) { /* Special hack for function returning VOID */ estate->retval = (Datum) 0; estate->retisnull = false; estate->rettype = VOIDOID; } else { /* Normal case for scalar results */ estate->retval = exec_eval_expr(estate, stmt->expr, &(estate->retisnull), &(estate->rettype)); } return PLPGSQL_RC_RETURN; } /* ---------- * exec_stmt_return_next Evaluate an expression and add it to the * list of tuples returned by the current * SRF. * ---------- */ static int exec_stmt_return_next(PLpgSQL_execstate * estate, PLpgSQL_stmt_return_next * stmt) { TupleDesc tupdesc; int natts; HeapTuple tuple; bool free_tuple = false; if (!estate->retisset) elog(ERROR, "Cannot use RETURN NEXT in a non-SETOF function"); if (estate->tuple_store == NULL) exec_init_tuple_store(estate); /* rettupdesc will be filled by exec_init_tuple_store */ tupdesc = estate->rettupdesc; natts = tupdesc->natts; if (stmt->rec) { PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); if (!compatible_tupdesc(tupdesc, rec->tupdesc)) elog(ERROR, "Wrong record type supplied in RETURN NEXT"); tuple = rec->tup; } else if (stmt->row) { Datum *dvalues; char *nulls; int i; if (natts != stmt->row->nfields) elog(ERROR, "Wrong record type supplied in RETURN NEXT"); dvalues = (Datum *) palloc0(natts * sizeof(Datum)); nulls = (char *) palloc(natts * sizeof(char)); MemSet(nulls, 'n', natts); for (i = 0; i < natts; i++) { PLpgSQL_var *var; var = (PLpgSQL_var *) (estate->datums[stmt->row->varnos[i]]); if (var->datatype->typoid != tupdesc->attrs[i]->atttypid) elog(ERROR, "Wrong record type supplied in RETURN NEXT"); dvalues[i] = var->value; if (!var->isnull) nulls[i] = ' '; } tuple = heap_formtuple(tupdesc, dvalues, nulls); pfree(dvalues); pfree(nulls); free_tuple = true; } else if (stmt->expr) { Datum retval; bool isNull; Oid rettype; char nullflag; if (natts != 1) elog(ERROR, "Wrong result type supplied in RETURN NEXT"); retval = exec_eval_expr(estate, stmt->expr, &isNull, &rettype); /* coerce type if needed */ if (!isNull && rettype != tupdesc->attrs[0]->atttypid) { Oid targType = tupdesc->attrs[0]->atttypid; Oid typInput; Oid typElem; FmgrInfo finfo_input; getTypeInputInfo(targType, &typInput, &typElem); fmgr_info(typInput, &finfo_input); retval = exec_cast_value(retval, rettype, targType, &finfo_input, typElem, tupdesc->attrs[0]->atttypmod, &isNull); } nullflag = isNull ? 'n' : ' '; tuple = heap_formtuple(tupdesc, &retval, &nullflag); free_tuple = true; exec_eval_cleanup(estate); } else { elog(ERROR, "Blank RETURN NEXT not allowed"); tuple = NULL; /* keep compiler quiet */ } if (HeapTupleIsValid(tuple)) { MemoryContext oldcxt; oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt); tuplestore_puttuple(estate->tuple_store, tuple); MemoryContextSwitchTo(oldcxt); if (free_tuple) heap_freetuple(tuple); } return PLPGSQL_RC_OK; } static void exec_init_tuple_store(PLpgSQL_execstate * estate) { ReturnSetInfo *rsi = estate->rsi; MemoryContext oldcxt; /* * Check caller can handle a set result in the way we want */ if (!rsi || !IsA(rsi, ReturnSetInfo) || (rsi->allowedModes & SFRM_Materialize) == 0 || rsi->expectedDesc == NULL) elog(ERROR, "Set-valued function called in context that cannot accept a set"); estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory; oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt); estate->tuple_store = tuplestore_begin_heap(true, SortMem); MemoryContextSwitchTo(oldcxt); estate->rettupdesc = rsi->expectedDesc; } /* ---------- * exec_stmt_raise Build a message and throw it with * elog() * ---------- */ static int exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt) { HeapTuple typetup; Form_pg_type typeStruct; FmgrInfo finfo_output; char *extval; int pidx = 0; char c[2] = {0, 0}; char *cp; PLpgSQL_dstring ds; PLpgSQL_var *var; PLpgSQL_rec *rec; PLpgSQL_recfield *recfield; int fno; plpgsql_dstring_init(&ds); for (cp = stmt->message; *cp; cp++) { /* * Occurences of a single % are replaced by the next arguments * external representation. Double %'s are left as is so elog() * will also don't touch them. */ if ((c[0] = *cp) == '%') { cp++; if (*cp == '%') { plpgsql_dstring_append(&ds, c); plpgsql_dstring_append(&ds, c); continue; } cp--; if (pidx >= stmt->nparams) { plpgsql_dstring_append(&ds, c); plpgsql_dstring_append(&ds, c); continue; } switch (estate->datums[stmt->params[pidx]]->dtype) { case PLPGSQL_DTYPE_VAR: var = (PLpgSQL_var *) (estate->datums[stmt->params[pidx]]); if (var->isnull) extval = ""; else { typetup = SearchSysCache(TYPEOID, ObjectIdGetDatum(var->datatype->typoid), 0, 0, 0); if (!HeapTupleIsValid(typetup)) elog(ERROR, "cache lookup for type %u failed", var->datatype->typoid); typeStruct = (Form_pg_type) GETSTRUCT(typetup); fmgr_info(typeStruct->typoutput, &finfo_output); extval = DatumGetCString(FunctionCall3(&finfo_output, var->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(var->datatype->atttypmod))); ReleaseSysCache(typetup); } plpgsql_dstring_append(&ds, extval); break; case PLPGSQL_DTYPE_RECFIELD: recfield = (PLpgSQL_recfield *) (estate->datums[stmt->params[pidx]]); rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]); if (!HeapTupleIsValid(rec->tup)) extval = ""; else { fno = SPI_fnumber(rec->tupdesc, recfield->fieldname); if (fno == SPI_ERROR_NOATTRIBUTE) elog(ERROR, "record \"%s\" has no field named \"%s\"", rec->refname, recfield->fieldname); extval = SPI_getvalue(rec->tup, rec->tupdesc, fno); if (extval == NULL) extval = ""; } plpgsql_dstring_append(&ds, extval); break; case PLPGSQL_DTYPE_TRIGARG: { PLpgSQL_trigarg *trigarg; int value; Oid valtype; bool valisnull = false; trigarg = (PLpgSQL_trigarg *) (estate->datums[stmt->params[pidx]]); value = (int) exec_eval_expr(estate, trigarg->argnum, &valisnull, &valtype); exec_eval_cleanup(estate); if (valisnull) extval = ""; else { if (value < 0 || value >= estate->trig_nargs) extval = ""; else extval = DatumGetCString(DirectFunctionCall1(textout, estate->trig_argv[value])); } plpgsql_dstring_append(&ds, extval); } break; default: c[0] = '?'; plpgsql_dstring_append(&ds, c); break; } pidx++; continue; } /* * Occurrences of single ' are removed. double ' are reduced to * single ones. */ if (*cp == '\'') { cp++; if (*cp == '\'') plpgsql_dstring_append(&ds, c); else cp--; continue; } plpgsql_dstring_append(&ds, c); } /* * Now suppress debug info and throw the elog() */ if (stmt->elog_level == ERROR) { error_info_func = NULL; error_info_stmt = NULL; error_info_text = NULL; } elog(stmt->elog_level, "%s", plpgsql_dstring_get(&ds)); plpgsql_dstring_free(&ds); return PLPGSQL_RC_OK; } /* ---------- * Initialize a mostly empty execution state * ---------- */ static void plpgsql_estate_setup(PLpgSQL_execstate * estate, PLpgSQL_function * func, ReturnSetInfo *rsi) { estate->retval = (Datum) 0; estate->retisnull = true; estate->rettype = InvalidOid; estate->fn_rettype = func->fn_rettype; estate->retistuple = func->fn_retistuple; estate->retisset = func->fn_retset; estate->rettupdesc = NULL; estate->exitlabel = NULL; estate->tuple_store = NULL; estate->tuple_store_cxt = NULL; estate->rsi = rsi; estate->trig_nargs = 0; estate->trig_argv = NULL; estate->found_varno = func->found_varno; estate->ndatums = func->ndatums; estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums); /* caller is expected to fill the datums array */ estate->eval_tuptable = NULL; estate->eval_processed = 0; estate->eval_lastoid = InvalidOid; estate->eval_econtext = NULL; } /* ---------- * Release temporary memory used by expression/subselect evaluation * * NB: the result of the evaluation is no longer valid after this is done, * unless it is a pass-by-value datatype. * ---------- */ static void exec_eval_cleanup(PLpgSQL_execstate * estate) { /* Clear result of a full SPI_exec */ if (estate->eval_tuptable != NULL) SPI_freetuptable(estate->eval_tuptable); estate->eval_tuptable = NULL; /* Clear result of exec_eval_simple_expr */ if (estate->eval_econtext != NULL) FreeExprContext(estate->eval_econtext); estate->eval_econtext = NULL; } /* ---------- * 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; _SPI_plan *spi_plan; void *plan; Oid *argtypes; /* * We need a temporary argtypes array to load with data. (The finished * plan structure will contain a copy of it.) * * +1 is just to avoid palloc(0) error. */ argtypes = (Oid *) palloc(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 named \"%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]]->dtype); } } /* * 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); spi_plan = (_SPI_plan *) expr->plan; expr->plan_argtypes = spi_plan->argtypes; expr->plan_simple_expr = NULL; exec_simple_check_plan(expr); SPI_freeplan(plan); pfree(argtypes); } /* ---------- * exec_stmt_execsql Execute an SQL statement not * returning any data. * ---------- */ static int exec_stmt_execsql(PLpgSQL_execstate * estate, PLpgSQL_stmt_execsql * stmt) { PLpgSQL_var *var; PLpgSQL_rec *rec; PLpgSQL_recfield *recfield; PLpgSQL_trigarg *trigarg; int tgargno; Oid tgargoid; int fno; int i; Datum *values; char *nulls; int rc; PLpgSQL_expr *expr = stmt->sqlstmt; bool isnull; /* * On the first call for this expression generate the plan */ if (expr->plan == NULL) exec_prepare_plan(estate, expr); /* * Now build up the values and nulls arguments for SPI_execp() */ values = palloc(sizeof(Datum) * (expr->nparams + 1)); nulls = palloc(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]]); values[i] = var->value; if (var->isnull) nulls[i] = 'n'; else nulls[i] = ' '; 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 named \"%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); values[i] = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull); if (isnull) nulls[i] = 'n'; else nulls[i] = ' '; break; case PLPGSQL_DTYPE_TRIGARG: trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]); tgargno = (int) exec_eval_expr(estate, trigarg->argnum, &isnull, &tgargoid); exec_eval_cleanup(estate); if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) { values[i] = 0; nulls[i] = 'n'; } else { values[i] = estate->trig_argv[tgargno]; nulls[i] = ' '; } break; default: elog(ERROR, "unknown parameter dtype %d in exec_stmt_execsql()", estate->datums[expr->params[i]]->dtype); } } nulls[i] = '\0'; /* * Execute the plan */ rc = SPI_execp(expr->plan, values, nulls, 0); switch (rc) { case SPI_OK_UTILITY: case SPI_OK_SELINTO: break; case SPI_OK_INSERT: case SPI_OK_DELETE: case SPI_OK_UPDATE: /* * If the INSERT, DELETE, or UPDATE query affected at least * one tuple, set the magic 'FOUND' variable to true. This * conforms with the behavior of PL/SQL. */ exec_set_found(estate, (SPI_processed != 0)); break; case SPI_OK_SELECT: elog(ERROR, "SELECT query has no destination for result data." "\n\tIf you want to discard the results, use PERFORM instead."); default: elog(ERROR, "error executing query \"%s\"", expr->query); } /* * Release any result tuples from SPI_execp (probably shouldn't be * any) */ SPI_freetuptable(SPI_tuptable); /* Save result info for GET DIAGNOSTICS */ estate->eval_processed = SPI_processed; estate->eval_lastoid = SPI_lastoid; pfree(values); pfree(nulls); return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_dynexecute Execute a dynamic SQL query not * returning any data. * ---------- */ static int exec_stmt_dynexecute(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynexecute * stmt) { Datum query; bool isnull = false; Oid restype; char *querystr; HeapTuple typetup; Form_pg_type typeStruct; FmgrInfo finfo_output; int exec_res; /* * First we evaluate the string expression after the EXECUTE keyword. * It's result is the querystring we have to execute. */ query = exec_eval_expr(estate, stmt->query, &isnull, &restype); if (isnull) elog(ERROR, "cannot EXECUTE NULL query"); /* * Get the C-String representation. */ typetup = SearchSysCache(TYPEOID, ObjectIdGetDatum(restype), 0, 0, 0); if (!HeapTupleIsValid(typetup)) elog(ERROR, "cache lookup for type %u failed", restype); typeStruct = (Form_pg_type) GETSTRUCT(typetup); fmgr_info(typeStruct->typoutput, &finfo_output); querystr = DatumGetCString(FunctionCall3(&finfo_output, query, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); ReleaseSysCache(typetup); exec_eval_cleanup(estate); /* * Call SPI_exec() without preparing a saved plan. The returncode can * be any standard OK. Note that while a SELECT is allowed, its * results will be discarded. */ exec_res = SPI_exec(querystr, 0); switch (exec_res) { case SPI_OK_SELECT: case SPI_OK_INSERT: case SPI_OK_UPDATE: case SPI_OK_DELETE: case SPI_OK_UTILITY: break; case 0: /* * Also allow a zero return, which implies the querystring * contained no commands. */ break; case SPI_OK_SELINTO: /* * We want to disallow SELECT INTO for now, because its * behavior is not consistent with SELECT INTO in a normal * plpgsql context. (We need to reimplement EXECUTE to parse * the string as a plpgsql command, not just feed it to * SPI_exec.) However, CREATE AS should be allowed ... and * since it produces the same parsetree as SELECT INTO, * there's no way to tell the difference except to look at the * source text. Wotta kluge! */ { char *ptr; for (ptr = querystr; *ptr; ptr++) if (!isspace((unsigned char) *ptr)) break; if (*ptr == 'S' || *ptr == 's') elog(ERROR, "EXECUTE of SELECT ... INTO is not implemented yet"); break; } default: elog(ERROR, "unexpected error %d in EXECUTE of query '%s'", exec_res, querystr); break; } /* Release any result from SPI_exec, as well as the querystring */ SPI_freetuptable(SPI_tuptable); pfree(querystr); /* Save result info for GET DIAGNOSTICS */ estate->eval_processed = SPI_processed; estate->eval_lastoid = SPI_lastoid; return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_dynfors Execute a dynamic query, assign each * tuple to a record or row and * execute a group of statements * for it. * ---------- */ static int exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt) { Datum query; bool isnull = false; Oid restype; char *querystr; PLpgSQL_rec *rec = NULL; PLpgSQL_row *row = NULL; SPITupleTable *tuptab; int rc = PLPGSQL_RC_OK; int i; int n; HeapTuple typetup; Form_pg_type typeStruct; FmgrInfo finfo_output; void *plan; Portal portal; bool found = false; /* * Determine if we assign to a record or a row */ if (stmt->rec != NULL) rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); else if (stmt->row != NULL) row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); else elog(ERROR, "unsupported target in exec_stmt_dynfors()"); /* * Evaluate the string expression after the EXECUTE keyword. It's * result is the querystring we have to execute. */ query = exec_eval_expr(estate, stmt->query, &isnull, &restype); if (isnull) elog(ERROR, "cannot EXECUTE NULL-query"); /* * Get the C-String representation. */ typetup = SearchSysCache(TYPEOID, ObjectIdGetDatum(restype), 0, 0, 0); if (!HeapTupleIsValid(typetup)) elog(ERROR, "cache lookup for type %u failed", restype); typeStruct = (Form_pg_type) GETSTRUCT(typetup); fmgr_info(typeStruct->typoutput, &finfo_output); querystr = DatumGetCString(FunctionCall3(&finfo_output, query, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); ReleaseSysCache(typetup); exec_eval_cleanup(estate); /* * Prepare a plan and open an implicit cursor for the query */ plan = SPI_prepare(querystr, 0, NULL); if (plan == NULL) elog(ERROR, "SPI_prepare() failed for dynamic query \"%s\"", querystr); portal = SPI_cursor_open(NULL, plan, NULL, NULL); if (portal == NULL) elog(ERROR, "failed to open implicit cursor for dynamic query \"%s\"", querystr); pfree(querystr); SPI_freeplan(plan); /* * Fetch the initial 10 tuples */ SPI_cursor_fetch(portal, true, 10); n = SPI_processed; tuptab = SPI_tuptable; /* * If the query didn't return any rows, set the target to NULL and * return with FOUND = false. */ if (n == 0) exec_move_row(estate, rec, row, NULL, NULL); else found = true; /* * Now do the loop */ while (n > 0) { for (i = 0; i < n; i++) { /* * Assign the tuple to the target */ exec_move_row(estate, rec, row, tuptab->vals[i], tuptab->tupdesc); /* * Execute the statements */ rc = exec_stmts(estate, stmt->body); /* * We're aborting the loop, so cleanup and set FOUND */ if (rc != PLPGSQL_RC_OK) { exec_set_found(estate, found); SPI_freetuptable(tuptab); SPI_cursor_close(portal); if (rc == PLPGSQL_RC_EXIT) { if (estate->exitlabel == NULL) /* unlabelled exit, finish the current loop */ rc = PLPGSQL_RC_OK; else if (stmt->label != NULL && strcmp(stmt->label, estate->exitlabel) == 0) { /* labelled exit, matches the current stmt's label */ estate->exitlabel = NULL; rc = PLPGSQL_RC_OK; } /* * otherwise, we processed a labelled exit that does * not match the current statement's label, if any: * return RC_EXIT so that the EXIT continues to * recurse upward. */ } return rc; } } SPI_freetuptable(tuptab); /* * Fetch the next 50 tuples */ SPI_cursor_fetch(portal, true, 50); n = SPI_processed; tuptab = SPI_tuptable; } /* * Close the cursor */ SPI_cursor_close(portal); /* * Set the FOUND variable to indicate the result of executing the loop * (namely, whether we looped one or more times). This must be set * here so that it does not interfere with the value of the FOUND * variable inside the loop processing itself. */ exec_set_found(estate, found); return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_open Execute an OPEN cursor statement * ---------- */ static int exec_stmt_open(PLpgSQL_execstate * estate, PLpgSQL_stmt_open * stmt) { PLpgSQL_var *curvar = NULL; char *curname = NULL; PLpgSQL_expr *query = NULL; Portal portal; PLpgSQL_var *var; PLpgSQL_rec *rec; PLpgSQL_recfield *recfield; PLpgSQL_trigarg *trigarg; int tgargno; Oid tgargoid; int i; Datum *values; char *nulls; int fno; bool isnull; /* ---------- * Get the cursor variable and if it has an assigned name, check * that it's not in use currently. * ---------- */ curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]); if (!curvar->isnull) { curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value)); if (SPI_cursor_find(curname) != NULL) elog(ERROR, "cursor \"%s\" already in use", curname); } /* ---------- * Process the OPEN according to it's type. * ---------- */ if (stmt->query != NULL) { /* ---------- * This is an OPEN refcursor FOR SELECT ... * * We just make sure the query is planned. The real work is * done downstairs. * ---------- */ query = stmt->query; if (query->plan == NULL) exec_prepare_plan(estate, query); } else if (stmt->dynquery != NULL) { /* ---------- * This is an OPEN refcursor FOR EXECUTE ... * ---------- */ Datum queryD; Oid restype; char *querystr; HeapTuple typetup; Form_pg_type typeStruct; FmgrInfo finfo_output; void *curplan; /* ---------- * We evaluate the string expression after the * EXECUTE keyword. It's result is the querystring we have * to execute. * ---------- */ queryD = exec_eval_expr(estate, stmt->dynquery, &isnull, &restype); if (isnull) elog(ERROR, "cannot EXECUTE NULL query"); /* ---------- * Get the C-String representation. * ---------- */ typetup = SearchSysCache(TYPEOID, ObjectIdGetDatum(restype), 0, 0, 0); if (!HeapTupleIsValid(typetup)) elog(ERROR, "cache lookup for type %u failed", restype); typeStruct = (Form_pg_type) GETSTRUCT(typetup); fmgr_info(typeStruct->typoutput, &finfo_output); querystr = DatumGetCString(FunctionCall3(&finfo_output, queryD, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); ReleaseSysCache(typetup); exec_eval_cleanup(estate); /* ---------- * Now we prepare a query plan for it and open a cursor * ---------- */ curplan = SPI_prepare(querystr, 0, NULL); if (curplan == NULL) elog(ERROR, "SPI_prepare() failed for dynamic query \"%s\"", querystr); portal = SPI_cursor_open(curname, curplan, NULL, NULL); if (portal == NULL) elog(ERROR, "Failed to open cursor"); pfree(querystr); SPI_freeplan(curplan); /* ---------- * Store the eventually assigned cursor name in the cursor variable * ---------- */ if (curvar->freeval) pfree((void *) (curvar->value)); curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name)); curvar->isnull = false; curvar->freeval = true; return PLPGSQL_RC_OK; } else { /* ---------- * This is an OPEN cursor * * Note: parser should already have checked that statement supplies * args iff cursor needs them, but we check again to be safe. * ---------- */ if (stmt->argquery != NULL) { /* ---------- * Er - OPEN CURSOR (args). We fake a SELECT ... INTO ... * statement to evaluate the args and put 'em into the * internal row. * ---------- */ PLpgSQL_stmt_select set_args; if (curvar->cursor_explicit_argrow < 0) elog(ERROR, "arguments given for cursor without arguments"); memset(&set_args, 0, sizeof(set_args)); set_args.cmd_type = PLPGSQL_STMT_SELECT; set_args.lineno = stmt->lineno; set_args.row = (PLpgSQL_row *) (estate->datums[curvar->cursor_explicit_argrow]); set_args.query = stmt->argquery; if (exec_stmt_select(estate, &set_args) != PLPGSQL_RC_OK) elog(ERROR, "open cursor failed during argument processing"); } else { if (curvar->cursor_explicit_argrow >= 0) elog(ERROR, "arguments required for cursor"); } query = curvar->cursor_explicit_expr; if (query->plan == NULL) exec_prepare_plan(estate, query); } /* ---------- * Here we go if we have a saved plan where we have to put * values into, either from an explicit cursor or from a * refcursor opened with OPEN ... FOR SELECT ...; * ---------- */ values = palloc(sizeof(Datum) * (query->nparams + 1)); nulls = palloc(query->nparams + 1); for (i = 0; i < query->nparams; i++) { switch (estate->datums[query->params[i]]->dtype) { case PLPGSQL_DTYPE_VAR: var = (PLpgSQL_var *) (estate->datums[query->params[i]]); values[i] = var->value; if (var->isnull) nulls[i] = 'n'; else nulls[i] = ' '; break; case PLPGSQL_DTYPE_RECFIELD: recfield = (PLpgSQL_recfield *) (estate->datums[query->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 named \"%s\"", rec->refname, recfield->fieldname); if (query->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); values[i] = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull); if (isnull) nulls[i] = 'n'; else nulls[i] = ' '; break; case PLPGSQL_DTYPE_TRIGARG: trigarg = (PLpgSQL_trigarg *) (estate->datums[query->params[i]]); tgargno = (int) exec_eval_expr(estate, trigarg->argnum, &isnull, &tgargoid); exec_eval_cleanup(estate); if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) { values[i] = 0; nulls[i] = 'n'; } else { values[i] = estate->trig_argv[tgargno]; nulls[i] = ' '; } break; default: elog(ERROR, "unknown parameter dtype %d in exec_stmt_open()", estate->datums[query->params[i]]->dtype); } } nulls[i] = '\0'; /* ---------- * Open the cursor * ---------- */ portal = SPI_cursor_open(curname, query->plan, values, nulls); if (portal == NULL) elog(ERROR, "Failed to open cursor"); pfree(values); pfree(nulls); if (curname) pfree(curname); /* ---------- * Store the eventually assigned portal name in the cursor variable * ---------- */ if (curvar->freeval) pfree((void *) (curvar->value)); curvar->value = DirectFunctionCall1(textin, CStringGetDatum(portal->name)); curvar->isnull = false; curvar->freeval = true; return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_fetch Fetch from a cursor into a target * ---------- */ static int exec_stmt_fetch(PLpgSQL_execstate * estate, PLpgSQL_stmt_fetch * stmt) { PLpgSQL_var *curvar = NULL; PLpgSQL_rec *rec = NULL; PLpgSQL_row *row = NULL; SPITupleTable *tuptab; Portal portal; char *curname; int n; /* ---------- * Get the portal of the cursor by name * ---------- */ curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]); if (curvar->isnull) elog(ERROR, "cursor variable \"%s\" is NULL", curvar->refname); curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value)); portal = SPI_cursor_find(curname); if (portal == NULL) elog(ERROR, "cursor \"%s\" is invalid", curname); pfree(curname); /* ---------- * Initialize the global found variable to false * ---------- */ exec_set_found(estate, false); /* ---------- * Determine if we fetch into a record or a row * ---------- */ if (stmt->rec != NULL) rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); else { if (stmt->row != NULL) row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); else elog(ERROR, "unsupported target in exec_stmt_select()"); } /* ---------- * Fetch 1 tuple from the cursor * ---------- */ SPI_cursor_fetch(portal, true, 1); n = SPI_processed; tuptab = SPI_tuptable; /* ---------- * If the FETCH didn't return a row, set the target * to NULL and return with FOUND = false. * ---------- */ if (n == 0) { exec_move_row(estate, rec, row, NULL, NULL); return PLPGSQL_RC_OK; } /* ---------- * Put the result into the target and set found to true * ---------- */ exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); exec_set_found(estate, true); SPI_freetuptable(tuptab); return PLPGSQL_RC_OK; } /* ---------- * exec_stmt_close Close a cursor * ---------- */ static int exec_stmt_close(PLpgSQL_execstate * estate, PLpgSQL_stmt_close * stmt) { PLpgSQL_var *curvar = NULL; Portal portal; char *curname; /* ---------- * Get the portal of the cursor by name * ---------- */ curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]); if (curvar->isnull) elog(ERROR, "cursor variable \"%s\" is NULL", curvar->refname); curname = DatumGetCString(DirectFunctionCall1(textout, curvar->value)); portal = SPI_cursor_find(curname); if (portal == NULL) elog(ERROR, "cursor \"%s\" is invalid", curname); pfree(curname); /* ---------- * And close it. * ---------- */ SPI_cursor_close(portal); return PLPGSQL_RC_OK; } /* ---------- * exec_assign_expr Put an expression's result into * a variable. * ---------- */ static void exec_assign_expr(PLpgSQL_execstate * estate, PLpgSQL_datum * target, PLpgSQL_expr * expr) { Datum value; Oid valtype; bool isnull = false; value = exec_eval_expr(estate, expr, &isnull, &valtype); exec_assign_value(estate, target, value, valtype, &isnull); exec_eval_cleanup(estate); } /* ---------- * exec_assign_value Put a value into a target field * ---------- */ static void exec_assign_value(PLpgSQL_execstate * estate, PLpgSQL_datum * target, Datum value, Oid valtype, bool *isNull) { PLpgSQL_var *var; PLpgSQL_rec *rec; PLpgSQL_recfield *recfield; int fno; int i; int natts; Datum *values; char *nulls; void *mustfree; Datum newvalue; bool attisnull; Oid atttype; int32 atttypmod; HeapTuple newtup; Oid typInput; Oid typElem; FmgrInfo finfo_input; switch (target->dtype) { case PLPGSQL_DTYPE_VAR: /* * Target field is a variable */ var = (PLpgSQL_var *) target; if (var->freeval) { pfree(DatumGetPointer(var->value)); var->freeval = false; } newvalue = exec_cast_value(value, valtype, var->datatype->typoid, &(var->datatype->typinput), var->datatype->typelem, var->datatype->atttypmod, isNull); if (*isNull && var->notnull) elog(ERROR, "NULL assignment to variable '%s' declared NOT NULL", var->refname); /* * If type is by-reference, make sure we have a freshly * palloc'd copy; the originally passed value may not live as * long as the variable! But we don't need to re-copy if * exec_cast_value performed a conversion; its output must * already be palloc'd. */ if (!var->datatype->typbyval && !*isNull) { if (newvalue == value) var->value = datumCopy(newvalue, false, var->datatype->typlen); else var->value = newvalue; var->freeval = true; } else var->value = newvalue; var->isnull = *isNull; break; case PLPGSQL_DTYPE_RECFIELD: /* * Target field is a record */ recfield = (PLpgSQL_recfield *) target; rec = (PLpgSQL_rec *) (estate->datums[recfield->recno]); /* * Check that there is already a tuple in the record. We need * that because records don't have any predefined field * structure. */ if (!HeapTupleIsValid(rec->tup)) elog(ERROR, "record \"%s\" is unassigned yet - don't know its tuple structure", rec->refname); /* * Get the number of the records field to change and the * number of attributes in the tuple. */ fno = SPI_fnumber(rec->tupdesc, recfield->fieldname); if (fno == SPI_ERROR_NOATTRIBUTE) elog(ERROR, "record \"%s\" has no field named \"%s\"", rec->refname, recfield->fieldname); fno--; natts = rec->tupdesc->natts; /* * Set up values/datums arrays for heap_formtuple. For all * the attributes except the one we want to replace, use the * value that's in the old tuple. */ values = palloc(sizeof(Datum) * natts); nulls = palloc(natts); for (i = 0; i < natts; i++) { if (i == fno) continue; values[i] = SPI_getbinval(rec->tup, rec->tupdesc, i + 1, &attisnull); if (attisnull) nulls[i] = 'n'; else nulls[i] = ' '; } /* * Now insert the new value, being careful to cast it to the * right type. */ atttype = SPI_gettypeid(rec->tupdesc, fno + 1); atttypmod = rec->tupdesc->attrs[fno]->atttypmod; getTypeInputInfo(atttype, &typInput, &typElem); fmgr_info(typInput, &finfo_input); attisnull = *isNull; values[fno] = exec_cast_value(value, valtype, atttype, &finfo_input, typElem, atttypmod, &attisnull); if (attisnull) nulls[fno] = 'n'; else nulls[fno] = ' '; /* * Avoid leaking the result of exec_cast_value, if it * performed a conversion to a pass-by-ref type. */ if (!attisnull && values[fno] != value && !get_typbyval(atttype)) mustfree = DatumGetPointer(values[fno]); else mustfree = NULL; /* * Now call heap_formtuple() to create a new tuple that * replaces the old one in the record. */ newtup = heap_formtuple(rec->tupdesc, values, nulls); if (rec->freetup) heap_freetuple(rec->tup); rec->tup = newtup; rec->freetup = true; pfree(values); pfree(nulls); if (mustfree) pfree(mustfree); break; default: elog(ERROR, "unknown dtype %d in exec_assign_value()", target->dtype); } } /* ---------- * exec_eval_expr Evaluate an expression and return * the result Datum. * * NOTE: caller must do exec_eval_cleanup when done with the Datum. * ---------- */ static Datum exec_eval_expr(PLpgSQL_execstate * estate, PLpgSQL_expr * expr, bool *isNull, Oid *rettype) { 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, NULL); if (rc != SPI_OK_SELECT) elog(ERROR, "query \"%s\" didn't return data", expr->query); /* * If there are no rows selected, the result is NULL. */ if (estate->eval_processed == 0) { *isNull = true; return (Datum) 0; } /* * Check that the expression returned one single Datum */ if (estate->eval_processed > 1) elog(ERROR, "query \"%s\" returned more than one row", expr->query); if (estate->eval_tuptable->tupdesc->natts != 1) elog(ERROR, "query \"%s\" returned %d columns", expr->query, estate->eval_tuptable->tupdesc->natts); /* * Return the result and its type */ *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1); return SPI_getbinval(estate->eval_tuptable->vals[0], estate->eval_tuptable->tupdesc, 1, isNull); } /* ---------- * exec_run_select Execute a select query * ---------- */ static int exec_run_select(PLpgSQL_execstate * estate, PLpgSQL_expr * expr, int maxtuples, Portal *portalP) { PLpgSQL_var *var; PLpgSQL_rec *rec; PLpgSQL_recfield *recfield; PLpgSQL_trigarg *trigarg; int tgargno; Oid tgargoid; int i; Datum *values; char *nulls; int rc; int fno; bool isnull; /* * On the first call for this expression generate the plan */ if (expr->plan == NULL) exec_prepare_plan(estate, expr); /* * Now build up the values and nulls arguments for SPI_execp() */ values = palloc(sizeof(Datum) * (expr->nparams + 1)); nulls = palloc(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]]); values[i] = var->value; if (var->isnull) nulls[i] = 'n'; else nulls[i] = ' '; 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 named \"%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); values[i] = SPI_getbinval(rec->tup, rec->tupdesc, fno, &isnull); if (isnull) nulls[i] = 'n'; else nulls[i] = ' '; break; case PLPGSQL_DTYPE_TRIGARG: trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]); tgargno = (int) exec_eval_expr(estate, trigarg->argnum, &isnull, &tgargoid); exec_eval_cleanup(estate); if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) { values[i] = 0; nulls[i] = 'n'; } else { values[i] = estate->trig_argv[tgargno]; nulls[i] = ' '; } break; default: elog(ERROR, "unknown parameter dtype %d in exec_eval_expr()", estate->datums[expr->params[i]]->dtype); } } nulls[i] = '\0'; /* * If a portal was requested, put the query into the portal */ if (portalP != NULL) { *portalP = SPI_cursor_open(NULL, expr->plan, values, nulls); if (*portalP == NULL) elog(ERROR, "failed to open implicit cursor for \"%s\"", expr->query); pfree(values); pfree(nulls); return SPI_OK_CURSOR; } /* * Execute the query */ rc = SPI_execp(expr->plan, values, nulls, maxtuples); if (rc != SPI_OK_SELECT) elog(ERROR, "query \"%s\" isn't a SELECT", expr->query); /* Save query results for eventual cleanup */ Assert(estate->eval_tuptable == NULL); estate->eval_tuptable = SPI_tuptable; estate->eval_processed = SPI_processed; estate->eval_lastoid = SPI_lastoid; pfree(values); pfree(nulls); return rc; } /* ---------- * 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; ExprContext *econtext; ParamListInfo paramLI; /* * Create an expression context to hold the arguments and the result * of this expression evaluation. This must be a child of the EState * we created in the SPI plan's context. */ econtext = CreateExprContext(expr->plan_simple_estate); /* * Param list can live in econtext's temporary memory context. */ paramLI = (ParamListInfo) MemoryContextAlloc(econtext->ecxt_per_tuple_memory, (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 named \"%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); exec_eval_cleanup(estate); 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 */ *rettype = expr->plan_simple_type; /* * Now call the executor to evaluate the expression */ SPI_push(); retval = ExecEvalExprSwitchContext(expr->plan_simple_expr, econtext, isNull, NULL); SPI_pop(); /* * Note: if pass-by-reference, the result is in the econtext's * temporary memory context. It will be freed when exec_eval_cleanup * is done. */ Assert(estate->eval_econtext == NULL); estate->eval_econtext = econtext; /* * That's it. */ return retval; } /* ---------- * exec_move_row Move one tuple's values into a record or row * ---------- */ static void exec_move_row(PLpgSQL_execstate * estate, PLpgSQL_rec * rec, PLpgSQL_row * row, HeapTuple tup, TupleDesc tupdesc) { /* * Record is simple - just put the tuple and its descriptor into the * record */ if (rec != NULL) { if (rec->freetup) { heap_freetuple(rec->tup); rec->freetup = false; } if (rec->freetupdesc) { FreeTupleDesc(rec->tupdesc); rec->freetupdesc = false; } if (HeapTupleIsValid(tup)) { rec->tup = heap_copytuple(tup); rec->tupdesc = CreateTupleDescCopy(tupdesc); rec->freetup = true; rec->freetupdesc = true; } else { rec->tup = NULL; rec->tupdesc = NULL; } return; } /* * Row is a bit more complicated in that we assign the individual * attributes of the tuple to the variables the row points to. * * NOTE: this code used to demand row->nfields == tup->t_data->t_natts, * but that's wrong. The tuple might have more fields than we * expected if it's from an inheritance-child table of the current * table, or it might have fewer if the table has had columns added by * ALTER TABLE. Ignore extra columns and assume NULL for missing * columns, the same as heap_getattr would do. */ if (row != NULL) { int t_natts; int i; if (HeapTupleIsValid(tup)) t_natts = tup->t_data->t_natts; else t_natts = 0; for (i = 0; i < row->nfields; i++) { PLpgSQL_var *var; Datum value; bool isnull; Oid valtype; var = (PLpgSQL_var *) (estate->datums[row->varnos[i]]); if (i < t_natts) { value = SPI_getbinval(tup, tupdesc, i + 1, &isnull); valtype = SPI_gettypeid(tupdesc, i + 1); } else { value = (Datum) 0; isnull = true; valtype = InvalidOid; } exec_assign_value(estate, estate->datums[row->varnos[i]], value, valtype, &isnull); } return; } elog(ERROR, "unsupported target in exec_move_row()"); } /* ---------- * exec_cast_value Cast a value if required * ---------- */ static Datum exec_cast_value(Datum value, Oid valtype, Oid reqtype, FmgrInfo *reqinput, Oid reqtypelem, int32 reqtypmod, bool *isnull) { if (!*isnull) { /* * If the type of the queries return value isn't that of the * variable, convert it. */ if (valtype != reqtype || reqtypmod != -1) { HeapTuple typetup; Form_pg_type typeStruct; FmgrInfo finfo_output; char *extval; typetup = SearchSysCache(TYPEOID, ObjectIdGetDatum(valtype), 0, 0, 0); if (!HeapTupleIsValid(typetup)) elog(ERROR, "cache lookup for type %u failed", valtype); typeStruct = (Form_pg_type) GETSTRUCT(typetup); fmgr_info(typeStruct->typoutput, &finfo_output); extval = DatumGetCString(FunctionCall3(&finfo_output, value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); value = FunctionCall3(reqinput, CStringGetDatum(extval), ObjectIdGetDatum(reqtypelem), Int32GetDatum(reqtypmod)); pfree(extval); ReleaseSysCache(typetup); } } return value; } /* ---------- * 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) { if (node == NULL) return TRUE; switch (nodeTag(node)) { case T_Const: return TRUE; case T_Param: return TRUE; case T_ArrayRef: { ArrayRef *expr = (ArrayRef *) node; if (!exec_simple_check_node((Node *) expr->refupperindexpr)) return FALSE; if (!exec_simple_check_node((Node *) expr->reflowerindexpr)) return FALSE; if (!exec_simple_check_node((Node *) expr->refexpr)) return FALSE; if (!exec_simple_check_node((Node *) expr->refassgnexpr)) return FALSE; return TRUE; } case T_FuncExpr: { FuncExpr *expr = (FuncExpr *) node; if (expr->funcretset) return FALSE; if (!exec_simple_check_node((Node *) expr->args)) return FALSE; return TRUE; } case T_OpExpr: { OpExpr *expr = (OpExpr *) node; if (expr->opretset) return FALSE; if (!exec_simple_check_node((Node *) expr->args)) return FALSE; return TRUE; } case T_DistinctExpr: { DistinctExpr *expr = (DistinctExpr *) node; if (expr->opretset) return FALSE; if (!exec_simple_check_node((Node *) expr->args)) return FALSE; return TRUE; } case T_BoolExpr: { BoolExpr *expr = (BoolExpr *) node; if (!exec_simple_check_node((Node *) expr->args)) return FALSE; return TRUE; } case T_FieldSelect: return exec_simple_check_node((Node *) ((FieldSelect *) node)->arg); case T_RelabelType: return exec_simple_check_node((Node *) ((RelabelType *) node)->arg); case T_CaseExpr: { CaseExpr *expr = (CaseExpr *) node; if (!exec_simple_check_node((Node *) expr->arg)) return FALSE; if (!exec_simple_check_node((Node *) expr->args)) return FALSE; if (!exec_simple_check_node((Node *) expr->defresult)) return FALSE; return TRUE; } case T_CaseWhen: { CaseWhen *when = (CaseWhen *) node; if (!exec_simple_check_node((Node *) when->expr)) return FALSE; if (!exec_simple_check_node((Node *) when->result)) return FALSE; return TRUE; } case T_NullTest: return exec_simple_check_node((Node *) ((NullTest *) node)->arg); case T_BooleanTest: return exec_simple_check_node((Node *) ((BooleanTest *) node)->arg); case T_List: { List *expr = (List *) node; List *l; foreach(l, expr) { if (!exec_simple_check_node(lfirst(l))) return FALSE; } 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; MemoryContext oldcontext; expr->plan_simple_expr = NULL; /* * 1. We can only evaluate queries that resulted in one single * execution plan */ if (length(spi_plan->ptlist) != 1) return; plan = (Plan *) lfirst(spi_plan->ptlist); /* * 2. It must be a RESULT plan --> no scan's required */ if (plan == NULL) /* utility statement produces this */ return; if (!IsA(plan, Result)) return; /* * 3. Can't have any subplan or qual clause, either */ if (plan->lefttree != NULL || plan->righttree != NULL || plan->initPlan != NULL || plan->qual != NULL || ((Result *) plan)->resconstantqual != NULL) return; /* * 4. The plan must have a single attribute as result */ if (length(plan->targetlist) != 1) return; tle = (TargetEntry *) lfirst(plan->targetlist); /* * 5. Check that all the nodes in the expression are non-scary. */ if (!exec_simple_check_node((Node *) tle->expr)) return; /* * Yes - this is a simple expression. Prepare to execute it. * We need an EState and an expression state tree, which we'll put * into the plan context so they will have appropriate lifespan. */ oldcontext = MemoryContextSwitchTo(spi_plan->plancxt); expr->plan_simple_estate = CreateExecutorState(); expr->plan_simple_expr = ExecPrepareExpr(tle->expr, expr->plan_simple_estate); MemoryContextSwitchTo(oldcontext); /* Also stash away the expression result type */ expr->plan_simple_type = exprType((Node *) tle->expr); } /* * Check two tupledescs have matching number and types of attributes */ static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2) { int i; if (td1->natts != td2->natts) return false; for (i = 0; i < td1->natts; i++) { if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid) return false; } return true; } /* ---------- * exec_set_found Set the global found variable * to true/false * ---------- */ static void exec_set_found(PLpgSQL_execstate * estate, bool state) { PLpgSQL_var *var; var = (PLpgSQL_var *) (estate->datums[estate->found_varno]); var->value = (Datum) state; var->isnull = false; }