/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 | } |