Coverage Report

Created: 2025-08-12 06:43

/src/postgres/src/backend/access/common/attmap.c
Line
Count
Source (jump to first uncovered line)
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-2025, 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
make_attrmap(int maplen)
41
0
{
42
0
  AttrMap    *res;
43
44
0
  res = (AttrMap *) palloc0(sizeof(AttrMap));
45
0
  res->maplen = maplen;
46
0
  res->attnums = (AttrNumber *) palloc0(sizeof(AttrNumber) * maplen);
47
0
  return res;
48
0
}
49
50
/*
51
 * free_attrmap
52
 *
53
 * Utility routine to release an attribute map.
54
 */
55
void
56
free_attrmap(AttrMap *map)
57
0
{
58
0
  pfree(map->attnums);
59
0
  pfree(map);
60
0
}
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
build_attrmap_by_position(TupleDesc indesc,
76
              TupleDesc outdesc,
77
              const char *msg)
78
0
{
79
0
  AttrMap    *attrMap;
80
0
  int     nincols;
81
0
  int     noutcols;
82
0
  int     n;
83
0
  int     i;
84
0
  int     j;
85
0
  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
0
  n = outdesc->natts;
92
0
  attrMap = make_attrmap(n);
93
94
0
  j = 0;            /* j is next physical input attribute */
95
0
  nincols = noutcols = 0;   /* these count non-dropped attributes */
96
0
  same = true;
97
0
  for (i = 0; i < n; i++)
98
0
  {
99
0
    Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
100
101
0
    if (outatt->attisdropped)
102
0
      continue;     /* attrMap->attnums[i] is already 0 */
103
0
    noutcols++;
104
0
    for (; j < indesc->natts; j++)
105
0
    {
106
0
      Form_pg_attribute inatt = TupleDescAttr(indesc, j);
107
108
0
      if (inatt->attisdropped)
109
0
        continue;
110
0
      nincols++;
111
112
      /* Found matching column, now check type */
113
0
      if (outatt->atttypid != inatt->atttypid ||
114
0
        (outatt->atttypmod != inatt->atttypmod && outatt->atttypmod >= 0))
115
0
        ereport(ERROR,
116
0
            (errcode(ERRCODE_DATATYPE_MISMATCH),
117
0
             errmsg_internal("%s", _(msg)),
118
0
             errdetail("Returned type %s does not match expected type %s in column \"%s\" (position %d).",
119
0
                   format_type_with_typemod(inatt->atttypid,
120
0
                              inatt->atttypmod),
121
0
                   format_type_with_typemod(outatt->atttypid,
122
0
                              outatt->atttypmod),
123
0
                   NameStr(outatt->attname),
124
0
                   noutcols)));
125
0
      attrMap->attnums[i] = (AttrNumber) (j + 1);
126
0
      j++;
127
0
      break;
128
0
    }
129
0
    if (attrMap->attnums[i] == 0)
130
0
      same = false;   /* we'll complain below */
131
0
  }
132
133
  /* Check for unused input columns */
134
0
  for (; j < indesc->natts; j++)
135
0
  {
136
0
    if (TupleDescCompactAttr(indesc, j)->attisdropped)
137
0
      continue;
138
0
    nincols++;
139
0
    same = false;     /* we'll complain below */
140
0
  }
141
142
  /* Report column count mismatch using the non-dropped-column counts */
143
0
  if (!same)
144
0
    ereport(ERROR,
145
0
        (errcode(ERRCODE_DATATYPE_MISMATCH),
146
0
         errmsg_internal("%s", _(msg)),
147
0
         errdetail("Number of returned columns (%d) does not match "
148
0
               "expected column count (%d).",
149
0
               nincols, noutcols)));
150
151
  /* Check if the map has a one-to-one match */
152
0
  if (check_attrmap_match(indesc, outdesc, attrMap))
153
0
  {
154
    /* Runtime conversion is not needed */
155
0
    free_attrmap(attrMap);
156
0
    return NULL;
157
0
  }
158
159
0
  return attrMap;
160
0
}
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
build_attrmap_by_name(TupleDesc indesc,
176
            TupleDesc outdesc,
177
            bool missing_ok)
178
0
{
179
0
  AttrMap    *attrMap;
180
0
  int     outnatts;
181
0
  int     innatts;
182
0
  int     i;
183
0
  int     nextindesc = -1;
184
185
0
  outnatts = outdesc->natts;
186
0
  innatts = indesc->natts;
187
188
0
  attrMap = make_attrmap(outnatts);
189
0
  for (i = 0; i < outnatts; i++)
190
0
  {
191
0
    Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
192
0
    char     *attname;
193
0
    Oid     atttypid;
194
0
    int32   atttypmod;
195
0
    int     j;
196
197
0
    if (outatt->attisdropped)
198
0
      continue;     /* attrMap->attnums[i] is already 0 */
199
0
    attname = NameStr(outatt->attname);
200
0
    atttypid = outatt->atttypid;
201
0
    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
0
    for (j = 0; j < innatts; j++)
215
0
    {
216
0
      Form_pg_attribute inatt;
217
218
0
      nextindesc++;
219
0
      if (nextindesc >= innatts)
220
0
        nextindesc = 0;
221
222
0
      inatt = TupleDescAttr(indesc, nextindesc);
223
0
      if (inatt->attisdropped)
224
0
        continue;
225
0
      if (strcmp(attname, NameStr(inatt->attname)) == 0)
226
0
      {
227
        /* Found it, check type */
228
0
        if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod)
229
0
          ereport(ERROR,
230
0
              (errcode(ERRCODE_DATATYPE_MISMATCH),
231
0
               errmsg("could not convert row type"),
232
0
               errdetail("Attribute \"%s\" of type %s does not match corresponding attribute of type %s.",
233
0
                     attname,
234
0
                     format_type_be(outdesc->tdtypeid),
235
0
                     format_type_be(indesc->tdtypeid))));
236
0
        attrMap->attnums[i] = inatt->attnum;
237
0
        break;
238
0
      }
239
0
    }
240
0
    if (attrMap->attnums[i] == 0 && !missing_ok)
241
0
      ereport(ERROR,
242
0
          (errcode(ERRCODE_DATATYPE_MISMATCH),
243
0
           errmsg("could not convert row type"),
244
0
           errdetail("Attribute \"%s\" of type %s does not exist in type %s.",
245
0
                 attname,
246
0
                 format_type_be(outdesc->tdtypeid),
247
0
                 format_type_be(indesc->tdtypeid))));
248
0
  }
249
0
  return attrMap;
250
0
}
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
build_attrmap_by_name_if_req(TupleDesc indesc,
262
               TupleDesc outdesc,
263
               bool missing_ok)
264
0
{
265
0
  AttrMap    *attrMap;
266
267
  /* Verify compatibility and prepare attribute-number map */
268
0
  attrMap = build_attrmap_by_name(indesc, outdesc, missing_ok);
269
270
  /* Check if the map has a one-to-one match */
271
0
  if (check_attrmap_match(indesc, outdesc, attrMap))
272
0
  {
273
    /* Runtime conversion is not needed */
274
0
    free_attrmap(attrMap);
275
0
    return NULL;
276
0
  }
277
278
0
  return attrMap;
279
0
}
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
check_attrmap_match(TupleDesc indesc,
289
          TupleDesc outdesc,
290
          AttrMap *attrMap)
291
0
{
292
0
  int     i;
293
294
  /* no match if attribute numbers are not the same */
295
0
  if (indesc->natts != outdesc->natts)
296
0
    return false;
297
298
0
  for (i = 0; i < attrMap->maplen; i++)
299
0
  {
300
0
    CompactAttribute *inatt = TupleDescCompactAttr(indesc, i);
301
0
    CompactAttribute *outatt;
302
303
    /*
304
     * If the input column has a missing attribute, we need a conversion.
305
     */
306
0
    if (inatt->atthasmissing)
307
0
      return false;
308
309
0
    if (attrMap->attnums[i] == (i + 1))
310
0
      continue;
311
312
0
    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
0
    if (attrMap->attnums[i] == 0 &&
320
0
      inatt->attisdropped &&
321
0
      inatt->attlen == outatt->attlen &&
322
0
      inatt->attalignby == outatt->attalignby)
323
0
      continue;
324
325
0
    return false;
326
0
  }
327
328
0
  return true;
329
0
}