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 */
64TupleConversionMap *
65convert_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 */
204TupleConversionMap *
205convert_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 */
245AttrNumber *
246convert_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 */
328AttrNumber *
329convert_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 */
389HeapTuple
390execute_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 */
427TupleTableSlot *
428execute_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 */
483void
484free_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