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 *
convert_tuples_by_position(TupleDesc indesc,TupleDesc outdesc,const char * msg)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 = TupleDescAttr(indesc, i);
148 Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
149
150 /*
151 * If the input column has a missing attribute, we need a
152 * conversion.
153 */
154 if (inatt->atthasmissing)
155 {
156 same = false;
157 break;
158 }
159
160 if (attrMap[i] == (i + 1))
161 continue;
162
163 /*
164 * If it's a dropped column and the corresponding input column is
165 * also dropped, we needn't convert. However, attlen and attalign
166 * must agree.
167 */
168 if (attrMap[i] == 0 &&
169 inatt->attisdropped &&
170 inatt->attlen == outatt->attlen &&
171 inatt->attalign == outatt->attalign)
172 continue;
173
174 same = false;
175 break;
176 }
177 }
178 else
179 same = false;
180
181 if (same)
182 {
183 /* Runtime conversion is not needed */
184 pfree(attrMap);
185 return NULL;
186 }
187
188 /* Prepare the map structure */
189 map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap));
190 map->indesc = indesc;
191 map->outdesc = outdesc;
192 map->attrMap = attrMap;
193 /* preallocate workspace for Datum arrays */
194 map->outvalues = (Datum *) palloc(n * sizeof(Datum));
195 map->outisnull = (bool *) palloc(n * sizeof(bool));
196 n = indesc->natts + 1; /* +1 for NULL */
197 map->invalues = (Datum *) palloc(n * sizeof(Datum));
198 map->inisnull = (bool *) palloc(n * sizeof(bool));
199 map->invalues[0] = (Datum) 0; /* set up the NULL entry */
200 map->inisnull[0] = true;
201
202 return map;
203 }
204
205 /*
206 * Set up for tuple conversion, matching input and output columns by name.
207 * (Dropped columns are ignored in both input and output.) This is intended
208 * for use when the rowtypes are related by inheritance, so we expect an exact
209 * match of both type and typmod. The error messages will be a bit unhelpful
210 * unless both rowtypes are named composite types.
211 */
212 TupleConversionMap *
convert_tuples_by_name(TupleDesc indesc,TupleDesc outdesc,const char * msg)213 convert_tuples_by_name(TupleDesc indesc,
214 TupleDesc outdesc,
215 const char *msg)
216 {
217 TupleConversionMap *map;
218 AttrNumber *attrMap;
219 int n = outdesc->natts;
220
221 /* Verify compatibility and prepare attribute-number map */
222 attrMap = convert_tuples_by_name_map_if_req(indesc, outdesc, msg);
223
224 if (attrMap == NULL)
225 {
226 /* runtime conversion is not needed */
227 return NULL;
228 }
229
230 /* Prepare the map structure */
231 map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap));
232 map->indesc = indesc;
233 map->outdesc = outdesc;
234 map->attrMap = attrMap;
235 /* preallocate workspace for Datum arrays */
236 map->outvalues = (Datum *) palloc(n * sizeof(Datum));
237 map->outisnull = (bool *) palloc(n * sizeof(bool));
238 n = indesc->natts + 1; /* +1 for NULL */
239 map->invalues = (Datum *) palloc(n * sizeof(Datum));
240 map->inisnull = (bool *) palloc(n * sizeof(bool));
241 map->invalues[0] = (Datum) 0; /* set up the NULL entry */
242 map->inisnull[0] = true;
243
244 return map;
245 }
246
247 /*
248 * Return a palloc'd bare attribute map for tuple conversion, matching input
249 * and output columns by name. (Dropped columns are ignored in both input and
250 * output.) This is normally a subroutine for convert_tuples_by_name, but can
251 * be used standalone.
252 */
253 AttrNumber *
convert_tuples_by_name_map(TupleDesc indesc,TupleDesc outdesc,const char * msg)254 convert_tuples_by_name_map(TupleDesc indesc,
255 TupleDesc outdesc,
256 const char *msg)
257 {
258 AttrNumber *attrMap;
259 int outnatts;
260 int innatts;
261 int i;
262 int nextindesc = -1;
263
264 outnatts = outdesc->natts;
265 innatts = indesc->natts;
266
267 attrMap = (AttrNumber *) palloc0(outnatts * sizeof(AttrNumber));
268 for (i = 0; i < outnatts; i++)
269 {
270 Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
271 char *attname;
272 Oid atttypid;
273 int32 atttypmod;
274 int j;
275
276 if (outatt->attisdropped)
277 continue; /* attrMap[i] is already 0 */
278 attname = NameStr(outatt->attname);
279 atttypid = outatt->atttypid;
280 atttypmod = outatt->atttypmod;
281
282 /*
283 * Now search for an attribute with the same name in the indesc. It
284 * seems likely that a partitioned table will have the attributes in
285 * the same order as the partition, so the search below is optimized
286 * for that case. It is possible that columns are dropped in one of
287 * the relations, but not the other, so we use the 'nextindesc'
288 * counter to track the starting point of the search. If the inner
289 * loop encounters dropped columns then it will have to skip over
290 * them, but it should leave 'nextindesc' at the correct position for
291 * the next outer loop.
292 */
293 for (j = 0; j < innatts; j++)
294 {
295 Form_pg_attribute inatt;
296
297 nextindesc++;
298 if (nextindesc >= innatts)
299 nextindesc = 0;
300
301 inatt = TupleDescAttr(indesc, nextindesc);
302 if (inatt->attisdropped)
303 continue;
304 if (strcmp(attname, NameStr(inatt->attname)) == 0)
305 {
306 /* Found it, check type */
307 if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod)
308 ereport(ERROR,
309 (errcode(ERRCODE_DATATYPE_MISMATCH),
310 errmsg_internal("%s", _(msg)),
311 errdetail("Attribute \"%s\" of type %s does not match corresponding attribute of type %s.",
312 attname,
313 format_type_be(outdesc->tdtypeid),
314 format_type_be(indesc->tdtypeid))));
315 attrMap[i] = inatt->attnum;
316 break;
317 }
318 }
319 if (attrMap[i] == 0)
320 ereport(ERROR,
321 (errcode(ERRCODE_DATATYPE_MISMATCH),
322 errmsg_internal("%s", _(msg)),
323 errdetail("Attribute \"%s\" of type %s does not exist in type %s.",
324 attname,
325 format_type_be(outdesc->tdtypeid),
326 format_type_be(indesc->tdtypeid))));
327 }
328 return attrMap;
329 }
330
331 /*
332 * Returns mapping created by convert_tuples_by_name_map, or NULL if no
333 * conversion not required. This is a convenience routine for
334 * convert_tuples_by_name() and other functions.
335 */
336 AttrNumber *
convert_tuples_by_name_map_if_req(TupleDesc indesc,TupleDesc outdesc,const char * msg)337 convert_tuples_by_name_map_if_req(TupleDesc indesc,
338 TupleDesc outdesc,
339 const char *msg)
340 {
341 AttrNumber *attrMap;
342 int n = outdesc->natts;
343 int i;
344 bool same;
345
346 /* Verify compatibility and prepare attribute-number map */
347 attrMap = convert_tuples_by_name_map(indesc, outdesc, msg);
348
349 /*
350 * Check to see if the map is one-to-one, in which case we need not do a
351 * tuple conversion.
352 */
353 if (indesc->natts == outdesc->natts)
354 {
355 same = true;
356 for (i = 0; i < n; i++)
357 {
358 Form_pg_attribute inatt = TupleDescAttr(indesc, i);
359 Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
360
361 /*
362 * If the input column has a missing attribute, we need a
363 * conversion.
364 */
365 if (inatt->atthasmissing)
366 {
367 same = false;
368 break;
369 }
370
371 if (attrMap[i] == (i + 1))
372 continue;
373
374 /*
375 * If it's a dropped column and the corresponding input column is
376 * also dropped, we needn't convert. However, attlen and attalign
377 * must agree.
378 */
379 if (attrMap[i] == 0 &&
380 inatt->attisdropped &&
381 inatt->attlen == outatt->attlen &&
382 inatt->attalign == outatt->attalign)
383 continue;
384
385 same = false;
386 break;
387 }
388 }
389 else
390 same = false;
391
392 if (same)
393 {
394 /* Runtime conversion is not needed */
395 pfree(attrMap);
396 return NULL;
397 }
398 else
399 return attrMap;
400 }
401
402 /*
403 * Perform conversion of a tuple according to the map.
404 */
405 HeapTuple
execute_attr_map_tuple(HeapTuple tuple,TupleConversionMap * map)406 execute_attr_map_tuple(HeapTuple tuple, TupleConversionMap *map)
407 {
408 AttrNumber *attrMap = map->attrMap;
409 Datum *invalues = map->invalues;
410 bool *inisnull = map->inisnull;
411 Datum *outvalues = map->outvalues;
412 bool *outisnull = map->outisnull;
413 int outnatts = map->outdesc->natts;
414 int i;
415
416 /*
417 * Extract all the values of the old tuple, offsetting the arrays so that
418 * invalues[0] is left NULL and invalues[1] is the first source attribute;
419 * this exactly matches the numbering convention in attrMap.
420 */
421 heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1);
422
423 /*
424 * Transpose into proper fields of the new tuple.
425 */
426 for (i = 0; i < outnatts; i++)
427 {
428 int j = attrMap[i];
429
430 outvalues[i] = invalues[j];
431 outisnull[i] = inisnull[j];
432 }
433
434 /*
435 * Now form the new tuple.
436 */
437 return heap_form_tuple(map->outdesc, outvalues, outisnull);
438 }
439
440 /*
441 * Perform conversion of a tuple slot according to the map.
442 */
443 TupleTableSlot *
execute_attr_map_slot(AttrNumber * attrMap,TupleTableSlot * in_slot,TupleTableSlot * out_slot)444 execute_attr_map_slot(AttrNumber *attrMap,
445 TupleTableSlot *in_slot,
446 TupleTableSlot *out_slot)
447 {
448 Datum *invalues;
449 bool *inisnull;
450 Datum *outvalues;
451 bool *outisnull;
452 int outnatts;
453 int i;
454
455 /* Sanity checks */
456 Assert(in_slot->tts_tupleDescriptor != NULL &&
457 out_slot->tts_tupleDescriptor != NULL);
458 Assert(in_slot->tts_values != NULL && out_slot->tts_values != NULL);
459
460 outnatts = out_slot->tts_tupleDescriptor->natts;
461
462 /* Extract all the values of the in slot. */
463 slot_getallattrs(in_slot);
464
465 /* Before doing the mapping, clear any old contents from the out slot */
466 ExecClearTuple(out_slot);
467
468 invalues = in_slot->tts_values;
469 inisnull = in_slot->tts_isnull;
470 outvalues = out_slot->tts_values;
471 outisnull = out_slot->tts_isnull;
472
473 /* Transpose into proper fields of the out slot. */
474 for (i = 0; i < outnatts; i++)
475 {
476 int j = attrMap[i] - 1;
477
478 /* attrMap[i] == 0 means it's a NULL datum. */
479 if (j == -1)
480 {
481 outvalues[i] = (Datum) 0;
482 outisnull[i] = true;
483 }
484 else
485 {
486 outvalues[i] = invalues[j];
487 outisnull[i] = inisnull[j];
488 }
489 }
490
491 ExecStoreVirtualTuple(out_slot);
492
493 return out_slot;
494 }
495
496 /*
497 * Perform conversion of bitmap of columns according to the map.
498 *
499 * The input and output bitmaps are offset by
500 * FirstLowInvalidHeapAttributeNumber to accommodate system cols, like the
501 * column-bitmaps in RangeTblEntry.
502 */
503 Bitmapset *
execute_attr_map_cols(Bitmapset * in_cols,TupleConversionMap * map)504 execute_attr_map_cols(Bitmapset *in_cols, TupleConversionMap *map)
505 {
506 AttrNumber *attrMap = map->attrMap;
507 int maplen = map->outdesc->natts;
508 Bitmapset *out_cols;
509 int out_attnum;
510
511 /* fast path for the common trivial case */
512 if (in_cols == NULL)
513 return NULL;
514
515 /*
516 * For each output column, check which input column it corresponds to.
517 */
518 out_cols = NULL;
519
520 for (out_attnum = FirstLowInvalidHeapAttributeNumber + 1;
521 out_attnum <= maplen;
522 out_attnum++)
523 {
524 int in_attnum;
525
526 if (out_attnum < 0)
527 {
528 /* System column. No mapping. */
529 in_attnum = out_attnum;
530 }
531 else if (out_attnum == 0)
532 continue;
533 else
534 {
535 /* normal user column */
536 in_attnum = attrMap[out_attnum - 1];
537
538 if (in_attnum == 0)
539 continue;
540 }
541
542 if (bms_is_member(in_attnum - FirstLowInvalidHeapAttributeNumber, in_cols))
543 out_cols = bms_add_member(out_cols, out_attnum - FirstLowInvalidHeapAttributeNumber);
544 }
545
546 return out_cols;
547 }
548
549 /*
550 * Free a TupleConversionMap structure.
551 */
552 void
free_conversion_map(TupleConversionMap * map)553 free_conversion_map(TupleConversionMap *map)
554 {
555 /* indesc and outdesc are not ours to free */
556 pfree(map->attrMap);
557 pfree(map->invalues);
558 pfree(map->inisnull);
559 pfree(map->outvalues);
560 pfree(map->outisnull);
561 pfree(map);
562 }
563