Support assignment to whole-row variables in plpgsql; also fix glitch

with using a trigger's NEW or OLD record as a whole-row variable in an
expression.  Fixes several long-standing complaints.
This commit is contained in:
Tom Lane 2004-06-04 02:37:06 +00:00
parent 0f059e1d13
commit 7eb2ff799e
2 changed files with 160 additions and 42 deletions

View File

@ -4,7 +4,7 @@
* procedural language
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.55 2004/06/04 00:07:52 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.56 2004/06/04 02:37:06 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -744,6 +744,16 @@ assign_var : T_SCALAR
check_assignable(yylval.scalar);
$$ = yylval.scalar->dno;
}
| T_ROW
{
check_assignable((PLpgSQL_datum *) yylval.row);
$$ = yylval.row->rowno;
}
| T_RECORD
{
check_assignable((PLpgSQL_datum *) yylval.rec);
$$ = yylval.rec->recno;
}
| assign_var '[' expr_until_rightbracket
{
PLpgSQL_arrayelem *new;
@ -1966,6 +1976,12 @@ check_assignable(PLpgSQL_datum *datum)
((PLpgSQL_var *) datum)->refname)));
}
break;
case PLPGSQL_DTYPE_ROW:
/* always assignable? */
break;
case PLPGSQL_DTYPE_REC:
/* always assignable? What about NEW/OLD? */
break;
case PLPGSQL_DTYPE_RECFIELD:
/* always assignable? */
break;

View File

@ -3,7 +3,7 @@
* procedural language
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.103 2004/06/04 00:07:52 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.104 2004/06/04 02:37:06 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
@ -2664,49 +2664,15 @@ exec_assign_value(PLpgSQL_execstate * estate,
PLpgSQL_datum * target,
Datum value, Oid valtype, bool *isNull)
{
PLpgSQL_var *var;
PLpgSQL_rec *rec;
PLpgSQL_recfield *recfield;
int fno;
int i;
int natts;
Datum *values;
char *nulls;
void *mustfree;
Datum newvalue;
bool attisnull;
Oid atttype;
int32 atttypmod;
int nsubscripts;
PLpgSQL_expr *subscripts[MAXDIM];
int subscriptvals[MAXDIM];
bool havenullsubscript,
oldarrayisnull;
Oid arraytypeid,
arrayelemtypeid,
arrayInputFn;
int16 elemtyplen;
bool elemtypbyval;
char elemtypalign;
Datum oldarrayval,
coerced_value;
ArrayType *newarrayval;
HeapTuple newtup;
switch (target->dtype)
{
case PLPGSQL_DTYPE_VAR:
{
/*
* Target is a variable
*/
var = (PLpgSQL_var *) target;
if (var->freeval)
{
pfree(DatumGetPointer(var->value));
var->freeval = false;
}
PLpgSQL_var *var = (PLpgSQL_var *) target;
Datum newvalue;
newvalue = exec_cast_value(value, valtype, var->datatype->typoid,
&(var->datatype->typinput),
@ -2720,6 +2686,12 @@ exec_assign_value(PLpgSQL_execstate * estate,
errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
var->refname)));
if (var->freeval)
{
pfree(DatumGetPointer(var->value));
var->freeval = false;
}
/*
* If type is by-reference, make sure we have a freshly
* palloc'd copy; the originally passed value may not live as
@ -2741,13 +2713,110 @@ exec_assign_value(PLpgSQL_execstate * estate,
var->value = newvalue;
var->isnull = *isNull;
break;
}
case PLPGSQL_DTYPE_ROW:
{
/*
* Target is a row variable
*/
PLpgSQL_row *row = (PLpgSQL_row *) target;
/* Source must be of RECORD or composite type */
if (!(valtype == RECORDOID ||
get_typtype(valtype) == 'c'))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot assign non-composite value to a row variable")));
if (*isNull)
{
/* If source is null, just assign nulls to the row */
exec_move_row(estate, NULL, row, NULL, NULL);
}
else
{
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
/* Else source is a tuple Datum, safe to do this: */
td = DatumGetHeapTupleHeader(value);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
exec_move_row(estate, NULL, row, &tmptup, tupdesc);
}
break;
}
case PLPGSQL_DTYPE_REC:
{
/*
* Target is a record variable
*/
PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
/* Source must be of RECORD or composite type */
if (!(valtype == RECORDOID ||
get_typtype(valtype) == 'c'))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot assign non-composite value to a record variable")));
if (*isNull)
{
/* If source is null, just assign nulls to the record */
exec_move_row(estate, rec, NULL, NULL, NULL);
}
else
{
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
HeapTupleData tmptup;
/* Else source is a tuple Datum, safe to do this: */
td = DatumGetHeapTupleHeader(value);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
exec_move_row(estate, rec, NULL, &tmptup, tupdesc);
}
break;
}
case PLPGSQL_DTYPE_RECFIELD:
{
/*
* Target is a field of a record
*/
recfield = (PLpgSQL_recfield *) target;
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
PLpgSQL_rec *rec;
int fno;
HeapTuple newtup;
int natts;
int i;
Datum *values;
char *nulls;
void *mustfree;
bool attisnull;
Oid atttype;
int32 atttypmod;
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
/*
@ -2839,8 +2908,25 @@ exec_assign_value(PLpgSQL_execstate * estate,
pfree(mustfree);
break;
}
case PLPGSQL_DTYPE_ARRAYELEM:
{
int nsubscripts;
int i;
PLpgSQL_expr *subscripts[MAXDIM];
int subscriptvals[MAXDIM];
bool havenullsubscript,
oldarrayisnull;
Oid arraytypeid,
arrayelemtypeid,
arrayInputFn;
int16 elemtyplen;
bool elemtypbyval;
char elemtypalign;
Datum oldarrayval,
coerced_value;
ArrayType *newarrayval;
/*
* Target is an element of an array
@ -2942,6 +3028,7 @@ exec_assign_value(PLpgSQL_execstate * estate,
*/
pfree(newarrayval);
break;
}
default:
elog(ERROR, "unrecognized dtype: %d", target->dtype);
@ -2993,6 +3080,8 @@ exec_eval_datum(PLpgSQL_execstate * estate,
if (!row->rowtupdesc) /* should not happen */
elog(ERROR, "row variable has no tupdesc");
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(row->rowtupdesc);
tup = make_tuple_from_row(estate, row, row->rowtupdesc);
if (tup == NULL) /* should not happen */
elog(ERROR, "row not compatible with its own tupdesc");
@ -3010,6 +3099,7 @@ exec_eval_datum(PLpgSQL_execstate * estate,
case PLPGSQL_DTYPE_REC:
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
HeapTupleData worktup;
if (!HeapTupleIsValid(rec->tup))
ereport(ERROR,
@ -3017,8 +3107,20 @@ exec_eval_datum(PLpgSQL_execstate * estate,
errmsg("record \"%s\" is not assigned yet",
rec->refname),
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
Assert(rec->tupdesc != NULL);
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(rec->tupdesc);
/*
* In a trigger, the NEW and OLD parameters are likely to be
* on-disk tuples that don't have the desired Datum fields.
* Copy the tuple body and insert the right values.
*/
heap_copytuple_with_tuple(rec->tup, &worktup);
HeapTupleHeaderSetDatumLength(worktup.t_data, worktup.t_len);
HeapTupleHeaderSetTypeId(worktup.t_data, rec->tupdesc->tdtypeid);
HeapTupleHeaderSetTypMod(worktup.t_data, rec->tupdesc->tdtypmod);
*typeid = rec->tupdesc->tdtypeid;
*value = HeapTupleGetDatum(rec->tup);
*value = HeapTupleGetDatum(&worktup);
*isnull = false;
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
ereport(ERROR,