From e524cbdc45ec6d677b1dd49ee64dd403959eda0f Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Sat, 28 Feb 2015 14:34:35 -0500 Subject: [PATCH] Track typmods in plpgsql expression evaluation and assignment. The main value of this change is to avoid expensive I/O conversions when assigning to a variable that has a typmod specification, if the value to be assigned is already known to have the right typmod. This is particularly valuable for arrays with typmod specifications; formerly, in an assignment to an array element the entire array would invariably get put through double I/O conversion to check the typmod, to absolutely no purpose since we'd already properly coerced the new element value. Extracted from my "expanded arrays" patch; this seems worth committing separately, whatever becomes of that patch, since it's really an independent issue. As long as we're changing the function signatures, take the opportunity to rationalize the argument lists of exec_assign_value, exec_cast_value, and exec_simple_cast_value; that is, put the arguments into a saner order, and get rid of the bizarre choice to pass exec_assign_value's isNull flag by reference. --- src/pl/plpgsql/src/pl_exec.c | 286 ++++++++++++++++++++--------------- src/pl/plpgsql/src/plpgsql.h | 1 + 2 files changed, 166 insertions(+), 121 deletions(-) diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index edcb230d5d..41a68f829a 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -158,7 +158,8 @@ static bool exec_eval_simple_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, Datum *result, bool *isNull, - Oid *rettype); + Oid *rettype, + int32 *rettypmod); static void exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target, @@ -168,7 +169,8 @@ static void exec_assign_c_string(PLpgSQL_execstate *estate, const char *str); static void exec_assign_value(PLpgSQL_execstate *estate, PLpgSQL_datum *target, - Datum value, Oid valtype, bool *isNull); + Datum value, bool isNull, + Oid valtype, int32 valtypmod); static void exec_eval_datum(PLpgSQL_execstate *estate, PLpgSQL_datum *datum, Oid *typeid, @@ -184,7 +186,8 @@ static bool exec_eval_boolean(PLpgSQL_execstate *estate, static Datum exec_eval_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, bool *isNull, - Oid *rettype); + Oid *rettype, + int32 *rettypmod); static int exec_run_select(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, long maxtuples, Portal *portalP); static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt, @@ -208,16 +211,15 @@ static void exec_move_row_from_datum(PLpgSQL_execstate *estate, static char *convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype); static Datum exec_cast_value(PLpgSQL_execstate *estate, - Datum value, Oid valtype, - Oid reqtype, + Datum value, bool isnull, + Oid valtype, int32 valtypmod, + Oid reqtype, int32 reqtypmod, FmgrInfo *reqinput, - Oid reqtypioparam, - int32 reqtypmod, - bool isnull); + Oid reqtypioparam); static Datum exec_simple_cast_value(PLpgSQL_execstate *estate, - Datum value, Oid valtype, - Oid reqtype, int32 reqtypmod, - bool isnull); + Datum value, bool isnull, + Oid valtype, int32 valtypmod, + Oid reqtype, int32 reqtypmod); static void exec_init_tuple_store(PLpgSQL_execstate *estate); static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void plpgsql_create_econtext(PLpgSQL_execstate *estate); @@ -452,12 +454,13 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo, /* Cast value to proper type */ estate.retval = exec_cast_value(&estate, estate.retval, + fcinfo->isnull, estate.rettype, - func->fn_rettype, - &(func->fn_retinput), - func->fn_rettypioparam, -1, - fcinfo->isnull); + func->fn_rettype, + -1, + &(func->fn_retinput), + func->fn_rettypioparam); /* * If the function's return type isn't by value, copy the value @@ -1077,15 +1080,13 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) * exec_assign_value.) */ if (!var->datatype->typinput.fn_strict) - { - bool valIsNull = true; - exec_assign_value(estate, (PLpgSQL_datum *) var, (Datum) 0, + true, UNKNOWNOID, - &valIsNull); - } + -1); + if (var->notnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -1556,20 +1557,19 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt) { PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); PLpgSQL_datum *var = estate->datums[diag_item->target]; - bool isnull = false; switch (diag_item->kind) { case PLPGSQL_GETDIAG_ROW_COUNT: exec_assign_value(estate, var, UInt32GetDatum(estate->eval_processed), - INT4OID, &isnull); + false, INT4OID, -1); break; case PLPGSQL_GETDIAG_RESULT_OID: exec_assign_value(estate, var, ObjectIdGetDatum(estate->eval_lastoid), - OIDOID, &isnull); + false, OIDOID, -1); break; case PLPGSQL_GETDIAG_ERROR_CONTEXT: @@ -1688,9 +1688,11 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt) { /* simple case */ Datum t_val; - Oid t_oid; + Oid t_typoid; + int32 t_typmod; - t_val = exec_eval_expr(estate, stmt->t_expr, &isnull, &t_oid); + t_val = exec_eval_expr(estate, stmt->t_expr, + &isnull, &t_typoid, &t_typmod); t_var = (PLpgSQL_var *) estate->datums[stmt->t_varno]; @@ -1699,17 +1701,19 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt) * what we're modifying here is an execution copy of the datum, so * this doesn't affect the originally stored function parse tree. */ - if (t_var->datatype->typoid != t_oid) - t_var->datatype = plpgsql_build_datatype(t_oid, - -1, + if (t_var->datatype->typoid != t_typoid || + t_var->datatype->atttypmod != t_typmod) + t_var->datatype = plpgsql_build_datatype(t_typoid, + t_typmod, estate->func->fn_input_collation); /* now we can assign to the variable */ exec_assign_value(estate, (PLpgSQL_datum *) t_var, t_val, - t_oid, - &isnull); + isnull, + t_typoid, + t_typmod); exec_eval_cleanup(estate); } @@ -1885,6 +1889,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt) Datum value; bool isnull; Oid valtype; + int32 valtypmod; int32 loop_value; int32 end_value; int32 step_value; @@ -1896,11 +1901,14 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt) /* * Get the value of the lower bound */ - value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype); - value = exec_cast_value(estate, value, valtype, var->datatype->typoid, + value = exec_eval_expr(estate, stmt->lower, + &isnull, &valtype, &valtypmod); + value = exec_cast_value(estate, value, isnull, + valtype, valtypmod, + var->datatype->typoid, + var->datatype->atttypmod, &(var->datatype->typinput), - var->datatype->typioparam, - var->datatype->atttypmod, isnull); + var->datatype->typioparam); if (isnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -1911,11 +1919,14 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt) /* * Get the value of the upper bound */ - value = exec_eval_expr(estate, stmt->upper, &isnull, &valtype); - value = exec_cast_value(estate, value, valtype, var->datatype->typoid, + value = exec_eval_expr(estate, stmt->upper, + &isnull, &valtype, &valtypmod); + value = exec_cast_value(estate, value, isnull, + valtype, valtypmod, + var->datatype->typoid, + var->datatype->atttypmod, &(var->datatype->typinput), - var->datatype->typioparam, - var->datatype->atttypmod, isnull); + var->datatype->typioparam); if (isnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -1928,11 +1939,14 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt) */ if (stmt->step) { - value = exec_eval_expr(estate, stmt->step, &isnull, &valtype); - value = exec_cast_value(estate, value, valtype, var->datatype->typoid, + value = exec_eval_expr(estate, stmt->step, + &isnull, &valtype, &valtypmod); + value = exec_cast_value(estate, value, isnull, + valtype, valtypmod, + var->datatype->typoid, + var->datatype->atttypmod, &(var->datatype->typinput), - var->datatype->typioparam, - var->datatype->atttypmod, isnull); + var->datatype->typioparam); if (isnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -2227,17 +2241,19 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt) { ArrayType *arr; Oid arrtype; + int32 arrtypmod; PLpgSQL_datum *loop_var; Oid loop_var_elem_type; bool found = false; int rc = PLPGSQL_RC_OK; ArrayIterator array_iterator; Oid iterator_result_type; + int32 iterator_result_typmod; Datum value; bool isnull; /* get the value of the array expression */ - value = exec_eval_expr(estate, stmt->expr, &isnull, &arrtype); + value = exec_eval_expr(estate, stmt->expr, &isnull, &arrtype, &arrtypmod); if (isnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -2305,11 +2321,13 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt) { /* When slicing, nominal type of result is same as array type */ iterator_result_type = arrtype; + iterator_result_typmod = arrtypmod; } else { /* Without slicing, results are individual array elements */ iterator_result_type = ARR_ELEMTYPE(arr); + iterator_result_typmod = arrtypmod; } /* Iterate over the array elements or slices */ @@ -2318,8 +2336,8 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt) found = true; /* looped at least once */ /* Assign current element/slice to the loop variable */ - exec_assign_value(estate, loop_var, value, iterator_result_type, - &isnull); + exec_assign_value(estate, loop_var, value, isnull, + iterator_result_type, iterator_result_typmod); /* In slice case, value is temporary; must free it to avoid leakage */ if (stmt->slice > 0) @@ -2503,9 +2521,12 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) if (stmt->expr != NULL) { + int32 rettypmod; + estate->retval = exec_eval_expr(estate, stmt->expr, &(estate->retisnull), - &(estate->rettype)); + &(estate->rettype), + &rettypmod); if (estate->retistuple && !estate->retisnull) { @@ -2595,10 +2616,11 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, /* coerce type if needed */ retval = exec_simple_cast_value(estate, retval, + isNull, var->datatype->typoid, + var->datatype->atttypmod, tupdesc->attrs[0]->atttypid, - tupdesc->attrs[0]->atttypmod, - isNull); + tupdesc->attrs[0]->atttypmod); tuplestore_putvalues(estate->tuple_store, tupdesc, &retval, &isNull); @@ -2654,11 +2676,13 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, Datum retval; bool isNull; Oid rettype; + int32 rettypmod; retval = exec_eval_expr(estate, stmt->expr, &isNull, - &rettype); + &rettype, + &rettypmod); if (estate->retistuple) { @@ -2718,10 +2742,11 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, /* coerce type if needed */ retval = exec_simple_cast_value(estate, retval, + isNull, rettype, + rettypmod, tupdesc->attrs[0]->atttypid, - tupdesc->attrs[0]->atttypmod, - isNull); + tupdesc->attrs[0]->atttypmod); tuplestore_putvalues(estate->tuple_store, tupdesc, &retval, &isNull); @@ -2924,6 +2949,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt) if (cp[0] == '%') { Oid paramtypeid; + int32 paramtypmod; Datum paramvalue; bool paramisnull; char *extval; @@ -2942,7 +2968,8 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt) paramvalue = exec_eval_expr(estate, (PLpgSQL_expr *) lfirst(current_param), ¶misnull, - ¶mtypeid); + ¶mtypeid, + ¶mtypmod); if (paramisnull) extval = "<NULL>"; @@ -2972,11 +2999,13 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt) Datum optionvalue; bool optionisnull; Oid optiontypeid; + int32 optiontypmod; char *extval; optionvalue = exec_eval_expr(estate, opt->expr, &optionisnull, - &optiontypeid); + &optiontypeid, + &optiontypmod); if (optionisnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -3478,8 +3507,9 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynexecute *stmt) { Datum query; - bool isnull = false; + bool isnull; Oid restype; + int32 restypmod; char *querystr; int exec_res; PreparedParamsData *ppd = NULL; @@ -3488,7 +3518,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, * First we evaluate the string expression after the EXECUTE keyword. Its * result is the querystring we have to execute. */ - query = exec_eval_expr(estate, stmt->query, &isnull, &restype); + query = exec_eval_expr(estate, stmt->query, &isnull, &restype, &restypmod); if (isnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -3982,11 +4012,12 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target, PLpgSQL_expr *expr) { Datum value; + bool isnull; Oid valtype; - bool isnull = false; + int32 valtypmod; - value = exec_eval_expr(estate, expr, &isnull, &valtype); - exec_assign_value(estate, target, value, valtype, &isnull); + value = exec_eval_expr(estate, expr, &isnull, &valtype, &valtypmod); + exec_assign_value(estate, target, value, isnull, valtype, valtypmod); exec_eval_cleanup(estate); } @@ -4002,14 +4033,13 @@ exec_assign_c_string(PLpgSQL_execstate *estate, PLpgSQL_datum *target, const char *str) { text *value; - bool isnull = false; if (str != NULL) value = cstring_to_text(str); else value = cstring_to_text(""); - exec_assign_value(estate, target, PointerGetDatum(value), - TEXTOID, &isnull); + exec_assign_value(estate, target, PointerGetDatum(value), false, + TEXTOID, -1); pfree(value); } @@ -4025,7 +4055,8 @@ exec_assign_c_string(PLpgSQL_execstate *estate, PLpgSQL_datum *target, static void exec_assign_value(PLpgSQL_execstate *estate, PLpgSQL_datum *target, - Datum value, Oid valtype, bool *isNull) + Datum value, bool isNull, + Oid valtype, int32 valtypmod) { switch (target->dtype) { @@ -4039,14 +4070,15 @@ exec_assign_value(PLpgSQL_execstate *estate, newvalue = exec_cast_value(estate, value, + isNull, valtype, + valtypmod, var->datatype->typoid, - &(var->datatype->typinput), - var->datatype->typioparam, var->datatype->atttypmod, - *isNull); + &(var->datatype->typinput), + var->datatype->typioparam); - if (*isNull && var->notnull) + if (isNull && var->notnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL", @@ -4057,7 +4089,7 @@ exec_assign_value(PLpgSQL_execstate *estate, * probably in the eval_econtext) into the procedure's memory * context. */ - if (!var->datatype->typbyval && !*isNull) + if (!var->datatype->typbyval && !isNull) newvalue = datumCopy(newvalue, false, var->datatype->typlen); @@ -4072,8 +4104,8 @@ exec_assign_value(PLpgSQL_execstate *estate, free_var(var); var->value = newvalue; - var->isnull = *isNull; - if (!var->datatype->typbyval && !*isNull) + var->isnull = isNull; + if (!var->datatype->typbyval && !isNull) var->freeval = true; break; } @@ -4085,7 +4117,7 @@ exec_assign_value(PLpgSQL_execstate *estate, */ PLpgSQL_row *row = (PLpgSQL_row *) target; - if (*isNull) + if (isNull) { /* If source is null, just assign nulls to the row */ exec_move_row(estate, NULL, row, NULL, NULL); @@ -4109,7 +4141,7 @@ exec_assign_value(PLpgSQL_execstate *estate, */ PLpgSQL_rec *rec = (PLpgSQL_rec *) target; - if (*isNull) + if (isNull) { /* If source is null, just assign nulls to the record */ exec_move_row(estate, rec, NULL, NULL, NULL); @@ -4139,7 +4171,6 @@ exec_assign_value(PLpgSQL_execstate *estate, Datum *values; bool *nulls; bool *replaces; - bool attisnull; Oid atttype; int32 atttypmod; @@ -4187,16 +4218,16 @@ exec_assign_value(PLpgSQL_execstate *estate, * Now insert the new value, being careful to cast it to the * right type. */ - atttype = SPI_gettypeid(rec->tupdesc, fno + 1); + atttype = rec->tupdesc->attrs[fno]->atttypid; atttypmod = rec->tupdesc->attrs[fno]->atttypmod; - attisnull = *isNull; values[fno] = exec_simple_cast_value(estate, value, + isNull, valtype, + valtypmod, atttype, - atttypmod, - attisnull); - nulls[fno] = attisnull; + atttypmod); + nulls[fno] = isNull; /* * Now call heap_modify_tuple() to create a new tuple that @@ -4354,10 +4385,11 @@ exec_assign_value(PLpgSQL_execstate *estate, /* Coerce source value to match array element type. */ coerced_value = exec_simple_cast_value(estate, value, + isNull, valtype, + valtypmod, arrayelem->elemtypoid, - arrayelem->arraytypmod, - *isNull); + arrayelem->arraytypmod); /* * If the original array is null, cons up an empty array so @@ -4370,7 +4402,7 @@ exec_assign_value(PLpgSQL_execstate *estate, * corresponds to the current behavior of ExecEvalArrayRef(). */ if (arrayelem->arraytyplen > 0 && /* fixed-length array? */ - (oldarrayisnull || *isNull)) + (oldarrayisnull || isNull)) return; /* empty array, if any, and newarraydatum are short-lived */ @@ -4386,7 +4418,7 @@ exec_assign_value(PLpgSQL_execstate *estate, nsubscripts, subscriptvals, coerced_value, - *isNull, + isNull, arrayelem->arraytyplen, arrayelem->elemtyplen, arrayelem->elemtypbyval, @@ -4400,10 +4432,11 @@ exec_assign_value(PLpgSQL_execstate *estate, * coercing the base array type back up to the domain will * happen within exec_assign_value. */ - *isNull = false; exec_assign_value(estate, target, newarraydatum, - arrayelem->arraytypoid, isNull); + false, + arrayelem->arraytypoid, + arrayelem->arraytypmod); break; } @@ -4724,11 +4757,12 @@ exec_eval_integer(PLpgSQL_execstate *estate, { Datum exprdatum; Oid exprtypeid; + int32 exprtypmod; - exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid); - exprdatum = exec_simple_cast_value(estate, exprdatum, exprtypeid, - INT4OID, -1, - *isNull); + exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod); + exprdatum = exec_simple_cast_value(estate, exprdatum, *isNull, + exprtypeid, exprtypmod, + INT4OID, -1); return DatumGetInt32(exprdatum); } @@ -4746,17 +4780,18 @@ exec_eval_boolean(PLpgSQL_execstate *estate, { Datum exprdatum; Oid exprtypeid; + int32 exprtypmod; - exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid); - exprdatum = exec_simple_cast_value(estate, exprdatum, exprtypeid, - BOOLOID, -1, - *isNull); + exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod); + exprdatum = exec_simple_cast_value(estate, exprdatum, *isNull, + exprtypeid, exprtypmod, + BOOLOID, -1); return DatumGetBool(exprdatum); } /* ---------- * exec_eval_expr Evaluate an expression and return - * the result Datum. + * the result Datum, along with data type/typmod. * * NOTE: caller must do exec_eval_cleanup when done with the Datum. * ---------- @@ -4765,7 +4800,8 @@ static Datum exec_eval_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, bool *isNull, - Oid *rettype) + Oid *rettype, + int32 *rettypmod) { Datum result = 0; int rc; @@ -4780,7 +4816,8 @@ exec_eval_expr(PLpgSQL_execstate *estate, * If this is a simple expression, bypass SPI and use the executor * directly */ - if (exec_eval_simple_expr(estate, expr, &result, isNull, rettype)) + if (exec_eval_simple_expr(estate, expr, + &result, isNull, rettype, rettypmod)) return result; /* @@ -4807,7 +4844,8 @@ exec_eval_expr(PLpgSQL_execstate *estate, /* * ... and get the column's datatype. */ - *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1); + *rettype = estate->eval_tuptable->tupdesc->attrs[0]->atttypid; + *rettypmod = estate->eval_tuptable->tupdesc->attrs[0]->atttypmod; /* * If there are no rows selected, the result is a NULL of that type. @@ -5060,8 +5098,8 @@ loop_exit: * exec_eval_simple_expr - Evaluate a simple expression returning * a Datum by directly calling ExecEvalExpr(). * - * If successful, store results into *result, *isNull, *rettype and return - * TRUE. If the expression cannot be handled by simple evaluation, + * If successful, store results into *result, *isNull, *rettype, *rettypmod + * and return TRUE. If the expression cannot be handled by simple evaluation, * return FALSE. * * Because we only store one execution tree for a simple expression, we @@ -5092,7 +5130,8 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, PLpgSQL_expr *expr, Datum *result, bool *isNull, - Oid *rettype) + Oid *rettype, + int32 *rettypmod) { ExprContext *econtext = estate->eval_econtext; LocalTransactionId curlxid = MyProc->lxid; @@ -5142,6 +5181,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate, * Pass back previously-determined result type. */ *rettype = expr->expr_simple_type; + *rettypmod = expr->expr_simple_typmod; /* * Prepare the expression for execution, if it's not been done already in @@ -5457,6 +5497,7 @@ exec_move_row(PLpgSQL_execstate *estate, Datum value; bool isnull; Oid valtype; + int32 valtypmod; if (row->varnos[fnum] < 0) continue; /* skip dropped column in row struct */ @@ -5475,23 +5516,20 @@ exec_move_row(PLpgSQL_execstate *estate, value = (Datum) 0; isnull = true; } - valtype = SPI_gettypeid(tupdesc, anum + 1); + valtype = tupdesc->attrs[anum]->atttypid; + valtypmod = tupdesc->attrs[anum]->atttypmod; anum++; } else { value = (Datum) 0; isnull = true; - - /* - * InvalidOid is OK because exec_assign_value doesn't care - * about the type of a source NULL - */ - valtype = InvalidOid; + valtype = UNKNOWNOID; + valtypmod = -1; } exec_assign_value(estate, (PLpgSQL_datum *) var, - value, valtype, &isnull); + value, isnull, valtype, valtypmod); } return; @@ -5675,17 +5713,17 @@ convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype) */ static Datum exec_cast_value(PLpgSQL_execstate *estate, - Datum value, Oid valtype, - Oid reqtype, + Datum value, bool isnull, + Oid valtype, int32 valtypmod, + Oid reqtype, int32 reqtypmod, FmgrInfo *reqinput, - Oid reqtypioparam, - int32 reqtypmod, - bool isnull) + Oid reqtypioparam) { /* * If the type of the given value isn't what's requested, convert it. */ - if (valtype != reqtype || reqtypmod != -1) + if (valtype != reqtype || + (valtypmod != reqtypmod && reqtypmod != -1)) { MemoryContext oldcontext; @@ -5719,11 +5757,12 @@ exec_cast_value(PLpgSQL_execstate *estate, */ static Datum exec_simple_cast_value(PLpgSQL_execstate *estate, - Datum value, Oid valtype, - Oid reqtype, int32 reqtypmod, - bool isnull) + Datum value, bool isnull, + Oid valtype, int32 valtypmod, + Oid reqtype, int32 reqtypmod) { - if (valtype != reqtype || reqtypmod != -1) + if (valtype != reqtype || + (valtypmod != reqtypmod && reqtypmod != -1)) { Oid typinput; Oid typioparam; @@ -5735,12 +5774,13 @@ exec_simple_cast_value(PLpgSQL_execstate *estate, value = exec_cast_value(estate, value, + isnull, valtype, + valtypmod, reqtype, - &finfo_input, - typioparam, reqtypmod, - isnull); + &finfo_input, + typioparam); } return value; @@ -6171,6 +6211,7 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan) expr->expr_simple_lxid = InvalidLocalTransactionId; /* Also stash away the expression result type */ expr->expr_simple_type = exprType((Node *) tle->expr); + expr->expr_simple_typmod = exprTypmod((Node *) tle->expr); } /* ---------- @@ -6371,10 +6412,12 @@ exec_eval_using_params(PLpgSQL_execstate *estate, List *params) { PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc); bool isnull; + int32 ppdtypmod; ppd->values[i] = exec_eval_expr(estate, param, &isnull, - &ppd->types[i]); + &ppd->types[i], + &ppdtypmod); ppd->nulls[i] = isnull ? 'n' : ' '; ppd->freevals[i] = false; @@ -6452,13 +6495,14 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate, Datum query; bool isnull; Oid restype; + int32 restypmod; char *querystr; /* * Evaluate the string expression after the EXECUTE keyword. Its result is * the querystring we have to execute. */ - query = exec_eval_expr(estate, dynquery, &isnull, &restype); + query = exec_eval_expr(estate, dynquery, &isnull, &restype, &restypmod); if (isnull) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 337b98980a..624c91e468 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -226,6 +226,7 @@ typedef struct PLpgSQL_expr Expr *expr_simple_expr; /* NULL means not a simple expr */ int expr_simple_generation; /* plancache generation we checked */ Oid expr_simple_type; /* result type Oid, if simple */ + int32 expr_simple_typmod; /* result typmod, if simple */ /* * if expr is simple AND prepared in current transaction,