diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 32e66287ebd6e7a5740c6aa19fc2ed05a9cc1001..f275269826f5bb2c2da8985c3da65ff556ed32d4 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.46 2001/07/12 17:42:07 momjian Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.47 2001/08/02 21:31:23 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -114,6 +114,10 @@ static int exec_stmt_dynexecute(PLpgSQL_execstate * estate, static int exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt); +static void plpgsql_estate_setup(PLpgSQL_execstate * estate, + PLpgSQL_function * func); +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); @@ -271,16 +275,7 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) /* * Setup the execution state */ - estate.retval = 0; - estate.retisnull = false; - estate.rettype = InvalidOid; - estate.retistuple = func->fn_retistuple; - estate.retisset = func->fn_retset; - estate.exitlabel = NULL; - - estate.found_varno = func->found_varno; - estate.ndatums = func->ndatums; - estate.datums = palloc(sizeof(PLpgSQL_datum *) * estate.ndatums); + plpgsql_estate_setup(&estate, func); /* * Make local execution copies of all the datums @@ -434,6 +429,9 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) } } + /* Clean up any leftover temporary memory */ + exec_eval_cleanup(&estate); + /* * Restore the previous error info and elog() jump target */ @@ -571,16 +569,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func, /* * Setup the execution state */ - estate.retval = 0; - estate.retisnull = false; - estate.rettype = InvalidOid; - estate.retistuple = func->fn_retistuple; - estate.retisset = func->fn_retset; - estate.exitlabel = NULL; - - estate.found_varno = func->found_varno; - estate.ndatums = func->ndatums; - estate.datums = palloc(sizeof(PLpgSQL_datum *) * estate.ndatums); + plpgsql_estate_setup(&estate, func); /* * Make local execution copies of all the datums @@ -616,10 +605,12 @@ plpgsql_exec_trigger(PLpgSQL_function * func, * variable */ 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; var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]); - var->isnull = false; - var->freeval = false; if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) { @@ -649,21 +640,25 @@ plpgsql_exec_trigger(PLpgSQL_function * func, { rec_new->tup = NULL; rec_new->tupdesc = NULL; + rec_old->tup = NULL; + rec_old->tupdesc = NULL; var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN")); } + var->isnull = false; + var->freeval = true; /* * Fill all the other special tg_ variables */ var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]); var->isnull = false; - var->freeval = 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 = 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)) @@ -673,7 +668,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func, var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]); var->isnull = false; - var->freeval = 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)) @@ -684,17 +679,18 @@ plpgsql_exec_trigger(PLpgSQL_function * func, var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]); var->isnull = false; var->freeval = false; - var->value = (Datum) (trigdata->tg_relation->rd_id); + 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 = (Datum) (trigdata->tg_trigger->tgnargs); + var->value = Int16GetDatum(trigdata->tg_trigger->tgnargs); /* * Put the actual call argument values into the special execution @@ -784,9 +780,13 @@ plpgsql_exec_trigger(PLpgSQL_function * func, 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 */ @@ -1085,9 +1085,6 @@ exec_stmt_assign(PLpgSQL_execstate * estate, PLpgSQL_stmt_assign * stmt) */ int rc; - SPI_tuptable = NULL; - SPI_processed = 0; - /* * If not already done create a plan for this expression */ @@ -1098,7 +1095,7 @@ exec_stmt_assign(PLpgSQL_execstate * estate, PLpgSQL_stmt_assign * stmt) if (rc != SPI_OK_SELECT) elog(ERROR, "query \"%s\" didn't return data", expr->query); - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); } return PLPGSQL_RC_OK; @@ -1132,13 +1129,15 @@ exec_stmt_getdiag(PLpgSQL_execstate * estate, PLpgSQL_stmt_getdiag * stmt) { case PLPGSQL_GETDIAG_ROW_COUNT: - exec_assign_value(estate, var, UInt32GetDatum(SPI_processed), + exec_assign_value(estate, var, + UInt32GetDatum(estate->eval_processed), INT4OID, &isnull); break; case PLPGSQL_GETDIAG_RESULT_OID: - exec_assign_value(estate, var, ObjectIdGetDatum(SPI_lastoid), + exec_assign_value(estate, var, + ObjectIdGetDatum(estate->eval_lastoid), OIDOID, &isnull); break; @@ -1166,9 +1165,9 @@ exec_stmt_if(PLpgSQL_execstate * estate, PLpgSQL_stmt_if * stmt) bool isnull = false; value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype); - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); - if (value) + if (!isnull && DatumGetBool(value)) { if (stmt->true_body != NULL) return exec_stmts(estate, stmt->true_body); @@ -1241,8 +1240,8 @@ exec_stmt_while(PLpgSQL_execstate * estate, PLpgSQL_stmt_while * stmt) for (;;) { value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype); - SPI_freetuptable(SPI_tuptable); - if (!value) + exec_eval_cleanup(estate); + if (isnull || !DatumGetBool(value)) break; rc = exec_stmts(estate, stmt->body); @@ -1289,12 +1288,12 @@ exec_stmt_fori(PLpgSQL_execstate * estate, PLpgSQL_stmt_fori * stmt) bool isnull = false; int rc; + 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); - var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]); - value = exec_cast_value(value, valtype, var->datatype->typoid, &(var->datatype->typinput), var->datatype->typelem, @@ -1303,7 +1302,7 @@ exec_stmt_fori(PLpgSQL_execstate * estate, PLpgSQL_stmt_fori * stmt) elog(ERROR, "lower bound of FOR loop cannot be NULL"); var->value = value; var->isnull = false; - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); /* * Get the value of the upper bound @@ -1315,7 +1314,7 @@ exec_stmt_fori(PLpgSQL_execstate * estate, PLpgSQL_stmt_fori * stmt) var->datatype->atttypmod, &isnull); if (isnull) elog(ERROR, "upper bound of FOR loop cannot be NULL"); - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); /* * Now do the loop @@ -1423,8 +1422,10 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt) * 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 row, set the target to NULL and @@ -1447,12 +1448,8 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt) */ for (;;) { - tuptab = SPI_tuptable; - SPI_tuptable = NULL; - for (i = 0; i < n; i++) { - /* * Assign the tuple to the target */ @@ -1501,7 +1498,10 @@ exec_stmt_fors(PLpgSQL_execstate * estate, PLpgSQL_stmt_fors * stmt) * Fetch the next 50 tuples */ SPI_cursor_fetch(portal, true, 50); - if ((n = SPI_processed) == 0) + n = SPI_processed; + tuptab = SPI_tuptable; + + if (n == 0) break; } @@ -1525,7 +1525,7 @@ exec_stmt_select(PLpgSQL_execstate * estate, PLpgSQL_stmt_select * stmt) PLpgSQL_rec *rec = NULL; PLpgSQL_row *row = NULL; SPITupleTable *tuptab; - int n; + uint32 n; /* * Initialize the global found variable to false @@ -1537,21 +1537,16 @@ exec_stmt_select(PLpgSQL_execstate * estate, PLpgSQL_stmt_select * stmt) */ 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 - { - if (stmt->row != NULL) - row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); - else - elog(ERROR, "unsupported target in exec_stmt_select()"); - } + elog(ERROR, "unsupported target in exec_stmt_select()"); /* * Run the query */ exec_run_select(estate, stmt->query, 1, NULL); - n = SPI_processed; - tuptab = SPI_tuptable; - SPI_tuptable = NULL; + n = estate->eval_processed; /* * If the query didn't return any row, set the target to NULL and @@ -1560,15 +1555,18 @@ exec_stmt_select(PLpgSQL_execstate * estate, PLpgSQL_stmt_select * stmt) 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); - SPI_freetuptable(tuptab); + + exec_eval_cleanup(estate); return PLPGSQL_RC_OK; } @@ -1591,8 +1589,8 @@ exec_stmt_exit(PLpgSQL_execstate * estate, PLpgSQL_stmt_exit * stmt) if (stmt->cond != NULL) { value = exec_eval_expr(estate, stmt->cond, &isnull, &valtype); - SPI_freetuptable(SPI_tuptable); - if (!value) + exec_eval_cleanup(estate); + if (isnull || !DatumGetBool(value)) return PLPGSQL_RC_OK; } @@ -1611,29 +1609,38 @@ exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt) { 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]); - estate->retval = (Datum) (rec->tup); - estate->rettupdesc = rec->tupdesc; - estate->retisnull = !HeapTupleIsValid(rec->tup); - + if (HeapTupleIsValid(rec->tup)) + { + estate->retval = (Datum) SPI_copytuple(rec->tup); + estate->rettupdesc = SPI_copytupledesc(rec->tupdesc); + estate->retisnull = false; + } return PLPGSQL_RC_RETURN; } - if (stmt->expr == NULL) - { - estate->retval = (Datum) 0; - estate->rettupdesc = NULL; - estate->retisnull = true; - } - else + if (stmt->expr != NULL) { exec_run_select(estate, stmt->expr, 1, NULL); - estate->retval = (Datum) SPI_copytuple(SPI_tuptable->vals[0]); - estate->rettupdesc = SPI_tuptable->tupdesc; - estate->retisnull = false; + if (estate->eval_processed > 0) + { + estate->retval = (Datum) SPI_copytuple(estate->eval_tuptable->vals[0]); + estate->rettupdesc = SPI_copytupledesc(estate->eval_tuptable->tupdesc); + estate->retisnull = false; + } + /* + * Okay to clean up here, since we already copied result tuple + * to upper executor. + */ + exec_eval_cleanup(estate); } return PLPGSQL_RC_RETURN; } @@ -1706,7 +1713,7 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt) ObjectIdGetDatum(var->datatype->typoid), 0, 0, 0); if (!HeapTupleIsValid(typetup)) - elog(ERROR, "cache lookup for type %u failed (1)", + elog(ERROR, "cache lookup for type %u failed", var->datatype->typoid); typeStruct = (Form_pg_type) GETSTRUCT(typetup); @@ -1750,7 +1757,7 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt) (estate->datums[stmt->params[pidx]]); value = (int) exec_eval_expr(estate, trigarg->argnum, &valisnull, &valtype); - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); if (valisnull) extval = "<INDEX_IS_NULL>"; else @@ -1775,7 +1782,7 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt) } /* - * Occurences of single ' are removed. double ' are reduced to + * Occurrences of single ' are removed. double ' are reduced to * single ones. */ if (*cp == '\'') @@ -1806,6 +1813,58 @@ exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt) } +/* ---------- + * Initialize an empty estate + * ---------- + */ +static void +plpgsql_estate_setup(PLpgSQL_execstate * estate, + PLpgSQL_function * func) +{ + estate->retval = (Datum) 0; + estate->retisnull = true; + estate->rettype = InvalidOid; + estate->retistuple = func->fn_retistuple; + estate->rettupdesc = NULL; + estate->retisset = func->fn_retset; + estate->exitlabel = NULL; + + 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 * ---------- @@ -1823,9 +1882,12 @@ exec_prepare_plan(PLpgSQL_execstate * estate, Oid *argtypes; /* - * Setup the argtypes array + * 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 = malloc(sizeof(Oid *) * (expr->nparams + 1)); + argtypes = palloc(sizeof(Oid *) * (expr->nparams + 1)); for (i = 0; i < expr->nparams; i++) { @@ -1865,9 +1927,11 @@ exec_prepare_plan(PLpgSQL_execstate * estate, if (plan == NULL) elog(ERROR, "SPI_prepare() failed on \"%s\"", expr->query); expr->plan = SPI_saveplan(plan); - expr->plan_argtypes = argtypes; + expr->plan_argtypes = ((_SPI_plan *) expr->plan)->argtypes; expr->plan_simple_expr = NULL; exec_simple_check_plan(expr); + + pfree(argtypes); } @@ -1943,7 +2007,7 @@ exec_stmt_execsql(PLpgSQL_execstate * estate, trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]); tgargno = (int) exec_eval_expr(estate, trigarg->argnum, &isnull, &tgargoid); - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) { values[i] = 0; @@ -1982,6 +2046,14 @@ exec_stmt_execsql(PLpgSQL_execstate * estate, 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); @@ -2022,7 +2094,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate * estate, ObjectIdGetDatum(restype), 0, 0, 0); if (!HeapTupleIsValid(typetup)) - elog(ERROR, "cache lookup for type %u failed (1)", restype); + elog(ERROR, "cache lookup for type %u failed", restype); typeStruct = (Form_pg_type) GETSTRUCT(typetup); fmgr_info(typeStruct->typoutput, &finfo_output); @@ -2031,9 +2103,8 @@ exec_stmt_dynexecute(PLpgSQL_execstate * estate, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); - SPI_freetuptable(SPI_tuptable); - ReleaseSysCache(typetup); + exec_eval_cleanup(estate); /* * Call SPI_exec() without preparing a saved plan. The returncode can @@ -2075,7 +2146,14 @@ exec_stmt_dynexecute(PLpgSQL_execstate * estate, 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; } @@ -2139,7 +2217,7 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt) ObjectIdGetDatum(restype), 0, 0, 0); if (!HeapTupleIsValid(typetup)) - elog(ERROR, "cache lookup for type %u failed (1)", restype); + elog(ERROR, "cache lookup for type %u failed", restype); typeStruct = (Form_pg_type) GETSTRUCT(typetup); fmgr_info(typeStruct->typoutput, &finfo_output); @@ -2148,9 +2226,8 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt) ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); - SPI_freetuptable(SPI_tuptable); - ReleaseSysCache(typetup); + exec_eval_cleanup(estate); /* * Prepare a plan and open an implicit cursor for the query @@ -2170,6 +2247,7 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt) */ SPI_cursor_fetch(portal, true, 10); n = SPI_processed; + tuptab = SPI_tuptable; /* * If the query didn't return any row, set the target to NULL and @@ -2192,12 +2270,8 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt) */ for (;;) { - tuptab = SPI_tuptable; - SPI_tuptable = NULL; - for (i = 0; i < n; i++) { - /* * Assign the tuple to the target */ @@ -2246,7 +2320,10 @@ exec_stmt_dynfors(PLpgSQL_execstate * estate, PLpgSQL_stmt_dynfors * stmt) * Fetch the next 50 tuples */ SPI_cursor_fetch(portal, true, 50); - if ((n = SPI_processed) == 0) + n = SPI_processed; + tuptab = SPI_tuptable; + + if (n == 0) break; } @@ -2346,7 +2423,7 @@ exec_stmt_open(PLpgSQL_execstate * estate, PLpgSQL_stmt_open * stmt) ObjectIdGetDatum(restype), 0, 0, 0); if (!HeapTupleIsValid(typetup)) - elog(ERROR, "cache lookup for type %u failed (1)", restype); + elog(ERROR, "cache lookup for type %u failed", restype); typeStruct = (Form_pg_type) GETSTRUCT(typetup); fmgr_info(typeStruct->typoutput, &finfo_output); @@ -2355,8 +2432,8 @@ exec_stmt_open(PLpgSQL_execstate * estate, PLpgSQL_stmt_open * stmt) ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); - SPI_freetuptable(SPI_tuptable); ReleaseSysCache(typetup); + exec_eval_cleanup(estate); /* ---------- * Now we prepare a query plan for it and open a cursor @@ -2459,7 +2536,7 @@ exec_stmt_open(PLpgSQL_execstate * estate, PLpgSQL_stmt_open * stmt) trigarg = (PLpgSQL_trigarg *) (estate->datums[query->params[i]]); tgargno = (int) exec_eval_expr(estate, trigarg->argnum, &isnull, &tgargoid); - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) { values[i] = 0; @@ -2489,6 +2566,8 @@ exec_stmt_open(PLpgSQL_execstate * estate, PLpgSQL_stmt_open * stmt) pfree(values); pfree(nulls); + if (curname) + pfree(curname); /* ---------- * Store the eventually assigned portal name in the cursor variable @@ -2515,6 +2594,7 @@ 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; @@ -2559,6 +2639,7 @@ exec_stmt_fetch(PLpgSQL_execstate * estate, PLpgSQL_stmt_fetch * stmt) */ SPI_cursor_fetch(portal, true, 1); n = SPI_processed; + tuptab = SPI_tuptable; /* ---------- * If the FETCH didn't return a row, set the target @@ -2575,11 +2656,10 @@ exec_stmt_fetch(PLpgSQL_execstate * estate, PLpgSQL_stmt_fetch * stmt) * Put the result into the target and set found to true * ---------- */ - exec_move_row(estate, rec, row, SPI_tuptable->vals[0], - SPI_tuptable->tupdesc); + exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc); exec_set_found(estate, true); - SPI_freetuptable(SPI_tuptable); + SPI_freetuptable(tuptab); return PLPGSQL_RC_OK; } @@ -2635,8 +2715,7 @@ exec_assign_expr(PLpgSQL_execstate * estate, PLpgSQL_datum * target, value = exec_eval_expr(estate, expr, &isnull, &valtype); exec_assign_value(estate, target, value, valtype, &isnull); - - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); } @@ -2657,6 +2736,7 @@ exec_assign_value(PLpgSQL_execstate * estate, int natts; Datum *values; char *nulls; + void *mustfree; Datum newvalue; bool attisnull; Oid atttype; @@ -2687,23 +2767,34 @@ exec_assign_value(PLpgSQL_execstate * estate, var->datatype->atttypmod, isNull); - if (!var->datatype->typbyval && newvalue == value && !*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) { - int len; - if (var->datatype->typlen < 0) - len = VARSIZE(newvalue); + if (newvalue == value) + { + int len; + + if (var->datatype->typlen < 0) + len = VARSIZE(newvalue); + else + len = var->datatype->typlen; + var->value = (Datum) palloc(len); + memcpy((void *)(var->value), (void *)newvalue, len); + } else - len = var->datatype->typlen; - var->value = (Datum)palloc(len); - memcpy((void *)(var->value), (void *)newvalue, len); + var->value = newvalue; var->freeval = true; } else var->value = newvalue; - - if (*isNull && var->notnull) - elog(ERROR, "NULL assignment to variable '%s' declared NOT NULL", var->refname); - var->isnull = *isNull; break; @@ -2734,62 +2825,64 @@ exec_assign_value(PLpgSQL_execstate * estate, natts = rec->tupdesc->natts; /* - * We loop over the attributes of the rec's current tuple and - * collect the values in a Datum array along with the nulls - * information. + * 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 + 1); + nulls = palloc(natts); for (i = 0; i < natts; i++) { - - /* - * If this isn't the field we assign to, just use the - * value that's already in the tuple. - */ - if (i != fno) - { - values[i] = SPI_getbinval(rec->tup, rec->tupdesc, - i + 1, &attisnull); - if (attisnull) - nulls[i] = 'n'; - else - nulls[i] = ' '; + if (i == fno) continue; - } - - /* - * This is the field to change. Get its type and cast the - * value we insert to that type. - */ - atttype = SPI_gettypeid(rec->tupdesc, i + 1); - atttypmod = rec->tupdesc->attrs[i]->atttypmod; - typetup = SearchSysCache(TYPEOID, - ObjectIdGetDatum(atttype), - 0, 0, 0); - if (!HeapTupleIsValid(typetup)) - elog(ERROR, "cache lookup for type %u failed", atttype); - typeStruct = (Form_pg_type) GETSTRUCT(typetup); - fmgr_info(typeStruct->typinput, &finfo_input); - - attisnull = *isNull; - values[i] = exec_cast_value(value, valtype, - atttype, &finfo_input, - typeStruct->typelem, - atttypmod, &attisnull); + values[i] = SPI_getbinval(rec->tup, rec->tupdesc, + i + 1, &attisnull); if (attisnull) nulls[i] = 'n'; else nulls[i] = ' '; - ReleaseSysCache(typetup); } + /* + * 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; + typetup = SearchSysCache(TYPEOID, + ObjectIdGetDatum(atttype), + 0, 0, 0); + if (!HeapTupleIsValid(typetup)) + elog(ERROR, "cache lookup for type %u failed", atttype); + typeStruct = (Form_pg_type) GETSTRUCT(typetup); + fmgr_info(typeStruct->typinput, &finfo_input); + + attisnull = *isNull; + values[fno] = exec_cast_value(value, valtype, + atttype, &finfo_input, + typeStruct->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 (!typeStruct->typbyval && !attisnull && values[fno] != value) + mustfree = DatumGetPointer(values[fno]); + else + mustfree = NULL; + + ReleaseSysCache(typetup); + /* * Now call heap_formtuple() to create a new tuple that * replaces the old one in the record. */ - nulls[i] = '\0'; newtup = heap_formtuple(rec->tupdesc, values, nulls); if (rec->freetup) @@ -2800,6 +2893,8 @@ exec_assign_value(PLpgSQL_execstate * estate, pfree(values); pfree(nulls); + if (mustfree) + pfree(mustfree); break; @@ -2813,6 +2908,8 @@ exec_assign_value(PLpgSQL_execstate * estate, /* ---------- * 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 @@ -2823,9 +2920,6 @@ exec_eval_expr(PLpgSQL_execstate * estate, { int rc; - SPI_tuptable = NULL; - SPI_processed = 0; - /* * If not already done create a plan for this expression */ @@ -2846,7 +2940,7 @@ exec_eval_expr(PLpgSQL_execstate * estate, /* * If there are no rows selected, the result is NULL. */ - if (SPI_processed == 0) + if (estate->eval_processed == 0) { *isNull = true; return (Datum) 0; @@ -2855,17 +2949,18 @@ exec_eval_expr(PLpgSQL_execstate * estate, /* * Check that the expression returned one single Datum */ - if (SPI_processed > 1) + if (estate->eval_processed > 1) elog(ERROR, "query \"%s\" returned more than one row", expr->query); - if (SPI_tuptable->tupdesc->natts != 1) + if (estate->eval_tuptable->tupdesc->natts != 1) elog(ERROR, "query \"%s\" returned %d columns", expr->query, - SPI_tuptable->tupdesc->natts); + estate->eval_tuptable->tupdesc->natts); /* * Return the result and its type */ - *rettype = SPI_gettypeid(SPI_tuptable->tupdesc, 1); - return SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, isNull); + *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1); + return SPI_getbinval(estate->eval_tuptable->vals[0], + estate->eval_tuptable->tupdesc, 1, isNull); } @@ -2939,7 +3034,7 @@ exec_run_select(PLpgSQL_execstate * estate, trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]); tgargno = (int) exec_eval_expr(estate, trigarg->argnum, &isnull, &tgargoid); - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) { values[i] = 0; @@ -2960,7 +3055,7 @@ exec_run_select(PLpgSQL_execstate * estate, nulls[i] = '\0'; /* - * Execute the query + * If a portal was requested, put the query into the portal */ if (portalP != NULL) { @@ -2972,9 +3067,20 @@ exec_run_select(PLpgSQL_execstate * estate, 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); @@ -2993,6 +3099,7 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate, bool *isNull, Oid *rettype) { + _SPI_plan *spi_plan = (_SPI_plan *) expr->plan; Datum retval; PLpgSQL_var *var; PLpgSQL_rec *rec; @@ -3009,16 +3116,19 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate, /* * Create a simple expression context to hold the arguments. * - * NOTE: we pass TopMemoryContext as the query-lifetime context for - * function cache nodes and suchlike allocations. This is necessary - * because that's where the expression tree itself is (it'll never be - * freed in this backend, and the function cache nodes must live as - * long as it does). The memory allocation for plpgsql's plan trees - * really needs to be redesigned... - */ - econtext = MakeExprContext(NULL, TopMemoryContext); - paramLI = (ParamListInfo) palloc((expr->nparams + 1) * - sizeof(ParamListInfoData)); + * NOTE: we pass the SPI plan's context as the query-lifetime context for + * function cache nodes and suchlike allocations. This is appropriate + * because that's where the expression tree itself is, and the function + * cache nodes must live as long as it does. + */ + econtext = MakeExprContext(NULL, spi_plan->plancxt); + + /* + * 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; /* @@ -3059,7 +3169,7 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate, trigarg = (PLpgSQL_trigarg *) (estate->datums[expr->params[i]]); tgargno = (int) exec_eval_expr(estate, trigarg->argnum, &isnull, &tgargoid); - SPI_freetuptable(SPI_tuptable); + exec_eval_cleanup(estate); if (isnull || tgargno < 0 || tgargno >= estate->trig_nargs) { paramLI->value = 0; @@ -3094,19 +3204,11 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate, SPI_pop(); /* - * Copy the result out of the expression-evaluation memory context, so - * that we can free the expression context. + * 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. */ - if (!*isNull) - { - int16 typeLength; - bool byValue; - - get_typlenbyval(*rettype, &typeLength, &byValue); - retval = datumCopy(retval, byValue, typeLength); - } - - FreeExprContext(econtext); + Assert(estate->eval_econtext == NULL); + estate->eval_econtext = econtext; /* * That's it. @@ -3224,7 +3326,6 @@ exec_cast_value(Datum value, Oid valtype, { if (!*isnull) { - /* * If the type of the queries return value isn't that of the * variable, convert it. @@ -3329,12 +3430,6 @@ exec_simple_check_plan(PLpgSQL_expr * expr) TargetEntry *tle; expr->plan_simple_expr = NULL; - /* - * Disabled for now until we can execute simple expressions - * without collecting all the memory allocations until procedure - * returns. 05/17/2001 Jan - */ - return; /* * 1. We can only evaluate queries that resulted in one single @@ -3402,5 +3497,3 @@ exec_set_found(PLpgSQL_execstate * estate, bool state) var->value = (Datum) state; var->isnull = false; } - - diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index fd507bc8b14f6b940f664f0985428b7ea95afead..c5942fa06983de31646c2179e919245f6ec277bc 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.16 2001/07/12 17:42:08 momjian Exp $ + * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.17 2001/08/02 21:31:23 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -502,6 +502,12 @@ typedef struct int found_varno; int ndatums; PLpgSQL_datum **datums; + + /* temporary state for results from evaluation of query or expr */ + SPITupleTable *eval_tuptable; + uint32 eval_processed; + Oid eval_lastoid; + ExprContext *eval_econtext; } PLpgSQL_execstate;