LCOV - code coverage report
Current view: top level - src/backend/access/common - attmap.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 96.8 % 94 91
Test Date: 2026-03-15 21:15:10 Functions: 100.0 % 6 6
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * attmap.c
       4              :  *    Attribute mapping support.
       5              :  *
       6              :  * This file provides utility routines to build and manage attribute
       7              :  * mappings by comparing input and output TupleDescs.  Such mappings
       8              :  * are typically used by DDL operating on inheritance and partition trees
       9              :  * to do a conversion between rowtypes logically equivalent but with
      10              :  * columns in a different order, taking into account dropped columns.
      11              :  * They are also used by the tuple conversion routines in tupconvert.c.
      12              :  *
      13              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      14              :  * Portions Copyright (c) 1994, Regents of the University of California
      15              :  *
      16              :  *
      17              :  * IDENTIFICATION
      18              :  *    src/backend/access/common/attmap.c
      19              :  *
      20              :  *-------------------------------------------------------------------------
      21              :  */
      22              : 
      23              : #include "postgres.h"
      24              : 
      25              : #include "access/attmap.h"
      26              : #include "utils/builtins.h"
      27              : 
      28              : 
      29              : static bool check_attrmap_match(TupleDesc indesc,
      30              :                                 TupleDesc outdesc,
      31              :                                 AttrMap *attrMap);
      32              : 
      33              : /*
      34              :  * make_attrmap
      35              :  *
      36              :  * Utility routine to allocate an attribute map in the current memory
      37              :  * context.
      38              :  */
      39              : AttrMap *
      40        33866 : make_attrmap(int maplen)
      41              : {
      42              :     AttrMap    *res;
      43              : 
      44        33866 :     res = palloc0_object(AttrMap);
      45        33866 :     res->maplen = maplen;
      46        33866 :     res->attnums = palloc0_array(AttrNumber, maplen);
      47        33866 :     return res;
      48              : }
      49              : 
      50              : /*
      51              :  * free_attrmap
      52              :  *
      53              :  * Utility routine to release an attribute map.
      54              :  */
      55              : void
      56        17749 : free_attrmap(AttrMap *map)
      57              : {
      58        17749 :     pfree(map->attnums);
      59        17749 :     pfree(map);
      60        17749 : }
      61              : 
      62              : /*
      63              :  * build_attrmap_by_position
      64              :  *
      65              :  * Return a palloc'd bare attribute map for tuple conversion, matching input
      66              :  * and output columns by position.  Dropped columns are ignored in both input
      67              :  * and output, marked as 0.  This is normally a subroutine for
      68              :  * convert_tuples_by_position in tupconvert.c, but it can be used standalone.
      69              :  *
      70              :  * Note: the errdetail messages speak of indesc as the "returned" rowtype,
      71              :  * outdesc as the "expected" rowtype.  This is okay for current uses but
      72              :  * might need generalization in future.
      73              :  */
      74              : AttrMap *
      75         4812 : build_attrmap_by_position(TupleDesc indesc,
      76              :                           TupleDesc outdesc,
      77              :                           const char *msg)
      78              : {
      79              :     AttrMap    *attrMap;
      80              :     int         nincols;
      81              :     int         noutcols;
      82              :     int         n;
      83              :     int         i;
      84              :     int         j;
      85              :     bool        same;
      86              : 
      87              :     /*
      88              :      * The length is computed as the number of attributes of the expected
      89              :      * rowtype as it includes dropped attributes in its count.
      90              :      */
      91         4812 :     n = outdesc->natts;
      92         4812 :     attrMap = make_attrmap(n);
      93              : 
      94         4812 :     j = 0;                      /* j is next physical input attribute */
      95         4812 :     nincols = noutcols = 0;     /* these count non-dropped attributes */
      96         4812 :     same = true;
      97        17441 :     for (i = 0; i < n; i++)
      98              :     {
      99        12639 :         Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
     100              : 
     101        12639 :         if (outatt->attisdropped)
     102           76 :             continue;           /* attrMap->attnums[i] is already 0 */
     103        12563 :         noutcols++;
     104        12572 :         for (; j < indesc->natts; j++)
     105              :         {
     106        12568 :             Form_pg_attribute inatt = TupleDescAttr(indesc, j);
     107              : 
     108        12568 :             if (inatt->attisdropped)
     109            9 :                 continue;
     110        12559 :             nincols++;
     111              : 
     112              :             /* Found matching column, now check type */
     113        12559 :             if (outatt->atttypid != inatt->atttypid ||
     114        12549 :                 (outatt->atttypmod != inatt->atttypmod && outatt->atttypmod >= 0))
     115           10 :                 ereport(ERROR,
     116              :                         (errcode(ERRCODE_DATATYPE_MISMATCH),
     117              :                          errmsg_internal("%s", _(msg)),
     118              :                          errdetail("Returned type %s does not match expected type %s in column \"%s\" (position %d).",
     119              :                                    format_type_with_typemod(inatt->atttypid,
     120              :                                                             inatt->atttypmod),
     121              :                                    format_type_with_typemod(outatt->atttypid,
     122              :                                                             outatt->atttypmod),
     123              :                                    NameStr(outatt->attname),
     124              :                                    noutcols)));
     125        12549 :             attrMap->attnums[i] = (AttrNumber) (j + 1);
     126        12549 :             j++;
     127        12549 :             break;
     128              :         }
     129        12553 :         if (attrMap->attnums[i] == 0)
     130            4 :             same = false;       /* we'll complain below */
     131              :     }
     132              : 
     133              :     /* Check for unused input columns */
     134         4805 :     for (; j < indesc->natts; j++)
     135              :     {
     136            3 :         if (TupleDescCompactAttr(indesc, j)->attisdropped)
     137            0 :             continue;
     138            3 :         nincols++;
     139            3 :         same = false;           /* we'll complain below */
     140              :     }
     141              : 
     142              :     /* Report column count mismatch using the non-dropped-column counts */
     143         4802 :     if (!same)
     144            7 :         ereport(ERROR,
     145              :                 (errcode(ERRCODE_DATATYPE_MISMATCH),
     146              :                  errmsg_internal("%s", _(msg)),
     147              :                  errdetail("Number of returned columns (%d) does not match "
     148              :                            "expected column count (%d).",
     149              :                            nincols, noutcols)));
     150              : 
     151              :     /* Check if the map has a one-to-one match */
     152         4795 :     if (check_attrmap_match(indesc, outdesc, attrMap))
     153              :     {
     154              :         /* Runtime conversion is not needed */
     155         4747 :         free_attrmap(attrMap);
     156         4747 :         return NULL;
     157              :     }
     158              : 
     159           48 :     return attrMap;
     160              : }
     161              : 
     162              : /*
     163              :  * build_attrmap_by_name
     164              :  *
     165              :  * Return a palloc'd bare attribute map for tuple conversion, matching input
     166              :  * and output columns by name.  (Dropped columns are ignored in both input and
     167              :  * output.)  This is normally a subroutine for convert_tuples_by_name in
     168              :  * tupconvert.c, but can be used standalone.
     169              :  *
     170              :  * If 'missing_ok' is true, a column from 'outdesc' not being present in
     171              :  * 'indesc' is not flagged as an error; AttrMap.attnums[] entry for such an
     172              :  * outdesc column will be 0 in that case.
     173              :  */
     174              : AttrMap *
     175        22599 : build_attrmap_by_name(TupleDesc indesc,
     176              :                       TupleDesc outdesc,
     177              :                       bool missing_ok)
     178              : {
     179              :     AttrMap    *attrMap;
     180              :     int         outnatts;
     181              :     int         innatts;
     182              :     int         i;
     183        22599 :     int         nextindesc = -1;
     184              : 
     185        22599 :     outnatts = outdesc->natts;
     186        22599 :     innatts = indesc->natts;
     187              : 
     188        22599 :     attrMap = make_attrmap(outnatts);
     189        78040 :     for (i = 0; i < outnatts; i++)
     190              :     {
     191        55441 :         Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
     192              :         char       *attname;
     193              :         Oid         atttypid;
     194              :         int32       atttypmod;
     195              :         int         j;
     196              : 
     197        55441 :         if (outatt->attisdropped)
     198         2259 :             continue;           /* attrMap->attnums[i] is already 0 */
     199        53182 :         attname = NameStr(outatt->attname);
     200        53182 :         atttypid = outatt->atttypid;
     201        53182 :         atttypmod = outatt->atttypmod;
     202              : 
     203              :         /*
     204              :          * Now search for an attribute with the same name in the indesc. It
     205              :          * seems likely that a partitioned table will have the attributes in
     206              :          * the same order as the partition, so the search below is optimized
     207              :          * for that case.  It is possible that columns are dropped in one of
     208              :          * the relations, but not the other, so we use the 'nextindesc'
     209              :          * counter to track the starting point of the search.  If the inner
     210              :          * loop encounters dropped columns then it will have to skip over
     211              :          * them, but it should leave 'nextindesc' at the correct position for
     212              :          * the next outer loop.
     213              :          */
     214        63919 :         for (j = 0; j < innatts; j++)
     215              :         {
     216              :             Form_pg_attribute inatt;
     217              : 
     218        63804 :             nextindesc++;
     219        63804 :             if (nextindesc >= innatts)
     220         3601 :                 nextindesc = 0;
     221              : 
     222        63804 :             inatt = TupleDescAttr(indesc, nextindesc);
     223        63804 :             if (inatt->attisdropped)
     224         2012 :                 continue;
     225        61792 :             if (strcmp(attname, NameStr(inatt->attname)) == 0)
     226              :             {
     227              :                 /* Found it, check type */
     228        53067 :                 if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod)
     229            0 :                     ereport(ERROR,
     230              :                             (errcode(ERRCODE_DATATYPE_MISMATCH),
     231              :                              errmsg("could not convert row type"),
     232              :                              errdetail("Attribute \"%s\" of type %s does not match corresponding attribute of type %s.",
     233              :                                        attname,
     234              :                                        format_type_be(outdesc->tdtypeid),
     235              :                                        format_type_be(indesc->tdtypeid))));
     236        53067 :                 attrMap->attnums[i] = inatt->attnum;
     237        53067 :                 break;
     238              :             }
     239              :         }
     240        53182 :         if (attrMap->attnums[i] == 0 && !missing_ok)
     241            0 :             ereport(ERROR,
     242              :                     (errcode(ERRCODE_DATATYPE_MISMATCH),
     243              :                      errmsg("could not convert row type"),
     244              :                      errdetail("Attribute \"%s\" of type %s does not exist in type %s.",
     245              :                                attname,
     246              :                                format_type_be(outdesc->tdtypeid),
     247              :                                format_type_be(indesc->tdtypeid))));
     248              :     }
     249        22599 :     return attrMap;
     250              : }
     251              : 
     252              : /*
     253              :  * build_attrmap_by_name_if_req
     254              :  *
     255              :  * Returns mapping created by build_attrmap_by_name, or NULL if no
     256              :  * conversion is required.  This is a convenience routine for
     257              :  * convert_tuples_by_name() in tupconvert.c and other functions, but it
     258              :  * can be used standalone.
     259              :  */
     260              : AttrMap *
     261         7865 : build_attrmap_by_name_if_req(TupleDesc indesc,
     262              :                              TupleDesc outdesc,
     263              :                              bool missing_ok)
     264              : {
     265              :     AttrMap    *attrMap;
     266              : 
     267              :     /* Verify compatibility and prepare attribute-number map */
     268         7865 :     attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
     269              : 
     270              :     /* Check if the map has a one-to-one match */
     271         7865 :     if (check_attrmap_match(indesc, outdesc, attrMap))
     272              :     {
     273              :         /* Runtime conversion is not needed */
     274         6179 :         free_attrmap(attrMap);
     275         6179 :         return NULL;
     276              :     }
     277              : 
     278         1686 :     return attrMap;
     279              : }
     280              : 
     281              : /*
     282              :  * check_attrmap_match
     283              :  *
     284              :  * Check to see if the map is a one-to-one match, in which case we need
     285              :  * not to do a tuple conversion, and the attribute map is not necessary.
     286              :  */
     287              : static bool
     288        12660 : check_attrmap_match(TupleDesc indesc,
     289              :                     TupleDesc outdesc,
     290              :                     AttrMap *attrMap)
     291              : {
     292              :     int         i;
     293              : 
     294              :     /* no match if attribute numbers are not the same */
     295        12660 :     if (indesc->natts != outdesc->natts)
     296          776 :         return false;
     297              : 
     298        39473 :     for (i = 0; i < attrMap->maplen; i++)
     299              :     {
     300        28547 :         CompactAttribute *inatt = TupleDescCompactAttr(indesc, i);
     301              :         CompactAttribute *outatt;
     302              : 
     303              :         /*
     304              :          * If the input column has a missing attribute, we need a conversion.
     305              :          */
     306        28547 :         if (inatt->atthasmissing)
     307           25 :             return false;
     308              : 
     309        28522 :         if (attrMap->attnums[i] == (i + 1))
     310        27532 :             continue;
     311              : 
     312          990 :         outatt = TupleDescCompactAttr(outdesc, i);
     313              : 
     314              :         /*
     315              :          * If it's a dropped column and the corresponding input column is also
     316              :          * dropped, we don't need a conversion.  However, attlen and
     317              :          * attalignby must agree.
     318              :          */
     319          990 :         if (attrMap->attnums[i] == 0 &&
     320           79 :             inatt->attisdropped &&
     321           57 :             inatt->attlen == outatt->attlen &&
     322           57 :             inatt->attalignby == outatt->attalignby)
     323           57 :             continue;
     324              : 
     325          933 :         return false;
     326              :     }
     327              : 
     328        10926 :     return true;
     329              : }
        

Generated by: LCOV version 2.0-1