From 7eb2ff799e0bfcc59d160964f0aafed4901db4ba Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 4 Jun 2004 02:37:06 +0000
Subject: [PATCH] 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.

---
 src/pl/plpgsql/src/gram.y    |  18 +++-
 src/pl/plpgsql/src/pl_exec.c | 184 +++++++++++++++++++++++++++--------
 2 files changed, 160 insertions(+), 42 deletions(-)

diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index 7abc1e19b83..826e1539d1e 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -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;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 149c96ec7ba..5a3d77a07c6 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -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,
-- 
GitLab