1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * tupconvert.c |
4 | * Tuple conversion support. |
5 | * |
6 | * These functions provide conversion between rowtypes that are logically |
7 | * equivalent but might have columns in a different order or different sets of |
8 | * dropped columns. |
9 | * |
10 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
11 | * Portions Copyright (c) 1994, Regents of the University of California |
12 | * |
13 | * |
14 | * IDENTIFICATION |
15 | * src/backend/access/common/tupconvert.c |
16 | * |
17 | *------------------------------------------------------------------------- |
18 | */ |
19 | #include "postgres.h" |
20 | |
21 | #include "access/htup_details.h" |
22 | #include "access/tupconvert.h" |
23 | #include "executor/tuptable.h" |
24 | #include "utils/builtins.h" |
25 | |
26 | |
27 | /* |
28 | * The conversion setup routines have the following common API: |
29 | * |
30 | * The setup routine checks whether the given source and destination tuple |
31 | * descriptors are logically compatible. If not, it throws an error. |
32 | * If so, it returns NULL if they are physically compatible (ie, no conversion |
33 | * is needed), else a TupleConversionMap that can be used by execute_attr_map_tuple |
34 | * to perform the conversion. |
35 | * |
36 | * The TupleConversionMap, if needed, is palloc'd in the caller's memory |
37 | * context. Also, the given tuple descriptors are referenced by the map, |
38 | * so they must survive as long as the map is needed. |
39 | * |
40 | * The caller must supply a suitable primary error message to be used if |
41 | * a compatibility error is thrown. Recommended coding practice is to use |
42 | * gettext_noop() on this string, so that it is translatable but won't |
43 | * actually be translated unless the error gets thrown. |
44 | * |
45 | * |
46 | * Implementation notes: |
47 | * |
48 | * The key component of a TupleConversionMap is an attrMap[] array with |
49 | * one entry per output column. This entry contains the 1-based index of |
50 | * the corresponding input column, or zero to force a NULL value (for |
51 | * a dropped output column). The TupleConversionMap also contains workspace |
52 | * arrays. |
53 | */ |
54 | |
55 | |
56 | /* |
57 | * Set up for tuple conversion, matching input and output columns by |
58 | * position. (Dropped columns are ignored in both input and output.) |
59 | * |
60 | * Note: the errdetail messages speak of indesc as the "returned" rowtype, |
61 | * outdesc as the "expected" rowtype. This is okay for current uses but |
62 | * might need generalization in future. |
63 | */ |
64 | TupleConversionMap * |
65 | convert_tuples_by_position(TupleDesc indesc, |
66 | TupleDesc outdesc, |
67 | const char *msg) |
68 | { |
69 | TupleConversionMap *map; |
70 | AttrNumber *attrMap; |
71 | int nincols; |
72 | int noutcols; |
73 | int n; |
74 | int i; |
75 | int j; |
76 | bool same; |
77 | |
78 | /* Verify compatibility and prepare attribute-number map */ |
79 | n = outdesc->natts; |
80 | attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber)); |
81 | j = 0; /* j is next physical input attribute */ |
82 | nincols = noutcols = 0; /* these count non-dropped attributes */ |
83 | same = true; |
84 | for (i = 0; i < n; i++) |
85 | { |
86 | Form_pg_attribute att = TupleDescAttr(outdesc, i); |
87 | Oid atttypid; |
88 | int32 atttypmod; |
89 | |
90 | if (att->attisdropped) |
91 | continue; /* attrMap[i] is already 0 */ |
92 | noutcols++; |
93 | atttypid = att->atttypid; |
94 | atttypmod = att->atttypmod; |
95 | for (; j < indesc->natts; j++) |
96 | { |
97 | att = TupleDescAttr(indesc, j); |
98 | if (att->attisdropped) |
99 | continue; |
100 | nincols++; |
101 | /* Found matching column, check type */ |
102 | if (atttypid != att->atttypid || |
103 | (atttypmod != att->atttypmod && atttypmod >= 0)) |
104 | ereport(ERROR, |
105 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
106 | errmsg_internal("%s" , _(msg)), |
107 | errdetail("Returned type %s does not match expected type %s in column %d." , |
108 | format_type_with_typemod(att->atttypid, |
109 | att->atttypmod), |
110 | format_type_with_typemod(atttypid, |
111 | atttypmod), |
112 | noutcols))); |
113 | attrMap[i] = (AttrNumber) (j + 1); |
114 | j++; |
115 | break; |
116 | } |
117 | if (attrMap[i] == 0) |
118 | same = false; /* we'll complain below */ |
119 | } |
120 | |
121 | /* Check for unused input columns */ |
122 | for (; j < indesc->natts; j++) |
123 | { |
124 | if (TupleDescAttr(indesc, j)->attisdropped) |
125 | continue; |
126 | nincols++; |
127 | same = false; /* we'll complain below */ |
128 | } |
129 | |
130 | /* Report column count mismatch using the non-dropped-column counts */ |
131 | if (!same) |
132 | ereport(ERROR, |
133 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
134 | errmsg_internal("%s" , _(msg)), |
135 | errdetail("Number of returned columns (%d) does not match " |
136 | "expected column count (%d)." , |
137 | nincols, noutcols))); |
138 | |
139 | /* |
140 | * Check to see if the map is one-to-one, in which case we need not do a |
141 | * tuple conversion. |
142 | */ |
143 | if (indesc->natts == outdesc->natts) |
144 | { |
145 | for (i = 0; i < n; i++) |
146 | { |
147 | Form_pg_attribute inatt; |
148 | Form_pg_attribute outatt; |
149 | |
150 | if (attrMap[i] == (i + 1)) |
151 | continue; |
152 | |
153 | /* |
154 | * If it's a dropped column and the corresponding input column is |
155 | * also dropped, we needn't convert. However, attlen and attalign |
156 | * must agree. |
157 | */ |
158 | inatt = TupleDescAttr(indesc, i); |
159 | outatt = TupleDescAttr(outdesc, i); |
160 | if (attrMap[i] == 0 && |
161 | inatt->attisdropped && |
162 | inatt->attlen == outatt->attlen && |
163 | inatt->attalign == outatt->attalign) |
164 | continue; |
165 | |
166 | same = false; |
167 | break; |
168 | } |
169 | } |
170 | else |
171 | same = false; |
172 | |
173 | if (same) |
174 | { |
175 | /* Runtime conversion is not needed */ |
176 | pfree(attrMap); |
177 | return NULL; |
178 | } |
179 | |
180 | /* Prepare the map structure */ |
181 | map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap)); |
182 | map->indesc = indesc; |
183 | map->outdesc = outdesc; |
184 | map->attrMap = attrMap; |
185 | /* preallocate workspace for Datum arrays */ |
186 | map->outvalues = (Datum *) palloc(n * sizeof(Datum)); |
187 | map->outisnull = (bool *) palloc(n * sizeof(bool)); |
188 | n = indesc->natts + 1; /* +1 for NULL */ |
189 | map->invalues = (Datum *) palloc(n * sizeof(Datum)); |
190 | map->inisnull = (bool *) palloc(n * sizeof(bool)); |
191 | map->invalues[0] = (Datum) 0; /* set up the NULL entry */ |
192 | map->inisnull[0] = true; |
193 | |
194 | return map; |
195 | } |
196 | |
197 | /* |
198 | * Set up for tuple conversion, matching input and output columns by name. |
199 | * (Dropped columns are ignored in both input and output.) This is intended |
200 | * for use when the rowtypes are related by inheritance, so we expect an exact |
201 | * match of both type and typmod. The error messages will be a bit unhelpful |
202 | * unless both rowtypes are named composite types. |
203 | */ |
204 | TupleConversionMap * |
205 | convert_tuples_by_name(TupleDesc indesc, |
206 | TupleDesc outdesc, |
207 | const char *msg) |
208 | { |
209 | TupleConversionMap *map; |
210 | AttrNumber *attrMap; |
211 | int n = outdesc->natts; |
212 | |
213 | /* Verify compatibility and prepare attribute-number map */ |
214 | attrMap = convert_tuples_by_name_map_if_req(indesc, outdesc, msg); |
215 | |
216 | if (attrMap == NULL) |
217 | { |
218 | /* runtime conversion is not needed */ |
219 | return NULL; |
220 | } |
221 | |
222 | /* Prepare the map structure */ |
223 | map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap)); |
224 | map->indesc = indesc; |
225 | map->outdesc = outdesc; |
226 | map->attrMap = attrMap; |
227 | /* preallocate workspace for Datum arrays */ |
228 | map->outvalues = (Datum *) palloc(n * sizeof(Datum)); |
229 | map->outisnull = (bool *) palloc(n * sizeof(bool)); |
230 | n = indesc->natts + 1; /* +1 for NULL */ |
231 | map->invalues = (Datum *) palloc(n * sizeof(Datum)); |
232 | map->inisnull = (bool *) palloc(n * sizeof(bool)); |
233 | map->invalues[0] = (Datum) 0; /* set up the NULL entry */ |
234 | map->inisnull[0] = true; |
235 | |
236 | return map; |
237 | } |
238 | |
239 | /* |
240 | * Return a palloc'd bare attribute map for tuple conversion, matching input |
241 | * and output columns by name. (Dropped columns are ignored in both input and |
242 | * output.) This is normally a subroutine for convert_tuples_by_name, but can |
243 | * be used standalone. |
244 | */ |
245 | AttrNumber * |
246 | convert_tuples_by_name_map(TupleDesc indesc, |
247 | TupleDesc outdesc, |
248 | const char *msg) |
249 | { |
250 | AttrNumber *attrMap; |
251 | int outnatts; |
252 | int innatts; |
253 | int i; |
254 | int nextindesc = -1; |
255 | |
256 | outnatts = outdesc->natts; |
257 | innatts = indesc->natts; |
258 | |
259 | attrMap = (AttrNumber *) palloc0(outnatts * sizeof(AttrNumber)); |
260 | for (i = 0; i < outnatts; i++) |
261 | { |
262 | Form_pg_attribute outatt = TupleDescAttr(outdesc, i); |
263 | char *attname; |
264 | Oid atttypid; |
265 | int32 atttypmod; |
266 | int j; |
267 | |
268 | if (outatt->attisdropped) |
269 | continue; /* attrMap[i] is already 0 */ |
270 | attname = NameStr(outatt->attname); |
271 | atttypid = outatt->atttypid; |
272 | atttypmod = outatt->atttypmod; |
273 | |
274 | /* |
275 | * Now search for an attribute with the same name in the indesc. It |
276 | * seems likely that a partitioned table will have the attributes in |
277 | * the same order as the partition, so the search below is optimized |
278 | * for that case. It is possible that columns are dropped in one of |
279 | * the relations, but not the other, so we use the 'nextindesc' |
280 | * counter to track the starting point of the search. If the inner |
281 | * loop encounters dropped columns then it will have to skip over |
282 | * them, but it should leave 'nextindesc' at the correct position for |
283 | * the next outer loop. |
284 | */ |
285 | for (j = 0; j < innatts; j++) |
286 | { |
287 | Form_pg_attribute inatt; |
288 | |
289 | nextindesc++; |
290 | if (nextindesc >= innatts) |
291 | nextindesc = 0; |
292 | |
293 | inatt = TupleDescAttr(indesc, nextindesc); |
294 | if (inatt->attisdropped) |
295 | continue; |
296 | if (strcmp(attname, NameStr(inatt->attname)) == 0) |
297 | { |
298 | /* Found it, check type */ |
299 | if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod) |
300 | ereport(ERROR, |
301 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
302 | errmsg_internal("%s" , _(msg)), |
303 | errdetail("Attribute \"%s\" of type %s does not match corresponding attribute of type %s." , |
304 | attname, |
305 | format_type_be(outdesc->tdtypeid), |
306 | format_type_be(indesc->tdtypeid)))); |
307 | attrMap[i] = inatt->attnum; |
308 | break; |
309 | } |
310 | } |
311 | if (attrMap[i] == 0) |
312 | ereport(ERROR, |
313 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
314 | errmsg_internal("%s" , _(msg)), |
315 | errdetail("Attribute \"%s\" of type %s does not exist in type %s." , |
316 | attname, |
317 | format_type_be(outdesc->tdtypeid), |
318 | format_type_be(indesc->tdtypeid)))); |
319 | } |
320 | return attrMap; |
321 | } |
322 | |
323 | /* |
324 | * Returns mapping created by convert_tuples_by_name_map, or NULL if no |
325 | * conversion not required. This is a convenience routine for |
326 | * convert_tuples_by_name() and other functions. |
327 | */ |
328 | AttrNumber * |
329 | convert_tuples_by_name_map_if_req(TupleDesc indesc, |
330 | TupleDesc outdesc, |
331 | const char *msg) |
332 | { |
333 | AttrNumber *attrMap; |
334 | int n = outdesc->natts; |
335 | int i; |
336 | bool same; |
337 | |
338 | /* Verify compatibility and prepare attribute-number map */ |
339 | attrMap = convert_tuples_by_name_map(indesc, outdesc, msg); |
340 | |
341 | /* |
342 | * Check to see if the map is one-to-one, in which case we need not do a |
343 | * tuple conversion. |
344 | */ |
345 | if (indesc->natts == outdesc->natts) |
346 | { |
347 | same = true; |
348 | for (i = 0; i < n; i++) |
349 | { |
350 | Form_pg_attribute inatt; |
351 | Form_pg_attribute outatt; |
352 | |
353 | if (attrMap[i] == (i + 1)) |
354 | continue; |
355 | |
356 | /* |
357 | * If it's a dropped column and the corresponding input column is |
358 | * also dropped, we needn't convert. However, attlen and attalign |
359 | * must agree. |
360 | */ |
361 | inatt = TupleDescAttr(indesc, i); |
362 | outatt = TupleDescAttr(outdesc, i); |
363 | if (attrMap[i] == 0 && |
364 | inatt->attisdropped && |
365 | inatt->attlen == outatt->attlen && |
366 | inatt->attalign == outatt->attalign) |
367 | continue; |
368 | |
369 | same = false; |
370 | break; |
371 | } |
372 | } |
373 | else |
374 | same = false; |
375 | |
376 | if (same) |
377 | { |
378 | /* Runtime conversion is not needed */ |
379 | pfree(attrMap); |
380 | return NULL; |
381 | } |
382 | else |
383 | return attrMap; |
384 | } |
385 | |
386 | /* |
387 | * Perform conversion of a tuple according to the map. |
388 | */ |
389 | HeapTuple |
390 | execute_attr_map_tuple(HeapTuple tuple, TupleConversionMap *map) |
391 | { |
392 | AttrNumber *attrMap = map->attrMap; |
393 | Datum *invalues = map->invalues; |
394 | bool *inisnull = map->inisnull; |
395 | Datum *outvalues = map->outvalues; |
396 | bool *outisnull = map->outisnull; |
397 | int outnatts = map->outdesc->natts; |
398 | int i; |
399 | |
400 | /* |
401 | * Extract all the values of the old tuple, offsetting the arrays so that |
402 | * invalues[0] is left NULL and invalues[1] is the first source attribute; |
403 | * this exactly matches the numbering convention in attrMap. |
404 | */ |
405 | heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1); |
406 | |
407 | /* |
408 | * Transpose into proper fields of the new tuple. |
409 | */ |
410 | for (i = 0; i < outnatts; i++) |
411 | { |
412 | int j = attrMap[i]; |
413 | |
414 | outvalues[i] = invalues[j]; |
415 | outisnull[i] = inisnull[j]; |
416 | } |
417 | |
418 | /* |
419 | * Now form the new tuple. |
420 | */ |
421 | return heap_form_tuple(map->outdesc, outvalues, outisnull); |
422 | } |
423 | |
424 | /* |
425 | * Perform conversion of a tuple slot according to the map. |
426 | */ |
427 | TupleTableSlot * |
428 | execute_attr_map_slot(AttrNumber *attrMap, |
429 | TupleTableSlot *in_slot, |
430 | TupleTableSlot *out_slot) |
431 | { |
432 | Datum *invalues; |
433 | bool *inisnull; |
434 | Datum *outvalues; |
435 | bool *outisnull; |
436 | int outnatts; |
437 | int i; |
438 | |
439 | /* Sanity checks */ |
440 | Assert(in_slot->tts_tupleDescriptor != NULL && |
441 | out_slot->tts_tupleDescriptor != NULL); |
442 | Assert(in_slot->tts_values != NULL && out_slot->tts_values != NULL); |
443 | |
444 | outnatts = out_slot->tts_tupleDescriptor->natts; |
445 | |
446 | /* Extract all the values of the in slot. */ |
447 | slot_getallattrs(in_slot); |
448 | |
449 | /* Before doing the mapping, clear any old contents from the out slot */ |
450 | ExecClearTuple(out_slot); |
451 | |
452 | invalues = in_slot->tts_values; |
453 | inisnull = in_slot->tts_isnull; |
454 | outvalues = out_slot->tts_values; |
455 | outisnull = out_slot->tts_isnull; |
456 | |
457 | /* Transpose into proper fields of the out slot. */ |
458 | for (i = 0; i < outnatts; i++) |
459 | { |
460 | int j = attrMap[i] - 1; |
461 | |
462 | /* attrMap[i] == 0 means it's a NULL datum. */ |
463 | if (j == -1) |
464 | { |
465 | outvalues[i] = (Datum) 0; |
466 | outisnull[i] = true; |
467 | } |
468 | else |
469 | { |
470 | outvalues[i] = invalues[j]; |
471 | outisnull[i] = inisnull[j]; |
472 | } |
473 | } |
474 | |
475 | ExecStoreVirtualTuple(out_slot); |
476 | |
477 | return out_slot; |
478 | } |
479 | |
480 | /* |
481 | * Free a TupleConversionMap structure. |
482 | */ |
483 | void |
484 | free_conversion_map(TupleConversionMap *map) |
485 | { |
486 | /* indesc and outdesc are not ours to free */ |
487 | pfree(map->attrMap); |
488 | pfree(map->invalues); |
489 | pfree(map->inisnull); |
490 | pfree(map->outvalues); |
491 | pfree(map->outisnull); |
492 | pfree(map); |
493 | } |
494 | |