]> granicus.if.org Git - postgresql/commitdiff
Support assignment to whole-row variables in plpgsql; also fix glitch
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 4 Jun 2004 02:37:06 +0000 (02:37 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 4 Jun 2004 02:37:06 +0000 (02:37 +0000)
with using a trigger's NEW or OLD record as a whole-row variable in an
expression.  Fixes several long-standing complaints.

src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_exec.c

index 7abc1e19b83d0ab379fc312f77a3465b8ed90e01..826e1539d1e25cb68f4ae24b2f9586992c7f73fd 100644 (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;
index 149c96ec7baccc5d9c3a4d5c5b274227d32ff5e4..5a3d77a07c60ed618e50cece2155672779f4e3f3 100644 (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_RECFIELD:
+               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,