LCOV - code coverage report
Current view: top level - src/backend/access/common - tupdesc.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 91.2 % 422 385
Test Date: 2026-02-28 14:14:49 Functions: 92.0 % 25 23
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * tupdesc.c
       4              :  *    POSTGRES tuple descriptor support code
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  *
      10              :  * IDENTIFICATION
      11              :  *    src/backend/access/common/tupdesc.c
      12              :  *
      13              :  * NOTES
      14              :  *    some of the executor utility code such as "ExecTypeFromTL" should be
      15              :  *    moved here.
      16              :  *
      17              :  *-------------------------------------------------------------------------
      18              :  */
      19              : 
      20              : #include "postgres.h"
      21              : 
      22              : #include "access/htup_details.h"
      23              : #include "access/toast_compression.h"
      24              : #include "access/tupdesc_details.h"
      25              : #include "catalog/catalog.h"
      26              : #include "catalog/pg_collation.h"
      27              : #include "catalog/pg_type.h"
      28              : #include "common/hashfn.h"
      29              : #include "utils/builtins.h"
      30              : #include "utils/datum.h"
      31              : #include "utils/resowner.h"
      32              : #include "utils/syscache.h"
      33              : 
      34              : /* ResourceOwner callbacks to hold tupledesc references  */
      35              : static void ResOwnerReleaseTupleDesc(Datum res);
      36              : static char *ResOwnerPrintTupleDesc(Datum res);
      37              : 
      38              : static const ResourceOwnerDesc tupdesc_resowner_desc =
      39              : {
      40              :     .name = "tupdesc reference",
      41              :     .release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
      42              :     .release_priority = RELEASE_PRIO_TUPDESC_REFS,
      43              :     .ReleaseResource = ResOwnerReleaseTupleDesc,
      44              :     .DebugPrint = ResOwnerPrintTupleDesc
      45              : };
      46              : 
      47              : /* Convenience wrappers over ResourceOwnerRemember/Forget */
      48              : static inline void
      49     16927577 : ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
      50              : {
      51     16927577 :     ResourceOwnerRemember(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc);
      52     16927577 : }
      53              : 
      54              : static inline void
      55     16919550 : ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
      56              : {
      57     16919550 :     ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc);
      58     16919550 : }
      59              : 
      60              : /*
      61              :  * populate_compact_attribute_internal
      62              :  *      Helper function for populate_compact_attribute()
      63              :  */
      64              : static inline void
      65     38552796 : populate_compact_attribute_internal(Form_pg_attribute src,
      66              :                                     CompactAttribute *dst)
      67              : {
      68     38552796 :     memset(dst, 0, sizeof(CompactAttribute));
      69              : 
      70     38552796 :     dst->attcacheoff = -1;
      71     38552796 :     dst->attlen = src->attlen;
      72              : 
      73     38552796 :     dst->attbyval = src->attbyval;
      74     38552796 :     dst->attispackable = (src->attstorage != TYPSTORAGE_PLAIN);
      75     38552796 :     dst->atthasmissing = src->atthasmissing;
      76     38552796 :     dst->attisdropped = src->attisdropped;
      77     38552796 :     dst->attgenerated = (src->attgenerated != '\0');
      78              : 
      79              :     /*
      80              :      * Assign nullability status for this column.  Assuming that a constraint
      81              :      * exists, at this point we don't know if a not-null constraint is valid,
      82              :      * so we assign UNKNOWN unless the table is a catalog, in which case we
      83              :      * know it's valid.
      84              :      */
      85     60201837 :     dst->attnullability = !src->attnotnull ? ATTNULLABLE_UNRESTRICTED :
      86     21649041 :         IsCatalogRelationOid(src->attrelid) ? ATTNULLABLE_VALID :
      87              :         ATTNULLABLE_UNKNOWN;
      88              : 
      89              :     /* Compute numeric alignment requirement, too */
      90     38552796 :     dst->attalignby = typalign_to_alignby(src->attalign);
      91     38552796 : }
      92              : 
      93              : /*
      94              :  * populate_compact_attribute
      95              :  *      Fill in the corresponding CompactAttribute element from the
      96              :  *      Form_pg_attribute for the given attribute number.  This must be called
      97              :  *      whenever a change is made to a Form_pg_attribute in the TupleDesc.
      98              :  */
      99              : void
     100     38552796 : populate_compact_attribute(TupleDesc tupdesc, int attnum)
     101              : {
     102     38552796 :     Form_pg_attribute src = TupleDescAttr(tupdesc, attnum);
     103              :     CompactAttribute *dst;
     104              : 
     105              :     /*
     106              :      * Don't use TupleDescCompactAttr to prevent infinite recursion in assert
     107              :      * builds.
     108              :      */
     109     38552796 :     dst = &tupdesc->compact_attrs[attnum];
     110              : 
     111     38552796 :     populate_compact_attribute_internal(src, dst);
     112     38552796 : }
     113              : 
     114              : /*
     115              :  * verify_compact_attribute
     116              :  *      In Assert enabled builds, we verify that the CompactAttribute is
     117              :  *      populated correctly.  This helps find bugs in places such as ALTER
     118              :  *      TABLE where code makes changes to the FormData_pg_attribute but
     119              :  *      forgets to call populate_compact_attribute().
     120              :  *
     121              :  * This is used in TupleDescCompactAttr(), but declared here to allow access
     122              :  * to populate_compact_attribute_internal().
     123              :  */
     124              : void
     125            0 : verify_compact_attribute(TupleDesc tupdesc, int attnum)
     126              : {
     127              : #ifdef USE_ASSERT_CHECKING
     128              :     CompactAttribute cattr;
     129              :     Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum);
     130              :     CompactAttribute tmp;
     131              : 
     132              :     /*
     133              :      * Make a temp copy of the TupleDesc's CompactAttribute.  This may be a
     134              :      * shared TupleDesc and the attcacheoff might get changed by another
     135              :      * backend.
     136              :      */
     137              :     memcpy(&cattr, &tupdesc->compact_attrs[attnum], sizeof(CompactAttribute));
     138              : 
     139              :     /*
     140              :      * Populate the temporary CompactAttribute from the corresponding
     141              :      * Form_pg_attribute
     142              :      */
     143              :     populate_compact_attribute_internal(attr, &tmp);
     144              : 
     145              :     /*
     146              :      * Make the attcacheoff match since it's been reset to -1 by
     147              :      * populate_compact_attribute_internal.  Same with attnullability.
     148              :      */
     149              :     tmp.attcacheoff = cattr.attcacheoff;
     150              :     tmp.attnullability = cattr.attnullability;
     151              : 
     152              :     /* Check the freshly populated CompactAttribute matches the TupleDesc's */
     153              :     Assert(memcmp(&tmp, &cattr, sizeof(CompactAttribute)) == 0);
     154              : #endif
     155            0 : }
     156              : 
     157              : /*
     158              :  * CreateTemplateTupleDesc
     159              :  *      This function allocates an empty tuple descriptor structure.
     160              :  *
     161              :  * Tuple type ID information is initially set for an anonymous record type;
     162              :  * caller can overwrite this if needed.
     163              :  */
     164              : TupleDesc
     165      5563564 : CreateTemplateTupleDesc(int natts)
     166              : {
     167              :     TupleDesc   desc;
     168              : 
     169              :     /*
     170              :      * sanity checks
     171              :      */
     172              :     Assert(natts >= 0);
     173              : 
     174              :     /*
     175              :      * Allocate enough memory for the tuple descriptor, the CompactAttribute
     176              :      * array and also an array of FormData_pg_attribute.
     177              :      *
     178              :      * Note: the FormData_pg_attribute array stride is
     179              :      * sizeof(FormData_pg_attribute), since we declare the array elements as
     180              :      * FormData_pg_attribute for notational convenience.  However, we only
     181              :      * guarantee that the first ATTRIBUTE_FIXED_PART_SIZE bytes of each entry
     182              :      * are valid; most code that copies tupdesc entries around copies just
     183              :      * that much.  In principle that could be less due to trailing padding,
     184              :      * although with the current definition of pg_attribute there probably
     185              :      * isn't any padding.
     186              :      */
     187      5563564 :     desc = (TupleDesc) palloc(offsetof(struct TupleDescData, compact_attrs) +
     188      5563564 :                               natts * sizeof(CompactAttribute) +
     189              :                               natts * sizeof(FormData_pg_attribute));
     190              : 
     191              :     /*
     192              :      * Initialize other fields of the tupdesc.
     193              :      */
     194      5563564 :     desc->natts = natts;
     195      5563564 :     desc->constr = NULL;
     196      5563564 :     desc->tdtypeid = RECORDOID;
     197      5563564 :     desc->tdtypmod = -1;
     198      5563564 :     desc->tdrefcount = -1;       /* assume not reference-counted */
     199              : 
     200      5563564 :     return desc;
     201              : }
     202              : 
     203              : /*
     204              :  * CreateTupleDesc
     205              :  *      This function allocates a new TupleDesc by copying a given
     206              :  *      Form_pg_attribute array.
     207              :  *
     208              :  * Tuple type ID information is initially set for an anonymous record type;
     209              :  * caller can overwrite this if needed.
     210              :  */
     211              : TupleDesc
     212       595073 : CreateTupleDesc(int natts, Form_pg_attribute *attrs)
     213              : {
     214              :     TupleDesc   desc;
     215              :     int         i;
     216              : 
     217       595073 :     desc = CreateTemplateTupleDesc(natts);
     218              : 
     219      9098945 :     for (i = 0; i < natts; ++i)
     220              :     {
     221      8503872 :         memcpy(TupleDescAttr(desc, i), attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
     222      8503872 :         populate_compact_attribute(desc, i);
     223              :     }
     224       595073 :     return desc;
     225              : }
     226              : 
     227              : /*
     228              :  * CreateTupleDescCopy
     229              :  *      This function creates a new TupleDesc by copying from an existing
     230              :  *      TupleDesc.
     231              :  *
     232              :  * !!! Constraints and defaults are not copied !!!
     233              :  */
     234              : TupleDesc
     235       217426 : CreateTupleDescCopy(TupleDesc tupdesc)
     236              : {
     237              :     TupleDesc   desc;
     238              :     int         i;
     239              : 
     240       217426 :     desc = CreateTemplateTupleDesc(tupdesc->natts);
     241              : 
     242              :     /* Flat-copy the attribute array */
     243       217426 :     memcpy(TupleDescAttr(desc, 0),
     244       217426 :            TupleDescAttr(tupdesc, 0),
     245       217426 :            desc->natts * sizeof(FormData_pg_attribute));
     246              : 
     247              :     /*
     248              :      * Since we're not copying constraints and defaults, clear fields
     249              :      * associated with them.
     250              :      */
     251      1160670 :     for (i = 0; i < desc->natts; i++)
     252              :     {
     253       943244 :         Form_pg_attribute att = TupleDescAttr(desc, i);
     254              : 
     255       943244 :         att->attnotnull = false;
     256       943244 :         att->atthasdef = false;
     257       943244 :         att->atthasmissing = false;
     258       943244 :         att->attidentity = '\0';
     259       943244 :         att->attgenerated = '\0';
     260              : 
     261       943244 :         populate_compact_attribute(desc, i);
     262              :     }
     263              : 
     264              :     /* We can copy the tuple type identification, too */
     265       217426 :     desc->tdtypeid = tupdesc->tdtypeid;
     266       217426 :     desc->tdtypmod = tupdesc->tdtypmod;
     267              : 
     268       217426 :     return desc;
     269              : }
     270              : 
     271              : /*
     272              :  * CreateTupleDescTruncatedCopy
     273              :  *      This function creates a new TupleDesc with only the first 'natts'
     274              :  *      attributes from an existing TupleDesc
     275              :  *
     276              :  * !!! Constraints and defaults are not copied !!!
     277              :  */
     278              : TupleDesc
     279        18406 : CreateTupleDescTruncatedCopy(TupleDesc tupdesc, int natts)
     280              : {
     281              :     TupleDesc   desc;
     282              :     int         i;
     283              : 
     284              :     Assert(natts <= tupdesc->natts);
     285              : 
     286        18406 :     desc = CreateTemplateTupleDesc(natts);
     287              : 
     288              :     /* Flat-copy the attribute array */
     289        18406 :     memcpy(TupleDescAttr(desc, 0),
     290        18406 :            TupleDescAttr(tupdesc, 0),
     291        18406 :            desc->natts * sizeof(FormData_pg_attribute));
     292              : 
     293              :     /*
     294              :      * Since we're not copying constraints and defaults, clear fields
     295              :      * associated with them.
     296              :      */
     297        42852 :     for (i = 0; i < desc->natts; i++)
     298              :     {
     299        24446 :         Form_pg_attribute att = TupleDescAttr(desc, i);
     300              : 
     301        24446 :         att->attnotnull = false;
     302        24446 :         att->atthasdef = false;
     303        24446 :         att->atthasmissing = false;
     304        24446 :         att->attidentity = '\0';
     305        24446 :         att->attgenerated = '\0';
     306              : 
     307        24446 :         populate_compact_attribute(desc, i);
     308              :     }
     309              : 
     310              :     /* We can copy the tuple type identification, too */
     311        18406 :     desc->tdtypeid = tupdesc->tdtypeid;
     312        18406 :     desc->tdtypmod = tupdesc->tdtypmod;
     313              : 
     314        18406 :     return desc;
     315              : }
     316              : 
     317              : /*
     318              :  * CreateTupleDescCopyConstr
     319              :  *      This function creates a new TupleDesc by copying from an existing
     320              :  *      TupleDesc (including its constraints and defaults).
     321              :  */
     322              : TupleDesc
     323       470550 : CreateTupleDescCopyConstr(TupleDesc tupdesc)
     324              : {
     325              :     TupleDesc   desc;
     326       470550 :     TupleConstr *constr = tupdesc->constr;
     327              :     int         i;
     328              : 
     329       470550 :     desc = CreateTemplateTupleDesc(tupdesc->natts);
     330              : 
     331              :     /* Flat-copy the attribute array */
     332       470550 :     memcpy(TupleDescAttr(desc, 0),
     333       470550 :            TupleDescAttr(tupdesc, 0),
     334       470550 :            desc->natts * sizeof(FormData_pg_attribute));
     335              : 
     336      6906259 :     for (i = 0; i < desc->natts; i++)
     337              :     {
     338      6435709 :         populate_compact_attribute(desc, i);
     339              : 
     340      6435709 :         TupleDescCompactAttr(desc, i)->attnullability =
     341     12871418 :             TupleDescCompactAttr(tupdesc, i)->attnullability;
     342              :     }
     343              : 
     344              :     /* Copy the TupleConstr data structure, if any */
     345       470550 :     if (constr)
     346              :     {
     347       431311 :         TupleConstr *cpy = palloc0_object(TupleConstr);
     348              : 
     349       431311 :         cpy->has_not_null = constr->has_not_null;
     350       431311 :         cpy->has_generated_stored = constr->has_generated_stored;
     351       431311 :         cpy->has_generated_virtual = constr->has_generated_virtual;
     352              : 
     353       431311 :         if ((cpy->num_defval = constr->num_defval) > 0)
     354              :         {
     355         2118 :             cpy->defval = (AttrDefault *) palloc(cpy->num_defval * sizeof(AttrDefault));
     356         2118 :             memcpy(cpy->defval, constr->defval, cpy->num_defval * sizeof(AttrDefault));
     357         5160 :             for (i = cpy->num_defval - 1; i >= 0; i--)
     358         3042 :                 cpy->defval[i].adbin = pstrdup(constr->defval[i].adbin);
     359              :         }
     360              : 
     361       431311 :         if (constr->missing)
     362              :         {
     363          322 :             cpy->missing = (AttrMissing *) palloc(tupdesc->natts * sizeof(AttrMissing));
     364          322 :             memcpy(cpy->missing, constr->missing, tupdesc->natts * sizeof(AttrMissing));
     365         2474 :             for (i = tupdesc->natts - 1; i >= 0; i--)
     366              :             {
     367         2152 :                 if (constr->missing[i].am_present)
     368              :                 {
     369          574 :                     CompactAttribute *attr = TupleDescCompactAttr(tupdesc, i);
     370              : 
     371          574 :                     cpy->missing[i].am_value = datumCopy(constr->missing[i].am_value,
     372          574 :                                                          attr->attbyval,
     373          574 :                                                          attr->attlen);
     374              :                 }
     375              :             }
     376              :         }
     377              : 
     378       431311 :         if ((cpy->num_check = constr->num_check) > 0)
     379              :         {
     380         1184 :             cpy->check = (ConstrCheck *) palloc(cpy->num_check * sizeof(ConstrCheck));
     381         1184 :             memcpy(cpy->check, constr->check, cpy->num_check * sizeof(ConstrCheck));
     382         3225 :             for (i = cpy->num_check - 1; i >= 0; i--)
     383              :             {
     384         2041 :                 cpy->check[i].ccname = pstrdup(constr->check[i].ccname);
     385         2041 :                 cpy->check[i].ccbin = pstrdup(constr->check[i].ccbin);
     386         2041 :                 cpy->check[i].ccenforced = constr->check[i].ccenforced;
     387         2041 :                 cpy->check[i].ccvalid = constr->check[i].ccvalid;
     388         2041 :                 cpy->check[i].ccnoinherit = constr->check[i].ccnoinherit;
     389              :             }
     390              :         }
     391              : 
     392       431311 :         desc->constr = cpy;
     393              :     }
     394              : 
     395              :     /* We can copy the tuple type identification, too */
     396       470550 :     desc->tdtypeid = tupdesc->tdtypeid;
     397       470550 :     desc->tdtypmod = tupdesc->tdtypmod;
     398              : 
     399       470550 :     return desc;
     400              : }
     401              : 
     402              : /*
     403              :  * TupleDescCopy
     404              :  *      Copy a tuple descriptor into caller-supplied memory.
     405              :  *      The memory may be shared memory mapped at any address, and must
     406              :  *      be sufficient to hold TupleDescSize(src) bytes.
     407              :  *
     408              :  * !!! Constraints and defaults are not copied !!!
     409              :  */
     410              : void
     411          166 : TupleDescCopy(TupleDesc dst, TupleDesc src)
     412              : {
     413              :     int         i;
     414              : 
     415              :     /* Flat-copy the header and attribute arrays */
     416          166 :     memcpy(dst, src, TupleDescSize(src));
     417              : 
     418              :     /*
     419              :      * Since we're not copying constraints and defaults, clear fields
     420              :      * associated with them.
     421              :      */
     422          628 :     for (i = 0; i < dst->natts; i++)
     423              :     {
     424          462 :         Form_pg_attribute att = TupleDescAttr(dst, i);
     425              : 
     426          462 :         att->attnotnull = false;
     427          462 :         att->atthasdef = false;
     428          462 :         att->atthasmissing = false;
     429          462 :         att->attidentity = '\0';
     430          462 :         att->attgenerated = '\0';
     431              : 
     432          462 :         populate_compact_attribute(dst, i);
     433              :     }
     434          166 :     dst->constr = NULL;
     435              : 
     436              :     /*
     437              :      * Also, assume the destination is not to be ref-counted.  (Copying the
     438              :      * source's refcount would be wrong in any case.)
     439              :      */
     440          166 :     dst->tdrefcount = -1;
     441          166 : }
     442              : 
     443              : /*
     444              :  * TupleDescCopyEntry
     445              :  *      This function copies a single attribute structure from one tuple
     446              :  *      descriptor to another.
     447              :  *
     448              :  * !!! Constraints and defaults are not copied !!!
     449              :  */
     450              : void
     451         2183 : TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
     452              :                    TupleDesc src, AttrNumber srcAttno)
     453              : {
     454         2183 :     Form_pg_attribute dstAtt = TupleDescAttr(dst, dstAttno - 1);
     455         2183 :     Form_pg_attribute srcAtt = TupleDescAttr(src, srcAttno - 1);
     456              : 
     457              :     /*
     458              :      * sanity checks
     459              :      */
     460              :     Assert(src);
     461              :     Assert(dst);
     462              :     Assert(srcAttno >= 1);
     463              :     Assert(srcAttno <= src->natts);
     464              :     Assert(dstAttno >= 1);
     465              :     Assert(dstAttno <= dst->natts);
     466              : 
     467         2183 :     memcpy(dstAtt, srcAtt, ATTRIBUTE_FIXED_PART_SIZE);
     468              : 
     469         2183 :     dstAtt->attnum = dstAttno;
     470              : 
     471              :     /* since we're not copying constraints or defaults, clear these */
     472         2183 :     dstAtt->attnotnull = false;
     473         2183 :     dstAtt->atthasdef = false;
     474         2183 :     dstAtt->atthasmissing = false;
     475         2183 :     dstAtt->attidentity = '\0';
     476         2183 :     dstAtt->attgenerated = '\0';
     477              : 
     478         2183 :     populate_compact_attribute(dst, dstAttno - 1);
     479         2183 : }
     480              : 
     481              : /*
     482              :  * Free a TupleDesc including all substructure
     483              :  */
     484              : void
     485       755064 : FreeTupleDesc(TupleDesc tupdesc)
     486              : {
     487              :     int         i;
     488              : 
     489              :     /*
     490              :      * Possibly this should assert tdrefcount == 0, to disallow explicit
     491              :      * freeing of un-refcounted tupdescs?
     492              :      */
     493              :     Assert(tupdesc->tdrefcount <= 0);
     494              : 
     495       755064 :     if (tupdesc->constr)
     496              :     {
     497       230586 :         if (tupdesc->constr->num_defval > 0)
     498              :         {
     499        17414 :             AttrDefault *attrdef = tupdesc->constr->defval;
     500              : 
     501        42321 :             for (i = tupdesc->constr->num_defval - 1; i >= 0; i--)
     502        24907 :                 pfree(attrdef[i].adbin);
     503        17414 :             pfree(attrdef);
     504              :         }
     505       230586 :         if (tupdesc->constr->missing)
     506              :         {
     507         1896 :             AttrMissing *attrmiss = tupdesc->constr->missing;
     508              : 
     509        13537 :             for (i = tupdesc->natts - 1; i >= 0; i--)
     510              :             {
     511        11641 :                 if (attrmiss[i].am_present
     512         3966 :                     && !TupleDescAttr(tupdesc, i)->attbyval)
     513         1463 :                     pfree(DatumGetPointer(attrmiss[i].am_value));
     514              :             }
     515         1896 :             pfree(attrmiss);
     516              :         }
     517       230586 :         if (tupdesc->constr->num_check > 0)
     518              :         {
     519         5935 :             ConstrCheck *check = tupdesc->constr->check;
     520              : 
     521        15892 :             for (i = tupdesc->constr->num_check - 1; i >= 0; i--)
     522              :             {
     523         9957 :                 pfree(check[i].ccname);
     524         9957 :                 pfree(check[i].ccbin);
     525              :             }
     526         5935 :             pfree(check);
     527              :         }
     528       230586 :         pfree(tupdesc->constr);
     529              :     }
     530              : 
     531       755064 :     pfree(tupdesc);
     532       755064 : }
     533              : 
     534              : /*
     535              :  * Increment the reference count of a tupdesc, and log the reference in
     536              :  * CurrentResourceOwner.
     537              :  *
     538              :  * Do not apply this to tupdescs that are not being refcounted.  (Use the
     539              :  * macro PinTupleDesc for tupdescs of uncertain status.)
     540              :  */
     541              : void
     542     16927577 : IncrTupleDescRefCount(TupleDesc tupdesc)
     543              : {
     544              :     Assert(tupdesc->tdrefcount >= 0);
     545              : 
     546     16927577 :     ResourceOwnerEnlarge(CurrentResourceOwner);
     547     16927577 :     tupdesc->tdrefcount++;
     548     16927577 :     ResourceOwnerRememberTupleDesc(CurrentResourceOwner, tupdesc);
     549     16927577 : }
     550              : 
     551              : /*
     552              :  * Decrement the reference count of a tupdesc, remove the corresponding
     553              :  * reference from CurrentResourceOwner, and free the tupdesc if no more
     554              :  * references remain.
     555              :  *
     556              :  * Do not apply this to tupdescs that are not being refcounted.  (Use the
     557              :  * macro ReleaseTupleDesc for tupdescs of uncertain status.)
     558              :  */
     559              : void
     560     16919550 : DecrTupleDescRefCount(TupleDesc tupdesc)
     561              : {
     562              :     Assert(tupdesc->tdrefcount > 0);
     563              : 
     564     16919550 :     ResourceOwnerForgetTupleDesc(CurrentResourceOwner, tupdesc);
     565     16919550 :     if (--tupdesc->tdrefcount == 0)
     566            0 :         FreeTupleDesc(tupdesc);
     567     16919550 : }
     568              : 
     569              : /*
     570              :  * Compare two TupleDesc structures for logical equality
     571              :  */
     572              : bool
     573       223573 : equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
     574              : {
     575              :     int         i,
     576              :                 n;
     577              : 
     578       223573 :     if (tupdesc1->natts != tupdesc2->natts)
     579         1366 :         return false;
     580       222207 :     if (tupdesc1->tdtypeid != tupdesc2->tdtypeid)
     581            0 :         return false;
     582              : 
     583              :     /* tdtypmod and tdrefcount are not checked */
     584              : 
     585      1066827 :     for (i = 0; i < tupdesc1->natts; i++)
     586              :     {
     587       852376 :         Form_pg_attribute attr1 = TupleDescAttr(tupdesc1, i);
     588       852376 :         Form_pg_attribute attr2 = TupleDescAttr(tupdesc2, i);
     589              : 
     590              :         /*
     591              :          * We do not need to check every single field here: we can disregard
     592              :          * attrelid and attnum (which were used to place the row in the attrs
     593              :          * array in the first place).  It might look like we could dispense
     594              :          * with checking attlen/attbyval/attalign, since these are derived
     595              :          * from atttypid; but in the case of dropped columns we must check
     596              :          * them (since atttypid will be zero for all dropped columns) and in
     597              :          * general it seems safer to check them always.
     598              :          *
     599              :          * We intentionally ignore atthasmissing, since that's not very
     600              :          * relevant in tupdescs, which lack the attmissingval field.
     601              :          */
     602       852376 :         if (strcmp(NameStr(attr1->attname), NameStr(attr2->attname)) != 0)
     603          667 :             return false;
     604       851709 :         if (attr1->atttypid != attr2->atttypid)
     605          591 :             return false;
     606       851118 :         if (attr1->attlen != attr2->attlen)
     607            5 :             return false;
     608       851113 :         if (attr1->attndims != attr2->attndims)
     609            0 :             return false;
     610       851113 :         if (attr1->atttypmod != attr2->atttypmod)
     611           21 :             return false;
     612       851092 :         if (attr1->attbyval != attr2->attbyval)
     613           34 :             return false;
     614       851058 :         if (attr1->attalign != attr2->attalign)
     615            0 :             return false;
     616       851058 :         if (attr1->attstorage != attr2->attstorage)
     617          129 :             return false;
     618       850929 :         if (attr1->attcompression != attr2->attcompression)
     619           36 :             return false;
     620       850893 :         if (attr1->attnotnull != attr2->attnotnull)
     621          887 :             return false;
     622              : 
     623              :         /*
     624              :          * When the column has a not-null constraint, we also need to consider
     625              :          * its validity aspect, which only manifests in CompactAttribute->
     626              :          * attnullability, so verify that.
     627              :          */
     628       850006 :         if (attr1->attnotnull)
     629              :         {
     630       290849 :             CompactAttribute *cattr1 = TupleDescCompactAttr(tupdesc1, i);
     631       290849 :             CompactAttribute *cattr2 = TupleDescCompactAttr(tupdesc2, i);
     632              : 
     633              :             Assert(cattr1->attnullability != ATTNULLABLE_UNKNOWN);
     634              :             Assert((cattr1->attnullability == ATTNULLABLE_UNKNOWN) ==
     635              :                    (cattr2->attnullability == ATTNULLABLE_UNKNOWN));
     636              : 
     637       290849 :             if (cattr1->attnullability != cattr2->attnullability)
     638           53 :                 return false;
     639              :         }
     640       849953 :         if (attr1->atthasdef != attr2->atthasdef)
     641         2745 :             return false;
     642       847208 :         if (attr1->attidentity != attr2->attidentity)
     643           95 :             return false;
     644       847113 :         if (attr1->attgenerated != attr2->attgenerated)
     645           10 :             return false;
     646       847103 :         if (attr1->attisdropped != attr2->attisdropped)
     647            0 :             return false;
     648       847103 :         if (attr1->attislocal != attr2->attislocal)
     649         2058 :             return false;
     650       845045 :         if (attr1->attinhcount != attr2->attinhcount)
     651          425 :             return false;
     652       844620 :         if (attr1->attcollation != attr2->attcollation)
     653            0 :             return false;
     654              :         /* variable-length fields are not even present... */
     655              :     }
     656              : 
     657       214451 :     if (tupdesc1->constr != NULL)
     658              :     {
     659        79711 :         TupleConstr *constr1 = tupdesc1->constr;
     660        79711 :         TupleConstr *constr2 = tupdesc2->constr;
     661              : 
     662        79711 :         if (constr2 == NULL)
     663          115 :             return false;
     664        79596 :         if (constr1->has_not_null != constr2->has_not_null)
     665            0 :             return false;
     666        79596 :         if (constr1->has_generated_stored != constr2->has_generated_stored)
     667          316 :             return false;
     668        79280 :         if (constr1->has_generated_virtual != constr2->has_generated_virtual)
     669          212 :             return false;
     670        79068 :         n = constr1->num_defval;
     671        79068 :         if (n != (int) constr2->num_defval)
     672            0 :             return false;
     673              :         /* We assume here that both AttrDefault arrays are in adnum order */
     674        89140 :         for (i = 0; i < n; i++)
     675              :         {
     676        10072 :             AttrDefault *defval1 = constr1->defval + i;
     677        10072 :             AttrDefault *defval2 = constr2->defval + i;
     678              : 
     679        10072 :             if (defval1->adnum != defval2->adnum)
     680            0 :                 return false;
     681        10072 :             if (strcmp(defval1->adbin, defval2->adbin) != 0)
     682            0 :                 return false;
     683              :         }
     684        79068 :         if (constr1->missing)
     685              :         {
     686          368 :             if (!constr2->missing)
     687           42 :                 return false;
     688         1952 :             for (i = 0; i < tupdesc1->natts; i++)
     689              :             {
     690         1699 :                 AttrMissing *missval1 = constr1->missing + i;
     691         1699 :                 AttrMissing *missval2 = constr2->missing + i;
     692              : 
     693         1699 :                 if (missval1->am_present != missval2->am_present)
     694           73 :                     return false;
     695         1626 :                 if (missval1->am_present)
     696              :                 {
     697          541 :                     CompactAttribute *missatt1 = TupleDescCompactAttr(tupdesc1, i);
     698              : 
     699          541 :                     if (!datumIsEqual(missval1->am_value, missval2->am_value,
     700          541 :                                       missatt1->attbyval, missatt1->attlen))
     701            0 :                         return false;
     702              :                 }
     703              :             }
     704              :         }
     705        78700 :         else if (constr2->missing)
     706          191 :             return false;
     707        78762 :         n = constr1->num_check;
     708        78762 :         if (n != (int) constr2->num_check)
     709          881 :             return false;
     710              : 
     711              :         /*
     712              :          * Similarly, we rely here on the ConstrCheck entries being sorted by
     713              :          * name.  If there are duplicate names, the outcome of the comparison
     714              :          * is uncertain, but that should not happen.
     715              :          */
     716        79952 :         for (i = 0; i < n; i++)
     717              :         {
     718         2122 :             ConstrCheck *check1 = constr1->check + i;
     719         2122 :             ConstrCheck *check2 = constr2->check + i;
     720              : 
     721         2122 :             if (!(strcmp(check1->ccname, check2->ccname) == 0 &&
     722         2122 :                   strcmp(check1->ccbin, check2->ccbin) == 0 &&
     723         2122 :                   check1->ccenforced == check2->ccenforced &&
     724         2113 :                   check1->ccvalid == check2->ccvalid &&
     725         2071 :                   check1->ccnoinherit == check2->ccnoinherit))
     726           51 :                 return false;
     727              :         }
     728              :     }
     729       134740 :     else if (tupdesc2->constr != NULL)
     730          991 :         return false;
     731       211579 :     return true;
     732              : }
     733              : 
     734              : /*
     735              :  * equalRowTypes
     736              :  *
     737              :  * This determines whether two tuple descriptors have equal row types.  This
     738              :  * only checks those fields in pg_attribute that are applicable for row types,
     739              :  * while ignoring those fields that define the physical row storage or those
     740              :  * that define table column metadata.
     741              :  *
     742              :  * Specifically, this checks:
     743              :  *
     744              :  * - same number of attributes
     745              :  * - same composite type ID (but could both be zero)
     746              :  * - corresponding attributes (in order) have same the name, type, typmod,
     747              :  *   collation
     748              :  *
     749              :  * This is used to check whether two record types are compatible, whether
     750              :  * function return row types are the same, and other similar situations.
     751              :  *
     752              :  * (XXX There was some discussion whether attndims should be checked here, but
     753              :  * for now it has been decided not to.)
     754              :  *
     755              :  * Note: We deliberately do not check the tdtypmod field.  This allows
     756              :  * typcache.c to use this routine to see if a cached record type matches a
     757              :  * requested type.
     758              :  */
     759              : bool
     760       236203 : equalRowTypes(TupleDesc tupdesc1, TupleDesc tupdesc2)
     761              : {
     762       236203 :     if (tupdesc1->natts != tupdesc2->natts)
     763           84 :         return false;
     764       236119 :     if (tupdesc1->tdtypeid != tupdesc2->tdtypeid)
     765          949 :         return false;
     766              : 
     767      3885474 :     for (int i = 0; i < tupdesc1->natts; i++)
     768              :     {
     769      3654709 :         Form_pg_attribute attr1 = TupleDescAttr(tupdesc1, i);
     770      3654709 :         Form_pg_attribute attr2 = TupleDescAttr(tupdesc2, i);
     771              : 
     772      3654709 :         if (strcmp(NameStr(attr1->attname), NameStr(attr2->attname)) != 0)
     773         4392 :             return false;
     774      3650317 :         if (attr1->atttypid != attr2->atttypid)
     775           11 :             return false;
     776      3650306 :         if (attr1->atttypmod != attr2->atttypmod)
     777            2 :             return false;
     778      3650304 :         if (attr1->attcollation != attr2->attcollation)
     779            0 :             return false;
     780              : 
     781              :         /* Record types derived from tables could have dropped fields. */
     782      3650304 :         if (attr1->attisdropped != attr2->attisdropped)
     783            0 :             return false;
     784              :     }
     785              : 
     786       230765 :     return true;
     787              : }
     788              : 
     789              : /*
     790              :  * hashRowType
     791              :  *
     792              :  * If two tuple descriptors would be considered equal by equalRowTypes()
     793              :  * then their hash value will be equal according to this function.
     794              :  */
     795              : uint32
     796       246212 : hashRowType(TupleDesc desc)
     797              : {
     798              :     uint32      s;
     799              :     int         i;
     800              : 
     801       246212 :     s = hash_combine(0, hash_bytes_uint32(desc->natts));
     802       246212 :     s = hash_combine(s, hash_bytes_uint32(desc->tdtypeid));
     803      4074765 :     for (i = 0; i < desc->natts; ++i)
     804      3828553 :         s = hash_combine(s, hash_bytes_uint32(TupleDescAttr(desc, i)->atttypid));
     805              : 
     806       246212 :     return s;
     807              : }
     808              : 
     809              : /*
     810              :  * TupleDescInitEntry
     811              :  *      This function initializes a single attribute structure in
     812              :  *      a previously allocated tuple descriptor.
     813              :  *
     814              :  * If attributeName is NULL, the attname field is set to an empty string
     815              :  * (this is for cases where we don't know or need a name for the field).
     816              :  * Also, some callers use this function to change the datatype-related fields
     817              :  * in an existing tupdesc; they pass attributeName = NameStr(att->attname)
     818              :  * to indicate that the attname field shouldn't be modified.
     819              :  *
     820              :  * Note that attcollation is set to the default for the specified datatype.
     821              :  * If a nondefault collation is needed, insert it afterwards using
     822              :  * TupleDescInitEntryCollation.
     823              :  */
     824              : void
     825      7326362 : TupleDescInitEntry(TupleDesc desc,
     826              :                    AttrNumber attributeNumber,
     827              :                    const char *attributeName,
     828              :                    Oid oidtypeid,
     829              :                    int32 typmod,
     830              :                    int attdim)
     831              : {
     832              :     HeapTuple   tuple;
     833              :     Form_pg_type typeForm;
     834              :     Form_pg_attribute att;
     835              : 
     836              :     /*
     837              :      * sanity checks
     838              :      */
     839              :     Assert(desc);
     840              :     Assert(attributeNumber >= 1);
     841              :     Assert(attributeNumber <= desc->natts);
     842              :     Assert(attdim >= 0);
     843              :     Assert(attdim <= PG_INT16_MAX);
     844              : 
     845              :     /*
     846              :      * initialize the attribute fields
     847              :      */
     848      7326362 :     att = TupleDescAttr(desc, attributeNumber - 1);
     849              : 
     850      7326362 :     att->attrelid = 0;           /* dummy value */
     851              : 
     852              :     /*
     853              :      * Note: attributeName can be NULL, because the planner doesn't always
     854              :      * fill in valid resname values in targetlists, particularly for resjunk
     855              :      * attributes. Also, do nothing if caller wants to re-use the old attname.
     856              :      */
     857      7326362 :     if (attributeName == NULL)
     858     10325779 :         MemSet(NameStr(att->attname), 0, NAMEDATALEN);
     859      5129607 :     else if (attributeName != NameStr(att->attname))
     860      5127861 :         namestrcpy(&(att->attname), attributeName);
     861              : 
     862      7326362 :     att->atttypmod = typmod;
     863              : 
     864      7326362 :     att->attnum = attributeNumber;
     865      7326362 :     att->attndims = attdim;
     866              : 
     867      7326362 :     att->attnotnull = false;
     868      7326362 :     att->atthasdef = false;
     869      7326362 :     att->atthasmissing = false;
     870      7326362 :     att->attidentity = '\0';
     871      7326362 :     att->attgenerated = '\0';
     872      7326362 :     att->attisdropped = false;
     873      7326362 :     att->attislocal = true;
     874      7326362 :     att->attinhcount = 0;
     875              :     /* variable-length fields are not present in tupledescs */
     876              : 
     877      7326362 :     tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(oidtypeid));
     878      7326362 :     if (!HeapTupleIsValid(tuple))
     879            0 :         elog(ERROR, "cache lookup failed for type %u", oidtypeid);
     880      7326362 :     typeForm = (Form_pg_type) GETSTRUCT(tuple);
     881              : 
     882      7326362 :     att->atttypid = oidtypeid;
     883      7326362 :     att->attlen = typeForm->typlen;
     884      7326362 :     att->attbyval = typeForm->typbyval;
     885      7326362 :     att->attalign = typeForm->typalign;
     886      7326362 :     att->attstorage = typeForm->typstorage;
     887      7326362 :     att->attcompression = InvalidCompressionMethod;
     888      7326362 :     att->attcollation = typeForm->typcollation;
     889              : 
     890      7326362 :     populate_compact_attribute(desc, attributeNumber - 1);
     891              : 
     892      7326362 :     ReleaseSysCache(tuple);
     893      7326362 : }
     894              : 
     895              : /*
     896              :  * TupleDescInitBuiltinEntry
     897              :  *      Initialize a tuple descriptor without catalog access.  Only
     898              :  *      a limited range of builtin types are supported.
     899              :  */
     900              : void
     901         7168 : TupleDescInitBuiltinEntry(TupleDesc desc,
     902              :                           AttrNumber attributeNumber,
     903              :                           const char *attributeName,
     904              :                           Oid oidtypeid,
     905              :                           int32 typmod,
     906              :                           int attdim)
     907              : {
     908              :     Form_pg_attribute att;
     909              : 
     910              :     /* sanity checks */
     911              :     Assert(desc);
     912              :     Assert(attributeNumber >= 1);
     913              :     Assert(attributeNumber <= desc->natts);
     914              :     Assert(attdim >= 0);
     915              :     Assert(attdim <= PG_INT16_MAX);
     916              : 
     917              :     /* initialize the attribute fields */
     918         7168 :     att = TupleDescAttr(desc, attributeNumber - 1);
     919         7168 :     att->attrelid = 0;           /* dummy value */
     920              : 
     921              :     /* unlike TupleDescInitEntry, we require an attribute name */
     922              :     Assert(attributeName != NULL);
     923         7168 :     namestrcpy(&(att->attname), attributeName);
     924              : 
     925         7168 :     att->atttypmod = typmod;
     926              : 
     927         7168 :     att->attnum = attributeNumber;
     928         7168 :     att->attndims = attdim;
     929              : 
     930         7168 :     att->attnotnull = false;
     931         7168 :     att->atthasdef = false;
     932         7168 :     att->atthasmissing = false;
     933         7168 :     att->attidentity = '\0';
     934         7168 :     att->attgenerated = '\0';
     935         7168 :     att->attisdropped = false;
     936         7168 :     att->attislocal = true;
     937         7168 :     att->attinhcount = 0;
     938              :     /* variable-length fields are not present in tupledescs */
     939              : 
     940         7168 :     att->atttypid = oidtypeid;
     941              : 
     942              :     /*
     943              :      * Our goal here is to support just enough types to let basic builtin
     944              :      * commands work without catalog access - e.g. so that we can do certain
     945              :      * things even in processes that are not connected to a database.
     946              :      */
     947         7168 :     switch (oidtypeid)
     948              :     {
     949         5742 :         case TEXTOID:
     950              :         case TEXTARRAYOID:
     951         5742 :             att->attlen = -1;
     952         5742 :             att->attbyval = false;
     953         5742 :             att->attalign = TYPALIGN_INT;
     954         5742 :             att->attstorage = TYPSTORAGE_EXTENDED;
     955         5742 :             att->attcompression = InvalidCompressionMethod;
     956         5742 :             att->attcollation = DEFAULT_COLLATION_OID;
     957         5742 :             break;
     958              : 
     959            0 :         case BOOLOID:
     960            0 :             att->attlen = 1;
     961            0 :             att->attbyval = true;
     962            0 :             att->attalign = TYPALIGN_CHAR;
     963            0 :             att->attstorage = TYPSTORAGE_PLAIN;
     964            0 :             att->attcompression = InvalidCompressionMethod;
     965            0 :             att->attcollation = InvalidOid;
     966            0 :             break;
     967              : 
     968            0 :         case INT4OID:
     969            0 :             att->attlen = 4;
     970            0 :             att->attbyval = true;
     971            0 :             att->attalign = TYPALIGN_INT;
     972            0 :             att->attstorage = TYPSTORAGE_PLAIN;
     973            0 :             att->attcompression = InvalidCompressionMethod;
     974            0 :             att->attcollation = InvalidOid;
     975            0 :             break;
     976              : 
     977         1258 :         case INT8OID:
     978         1258 :             att->attlen = 8;
     979         1258 :             att->attbyval = true;
     980         1258 :             att->attalign = TYPALIGN_DOUBLE;
     981         1258 :             att->attstorage = TYPSTORAGE_PLAIN;
     982         1258 :             att->attcompression = InvalidCompressionMethod;
     983         1258 :             att->attcollation = InvalidOid;
     984         1258 :             break;
     985              : 
     986          168 :         case OIDOID:
     987          168 :             att->attlen = 4;
     988          168 :             att->attbyval = true;
     989          168 :             att->attalign = TYPALIGN_INT;
     990          168 :             att->attstorage = TYPSTORAGE_PLAIN;
     991          168 :             att->attcompression = InvalidCompressionMethod;
     992          168 :             att->attcollation = InvalidOid;
     993          168 :             break;
     994              : 
     995            0 :         default:
     996            0 :             elog(ERROR, "unsupported type %u", oidtypeid);
     997              :     }
     998              : 
     999         7168 :     populate_compact_attribute(desc, attributeNumber - 1);
    1000         7168 : }
    1001              : 
    1002              : /*
    1003              :  * TupleDescInitEntryCollation
    1004              :  *
    1005              :  * Assign a nondefault collation to a previously initialized tuple descriptor
    1006              :  * entry.
    1007              :  */
    1008              : void
    1009      3444617 : TupleDescInitEntryCollation(TupleDesc desc,
    1010              :                             AttrNumber attributeNumber,
    1011              :                             Oid collationid)
    1012              : {
    1013              :     /*
    1014              :      * sanity checks
    1015              :      */
    1016              :     Assert(desc);
    1017              :     Assert(attributeNumber >= 1);
    1018              :     Assert(attributeNumber <= desc->natts);
    1019              : 
    1020      3444617 :     TupleDescAttr(desc, attributeNumber - 1)->attcollation = collationid;
    1021      3444617 : }
    1022              : 
    1023              : /*
    1024              :  * BuildDescFromLists
    1025              :  *
    1026              :  * Build a TupleDesc given lists of column names (as String nodes),
    1027              :  * column type OIDs, typmods, and collation OIDs.
    1028              :  *
    1029              :  * No constraints are generated.
    1030              :  *
    1031              :  * This is for use with functions returning RECORD.
    1032              :  */
    1033              : TupleDesc
    1034          708 : BuildDescFromLists(const List *names, const List *types, const List *typmods, const List *collations)
    1035              : {
    1036              :     int         natts;
    1037              :     AttrNumber  attnum;
    1038              :     ListCell   *l1;
    1039              :     ListCell   *l2;
    1040              :     ListCell   *l3;
    1041              :     ListCell   *l4;
    1042              :     TupleDesc   desc;
    1043              : 
    1044          708 :     natts = list_length(names);
    1045              :     Assert(natts == list_length(types));
    1046              :     Assert(natts == list_length(typmods));
    1047              :     Assert(natts == list_length(collations));
    1048              : 
    1049              :     /*
    1050              :      * allocate a new tuple descriptor
    1051              :      */
    1052          708 :     desc = CreateTemplateTupleDesc(natts);
    1053              : 
    1054          708 :     attnum = 0;
    1055         2496 :     forfour(l1, names, l2, types, l3, typmods, l4, collations)
    1056              :     {
    1057         1788 :         char       *attname = strVal(lfirst(l1));
    1058         1788 :         Oid         atttypid = lfirst_oid(l2);
    1059         1788 :         int32       atttypmod = lfirst_int(l3);
    1060         1788 :         Oid         attcollation = lfirst_oid(l4);
    1061              : 
    1062         1788 :         attnum++;
    1063              : 
    1064         1788 :         TupleDescInitEntry(desc, attnum, attname, atttypid, atttypmod, 0);
    1065         1788 :         TupleDescInitEntryCollation(desc, attnum, attcollation);
    1066              :     }
    1067              : 
    1068          708 :     return desc;
    1069              : }
    1070              : 
    1071              : /*
    1072              :  * Get default expression (or NULL if none) for the given attribute number.
    1073              :  */
    1074              : Node *
    1075        76197 : TupleDescGetDefault(TupleDesc tupdesc, AttrNumber attnum)
    1076              : {
    1077        76197 :     Node       *result = NULL;
    1078              : 
    1079        76197 :     if (tupdesc->constr)
    1080              :     {
    1081        76197 :         AttrDefault *attrdef = tupdesc->constr->defval;
    1082              : 
    1083       114856 :         for (int i = 0; i < tupdesc->constr->num_defval; i++)
    1084              :         {
    1085       114856 :             if (attrdef[i].adnum == attnum)
    1086              :             {
    1087        76197 :                 result = stringToNode(attrdef[i].adbin);
    1088        76197 :                 break;
    1089              :             }
    1090              :         }
    1091              :     }
    1092              : 
    1093        76197 :     return result;
    1094              : }
    1095              : 
    1096              : /* ResourceOwner callbacks */
    1097              : 
    1098              : static void
    1099         8027 : ResOwnerReleaseTupleDesc(Datum res)
    1100              : {
    1101         8027 :     TupleDesc   tupdesc = (TupleDesc) DatumGetPointer(res);
    1102              : 
    1103              :     /* Like DecrTupleDescRefCount, but don't call ResourceOwnerForget() */
    1104              :     Assert(tupdesc->tdrefcount > 0);
    1105         8027 :     if (--tupdesc->tdrefcount == 0)
    1106          258 :         FreeTupleDesc(tupdesc);
    1107         8027 : }
    1108              : 
    1109              : static char *
    1110            0 : ResOwnerPrintTupleDesc(Datum res)
    1111              : {
    1112            0 :     TupleDesc   tupdesc = (TupleDesc) DatumGetPointer(res);
    1113              : 
    1114            0 :     return psprintf("TupleDesc %p (%u,%d)",
    1115              :                     tupdesc, tupdesc->tdtypeid, tupdesc->tdtypmod);
    1116              : }
        

Generated by: LCOV version 2.0-1