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:
parent
0f059e1d13
commit
7eb2ff799e
@ -4,7 +4,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -744,6 +744,16 @@ assign_var : T_SCALAR
|
|||||||
check_assignable(yylval.scalar);
|
check_assignable(yylval.scalar);
|
||||||
$$ = yylval.scalar->dno;
|
$$ = 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
|
| assign_var '[' expr_until_rightbracket
|
||||||
{
|
{
|
||||||
PLpgSQL_arrayelem *new;
|
PLpgSQL_arrayelem *new;
|
||||||
@ -1966,6 +1976,12 @@ check_assignable(PLpgSQL_datum *datum)
|
|||||||
((PLpgSQL_var *) datum)->refname)));
|
((PLpgSQL_var *) datum)->refname)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case PLPGSQL_DTYPE_ROW:
|
||||||
|
/* always assignable? */
|
||||||
|
break;
|
||||||
|
case PLPGSQL_DTYPE_REC:
|
||||||
|
/* always assignable? What about NEW/OLD? */
|
||||||
|
break;
|
||||||
case PLPGSQL_DTYPE_RECFIELD:
|
case PLPGSQL_DTYPE_RECFIELD:
|
||||||
/* always assignable? */
|
/* always assignable? */
|
||||||
break;
|
break;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* procedural language
|
* procedural language
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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.
|
* This software is copyrighted by Jan Wieck - Hamburg.
|
||||||
*
|
*
|
||||||
@ -2664,49 +2664,15 @@ exec_assign_value(PLpgSQL_execstate * estate,
|
|||||||
PLpgSQL_datum * target,
|
PLpgSQL_datum * target,
|
||||||
Datum value, Oid valtype, bool *isNull)
|
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)
|
switch (target->dtype)
|
||||||
{
|
{
|
||||||
case PLPGSQL_DTYPE_VAR:
|
case PLPGSQL_DTYPE_VAR:
|
||||||
|
{
|
||||||
/*
|
/*
|
||||||
* Target is a variable
|
* Target is a variable
|
||||||
*/
|
*/
|
||||||
var = (PLpgSQL_var *) target;
|
PLpgSQL_var *var = (PLpgSQL_var *) target;
|
||||||
|
Datum newvalue;
|
||||||
if (var->freeval)
|
|
||||||
{
|
|
||||||
pfree(DatumGetPointer(var->value));
|
|
||||||
var->freeval = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
newvalue = exec_cast_value(value, valtype, var->datatype->typoid,
|
newvalue = exec_cast_value(value, valtype, var->datatype->typoid,
|
||||||
&(var->datatype->typinput),
|
&(var->datatype->typinput),
|
||||||
@ -2720,6 +2686,12 @@ exec_assign_value(PLpgSQL_execstate * estate,
|
|||||||
errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
|
errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
|
||||||
var->refname)));
|
var->refname)));
|
||||||
|
|
||||||
|
if (var->freeval)
|
||||||
|
{
|
||||||
|
pfree(DatumGetPointer(var->value));
|
||||||
|
var->freeval = false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If type is by-reference, make sure we have a freshly
|
* If type is by-reference, make sure we have a freshly
|
||||||
* palloc'd copy; the originally passed value may not live as
|
* 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->value = newvalue;
|
||||||
var->isnull = *isNull;
|
var->isnull = *isNull;
|
||||||
break;
|
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:
|
case PLPGSQL_DTYPE_RECFIELD:
|
||||||
|
{
|
||||||
/*
|
/*
|
||||||
* Target is a field of a record
|
* 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]);
|
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2839,8 +2908,25 @@ exec_assign_value(PLpgSQL_execstate * estate,
|
|||||||
pfree(mustfree);
|
pfree(mustfree);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case PLPGSQL_DTYPE_ARRAYELEM:
|
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
|
* Target is an element of an array
|
||||||
@ -2942,6 +3028,7 @@ exec_assign_value(PLpgSQL_execstate * estate,
|
|||||||
*/
|
*/
|
||||||
pfree(newarrayval);
|
pfree(newarrayval);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
elog(ERROR, "unrecognized dtype: %d", target->dtype);
|
elog(ERROR, "unrecognized dtype: %d", target->dtype);
|
||||||
@ -2993,6 +3080,8 @@ exec_eval_datum(PLpgSQL_execstate * estate,
|
|||||||
|
|
||||||
if (!row->rowtupdesc) /* should not happen */
|
if (!row->rowtupdesc) /* should not happen */
|
||||||
elog(ERROR, "row variable has no tupdesc");
|
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);
|
tup = make_tuple_from_row(estate, row, row->rowtupdesc);
|
||||||
if (tup == NULL) /* should not happen */
|
if (tup == NULL) /* should not happen */
|
||||||
elog(ERROR, "row not compatible with its own tupdesc");
|
elog(ERROR, "row not compatible with its own tupdesc");
|
||||||
@ -3010,6 +3099,7 @@ exec_eval_datum(PLpgSQL_execstate * estate,
|
|||||||
case PLPGSQL_DTYPE_REC:
|
case PLPGSQL_DTYPE_REC:
|
||||||
{
|
{
|
||||||
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
|
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
|
||||||
|
HeapTupleData worktup;
|
||||||
|
|
||||||
if (!HeapTupleIsValid(rec->tup))
|
if (!HeapTupleIsValid(rec->tup))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
@ -3017,8 +3107,20 @@ exec_eval_datum(PLpgSQL_execstate * estate,
|
|||||||
errmsg("record \"%s\" is not assigned yet",
|
errmsg("record \"%s\" is not assigned yet",
|
||||||
rec->refname),
|
rec->refname),
|
||||||
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
|
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;
|
*typeid = rec->tupdesc->tdtypeid;
|
||||||
*value = HeapTupleGetDatum(rec->tup);
|
*value = HeapTupleGetDatum(&worktup);
|
||||||
*isnull = false;
|
*isnull = false;
|
||||||
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
|
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user