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