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.
This commit is contained in:
Tom Lane 2015-02-28 14:34:35 -05:00
parent b514a7460d
commit e524cbdc45
2 changed files with 166 additions and 121 deletions

View File

@ -158,7 +158,8 @@ static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, PLpgSQL_expr *expr,
Datum *result, Datum *result,
bool *isNull, bool *isNull,
Oid *rettype); Oid *rettype,
int32 *rettypmod);
static void exec_assign_expr(PLpgSQL_execstate *estate, static void exec_assign_expr(PLpgSQL_execstate *estate,
PLpgSQL_datum *target, PLpgSQL_datum *target,
@ -168,7 +169,8 @@ static void exec_assign_c_string(PLpgSQL_execstate *estate,
const char *str); const char *str);
static void exec_assign_value(PLpgSQL_execstate *estate, static void exec_assign_value(PLpgSQL_execstate *estate,
PLpgSQL_datum *target, 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, static void exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum, PLpgSQL_datum *datum,
Oid *typeid, Oid *typeid,
@ -184,7 +186,8 @@ static bool exec_eval_boolean(PLpgSQL_execstate *estate,
static Datum exec_eval_expr(PLpgSQL_execstate *estate, static Datum exec_eval_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, PLpgSQL_expr *expr,
bool *isNull, bool *isNull,
Oid *rettype); Oid *rettype,
int32 *rettypmod);
static int exec_run_select(PLpgSQL_execstate *estate, static int exec_run_select(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, long maxtuples, Portal *portalP); PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt, 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, static char *convert_value_to_string(PLpgSQL_execstate *estate,
Datum value, Oid valtype); Datum value, Oid valtype);
static Datum exec_cast_value(PLpgSQL_execstate *estate, static Datum exec_cast_value(PLpgSQL_execstate *estate,
Datum value, Oid valtype, Datum value, bool isnull,
Oid reqtype, Oid valtype, int32 valtypmod,
FmgrInfo *reqinput,
Oid reqtypioparam,
int32 reqtypmod,
bool isnull);
static Datum exec_simple_cast_value(PLpgSQL_execstate *estate,
Datum value, Oid valtype,
Oid reqtype, int32 reqtypmod, Oid reqtype, int32 reqtypmod,
bool isnull); FmgrInfo *reqinput,
Oid reqtypioparam);
static Datum exec_simple_cast_value(PLpgSQL_execstate *estate,
Datum value, bool isnull,
Oid valtype, int32 valtypmod,
Oid reqtype, int32 reqtypmod);
static void exec_init_tuple_store(PLpgSQL_execstate *estate); static void exec_init_tuple_store(PLpgSQL_execstate *estate);
static void exec_set_found(PLpgSQL_execstate *estate, bool state); static void exec_set_found(PLpgSQL_execstate *estate, bool state);
static void plpgsql_create_econtext(PLpgSQL_execstate *estate); 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 */ /* Cast value to proper type */
estate.retval = exec_cast_value(&estate, estate.retval = exec_cast_value(&estate,
estate.retval, estate.retval,
fcinfo->isnull,
estate.rettype, estate.rettype,
func->fn_rettype,
&(func->fn_retinput),
func->fn_rettypioparam,
-1, -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 * 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.) * exec_assign_value.)
*/ */
if (!var->datatype->typinput.fn_strict) if (!var->datatype->typinput.fn_strict)
{
bool valIsNull = true;
exec_assign_value(estate, exec_assign_value(estate,
(PLpgSQL_datum *) var, (PLpgSQL_datum *) var,
(Datum) 0, (Datum) 0,
true,
UNKNOWNOID, UNKNOWNOID,
&valIsNull); -1);
}
if (var->notnull) if (var->notnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (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_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
PLpgSQL_datum *var = estate->datums[diag_item->target]; PLpgSQL_datum *var = estate->datums[diag_item->target];
bool isnull = false;
switch (diag_item->kind) switch (diag_item->kind)
{ {
case PLPGSQL_GETDIAG_ROW_COUNT: case PLPGSQL_GETDIAG_ROW_COUNT:
exec_assign_value(estate, var, exec_assign_value(estate, var,
UInt32GetDatum(estate->eval_processed), UInt32GetDatum(estate->eval_processed),
INT4OID, &isnull); false, INT4OID, -1);
break; break;
case PLPGSQL_GETDIAG_RESULT_OID: case PLPGSQL_GETDIAG_RESULT_OID:
exec_assign_value(estate, var, exec_assign_value(estate, var,
ObjectIdGetDatum(estate->eval_lastoid), ObjectIdGetDatum(estate->eval_lastoid),
OIDOID, &isnull); false, OIDOID, -1);
break; break;
case PLPGSQL_GETDIAG_ERROR_CONTEXT: case PLPGSQL_GETDIAG_ERROR_CONTEXT:
@ -1688,9 +1688,11 @@ exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
{ {
/* simple case */ /* simple case */
Datum t_val; 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]; 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 * what we're modifying here is an execution copy of the datum, so
* this doesn't affect the originally stored function parse tree. * this doesn't affect the originally stored function parse tree.
*/ */
if (t_var->datatype->typoid != t_oid) if (t_var->datatype->typoid != t_typoid ||
t_var->datatype = plpgsql_build_datatype(t_oid, t_var->datatype->atttypmod != t_typmod)
-1, t_var->datatype = plpgsql_build_datatype(t_typoid,
t_typmod,
estate->func->fn_input_collation); estate->func->fn_input_collation);
/* now we can assign to the variable */ /* now we can assign to the variable */
exec_assign_value(estate, exec_assign_value(estate,
(PLpgSQL_datum *) t_var, (PLpgSQL_datum *) t_var,
t_val, t_val,
t_oid, isnull,
&isnull); t_typoid,
t_typmod);
exec_eval_cleanup(estate); exec_eval_cleanup(estate);
} }
@ -1885,6 +1889,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
Datum value; Datum value;
bool isnull; bool isnull;
Oid valtype; Oid valtype;
int32 valtypmod;
int32 loop_value; int32 loop_value;
int32 end_value; int32 end_value;
int32 step_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 * Get the value of the lower bound
*/ */
value = exec_eval_expr(estate, stmt->lower, &isnull, &valtype); value = exec_eval_expr(estate, stmt->lower,
value = exec_cast_value(estate, value, valtype, var->datatype->typoid, &isnull, &valtype, &valtypmod);
value = exec_cast_value(estate, value, isnull,
valtype, valtypmod,
var->datatype->typoid,
var->datatype->atttypmod,
&(var->datatype->typinput), &(var->datatype->typinput),
var->datatype->typioparam, var->datatype->typioparam);
var->datatype->atttypmod, isnull);
if (isnull) if (isnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (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 * Get the value of the upper bound
*/ */
value = exec_eval_expr(estate, stmt->upper, &isnull, &valtype); value = exec_eval_expr(estate, stmt->upper,
value = exec_cast_value(estate, value, valtype, var->datatype->typoid, &isnull, &valtype, &valtypmod);
value = exec_cast_value(estate, value, isnull,
valtype, valtypmod,
var->datatype->typoid,
var->datatype->atttypmod,
&(var->datatype->typinput), &(var->datatype->typinput),
var->datatype->typioparam, var->datatype->typioparam);
var->datatype->atttypmod, isnull);
if (isnull) if (isnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
@ -1928,11 +1939,14 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
*/ */
if (stmt->step) if (stmt->step)
{ {
value = exec_eval_expr(estate, stmt->step, &isnull, &valtype); value = exec_eval_expr(estate, stmt->step,
value = exec_cast_value(estate, value, valtype, var->datatype->typoid, &isnull, &valtype, &valtypmod);
value = exec_cast_value(estate, value, isnull,
valtype, valtypmod,
var->datatype->typoid,
var->datatype->atttypmod,
&(var->datatype->typinput), &(var->datatype->typinput),
var->datatype->typioparam, var->datatype->typioparam);
var->datatype->atttypmod, isnull);
if (isnull) if (isnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
@ -2227,17 +2241,19 @@ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
{ {
ArrayType *arr; ArrayType *arr;
Oid arrtype; Oid arrtype;
int32 arrtypmod;
PLpgSQL_datum *loop_var; PLpgSQL_datum *loop_var;
Oid loop_var_elem_type; Oid loop_var_elem_type;
bool found = false; bool found = false;
int rc = PLPGSQL_RC_OK; int rc = PLPGSQL_RC_OK;
ArrayIterator array_iterator; ArrayIterator array_iterator;
Oid iterator_result_type; Oid iterator_result_type;
int32 iterator_result_typmod;
Datum value; Datum value;
bool isnull; bool isnull;
/* get the value of the array expression */ /* 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) if (isnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (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 */ /* When slicing, nominal type of result is same as array type */
iterator_result_type = arrtype; iterator_result_type = arrtype;
iterator_result_typmod = arrtypmod;
} }
else else
{ {
/* Without slicing, results are individual array elements */ /* Without slicing, results are individual array elements */
iterator_result_type = ARR_ELEMTYPE(arr); iterator_result_type = ARR_ELEMTYPE(arr);
iterator_result_typmod = arrtypmod;
} }
/* Iterate over the array elements or slices */ /* 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 */ found = true; /* looped at least once */
/* Assign current element/slice to the loop variable */ /* Assign current element/slice to the loop variable */
exec_assign_value(estate, loop_var, value, iterator_result_type, exec_assign_value(estate, loop_var, value, isnull,
&isnull); iterator_result_type, iterator_result_typmod);
/* In slice case, value is temporary; must free it to avoid leakage */ /* In slice case, value is temporary; must free it to avoid leakage */
if (stmt->slice > 0) if (stmt->slice > 0)
@ -2503,9 +2521,12 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
if (stmt->expr != NULL) if (stmt->expr != NULL)
{ {
int32 rettypmod;
estate->retval = exec_eval_expr(estate, stmt->expr, estate->retval = exec_eval_expr(estate, stmt->expr,
&(estate->retisnull), &(estate->retisnull),
&(estate->rettype)); &(estate->rettype),
&rettypmod);
if (estate->retistuple && !estate->retisnull) if (estate->retistuple && !estate->retisnull)
{ {
@ -2595,10 +2616,11 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
/* coerce type if needed */ /* coerce type if needed */
retval = exec_simple_cast_value(estate, retval = exec_simple_cast_value(estate,
retval, retval,
isNull,
var->datatype->typoid, var->datatype->typoid,
var->datatype->atttypmod,
tupdesc->attrs[0]->atttypid, tupdesc->attrs[0]->atttypid,
tupdesc->attrs[0]->atttypmod, tupdesc->attrs[0]->atttypmod);
isNull);
tuplestore_putvalues(estate->tuple_store, tupdesc, tuplestore_putvalues(estate->tuple_store, tupdesc,
&retval, &isNull); &retval, &isNull);
@ -2654,11 +2676,13 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
Datum retval; Datum retval;
bool isNull; bool isNull;
Oid rettype; Oid rettype;
int32 rettypmod;
retval = exec_eval_expr(estate, retval = exec_eval_expr(estate,
stmt->expr, stmt->expr,
&isNull, &isNull,
&rettype); &rettype,
&rettypmod);
if (estate->retistuple) if (estate->retistuple)
{ {
@ -2718,10 +2742,11 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
/* coerce type if needed */ /* coerce type if needed */
retval = exec_simple_cast_value(estate, retval = exec_simple_cast_value(estate,
retval, retval,
isNull,
rettype, rettype,
rettypmod,
tupdesc->attrs[0]->atttypid, tupdesc->attrs[0]->atttypid,
tupdesc->attrs[0]->atttypmod, tupdesc->attrs[0]->atttypmod);
isNull);
tuplestore_putvalues(estate->tuple_store, tupdesc, tuplestore_putvalues(estate->tuple_store, tupdesc,
&retval, &isNull); &retval, &isNull);
@ -2924,6 +2949,7 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
if (cp[0] == '%') if (cp[0] == '%')
{ {
Oid paramtypeid; Oid paramtypeid;
int32 paramtypmod;
Datum paramvalue; Datum paramvalue;
bool paramisnull; bool paramisnull;
char *extval; char *extval;
@ -2942,7 +2968,8 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
paramvalue = exec_eval_expr(estate, paramvalue = exec_eval_expr(estate,
(PLpgSQL_expr *) lfirst(current_param), (PLpgSQL_expr *) lfirst(current_param),
&paramisnull, &paramisnull,
&paramtypeid); &paramtypeid,
&paramtypmod);
if (paramisnull) if (paramisnull)
extval = "<NULL>"; extval = "<NULL>";
@ -2972,11 +2999,13 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
Datum optionvalue; Datum optionvalue;
bool optionisnull; bool optionisnull;
Oid optiontypeid; Oid optiontypeid;
int32 optiontypmod;
char *extval; char *extval;
optionvalue = exec_eval_expr(estate, opt->expr, optionvalue = exec_eval_expr(estate, opt->expr,
&optionisnull, &optionisnull,
&optiontypeid); &optiontypeid,
&optiontypmod);
if (optionisnull) if (optionisnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
@ -3478,8 +3507,9 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
PLpgSQL_stmt_dynexecute *stmt) PLpgSQL_stmt_dynexecute *stmt)
{ {
Datum query; Datum query;
bool isnull = false; bool isnull;
Oid restype; Oid restype;
int32 restypmod;
char *querystr; char *querystr;
int exec_res; int exec_res;
PreparedParamsData *ppd = NULL; PreparedParamsData *ppd = NULL;
@ -3488,7 +3518,7 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
* First we evaluate the string expression after the EXECUTE keyword. Its * First we evaluate the string expression after the EXECUTE keyword. Its
* result is the querystring we have to execute. * 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) if (isnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
@ -3982,11 +4012,12 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
PLpgSQL_expr *expr) PLpgSQL_expr *expr)
{ {
Datum value; Datum value;
bool isnull;
Oid valtype; Oid valtype;
bool isnull = false; int32 valtypmod;
value = exec_eval_expr(estate, expr, &isnull, &valtype); value = exec_eval_expr(estate, expr, &isnull, &valtype, &valtypmod);
exec_assign_value(estate, target, value, valtype, &isnull); exec_assign_value(estate, target, value, isnull, valtype, valtypmod);
exec_eval_cleanup(estate); exec_eval_cleanup(estate);
} }
@ -4002,14 +4033,13 @@ exec_assign_c_string(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
const char *str) const char *str)
{ {
text *value; text *value;
bool isnull = false;
if (str != NULL) if (str != NULL)
value = cstring_to_text(str); value = cstring_to_text(str);
else else
value = cstring_to_text(""); value = cstring_to_text("");
exec_assign_value(estate, target, PointerGetDatum(value), exec_assign_value(estate, target, PointerGetDatum(value), false,
TEXTOID, &isnull); TEXTOID, -1);
pfree(value); pfree(value);
} }
@ -4025,7 +4055,8 @@ exec_assign_c_string(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
static void static void
exec_assign_value(PLpgSQL_execstate *estate, exec_assign_value(PLpgSQL_execstate *estate,
PLpgSQL_datum *target, PLpgSQL_datum *target,
Datum value, Oid valtype, bool *isNull) Datum value, bool isNull,
Oid valtype, int32 valtypmod)
{ {
switch (target->dtype) switch (target->dtype)
{ {
@ -4039,14 +4070,15 @@ exec_assign_value(PLpgSQL_execstate *estate,
newvalue = exec_cast_value(estate, newvalue = exec_cast_value(estate,
value, value,
isNull,
valtype, valtype,
valtypmod,
var->datatype->typoid, var->datatype->typoid,
&(var->datatype->typinput),
var->datatype->typioparam,
var->datatype->atttypmod, var->datatype->atttypmod,
*isNull); &(var->datatype->typinput),
var->datatype->typioparam);
if (*isNull && var->notnull) if (isNull && var->notnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL", 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 * probably in the eval_econtext) into the procedure's memory
* context. * context.
*/ */
if (!var->datatype->typbyval && !*isNull) if (!var->datatype->typbyval && !isNull)
newvalue = datumCopy(newvalue, newvalue = datumCopy(newvalue,
false, false,
var->datatype->typlen); var->datatype->typlen);
@ -4072,8 +4104,8 @@ exec_assign_value(PLpgSQL_execstate *estate,
free_var(var); free_var(var);
var->value = newvalue; var->value = newvalue;
var->isnull = *isNull; var->isnull = isNull;
if (!var->datatype->typbyval && !*isNull) if (!var->datatype->typbyval && !isNull)
var->freeval = true; var->freeval = true;
break; break;
} }
@ -4085,7 +4117,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
*/ */
PLpgSQL_row *row = (PLpgSQL_row *) target; PLpgSQL_row *row = (PLpgSQL_row *) target;
if (*isNull) if (isNull)
{ {
/* If source is null, just assign nulls to the row */ /* If source is null, just assign nulls to the row */
exec_move_row(estate, NULL, row, NULL, NULL); exec_move_row(estate, NULL, row, NULL, NULL);
@ -4109,7 +4141,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
*/ */
PLpgSQL_rec *rec = (PLpgSQL_rec *) target; PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
if (*isNull) if (isNull)
{ {
/* If source is null, just assign nulls to the record */ /* If source is null, just assign nulls to the record */
exec_move_row(estate, rec, NULL, NULL, NULL); exec_move_row(estate, rec, NULL, NULL, NULL);
@ -4139,7 +4171,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
Datum *values; Datum *values;
bool *nulls; bool *nulls;
bool *replaces; bool *replaces;
bool attisnull;
Oid atttype; Oid atttype;
int32 atttypmod; int32 atttypmod;
@ -4187,16 +4218,16 @@ exec_assign_value(PLpgSQL_execstate *estate,
* Now insert the new value, being careful to cast it to the * Now insert the new value, being careful to cast it to the
* right type. * right type.
*/ */
atttype = SPI_gettypeid(rec->tupdesc, fno + 1); atttype = rec->tupdesc->attrs[fno]->atttypid;
atttypmod = rec->tupdesc->attrs[fno]->atttypmod; atttypmod = rec->tupdesc->attrs[fno]->atttypmod;
attisnull = *isNull;
values[fno] = exec_simple_cast_value(estate, values[fno] = exec_simple_cast_value(estate,
value, value,
isNull,
valtype, valtype,
valtypmod,
atttype, atttype,
atttypmod, atttypmod);
attisnull); nulls[fno] = isNull;
nulls[fno] = attisnull;
/* /*
* Now call heap_modify_tuple() to create a new tuple that * 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. */ /* Coerce source value to match array element type. */
coerced_value = exec_simple_cast_value(estate, coerced_value = exec_simple_cast_value(estate,
value, value,
isNull,
valtype, valtype,
valtypmod,
arrayelem->elemtypoid, arrayelem->elemtypoid,
arrayelem->arraytypmod, arrayelem->arraytypmod);
*isNull);
/* /*
* If the original array is null, cons up an empty array so * 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(). * corresponds to the current behavior of ExecEvalArrayRef().
*/ */
if (arrayelem->arraytyplen > 0 && /* fixed-length array? */ if (arrayelem->arraytyplen > 0 && /* fixed-length array? */
(oldarrayisnull || *isNull)) (oldarrayisnull || isNull))
return; return;
/* empty array, if any, and newarraydatum are short-lived */ /* empty array, if any, and newarraydatum are short-lived */
@ -4386,7 +4418,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
nsubscripts, nsubscripts,
subscriptvals, subscriptvals,
coerced_value, coerced_value,
*isNull, isNull,
arrayelem->arraytyplen, arrayelem->arraytyplen,
arrayelem->elemtyplen, arrayelem->elemtyplen,
arrayelem->elemtypbyval, arrayelem->elemtypbyval,
@ -4400,10 +4432,11 @@ exec_assign_value(PLpgSQL_execstate *estate,
* coercing the base array type back up to the domain will * coercing the base array type back up to the domain will
* happen within exec_assign_value. * happen within exec_assign_value.
*/ */
*isNull = false;
exec_assign_value(estate, target, exec_assign_value(estate, target,
newarraydatum, newarraydatum,
arrayelem->arraytypoid, isNull); false,
arrayelem->arraytypoid,
arrayelem->arraytypmod);
break; break;
} }
@ -4724,11 +4757,12 @@ exec_eval_integer(PLpgSQL_execstate *estate,
{ {
Datum exprdatum; Datum exprdatum;
Oid exprtypeid; Oid exprtypeid;
int32 exprtypmod;
exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid); exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
exprdatum = exec_simple_cast_value(estate, exprdatum, exprtypeid, exprdatum = exec_simple_cast_value(estate, exprdatum, *isNull,
INT4OID, -1, exprtypeid, exprtypmod,
*isNull); INT4OID, -1);
return DatumGetInt32(exprdatum); return DatumGetInt32(exprdatum);
} }
@ -4746,17 +4780,18 @@ exec_eval_boolean(PLpgSQL_execstate *estate,
{ {
Datum exprdatum; Datum exprdatum;
Oid exprtypeid; Oid exprtypeid;
int32 exprtypmod;
exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid); exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
exprdatum = exec_simple_cast_value(estate, exprdatum, exprtypeid, exprdatum = exec_simple_cast_value(estate, exprdatum, *isNull,
BOOLOID, -1, exprtypeid, exprtypmod,
*isNull); BOOLOID, -1);
return DatumGetBool(exprdatum); return DatumGetBool(exprdatum);
} }
/* ---------- /* ----------
* exec_eval_expr Evaluate an expression and return * 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. * NOTE: caller must do exec_eval_cleanup when done with the Datum.
* ---------- * ----------
@ -4765,7 +4800,8 @@ static Datum
exec_eval_expr(PLpgSQL_execstate *estate, exec_eval_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, PLpgSQL_expr *expr,
bool *isNull, bool *isNull,
Oid *rettype) Oid *rettype,
int32 *rettypmod)
{ {
Datum result = 0; Datum result = 0;
int rc; int rc;
@ -4780,7 +4816,8 @@ exec_eval_expr(PLpgSQL_execstate *estate,
* If this is a simple expression, bypass SPI and use the executor * If this is a simple expression, bypass SPI and use the executor
* directly * directly
*/ */
if (exec_eval_simple_expr(estate, expr, &result, isNull, rettype)) if (exec_eval_simple_expr(estate, expr,
&result, isNull, rettype, rettypmod))
return result; return result;
/* /*
@ -4807,7 +4844,8 @@ exec_eval_expr(PLpgSQL_execstate *estate,
/* /*
* ... and get the column's datatype. * ... 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. * 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 * exec_eval_simple_expr - Evaluate a simple expression returning
* a Datum by directly calling ExecEvalExpr(). * a Datum by directly calling ExecEvalExpr().
* *
* If successful, store results into *result, *isNull, *rettype and return * If successful, store results into *result, *isNull, *rettype, *rettypmod
* TRUE. If the expression cannot be handled by simple evaluation, * and return TRUE. If the expression cannot be handled by simple evaluation,
* return FALSE. * return FALSE.
* *
* Because we only store one execution tree for a simple expression, we * 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, PLpgSQL_expr *expr,
Datum *result, Datum *result,
bool *isNull, bool *isNull,
Oid *rettype) Oid *rettype,
int32 *rettypmod)
{ {
ExprContext *econtext = estate->eval_econtext; ExprContext *econtext = estate->eval_econtext;
LocalTransactionId curlxid = MyProc->lxid; LocalTransactionId curlxid = MyProc->lxid;
@ -5142,6 +5181,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
* Pass back previously-determined result type. * Pass back previously-determined result type.
*/ */
*rettype = expr->expr_simple_type; *rettype = expr->expr_simple_type;
*rettypmod = expr->expr_simple_typmod;
/* /*
* Prepare the expression for execution, if it's not been done already in * 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; Datum value;
bool isnull; bool isnull;
Oid valtype; Oid valtype;
int32 valtypmod;
if (row->varnos[fnum] < 0) if (row->varnos[fnum] < 0)
continue; /* skip dropped column in row struct */ continue; /* skip dropped column in row struct */
@ -5475,23 +5516,20 @@ exec_move_row(PLpgSQL_execstate *estate,
value = (Datum) 0; value = (Datum) 0;
isnull = true; isnull = true;
} }
valtype = SPI_gettypeid(tupdesc, anum + 1); valtype = tupdesc->attrs[anum]->atttypid;
valtypmod = tupdesc->attrs[anum]->atttypmod;
anum++; anum++;
} }
else else
{ {
value = (Datum) 0; value = (Datum) 0;
isnull = true; isnull = true;
valtype = UNKNOWNOID;
/* valtypmod = -1;
* InvalidOid is OK because exec_assign_value doesn't care
* about the type of a source NULL
*/
valtype = InvalidOid;
} }
exec_assign_value(estate, (PLpgSQL_datum *) var, exec_assign_value(estate, (PLpgSQL_datum *) var,
value, valtype, &isnull); value, isnull, valtype, valtypmod);
} }
return; return;
@ -5675,17 +5713,17 @@ convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype)
*/ */
static Datum static Datum
exec_cast_value(PLpgSQL_execstate *estate, exec_cast_value(PLpgSQL_execstate *estate,
Datum value, Oid valtype, Datum value, bool isnull,
Oid reqtype, Oid valtype, int32 valtypmod,
Oid reqtype, int32 reqtypmod,
FmgrInfo *reqinput, FmgrInfo *reqinput,
Oid reqtypioparam, Oid reqtypioparam)
int32 reqtypmod,
bool isnull)
{ {
/* /*
* If the type of the given value isn't what's requested, convert it. * 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; MemoryContext oldcontext;
@ -5719,11 +5757,12 @@ exec_cast_value(PLpgSQL_execstate *estate,
*/ */
static Datum static Datum
exec_simple_cast_value(PLpgSQL_execstate *estate, exec_simple_cast_value(PLpgSQL_execstate *estate,
Datum value, Oid valtype, Datum value, bool isnull,
Oid reqtype, int32 reqtypmod, Oid valtype, int32 valtypmod,
bool isnull) Oid reqtype, int32 reqtypmod)
{ {
if (valtype != reqtype || reqtypmod != -1) if (valtype != reqtype ||
(valtypmod != reqtypmod && reqtypmod != -1))
{ {
Oid typinput; Oid typinput;
Oid typioparam; Oid typioparam;
@ -5735,12 +5774,13 @@ exec_simple_cast_value(PLpgSQL_execstate *estate,
value = exec_cast_value(estate, value = exec_cast_value(estate,
value, value,
isnull,
valtype, valtype,
valtypmod,
reqtype, reqtype,
&finfo_input,
typioparam,
reqtypmod, reqtypmod,
isnull); &finfo_input,
typioparam);
} }
return value; return value;
@ -6171,6 +6211,7 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
expr->expr_simple_lxid = InvalidLocalTransactionId; expr->expr_simple_lxid = InvalidLocalTransactionId;
/* Also stash away the expression result type */ /* Also stash away the expression result type */
expr->expr_simple_type = exprType((Node *) tle->expr); 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); PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
bool isnull; bool isnull;
int32 ppdtypmod;
ppd->values[i] = exec_eval_expr(estate, param, ppd->values[i] = exec_eval_expr(estate, param,
&isnull, &isnull,
&ppd->types[i]); &ppd->types[i],
&ppdtypmod);
ppd->nulls[i] = isnull ? 'n' : ' '; ppd->nulls[i] = isnull ? 'n' : ' ';
ppd->freevals[i] = false; ppd->freevals[i] = false;
@ -6452,13 +6495,14 @@ exec_dynquery_with_params(PLpgSQL_execstate *estate,
Datum query; Datum query;
bool isnull; bool isnull;
Oid restype; Oid restype;
int32 restypmod;
char *querystr; char *querystr;
/* /*
* Evaluate the string expression after the EXECUTE keyword. Its result is * Evaluate the string expression after the EXECUTE keyword. Its result is
* the querystring we have to execute. * 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) if (isnull)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),

View File

@ -226,6 +226,7 @@ typedef struct PLpgSQL_expr
Expr *expr_simple_expr; /* NULL means not a simple expr */ Expr *expr_simple_expr; /* NULL means not a simple expr */
int expr_simple_generation; /* plancache generation we checked */ int expr_simple_generation; /* plancache generation we checked */
Oid expr_simple_type; /* result type Oid, if simple */ 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, * if expr is simple AND prepared in current transaction,