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,