I wrote: > What this patch does is to remove setup_param_list() overhead for the > common case of PLPGSQL_DTYPE_VAR variables (ie, any non-composite type). > It does that by the expedient of keeping the ParamExternData image of such > a variable valid at all times. That adds a few cycles to assignments to > these variables, but removes more cycles from each use of them. Unless > you believe that common plpgsql functions contain lots of dead stores, > this is a guaranteed win overall.
> I'm seeing about 10% overall speedup (vs HEAD, with casserts off) for > realistic simple plpgsql logic, such as this test case: Here is a version that is rebased up to HEAD. Dunno if anyone wants to re-review this, if not I'll go commit it. regards, tom lane
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 0ff2086..05268e3 100644 *** a/src/pl/plpgsql/src/pl_comp.c --- b/src/pl/plpgsql/src/pl_comp.c *************** PLpgSQL_stmt_block *plpgsql_parse_result *** 42,48 **** static int datums_alloc; int plpgsql_nDatums; PLpgSQL_datum **plpgsql_Datums; ! static int datums_last = 0; char *plpgsql_error_funcname; bool plpgsql_DumpExecTree = false; --- 42,48 ---- static int datums_alloc; int plpgsql_nDatums; PLpgSQL_datum **plpgsql_Datums; ! static int datums_last; char *plpgsql_error_funcname; bool plpgsql_DumpExecTree = false; *************** static Node *make_datum_param(PLpgSQL_ex *** 104,109 **** --- 104,111 ---- static PLpgSQL_row *build_row_from_class(Oid classOid); static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars); static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod, Oid collation); + static void plpgsql_start_datums(void); + static void plpgsql_finish_datums(PLpgSQL_function *function); static void compute_function_hashkey(FunctionCallInfo fcinfo, Form_pg_proc procStruct, PLpgSQL_func_hashkey *hashkey, *************** do_compile(FunctionCallInfo fcinfo, *** 371,383 **** plpgsql_ns_init(); plpgsql_ns_push(NameStr(procStruct->proname)); plpgsql_DumpExecTree = false; ! ! datums_alloc = 128; ! plpgsql_nDatums = 0; ! /* This is short-lived, so needn't allocate in function's cxt */ ! plpgsql_Datums = MemoryContextAlloc(compile_tmp_cxt, ! sizeof(PLpgSQL_datum *) * datums_alloc); ! datums_last = 0; switch (function->fn_is_trigger) { --- 373,379 ---- plpgsql_ns_init(); plpgsql_ns_push(NameStr(procStruct->proname)); plpgsql_DumpExecTree = false; ! plpgsql_start_datums(); switch (function->fn_is_trigger) { *************** do_compile(FunctionCallInfo fcinfo, *** 758,767 **** function->fn_nargs = procStruct->pronargs; for (i = 0; i < function->fn_nargs; i++) function->fn_argvarnos[i] = in_arg_varnos[i]; ! function->ndatums = plpgsql_nDatums; ! function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); ! for (i = 0; i < plpgsql_nDatums; i++) ! function->datums[i] = plpgsql_Datums[i]; /* Debug dump for completed functions */ if (plpgsql_DumpExecTree) --- 754,761 ---- function->fn_nargs = procStruct->pronargs; for (i = 0; i < function->fn_nargs; i++) function->fn_argvarnos[i] = in_arg_varnos[i]; ! ! plpgsql_finish_datums(function); /* Debug dump for completed functions */ if (plpgsql_DumpExecTree) *************** plpgsql_compile_inline(char *proc_source *** 804,810 **** PLpgSQL_variable *var; int parse_rc; MemoryContext func_cxt; - int i; /* * Setup the scanner input and error info. We assume that this function --- 798,803 ---- *************** plpgsql_compile_inline(char *proc_source *** 860,870 **** plpgsql_ns_init(); plpgsql_ns_push(func_name); plpgsql_DumpExecTree = false; ! ! datums_alloc = 128; ! plpgsql_nDatums = 0; ! plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc); ! datums_last = 0; /* Set up as though in a function returning VOID */ function->fn_rettype = VOIDOID; --- 853,859 ---- plpgsql_ns_init(); plpgsql_ns_push(func_name); plpgsql_DumpExecTree = false; ! plpgsql_start_datums(); /* Set up as though in a function returning VOID */ function->fn_rettype = VOIDOID; *************** plpgsql_compile_inline(char *proc_source *** 911,920 **** * Complete the function's info */ function->fn_nargs = 0; ! function->ndatums = plpgsql_nDatums; ! function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); ! for (i = 0; i < plpgsql_nDatums; i++) ! function->datums[i] = plpgsql_Datums[i]; /* * Pop the error context stack --- 900,907 ---- * Complete the function's info */ function->fn_nargs = 0; ! ! plpgsql_finish_datums(function); /* * Pop the error context stack *************** plpgsql_build_record(const char *refname *** 1965,1970 **** --- 1952,1958 ---- rec->tup = NULL; rec->tupdesc = NULL; rec->freetup = false; + rec->freetupdesc = false; plpgsql_adddatum((PLpgSQL_datum *) rec); if (add2namespace) plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->dno, rec->refname); *************** plpgsql_parse_err_condition(char *condna *** 2312,2317 **** --- 2300,2321 ---- } /* ---------- + * plpgsql_start_datums Initialize datum list at compile startup. + * ---------- + */ + static void + plpgsql_start_datums(void) + { + datums_alloc = 128; + plpgsql_nDatums = 0; + /* This is short-lived, so needn't allocate in function's cxt */ + plpgsql_Datums = MemoryContextAlloc(compile_tmp_cxt, + sizeof(PLpgSQL_datum *) * datums_alloc); + /* datums_last tracks what's been seen by plpgsql_add_initdatums() */ + datums_last = 0; + } + + /* ---------- * plpgsql_adddatum Add a variable, record or row * to the compiler's datum list. * ---------- *************** plpgsql_adddatum(PLpgSQL_datum *new) *** 2329,2334 **** --- 2333,2371 ---- plpgsql_Datums[plpgsql_nDatums++] = new; } + /* ---------- + * plpgsql_finish_datums Copy completed datum info into function struct. + * + * This is also responsible for building resettable_datums, a bitmapset + * of the dnos of all ROW, REC, and RECFIELD datums in the function. + * ---------- + */ + static void + plpgsql_finish_datums(PLpgSQL_function *function) + { + Bitmapset *resettable_datums = NULL; + int i; + + function->ndatums = plpgsql_nDatums; + function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); + for (i = 0; i < plpgsql_nDatums; i++) + { + function->datums[i] = plpgsql_Datums[i]; + switch (function->datums[i]->dtype) + { + case PLPGSQL_DTYPE_ROW: + case PLPGSQL_DTYPE_REC: + case PLPGSQL_DTYPE_RECFIELD: + resettable_datums = bms_add_member(resettable_datums, i); + break; + + default: + break; + } + } + function->resettable_datums = resettable_datums; + } + /* ---------- * plpgsql_add_initdatums Make an array of the datum numbers of diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 79dd6a2..7d4001c 100644 *** a/src/pl/plpgsql/src/pl_exec.c --- b/src/pl/plpgsql/src/pl_exec.c *************** static int exec_for_query(PLpgSQL_execst *** 216,221 **** --- 216,223 ---- Portal portal, bool prefetch_ok); static ParamListInfo setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr); + static ParamListInfo setup_unshared_param_list(PLpgSQL_execstate *estate, + PLpgSQL_expr *expr); static void plpgsql_param_fetch(ParamListInfo params, int paramid); static void exec_move_row(PLpgSQL_execstate *estate, PLpgSQL_rec *rec, *************** static void exec_init_tuple_store(PLpgSQ *** 242,249 **** static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void plpgsql_create_econtext(PLpgSQL_execstate *estate); static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate); ! static void free_var(PLpgSQL_var *var); ! static void assign_text_var(PLpgSQL_var *var, const char *str); static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate, List *params); static void free_params_data(PreparedParamsData *ppd); --- 244,253 ---- static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void plpgsql_create_econtext(PLpgSQL_execstate *estate); static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate); ! static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, ! Datum newvalue, bool isnull, bool freeable); ! static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, ! const char *str); static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate, List *params); static void free_params_data(PreparedParamsData *ppd); *************** plpgsql_exec_function(PLpgSQL_function * *** 312,320 **** { PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[n]; ! var->value = fcinfo->arg[i]; ! var->isnull = fcinfo->argnull[i]; ! var->freeval = false; /* * Force any array-valued parameter to be stored in --- 316,325 ---- { PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[n]; ! assign_simple_var(&estate, var, ! fcinfo->arg[i], ! fcinfo->argnull[i], ! false); /* * Force any array-valued parameter to be stored in *************** plpgsql_exec_function(PLpgSQL_function * *** 336,344 **** if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value))) { /* take ownership of R/W object */ ! var->value = TransferExpandedObject(var->value, ! CurrentMemoryContext); ! var->freeval = true; } else if (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(var->value))) { --- 341,351 ---- if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value))) { /* take ownership of R/W object */ ! assign_simple_var(&estate, var, ! TransferExpandedObject(var->value, ! CurrentMemoryContext), ! false, ! true); } else if (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(var->value))) { *************** plpgsql_exec_function(PLpgSQL_function * *** 347,356 **** else { /* flat array, so force to expanded form */ ! var->value = expand_array(var->value, ! CurrentMemoryContext, ! NULL); ! var->freeval = true; } } } --- 354,365 ---- else { /* flat array, so force to expanded form */ ! assign_simple_var(&estate, var, ! expand_array(var->value, ! CurrentMemoryContext, ! NULL), ! false, ! true); } } } *************** plpgsql_exec_trigger(PLpgSQL_function *f *** 641,716 **** var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]); if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) ! var->value = CStringGetTextDatum("INSERT"); else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) ! var->value = CStringGetTextDatum("UPDATE"); else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) ! var->value = CStringGetTextDatum("DELETE"); else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event)) ! var->value = CStringGetTextDatum("TRUNCATE"); else elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); - var->isnull = false; - var->freeval = true; var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]); ! var->value = DirectFunctionCall1(namein, ! CStringGetDatum(trigdata->tg_trigger->tgname)); ! var->isnull = false; ! var->freeval = true; var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]); if (TRIGGER_FIRED_BEFORE(trigdata->tg_event)) ! var->value = CStringGetTextDatum("BEFORE"); else if (TRIGGER_FIRED_AFTER(trigdata->tg_event)) ! var->value = CStringGetTextDatum("AFTER"); else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event)) ! var->value = CStringGetTextDatum("INSTEAD OF"); else elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF"); - var->isnull = false; - var->freeval = true; var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]); if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) ! var->value = CStringGetTextDatum("ROW"); else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) ! var->value = CStringGetTextDatum("STATEMENT"); else elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT"); - var->isnull = false; - var->freeval = true; var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]); ! var->value = ObjectIdGetDatum(trigdata->tg_relation->rd_id); ! var->isnull = false; ! var->freeval = false; var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]); ! var->value = DirectFunctionCall1(namein, ! CStringGetDatum(RelationGetRelationName(trigdata->tg_relation))); ! var->isnull = false; ! var->freeval = true; var = (PLpgSQL_var *) (estate.datums[func->tg_table_name_varno]); ! var->value = DirectFunctionCall1(namein, ! CStringGetDatum(RelationGetRelationName(trigdata->tg_relation))); ! var->isnull = false; ! var->freeval = true; var = (PLpgSQL_var *) (estate.datums[func->tg_table_schema_varno]); ! var->value = DirectFunctionCall1(namein, ! CStringGetDatum( ! get_namespace_name( RelationGetNamespace( ! trigdata->tg_relation)))); ! var->isnull = false; ! var->freeval = true; var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]); ! var->value = Int16GetDatum(trigdata->tg_trigger->tgnargs); ! var->isnull = false; ! var->freeval = false; var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]); if (trigdata->tg_trigger->tgnargs > 0) --- 650,718 ---- var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]); if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) ! assign_text_var(&estate, var, "INSERT"); else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) ! assign_text_var(&estate, var, "UPDATE"); else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) ! assign_text_var(&estate, var, "DELETE"); else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event)) ! assign_text_var(&estate, var, "TRUNCATE"); else elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]); ! assign_simple_var(&estate, var, ! DirectFunctionCall1(namein, ! CStringGetDatum(trigdata->tg_trigger->tgname)), ! false, true); var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]); if (TRIGGER_FIRED_BEFORE(trigdata->tg_event)) ! assign_text_var(&estate, var, "BEFORE"); else if (TRIGGER_FIRED_AFTER(trigdata->tg_event)) ! assign_text_var(&estate, var, "AFTER"); else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event)) ! assign_text_var(&estate, var, "INSTEAD OF"); else elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF"); var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]); if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) ! assign_text_var(&estate, var, "ROW"); else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event)) ! assign_text_var(&estate, var, "STATEMENT"); else elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT"); var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]); ! assign_simple_var(&estate, var, ! ObjectIdGetDatum(trigdata->tg_relation->rd_id), ! false, false); var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]); ! assign_simple_var(&estate, var, ! DirectFunctionCall1(namein, ! CStringGetDatum(RelationGetRelationName(trigdata->tg_relation))), ! false, true); var = (PLpgSQL_var *) (estate.datums[func->tg_table_name_varno]); ! assign_simple_var(&estate, var, ! DirectFunctionCall1(namein, ! CStringGetDatum(RelationGetRelationName(trigdata->tg_relation))), ! false, true); var = (PLpgSQL_var *) (estate.datums[func->tg_table_schema_varno]); ! assign_simple_var(&estate, var, ! DirectFunctionCall1(namein, ! CStringGetDatum(get_namespace_name( RelationGetNamespace( ! trigdata->tg_relation)))), ! false, true); var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]); ! assign_simple_var(&estate, var, ! Int16GetDatum(trigdata->tg_trigger->tgnargs), ! false, false); var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]); if (trigdata->tg_trigger->tgnargs > 0) *************** plpgsql_exec_trigger(PLpgSQL_function *f *** 730,747 **** dims[0] = nelems; lbs[0] = 0; ! var->value = PointerGetDatum(construct_md_array(elems, NULL, ! 1, dims, lbs, ! TEXTOID, ! -1, false, 'i')); ! var->isnull = false; ! var->freeval = true; } else { ! var->value = (Datum) 0; ! var->isnull = true; ! var->freeval = false; } estate.err_text = gettext_noop("during function entry"); --- 732,747 ---- dims[0] = nelems; lbs[0] = 0; ! assign_simple_var(&estate, var, ! PointerGetDatum(construct_md_array(elems, NULL, ! 1, dims, lbs, ! TEXTOID, ! -1, false, 'i')), ! false, true); } else { ! assign_simple_var(&estate, var, (Datum) 0, true, false); } estate.err_text = gettext_noop("during function entry"); *************** plpgsql_exec_event_trigger(PLpgSQL_funct *** 874,887 **** * Assign the special tg_ variables */ var = (PLpgSQL_var *) (estate.datums[func->tg_event_varno]); ! var->value = CStringGetTextDatum(trigdata->event); ! var->isnull = false; ! var->freeval = true; var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]); ! var->value = CStringGetTextDatum(trigdata->tag); ! var->isnull = false; ! var->freeval = true; /* * Let the instrumentation plugin peek at this function --- 874,883 ---- * Assign the special tg_ variables */ var = (PLpgSQL_var *) (estate.datums[func->tg_event_varno]); ! assign_text_var(&estate, var, trigdata->event); var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]); ! assign_text_var(&estate, var, trigdata->tag); /* * Let the instrumentation plugin peek at this function *************** copy_plpgsql_datum(PLpgSQL_datum *datum) *** 1012,1021 **** PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var)); memcpy(new, datum, sizeof(PLpgSQL_var)); ! /* Ensure the value is null (possibly not needed?) */ ! new->value = 0; ! new->isnull = true; ! new->freeval = false; result = (PLpgSQL_datum *) new; } --- 1008,1016 ---- PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var)); memcpy(new, datum, sizeof(PLpgSQL_var)); ! /* should be preset to null/non-freeable */ ! Assert(new->isnull); ! Assert(!new->freeval); result = (PLpgSQL_datum *) new; } *************** copy_plpgsql_datum(PLpgSQL_datum *datum) *** 1026,1036 **** PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec)); memcpy(new, datum, sizeof(PLpgSQL_rec)); ! /* Ensure the value is null (possibly not needed?) */ ! new->tup = NULL; ! new->tupdesc = NULL; ! new->freetup = false; ! new->freetupdesc = false; result = (PLpgSQL_datum *) new; } --- 1021,1031 ---- PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec)); memcpy(new, datum, sizeof(PLpgSQL_rec)); ! /* should be preset to null/non-freeable */ ! Assert(new->tup == NULL); ! Assert(new->tupdesc == NULL); ! Assert(!new->freetup); ! Assert(!new->freetupdesc); result = (PLpgSQL_datum *) new; } *************** exec_stmt_block(PLpgSQL_execstate *estat *** 1114,1125 **** { PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]); ! /* free any old value, in case re-entering block */ ! free_var(var); ! ! /* Initially it contains a NULL */ ! var->value = (Datum) 0; ! var->isnull = true; if (var->default_val == NULL) { --- 1109,1119 ---- { PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]); ! /* ! * Free any old value, in case re-entering block, and ! * initialize to NULL ! */ ! assign_simple_var(estate, var, (Datum) 0, true, false); if (var->default_val == NULL) { *************** exec_stmt_block(PLpgSQL_execstate *estat *** 1308,1316 **** errm_var = (PLpgSQL_var *) estate->datums[block->exceptions->sqlerrm_varno]; ! assign_text_var(state_var, unpack_sql_state(edata->sqlerrcode)); ! assign_text_var(errm_var, edata->message); /* * Also set up cur_error so the error data is accessible --- 1302,1310 ---- errm_var = (PLpgSQL_var *) estate->datums[block->exceptions->sqlerrm_varno]; ! assign_text_var(estate, state_var, unpack_sql_state(edata->sqlerrcode)); ! assign_text_var(estate, errm_var, edata->message); /* * Also set up cur_error so the error data is accessible *************** exec_stmt_case(PLpgSQL_execstate *estate *** 1788,1798 **** /* We can now discard any value we had for the temp variable */ if (t_var != NULL) ! { ! free_var(t_var); ! t_var->value = (Datum) 0; ! t_var->isnull = true; ! } /* Evaluate the statement(s), and we're done */ return exec_stmts(estate, cwt->stmts); --- 1782,1788 ---- /* We can now discard any value we had for the temp variable */ if (t_var != NULL) ! assign_simple_var(estate, t_var, (Datum) 0, true, false); /* Evaluate the statement(s), and we're done */ return exec_stmts(estate, cwt->stmts); *************** exec_stmt_case(PLpgSQL_execstate *estate *** 1801,1811 **** /* We can now discard any value we had for the temp variable */ if (t_var != NULL) ! { ! free_var(t_var); ! t_var->value = (Datum) 0; ! t_var->isnull = true; ! } /* SQL2003 mandates this error if there was no ELSE clause */ if (!stmt->have_else) --- 1791,1797 ---- /* We can now discard any value we had for the temp variable */ if (t_var != NULL) ! assign_simple_var(estate, t_var, (Datum) 0, true, false); /* SQL2003 mandates this error if there was no ELSE clause */ if (!stmt->have_else) *************** exec_stmt_fori(PLpgSQL_execstate *estate *** 2035,2042 **** /* * Assign current value to loop var */ ! var->value = Int32GetDatum(loop_value); ! var->isnull = false; /* * Execute the statements --- 2021,2027 ---- /* * Assign current value to loop var */ ! assign_simple_var(estate, var, Int32GetDatum(loop_value), false, false); /* * Execute the statements *************** exec_stmt_forc(PLpgSQL_execstate *estate *** 2228,2236 **** exec_prepare_plan(estate, query, curvar->cursor_options); /* ! * Set up ParamListInfo (hook function and possibly data values) */ ! paramLI = setup_param_list(estate, query); /* * Open the cursor (the paramlist will get copied into the portal) --- 2213,2221 ---- exec_prepare_plan(estate, query, curvar->cursor_options); /* ! * Set up short-lived ParamListInfo */ ! paramLI = setup_unshared_param_list(estate, query); /* * Open the cursor (the paramlist will get copied into the portal) *************** exec_stmt_forc(PLpgSQL_execstate *estate *** 2242,2252 **** elog(ERROR, "could not open cursor: %s", SPI_result_code_string(SPI_result)); /* * If cursor variable was NULL, store the generated portal name in it */ if (curname == NULL) ! assign_text_var(curvar, portal->name); /* * Execute the loop. We can't prefetch because the cursor is accessible --- 2227,2241 ---- elog(ERROR, "could not open cursor: %s", SPI_result_code_string(SPI_result)); + /* don't need paramlist any more */ + if (paramLI) + pfree(paramLI); + /* * If cursor variable was NULL, store the generated portal name in it */ if (curname == NULL) ! assign_text_var(estate, curvar, portal->name); /* * Execute the loop. We can't prefetch because the cursor is accessible *************** exec_stmt_forc(PLpgSQL_execstate *estate *** 2261,2271 **** SPI_cursor_close(portal); if (curname == NULL) ! { ! free_var(curvar); ! curvar->value = (Datum) 0; ! curvar->isnull = true; ! } if (curname) pfree(curname); --- 2250,2256 ---- SPI_cursor_close(portal); if (curname == NULL) ! assign_simple_var(estate, curvar, (Datum) 0, true, false); if (curname) pfree(curname); *************** plpgsql_estate_setup(PLpgSQL_execstate * *** 3314,3319 **** --- 3299,3305 ---- estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup; estate->paramLI->parserSetupArg = NULL; /* filled during use */ estate->paramLI->numParams = estate->ndatums; + estate->params_dirty = false; /* set up for use of appropriate simple-expression EState */ if (simple_eval_estate) *************** exec_stmt_execsql(PLpgSQL_execstate *est *** 3478,3484 **** } /* ! * Set up ParamListInfo (hook function and possibly data values) */ paramLI = setup_param_list(estate, expr); --- 3464,3470 ---- } /* ! * Set up ParamListInfo to pass to executor */ paramLI = setup_param_list(estate, expr); *************** exec_stmt_open(PLpgSQL_execstate *estate *** 3937,3943 **** * If cursor variable was NULL, store the generated portal name in it */ if (curname == NULL) ! assign_text_var(curvar, portal->name); return PLPGSQL_RC_OK; } --- 3923,3929 ---- * If cursor variable was NULL, store the generated portal name in it */ if (curname == NULL) ! assign_text_var(estate, curvar, portal->name); return PLPGSQL_RC_OK; } *************** exec_stmt_open(PLpgSQL_execstate *estate *** 3991,3999 **** } /* ! * Set up ParamListInfo (hook function and possibly data values) */ ! paramLI = setup_param_list(estate, query); /* * Open the cursor --- 3977,3985 ---- } /* ! * Set up short-lived ParamListInfo */ ! paramLI = setup_unshared_param_list(estate, query); /* * Open the cursor *************** exec_stmt_open(PLpgSQL_execstate *estate *** 4009,4018 **** * If cursor variable was NULL, store the generated portal name in it */ if (curname == NULL) ! assign_text_var(curvar, portal->name); if (curname) pfree(curname); return PLPGSQL_RC_OK; } --- 3995,4006 ---- * If cursor variable was NULL, store the generated portal name in it */ if (curname == NULL) ! assign_text_var(estate, curvar, portal->name); if (curname) pfree(curname); + if (paramLI) + pfree(paramLI); return PLPGSQL_RC_OK; } *************** exec_assign_value(PLpgSQL_execstate *est *** 4282,4300 **** } /* ! * Now free the old value, unless it's the same as the new ! * value (ie, we're doing "foo := foo"). Note that for ! * expanded objects, this test is necessary and cannot ! * reliably be made any earlier; we have to be looking at the ! * object's standard R/W pointer to be sure pointer equality ! * is meaningful. */ if (var->value != newvalue || var->isnull || isNull) ! free_var(var); ! ! var->value = newvalue; ! var->isnull = isNull; ! var->freeval = (!var->datatype->typbyval && !isNull); break; } --- 4270,4285 ---- } /* ! * Now free the old value, if any, and assign the new one. But ! * skip the assignment if old and new values are the same. ! * Note that for expanded objects, this test is necessary and ! * cannot reliably be made any earlier; we have to be looking ! * at the object's standard R/W pointer to be sure pointer ! * equality is meaningful. */ if (var->value != newvalue || var->isnull || isNull) ! assign_simple_var(estate, var, newvalue, isNull, ! (!var->datatype->typbyval && !isNull)); break; } *************** exec_assign_value(PLpgSQL_execstate *est *** 4638,4644 **** * * The type oid, typmod, value in Datum format, and null flag are returned. * ! * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums. * * NOTE: the returned Datum points right at the stored value in the case of * pass-by-reference datatypes. Generally callers should take care not to --- 4623,4630 ---- * * The type oid, typmod, value in Datum format, and null flag are returned. * ! * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums; ! * that's not needed because we never pass references to such datums to SPI. * * NOTE: the returned Datum points right at the stored value in the case of * pass-by-reference datatypes. Generally callers should take care not to *************** exec_run_select(PLpgSQL_execstate *estat *** 5082,5106 **** exec_prepare_plan(estate, expr, 0); /* - * Set up ParamListInfo (hook function and possibly data values) - */ - paramLI = setup_param_list(estate, expr); - - /* * If a portal was requested, put the query into the portal */ if (portalP != NULL) { *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan, paramLI, estate->readonly_func); if (*portalP == NULL) elog(ERROR, "could not open implicit cursor for query \"%s\": %s", expr->query, SPI_result_code_string(SPI_result)); return SPI_OK_CURSOR; } /* * Execute the query */ rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI, --- 5068,5099 ---- exec_prepare_plan(estate, expr, 0); /* * If a portal was requested, put the query into the portal */ if (portalP != NULL) { + /* + * Set up short-lived ParamListInfo + */ + paramLI = setup_unshared_param_list(estate, expr); + *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan, paramLI, estate->readonly_func); if (*portalP == NULL) elog(ERROR, "could not open implicit cursor for query \"%s\": %s", expr->query, SPI_result_code_string(SPI_result)); + if (paramLI) + pfree(paramLI); return SPI_OK_CURSOR; } /* + * Set up ParamListInfo to pass to executor + */ + paramLI = setup_param_list(estate, expr); + + /* * Execute the query */ rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI, *************** exec_eval_simple_expr(PLpgSQL_execstate *** 5402,5408 **** } /* ! * Set up param list. For safety, save and restore * estate->paramLI->parserSetupArg around our use of the param list. */ save_setup_arg = estate->paramLI->parserSetupArg; --- 5395,5401 ---- } /* ! * Set up ParamListInfo to pass to executor. For safety, save and restore * estate->paramLI->parserSetupArg around our use of the param list. */ save_setup_arg = estate->paramLI->parserSetupArg; *************** exec_eval_simple_expr(PLpgSQL_execstate *** 5451,5473 **** * Create a ParamListInfo to pass to SPI * * We share a single ParamListInfo array across all SPI calls made from this ! * estate. This is generally OK since any given slot in the array would ! * need to contain the same current datum value no matter which query or ! * expression we're evaluating. However, paramLI->parserSetupArg points to ! * the specific PLpgSQL_expr being evaluated. This is not an issue for ! * statement-level callers, but lower-level callers should save and restore ! * estate->paramLI->parserSetupArg just in case there's an active evaluation ! * at an outer call level. * ! * We fill in the values for any expression parameters that are plain ! * PLpgSQL_var datums; these are cheap and safe to evaluate, and by setting ! * them with PARAM_FLAG_CONST flags, we allow the planner to use those values ! * in custom plans. However, parameters that are not plain PLpgSQL_vars ! * should not be evaluated here, because they could throw errors (for example ! * "no such record field") and we do not want that to happen in a part of ! * the expression that might never be evaluated at runtime. To handle those ! * parameters, we set up a paramFetch hook for the executor to call when it ! * wants a not-presupplied value. */ static ParamListInfo setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) --- 5444,5473 ---- * Create a ParamListInfo to pass to SPI * * We share a single ParamListInfo array across all SPI calls made from this ! * estate, except calls creating cursors, which use setup_unshared_param_list ! * (see its comments for reasons why). A shared array is generally OK since ! * any given slot in the array would need to contain the same current datum ! * value no matter which query or expression we're evaluating. However, ! * paramLI->parserSetupArg points to the specific PLpgSQL_expr being ! * evaluated. This is not an issue for statement-level callers, but ! * lower-level callers must save and restore estate->paramLI->parserSetupArg ! * just in case there's an active evaluation at an outer call level. * ! * The general plan for passing parameters to SPI is that plain VAR datums ! * always have valid images in the shared param list. This is ensured by ! * assign_simple_var(), which also marks those params as PARAM_FLAG_CONST, ! * allowing the planner to use those values in custom plans. However, non-VAR ! * datums cannot conveniently be managed that way. For one thing, they could ! * throw errors (for example "no such record field") and we do not want that ! * to happen in a part of the expression that might never be evaluated at ! * runtime. For another thing, exec_eval_datum() may return short-lived ! * values stored in the estate's short-term memory context, which will not ! * necessarily survive to the next SPI operation. And for a third thing, ROW ! * and RECFIELD datums' values depend on other datums, and we don't have a ! * cheap way to track that. Therefore, param slots for non-VAR datum types ! * are always reset here and then filled on-demand by plpgsql_param_fetch(). ! * We can save a few cycles by not bothering with the reset loop unless at ! * least one such param has actually been filled by plpgsql_param_fetch(). */ static ParamListInfo setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) *************** setup_param_list(PLpgSQL_execstate *esta *** 5488,5515 **** */ if (expr->paramnos) { ! int dno; ! ! /* Use the common ParamListInfo for all evals in this estate */ paramLI = estate->paramLI; /* ! * Reset all entries to "invalid". It's pretty annoying to have to do ! * this, but we don't currently track enough information to know which ! * old entries might be obsolete. (There are a couple of nontrivial ! * issues that would have to be dealt with in order to do better here. ! * First, ROW and RECFIELD datums depend on other datums, and second, ! * exec_eval_datum() will return short-lived palloc'd values for ROW ! * and REC datums.) */ ! MemSet(paramLI->params, 0, estate->ndatums * sizeof(ParamExternData)); /* ! * Instantiate values for "safe" parameters of the expression. One of ! * them might be the variable the expression result will be assigned ! * to, in which case we can pass the variable's value as-is even if ! * it's a read-write expanded object; otherwise, convert read-write ! * pointers to read-only pointers for safety. */ dno = -1; while ((dno = bms_next_member(expr->paramnos, dno)) >= 0) --- 5488,5589 ---- */ if (expr->paramnos) { ! /* Use the common ParamListInfo */ paramLI = estate->paramLI; /* ! * If any resettable parameters have been passed to the executor since ! * last time, we need to reset those param slots to "invalid", for the ! * reasons mentioned in the comment above. */ ! if (estate->params_dirty) ! { ! Bitmapset *resettable_datums = estate->func->resettable_datums; ! int dno = -1; ! ! while ((dno = bms_next_member(resettable_datums, dno)) >= 0) ! { ! ParamExternData *prm = ¶mLI->params[dno]; ! ! prm->ptype = InvalidOid; ! } ! estate->params_dirty = false; ! } /* ! * Set up link to active expr where the hook functions can find it. ! * Callers must save and restore parserSetupArg if there is any chance ! * that they are interrupting an active use of parameters. ! */ ! paramLI->parserSetupArg = (void *) expr; ! ! /* ! * Also make sure this is set before parser hooks need it. There is ! * no need to save and restore, since the value is always correct once ! * set. (Should be set already, but let's be sure.) ! */ ! expr->func = estate->func; ! } ! else ! { ! /* ! * Expression requires no parameters. Be sure we represent this case ! * as a NULL ParamListInfo, so that plancache.c knows there is no ! * point in a custom plan. ! */ ! paramLI = NULL; ! } ! return paramLI; ! } ! ! /* ! * Create an unshared, short-lived ParamListInfo to pass to SPI ! * ! * When creating a cursor, we do not use the shared ParamListInfo array ! * but create a short-lived one that will contain only params actually ! * referenced by the query. The reason for this is that copyParamList() will ! * be used to copy the parameters into cursor-lifespan storage, and we don't ! * want it to copy anything that's not used by the specific cursor; that ! * could result in uselessly copying some large values. ! * ! * Caller should pfree the result after use, if it's not NULL. ! */ ! static ParamListInfo ! setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr) ! { ! ParamListInfo paramLI; ! ! /* ! * We must have created the SPIPlan already (hence, query text has been ! * parsed/analyzed at least once); else we cannot rely on expr->paramnos. ! */ ! Assert(expr->plan != NULL); ! ! /* ! * We only need a ParamListInfo if the expression has parameters. In ! * principle we should test with bms_is_empty(), but we use a not-null ! * test because it's faster. In current usage bits are never removed from ! * expr->paramnos, only added, so this test is correct anyway. ! */ ! if (expr->paramnos) ! { ! int dno; ! ! /* initialize ParamListInfo with one entry per datum, all invalid */ ! paramLI = (ParamListInfo) ! palloc0(offsetof(ParamListInfoData, params) + ! estate->ndatums * sizeof(ParamExternData)); ! paramLI->paramFetch = plpgsql_param_fetch; ! paramLI->paramFetchArg = (void *) estate; ! paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup; ! paramLI->parserSetupArg = (void *) expr; ! paramLI->numParams = estate->ndatums; ! ! /* ! * Instantiate values for "safe" parameters of the expression. We ! * could skip this and leave them to be filled by plpgsql_param_fetch; ! * but then the values would not be available for query planning, ! * since the planner doesn't call the paramFetch hook. */ dno = -1; while ((dno = bms_next_member(expr->paramnos, dno)) >= 0) *************** setup_param_list(PLpgSQL_execstate *esta *** 5534,5546 **** } /* - * Set up link to active expr where the hook functions can find it. - * Callers must save and restore parserSetupArg if there is any chance - * that they are interrupting an active use of parameters. - */ - paramLI->parserSetupArg = (void *) expr; - - /* * Also make sure this is set before parser hooks need it. There is * no need to save and restore, since the value is always correct once * set. (Should be set already, but let's be sure.) --- 5608,5613 ---- *************** plpgsql_param_fetch(ParamListInfo params *** 5581,5600 **** expr = (PLpgSQL_expr *) params->parserSetupArg; Assert(params->numParams == estate->ndatums); ! /* ! * Do nothing if asked for a value that's not supposed to be used by this ! * SQL expression. This avoids unwanted evaluations when functions such ! * as copyParamList try to materialize all the values. ! */ ! if (!bms_is_member(dno, expr->paramnos)) ! return; /* OK, evaluate the value and store into the appropriate paramlist slot */ - datum = estate->datums[dno]; prm = ¶ms->params[dno]; exec_eval_datum(estate, datum, &prm->ptype, &prmtypmod, &prm->value, &prm->isnull); /* * If it's a read/write expanded datum, convert reference to read-only, --- 5648,5697 ---- expr = (PLpgSQL_expr *) params->parserSetupArg; Assert(params->numParams == estate->ndatums); ! /* now we can access the target datum */ ! datum = estate->datums[dno]; ! ! /* need to behave slightly differently for shared and unshared arrays */ ! if (params != estate->paramLI) ! { ! /* ! * We're being called, presumably from copyParamList(), for cursor ! * parameters. Since copyParamList() will try to materialize every ! * single parameter slot, it's important to do nothing when asked for ! * a datum that's not supposed to be used by this SQL expression. ! * Otherwise we risk failures in exec_eval_datum(), not to mention ! * possibly copying a lot more data than the cursor actually uses. ! */ ! if (!bms_is_member(dno, expr->paramnos)) ! return; ! } ! else ! { ! /* ! * Normal evaluation cases. We don't need to sanity-check dno, but we ! * do need to mark the shared params array dirty if we're about to ! * evaluate a resettable datum. ! */ ! switch (datum->dtype) ! { ! case PLPGSQL_DTYPE_ROW: ! case PLPGSQL_DTYPE_REC: ! case PLPGSQL_DTYPE_RECFIELD: ! estate->params_dirty = true; ! break; ! ! default: ! break; ! } ! } /* OK, evaluate the value and store into the appropriate paramlist slot */ prm = ¶ms->params[dno]; exec_eval_datum(estate, datum, &prm->ptype, &prmtypmod, &prm->value, &prm->isnull); + /* We can always mark params as "const" for executor's purposes */ + prm->pflags = PARAM_FLAG_CONST; /* * If it's a read/write expanded datum, convert reference to read-only, *************** exec_set_found(PLpgSQL_execstate *estate *** 6663,6670 **** PLpgSQL_var *var; var = (PLpgSQL_var *) (estate->datums[estate->found_varno]); ! var->value = BoolGetDatum(state); ! var->isnull = false; } /* --- 6760,6766 ---- PLpgSQL_var *var; var = (PLpgSQL_var *) (estate->datums[estate->found_varno]); ! assign_simple_var(estate, var, BoolGetDatum(state), false, false); } /* *************** plpgsql_subxact_cb(SubXactEvent event, S *** 6799,6812 **** } /* ! * free_var --- pfree any pass-by-reference value of the variable. * ! * This should always be followed by some assignment to var->value, ! * as it leaves a dangling pointer. */ static void ! free_var(PLpgSQL_var *var) { if (var->freeval) { if (DatumIsReadWriteExpandedObject(var->value, --- 6895,6913 ---- } /* ! * assign_simple_var --- assign a new value to any VAR datum. * ! * This should be the only mechanism for assignment to simple variables, ! * lest we forget to update the paramLI image. */ static void ! assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, ! Datum newvalue, bool isnull, bool freeable) { + ParamExternData *prm; + + Assert(var->dtype == PLPGSQL_DTYPE_VAR); + /* Free the old value if needed */ if (var->freeval) { if (DatumIsReadWriteExpandedObject(var->value, *************** free_var(PLpgSQL_var *var) *** 6815,6834 **** DeleteExpandedObject(var->value); else pfree(DatumGetPointer(var->value)); - var->freeval = false; } } /* * free old value of a text variable and assign new value from C string */ static void ! assign_text_var(PLpgSQL_var *var, const char *str) { ! free_var(var); ! var->value = CStringGetTextDatum(str); ! var->isnull = false; ! var->freeval = true; } /* --- 6916,6944 ---- DeleteExpandedObject(var->value); else pfree(DatumGetPointer(var->value)); } + /* Assign new value to datum */ + var->value = newvalue; + var->isnull = isnull; + var->freeval = freeable; + /* And update the image in the common parameter list */ + prm = &estate->paramLI->params[var->dno]; + prm->value = MakeExpandedObjectReadOnly(newvalue, + isnull, + var->datatype->typlen); + prm->isnull = isnull; + /* these might be set already, but let's be sure */ + prm->pflags = PARAM_FLAG_CONST; + prm->ptype = var->datatype->typoid; } /* * free old value of a text variable and assign new value from C string */ static void ! assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str) { ! assign_simple_var(estate, var, CStringGetTextDatum(str), false, true); } /* diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 93c2504..0b78d95 100644 *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** typedef struct PLpgSQL_function *** 752,759 **** --- 752,763 ---- int extra_warnings; int extra_errors; + /* the datums representing the function's local variables */ int ndatums; PLpgSQL_datum **datums; + Bitmapset *resettable_datums; /* dnos of non-simple vars */ + + /* function body parsetree */ PLpgSQL_stmt_block *action; /* table for performing casts needed in this function */ *************** typedef struct PLpgSQL_execstate *** 796,801 **** --- 800,806 ---- /* we pass datums[i] to the executor, when needed, in paramLI->params[i] */ ParamListInfo paramLI; + bool params_dirty; /* T if any resettable datum has been passed */ /* EState to use for "simple" expression evaluation */ EState *simple_eval_estate;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers