1 /*-------------------------------------------------------------------------
2  *
3  * assign.c
4  *
5  *			  assign types to record variables
6  *
7  * by Pavel Stehule 2013-2021
8  *
9  *-------------------------------------------------------------------------
10  */
11 
12 #include "plpgsql_check.h"
13 
14 #include "access/htup_details.h"
15 #include "catalog/pg_type.h"
16 #include "parser/parse_coerce.h"
17 #include "utils/builtins.h"
18 #include "utils/lsyscache.h"
19 #include "utils/typcache.h"
20 
21 #if PG_VERSION_NUM >= 110000
22 
23 #define get_eval_mcontext(estate) \
24 	((estate)->eval_econtext->ecxt_per_tuple_memory)
25 #define eval_mcontext_alloc(estate, sz) \
26 	MemoryContextAlloc(get_eval_mcontext(estate), sz)
27 #define eval_mcontext_alloc0(estate, sz) \
28 	MemoryContextAllocZero(get_eval_mcontext(estate), sz)
29 
30 static bool compatible_tupdescs(TupleDesc src_tupdesc, TupleDesc dst_tupdesc);
31 
32 #endif
33 
34 /*
35  * Mark variable as used
36  */
37 void
plpgsql_check_record_variable_usage(PLpgSQL_checkstate * cstate,int dno,bool write)38 plpgsql_check_record_variable_usage(PLpgSQL_checkstate *cstate, int dno, bool write)
39 {
40 	if (dno >= 0)
41 	{
42 		if (!write)
43 			cstate->used_variables = bms_add_member(cstate->used_variables, dno);
44 		else
45 		{
46 			cstate->modif_variables = bms_add_member(cstate->modif_variables, dno);
47 
48 			/* raise extra warning when protected variable is modified */
49 			if (bms_is_member(dno, cstate->protected_variables))
50 			{
51 				PLpgSQL_variable *var = (PLpgSQL_variable *) cstate->estate->datums[dno];
52 				StringInfoData message;
53 
54 				initStringInfo(&message);
55 
56 				appendStringInfo(&message, "auto varible \"%s\" should not be modified by user", var->refname);
57 				plpgsql_check_put_error(cstate,
58 						  0, var->lineno,
59 						  message.data,
60 						  NULL,
61 						  NULL,
62 						  PLPGSQL_CHECK_WARNING_EXTRA,
63 						  0, NULL, NULL);
64 				pfree(message.data);
65 			}
66 		}
67 	}
68 }
69 
70 void
plpgsql_check_row_or_rec(PLpgSQL_checkstate * cstate,PLpgSQL_row * row,PLpgSQL_rec * rec)71 plpgsql_check_row_or_rec(PLpgSQL_checkstate *cstate, PLpgSQL_row *row, PLpgSQL_rec *rec)
72 {
73 	int			fnum;
74 
75 	if (row != NULL)
76 	{
77 
78 		for (fnum = 0; fnum < row->nfields; fnum++)
79 		{
80 			/* skip dropped columns */
81 			if (row->varnos[fnum] < 0)
82 				continue;
83 
84 			plpgsql_check_target(cstate, row->varnos[fnum], NULL, NULL);
85 		}
86 		plpgsql_check_record_variable_usage(cstate, row->dno, true);
87 	}
88 	else if (rec != NULL)
89 	{
90 		/*
91 		 * There are no checks done on records currently; just record that the
92 		 * variable is not unused.
93 		 */
94 		plpgsql_check_record_variable_usage(cstate, rec->dno, true);
95 	}
96 }
97 
98 /*
99  * Verify lvalue It doesn't repeat a checks that are done. Checks a subscript
100  * expressions, verify a validity of record's fields.
101  */
102 void
plpgsql_check_target(PLpgSQL_checkstate * cstate,int varno,Oid * expected_typoid,int * expected_typmod)103 plpgsql_check_target(PLpgSQL_checkstate *cstate, int varno, Oid *expected_typoid, int *expected_typmod)
104 {
105 	PLpgSQL_datum *target = cstate->estate->datums[varno];
106 
107 	plpgsql_check_record_variable_usage(cstate, varno, true);
108 
109 	switch (target->dtype)
110 	{
111 		case PLPGSQL_DTYPE_VAR:
112 			{
113 				PLpgSQL_var *var = (PLpgSQL_var *) target;
114 				PLpgSQL_type *tp = var->datatype;
115 
116 				if (expected_typoid != NULL)
117 					*expected_typoid = tp->typoid;
118 				if (expected_typmod != NULL)
119 					*expected_typmod = tp->atttypmod;
120 			}
121 			break;
122 
123 		case PLPGSQL_DTYPE_REC:
124 			{
125 				PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
126 
127 				plpgsql_check_recvar_info(rec, expected_typoid, expected_typmod);
128 			}
129 			break;
130 
131 		case PLPGSQL_DTYPE_ROW:
132 			{
133 				PLpgSQL_row *row = (PLpgSQL_row *) target;
134 
135 				if (row->rowtupdesc != NULL)
136 				{
137 					if (expected_typoid != NULL)
138 						*expected_typoid = row->rowtupdesc->tdtypeid;
139 					if (expected_typmod != NULL)
140 						*expected_typmod = row->rowtupdesc->tdtypmod;
141 				}
142 				else
143 				{
144 					if (expected_typoid != NULL)
145 						*expected_typoid = RECORDOID;
146 					if (expected_typmod != NULL)
147 						*expected_typmod = -1;
148 				}
149 
150 				plpgsql_check_row_or_rec(cstate, row, NULL);
151 
152 			}
153 			break;
154 
155 		case PLPGSQL_DTYPE_RECFIELD:
156 			{
157 				PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
158 				PLpgSQL_rec *rec;
159 				int			fno;
160 
161 				rec = (PLpgSQL_rec *) (cstate->estate->datums[recfield->recparentno]);
162 
163 				/*
164 				 * Check that there is already a tuple in the record. We need
165 				 * that because records don't have any predefined field
166 				 * structure.
167 				 */
168 				if (!HeapTupleIsValid(recvar_tuple(rec)))
169 					ereport(ERROR,
170 						  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
171 					errmsg("record \"%s\" is not assigned to tuple structure",
172 						   rec->refname)));
173 
174 				/*
175 				 * Get the number of the records field to change and the
176 				 * number of attributes in the tuple.  Note: disallow system
177 				 * column names because the code below won't cope.
178 				 */
179 				fno = SPI_fnumber(recvar_tupdesc(rec), recfield->fieldname);
180 				if (fno <= 0)
181 					ereport(ERROR,
182 							(errcode(ERRCODE_UNDEFINED_COLUMN),
183 							 errmsg("record \"%s\" has no field \"%s\"",
184 									rec->refname, recfield->fieldname)));
185 
186 				if (expected_typoid)
187 					*expected_typoid = SPI_gettypeid(recvar_tupdesc(rec), fno);
188 
189 				if (expected_typmod)
190 					*expected_typmod = TupleDescAttr(recvar_tupdesc(rec), fno - 1)->atttypmod;
191 			}
192 			break;
193 
194 #if PG_VERSION_NUM < 140000
195 
196 		case PLPGSQL_DTYPE_ARRAYELEM:
197 			{
198 				/*
199 				 * Target is an element of an array
200 				 */
201 				int			nsubscripts;
202 
203 				/*
204 				 * To handle constructs like x[1][2] := something, we have to
205 				 * be prepared to deal with a chain of arrayelem datums. Chase
206 				 * back to find the base array datum, and save the subscript
207 				 * expressions as we go.  (We are scanning right to left here,
208 				 * but want to evaluate the subscripts left-to-right to
209 				 * minimize surprises.)
210 				 */
211 				nsubscripts = 0;
212 				do
213 				{
214 					PLpgSQL_arrayelem *arrayelem = (PLpgSQL_arrayelem *) target;
215 
216 					if (nsubscripts++ >= MAXDIM)
217 						ereport(ERROR,
218 								(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
219 								 errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
220 										nsubscripts + 1, MAXDIM)));
221 
222 					plpgsql_check_expr(cstate, arrayelem->subscript);
223 
224 					target = cstate->estate->datums[arrayelem->arrayparentno];
225 				} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
226 
227 				if (expected_typoid || expected_typmod)
228 				{
229 					int			arraytypmod;
230 					Oid			arrayelemtypeid;
231 					Oid			arraytypeid;
232 
233 					plpgsql_check_target(cstate, target->dno, &arraytypeid, &arraytypmod);
234 
235 					/*
236 					 * If target is domain over array, reduce to base type
237 					 */
238 					arraytypeid = getBaseType(arraytypeid);
239 					arrayelemtypeid = get_element_type(arraytypeid);
240 
241 					if (!OidIsValid(arrayelemtypeid))
242 						ereport(ERROR,
243 								(errcode(ERRCODE_DATATYPE_MISMATCH),
244 								 errmsg("subscripted object is not an array")));
245 
246 					if (expected_typoid)
247 						*expected_typoid = arrayelemtypeid;
248 
249 					if (expected_typmod)
250 						*expected_typmod = arraytypmod;
251 				}
252 
253 				plpgsql_check_record_variable_usage(cstate, target->dno, true);
254 			}
255 			break;
256 
257 #endif
258 
259 		default:
260 			;		/* nope */
261 	}
262 }
263 
264 /*
265  * Check so target can accept typoid value
266  *
267  */
268 void
plpgsql_check_assign_to_target_type(PLpgSQL_checkstate * cstate,Oid target_typoid,int32 target_typmod,Oid value_typoid,bool isnull)269 plpgsql_check_assign_to_target_type(PLpgSQL_checkstate *cstate,
270 									Oid target_typoid,
271 									int32 target_typmod,
272 									Oid value_typoid,
273 									bool isnull)
274 {
275 	/* not used yet */
276 	(void) target_typmod;
277 
278 	/* the overhead UNKONWNOID --> TEXT is low */
279 	if (target_typoid == TEXTOID && value_typoid == UNKNOWNOID)
280 		return;
281 
282 	if (type_is_rowtype(value_typoid))
283 	{
284 		StringInfoData	str;
285 
286 		initStringInfo(&str);
287 		appendStringInfo(&str, "cannot cast composite value of \"%s\" type to a scalar value of \"%s\" type",
288 									format_type_be(value_typoid),
289 									format_type_be(target_typoid));
290 
291 		plpgsql_check_put_error(cstate,
292 					  ERRCODE_DATATYPE_MISMATCH, 0,
293 					  str.data,
294 					  NULL,
295 					  NULL,
296 					  PLPGSQL_CHECK_ERROR,
297 					  0, NULL, NULL);
298 	}
299 	else if (target_typoid != value_typoid && !isnull)
300 	{
301 		StringInfoData	str;
302 
303 		initStringInfo(&str);
304 		appendStringInfo(&str, "cast \"%s\" value to \"%s\" type",
305 									format_type_be(value_typoid),
306 									format_type_be(target_typoid));
307 
308 		/* accent warning when cast is without supported explicit casting */
309 		if (!can_coerce_type(1, &value_typoid, &target_typoid, COERCION_EXPLICIT))
310 			plpgsql_check_put_error(cstate,
311 						  ERRCODE_DATATYPE_MISMATCH, 0,
312 						  "target type is different type than source type",
313 						  str.data,
314 						  "There are no possible explicit coercion between those types, possibly bug!",
315 						  PLPGSQL_CHECK_WARNING_OTHERS,
316 						  0, NULL, NULL);
317 		else if (!can_coerce_type(1, &value_typoid, &target_typoid, COERCION_ASSIGNMENT))
318 			plpgsql_check_put_error(cstate,
319 						  ERRCODE_DATATYPE_MISMATCH, 0,
320 						  "target type is different type than source type",
321 						  str.data,
322 						  "The input expression type does not have an assignment cast to the target type.",
323 						  PLPGSQL_CHECK_WARNING_OTHERS,
324 						  0, NULL, NULL);
325 		else
326 		{
327 			/* highly probably only performance issue */
328 			plpgsql_check_put_error(cstate,
329 						  ERRCODE_DATATYPE_MISMATCH, 0,
330 						  "target type is different type than source type",
331 						  str.data,
332 						  "Hidden casting can be a performance issue.",
333 						  PLPGSQL_CHECK_WARNING_PERFORMANCE,
334 						  0, NULL, NULL);
335 		}
336 
337 		pfree(str.data);
338 	}
339 }
340 
341 /*
342  * Assign a tuple descriptor to variable specified by dno
343  */
344 void
plpgsql_check_assign_tupdesc_dno(PLpgSQL_checkstate * cstate,int varno,TupleDesc tupdesc,bool isnull)345 plpgsql_check_assign_tupdesc_dno(PLpgSQL_checkstate *cstate, int varno, TupleDesc tupdesc, bool isnull)
346 {
347 	PLpgSQL_datum *target = cstate->estate->datums[varno];
348 
349 	switch (target->dtype)
350 	{
351 		case PLPGSQL_DTYPE_VAR:
352 			{
353 				PLpgSQL_var *var = (PLpgSQL_var *) target;
354 
355 				plpgsql_check_assign_to_target_type(cstate,
356 									 var->datatype->typoid, var->datatype->atttypmod,
357 									 TupleDescAttr(tupdesc, 0)->atttypid,
358 									 isnull);
359 			}
360 			break;
361 
362 		case PLPGSQL_DTYPE_ROW:
363 			plpgsql_check_assign_tupdesc_row_or_rec(cstate, (PLpgSQL_row *) target, NULL, tupdesc, isnull);
364 			break;
365 
366 		case PLPGSQL_DTYPE_REC:
367 			plpgsql_check_assign_tupdesc_row_or_rec(cstate, NULL, (PLpgSQL_rec *) target, tupdesc, isnull);
368 			break;
369 
370 		case PLPGSQL_DTYPE_RECFIELD:
371 			{
372 				Oid		typoid;
373 				int		typmod;
374 
375 				plpgsql_check_target(cstate, varno, &typoid, &typmod);
376 
377 				plpgsql_check_assign_to_target_type(cstate,
378 									 typoid, typmod,
379 									 TupleDescAttr(tupdesc, 0)->atttypid,
380 									 isnull);
381 			}
382 			break;
383 
384 #if PG_VERSION_NUM < 140000
385 
386 		case PLPGSQL_DTYPE_ARRAYELEM:
387 			{
388 				Oid expected_typoid;
389 				int expected_typmod;
390 
391 				plpgsql_check_target(cstate, varno, &expected_typoid, &expected_typmod);
392 
393 				/* When target is composite type, then source is expanded already */
394 				if (type_is_rowtype(expected_typoid))
395 				{
396 					PLpgSQL_rec rec;
397 
398 					plpgsql_check_recval_init(&rec);
399 
400 					PG_TRY();
401 					{
402 						plpgsql_check_recval_assign_tupdesc(cstate, &rec,
403 											  lookup_rowtype_tupdesc_noerror(expected_typoid,
404 																			 expected_typmod,
405 																			 true),
406 																			 isnull);
407 
408 						plpgsql_check_assign_tupdesc_row_or_rec(cstate, NULL, &rec, tupdesc, isnull);
409 						plpgsql_check_recval_release(&rec);
410 					}
411 					PG_CATCH();
412 					{
413 						plpgsql_check_recval_release(&rec);
414 
415 						PG_RE_THROW();
416 					}
417 					PG_END_TRY();
418 				}
419 				else
420 					plpgsql_check_assign_to_target_type(cstate,
421 									    expected_typoid, expected_typmod,
422 									    TupleDescAttr(tupdesc, 0)->atttypid,
423 									    isnull);
424 			}
425 			break;
426 
427 #endif
428 
429 		default:
430 			;		/* nope */
431 	}
432 }
433 
434 /*
435  * We have to assign TupleDesc to all used record variables step by step. We
436  * would to use a exec routines for query preprocessing, so we must to create
437  * a typed NULL value, and this value is assigned to record variable.
438  */
439 void
plpgsql_check_assign_tupdesc_row_or_rec(PLpgSQL_checkstate * cstate,PLpgSQL_row * row,PLpgSQL_rec * rec,TupleDesc tupdesc,bool isnull)440 plpgsql_check_assign_tupdesc_row_or_rec(PLpgSQL_checkstate *cstate,
441 								  PLpgSQL_row *row,
442 								  PLpgSQL_rec *rec,
443 								  TupleDesc tupdesc,
444 								  bool isnull)
445 {
446 	if (tupdesc == NULL)
447 	{
448 		plpgsql_check_put_error(cstate,
449 					  0, 0,
450 					  "tuple descriptor is empty", NULL, NULL,
451 					  PLPGSQL_CHECK_WARNING_OTHERS,
452 					  0, NULL, NULL);
453 		return;
454 	}
455 
456 	/*
457 	 * row variable has assigned TupleDesc already, so don't be processed here
458 	 */
459 	if (rec != NULL)
460 	{
461 		PLpgSQL_rec *target = (PLpgSQL_rec *) (cstate->estate->datums[rec->dno]);
462 
463 		plpgsql_check_recval_release(target);
464 		plpgsql_check_recval_assign_tupdesc(cstate, target, tupdesc, isnull);
465 	}
466 	else if (row != NULL)
467 	{
468 		int			td_natts = tupdesc->natts;
469 		int			fnum;
470 		int			anum;
471 
472 		anum = 0;
473 		for (fnum = 0; fnum < row->nfields; fnum++)
474 		{
475 			if (row->varnos[fnum] < 0)
476 				continue;		/* skip dropped column in row struct */
477 
478 			while (anum < td_natts && TupleDescAttr(tupdesc, anum)->attisdropped)
479 				anum++;			/* skip dropped column in tuple */
480 
481 			if (anum < td_natts)
482 			{
483 				Oid	valtype = SPI_gettypeid(tupdesc, anum + 1);
484 				PLpgSQL_datum *target = cstate->estate->datums[row->varnos[fnum]];
485 
486 				switch (target->dtype)
487 				{
488 					case PLPGSQL_DTYPE_VAR:
489 						{
490 							PLpgSQL_var *var = (PLpgSQL_var *) target;
491 
492 							plpgsql_check_assign_to_target_type(cstate,
493 												 var->datatype->typoid,
494 												 var->datatype->atttypmod,
495 														 valtype,
496 														 isnull);
497 						}
498 						break;
499 
500 					case PLPGSQL_DTYPE_RECFIELD:
501 						{
502 							Oid	expected_typoid;
503 							int	expected_typmod;
504 
505 							plpgsql_check_target(cstate, target->dno, &expected_typoid, &expected_typmod);
506 							plpgsql_check_assign_to_target_type(cstate,
507 												 expected_typoid,
508 												 expected_typmod,
509 														valtype,
510 														isnull);
511 						}
512 						break;
513 					default:
514 						;		/* nope */
515 				}
516 
517 				anum++;
518 			}
519 		}
520 	}
521 }
522 
523 /*
524  * recval_init, recval_release, recval_assign_tupdesc
525  *
526  *   a set of functions designed to better portability between PostgreSQL 11
527  *   with expanded records support and older PostgreSQL versions.
528  */
529 void
plpgsql_check_recval_init(PLpgSQL_rec * rec)530 plpgsql_check_recval_init(PLpgSQL_rec *rec)
531 {
532 	Assert(rec->dtype == PLPGSQL_DTYPE_REC);
533 
534 #if PG_VERSION_NUM >= 110000
535 
536 	rec->erh = NULL;
537 
538 #else
539 
540 	rec->tup = NULL;
541 	rec->freetup = false;
542 	rec->freetupdesc = false;
543 
544 #endif
545 }
546 
547 void
plpgsql_check_recval_release(PLpgSQL_rec * rec)548 plpgsql_check_recval_release(PLpgSQL_rec *rec)
549 {
550 
551 #if PG_VERSION_NUM >= 110000
552 
553 	Assert(rec->dtype == PLPGSQL_DTYPE_REC);
554 
555 	if (rec->erh)
556 		DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
557 	rec->erh = NULL;
558 
559 #else
560 
561 	if (rec->freetup)
562 		heap_freetuple(rec->tup);
563 
564 	if (rec->freetupdesc)
565 		FreeTupleDesc(rec->tupdesc);
566 
567 	rec->freetup = false;
568 	rec->freetupdesc = false;
569 
570 #endif
571 
572 }
573 
574 /*
575  * is_null is true, when we assign NULL expression and type should not be checked.
576  */
577 void
plpgsql_check_recval_assign_tupdesc(PLpgSQL_checkstate * cstate,PLpgSQL_rec * rec,TupleDesc tupdesc,bool is_null)578 plpgsql_check_recval_assign_tupdesc(PLpgSQL_checkstate *cstate, PLpgSQL_rec *rec, TupleDesc tupdesc, bool is_null)
579 {
580 
581 #if PG_VERSION_NUM >= 110000
582 
583 	PLpgSQL_execstate	   *estate = cstate->estate;
584 	ExpandedRecordHeader   *newerh;
585 	MemoryContext			mcontext;
586 	TupleDesc	var_tupdesc;
587 	Datum	   *newvalues;
588 	bool	   *newnulls;
589 	char	   *chunk;
590 	int			vtd_natts;
591 	int			i;
592 
593 	mcontext = get_eval_mcontext(estate);
594 	plpgsql_check_recval_release(rec);
595 
596 	/*
597 	 * code is reduced version of make_expanded_record_for_rec
598 	 */
599 	if (rec->rectypeid != RECORDOID)
600 	{
601 		newerh = make_expanded_record_from_typeid(rec->rectypeid, -1,
602 													  mcontext);
603 	}
604 	else
605 	{
606 		if (!tupdesc)
607 			return;
608 
609 		newerh = make_expanded_record_from_tupdesc(tupdesc,
610 													   mcontext);
611 	}
612 
613 	/*
614 	 * code is reduced version of exec_move_row_from_field
615 	 */
616 	var_tupdesc = expanded_record_get_tupdesc(newerh);
617 	vtd_natts = var_tupdesc->natts;
618 
619 	if (!is_null && tupdesc != NULL && !compatible_tupdescs(var_tupdesc, tupdesc))
620 	{
621 		int		attn1 = 0;
622 		int		attn2 = 0;
623 		int		target_nfields = 0;
624 		int		src_nfields = 0;
625 		bool	src_field_is_valid = false;
626 		bool	target_field_is_valid = false;
627 		Form_pg_attribute sattr = NULL;
628 		Form_pg_attribute tattr = NULL;
629 
630 		while (attn1 < var_tupdesc->natts || attn2 < tupdesc->natts)
631 		{
632 			if (!target_field_is_valid && attn1 < var_tupdesc->natts)
633 			{
634 				tattr = TupleDescAttr(var_tupdesc, attn1);
635 				if (tattr->attisdropped)
636 				{
637 					attn1 += 1;
638 					continue;
639 				}
640 				target_field_is_valid = true;
641 				target_nfields += 1;
642 			}
643 
644 			if (!src_field_is_valid && attn2 < tupdesc->natts)
645 			{
646 				sattr = TupleDescAttr(tupdesc, attn2);
647 				if (sattr->attisdropped)
648 				{
649 					attn2 += 1;
650 					continue;
651 				}
652 				src_field_is_valid = true;
653 				src_nfields += 1;
654 			}
655 
656 			if (src_field_is_valid && target_field_is_valid)
657 			{
658 				plpgsql_check_assign_to_target_type(cstate,
659 												tattr->atttypid, tattr->atttypmod,
660 												sattr->atttypid,
661 												false);
662 
663 				/* try to search next tuple of fields */
664 				src_field_is_valid =  false;
665 				target_field_is_valid = false;
666 				attn1 += 1;
667 				attn2 += 1;
668 			}
669 			else
670 				break;
671 		}
672 
673 		if (src_nfields < target_nfields)
674 			plpgsql_check_put_error(cstate,
675 						  0, 0,
676 						  "too few attributes for composite variable",
677 						  NULL,
678 						  NULL,
679 						  PLPGSQL_CHECK_WARNING_OTHERS,
680 						  0, NULL, NULL);
681 		else if (src_nfields > target_nfields)
682 			plpgsql_check_put_error(cstate,
683 						  0, 0,
684 						  "too many attributes for composite variable",
685 						  NULL,
686 						  NULL,
687 						  PLPGSQL_CHECK_WARNING_OTHERS,
688 						  0, NULL, NULL);
689 	}
690 
691 	chunk = eval_mcontext_alloc(estate,
692 								vtd_natts * (sizeof(Datum) + sizeof(bool)));
693 	newvalues = (Datum *) chunk;
694 	newnulls = (bool *) (chunk + vtd_natts * sizeof(Datum));
695 
696 	for (i = 0; i < vtd_natts; i++)
697 	{
698 		newvalues[i] = (Datum) 0;
699 		newnulls[i] = true;
700 	}
701 
702 	expanded_record_set_fields(newerh, newvalues, newnulls, true);
703 
704 	TransferExpandedRecord(newerh, estate->datum_context);
705 	rec->erh = newerh;
706 
707 #else
708 
709 	bool	   *nulls;
710 	HeapTuple	tup;
711 
712 	(void) cstate;
713 	(void) is_null;
714 
715 	plpgsql_check_recval_release(rec);
716 
717 	if (!tupdesc)
718 		return;
719 
720 	/* initialize rec by NULLs */
721 	nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
722 	memset(nulls, true, tupdesc->natts * sizeof(bool));
723 
724 	rec->tupdesc = CreateTupleDescCopy(tupdesc);
725 	rec->freetupdesc = true;
726 
727 	tup = heap_form_tuple(tupdesc, NULL, nulls);
728 	if (HeapTupleIsValid(tup))
729 	{
730 		rec->tup = tup;
731 		rec->freetup = true;
732 	}
733 	else
734 		elog(ERROR, "cannot to build valid composite value");
735 
736 #endif
737 
738 }
739 
740 #if PG_VERSION_NUM >= 110000
741 
742 /*
743  * compatible_tupdescs: detect whether two tupdescs are physically compatible
744  *
745  * TRUE indicates that a tuple satisfying src_tupdesc can be used directly as
746  * a value for a composite variable using dst_tupdesc.
747  */
748 static bool
compatible_tupdescs(TupleDesc src_tupdesc,TupleDesc dst_tupdesc)749 compatible_tupdescs(TupleDesc src_tupdesc, TupleDesc dst_tupdesc)
750 {
751 	int			i;
752 
753 	/* Possibly we could allow src_tupdesc to have extra columns? */
754 	if (dst_tupdesc->natts != src_tupdesc->natts)
755 		return false;
756 
757 	for (i = 0; i < dst_tupdesc->natts; i++)
758 	{
759 		Form_pg_attribute dattr = TupleDescAttr(dst_tupdesc, i);
760 		Form_pg_attribute sattr = TupleDescAttr(src_tupdesc, i);
761 
762 		if (dattr->attisdropped != sattr->attisdropped)
763 			return false;
764 		if (!dattr->attisdropped)
765 		{
766 			/* Normal columns must match by type and typmod */
767 			if (dattr->atttypid != sattr->atttypid ||
768 				(dattr->atttypmod >= 0 &&
769 				 dattr->atttypmod != sattr->atttypmod))
770 				return false;
771 		}
772 		else
773 		{
774 			/* Dropped columns are OK as long as length/alignment match */
775 			if (dattr->attlen != sattr->attlen ||
776 				dattr->attalign != sattr->attalign)
777 				return false;
778 		}
779 	}
780 
781 	return true;
782 }
783 
784 #endif
785