Line data Source code
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
8 : * of dropped columns. There is some overlap of functionality with the
9 : * executor's "junkfilter" routines, but these functions work on bare
10 : * HeapTuples rather than TupleTableSlots.
11 : *
12 : * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
13 : * Portions Copyright (c) 1994, Regents of the University of California
14 : *
15 : *
16 : * IDENTIFICATION
17 : * src/backend/access/common/tupconvert.c
18 : *
19 : *-------------------------------------------------------------------------
20 : */
21 : #include "postgres.h"
22 :
23 : #include "access/htup_details.h"
24 : #include "access/tupconvert.h"
25 : #include "utils/builtins.h"
26 :
27 :
28 : /*
29 : * The conversion setup routines have the following common API:
30 : *
31 : * The setup routine checks whether the given source and destination tuple
32 : * descriptors are logically compatible. If not, it throws an error.
33 : * If so, it returns NULL if they are physically compatible (ie, no conversion
34 : * is needed), else a TupleConversionMap that can be used by do_convert_tuple
35 : * to perform the conversion.
36 : *
37 : * The TupleConversionMap, if needed, is palloc'd in the caller's memory
38 : * context. Also, the given tuple descriptors are referenced by the map,
39 : * so they must survive as long as the map is needed.
40 : *
41 : * The caller must supply a suitable primary error message to be used if
42 : * a compatibility error is thrown. Recommended coding practice is to use
43 : * gettext_noop() on this string, so that it is translatable but won't
44 : * actually be translated unless the error gets thrown.
45 : *
46 : *
47 : * Implementation notes:
48 : *
49 : * The key component of a TupleConversionMap is an attrMap[] array with
50 : * one entry per output column. This entry contains the 1-based index of
51 : * the corresponding input column, or zero to force a NULL value (for
52 : * a dropped output column). The TupleConversionMap also contains workspace
53 : * arrays.
54 : */
55 :
56 :
57 : /*
58 : * Set up for tuple conversion, matching input and output columns by
59 : * position. (Dropped columns are ignored in both input and output.)
60 : *
61 : * Note: the errdetail messages speak of indesc as the "returned" rowtype,
62 : * outdesc as the "expected" rowtype. This is okay for current uses but
63 : * might need generalization in future.
64 : */
65 : TupleConversionMap *
66 1769 : convert_tuples_by_position(TupleDesc indesc,
67 : TupleDesc outdesc,
68 : const char *msg)
69 : {
70 : TupleConversionMap *map;
71 : AttrNumber *attrMap;
72 : int nincols;
73 : int noutcols;
74 : int n;
75 : int i;
76 : int j;
77 : bool same;
78 :
79 : /* Verify compatibility and prepare attribute-number map */
80 1769 : n = outdesc->natts;
81 1769 : attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
82 1769 : j = 0; /* j is next physical input attribute */
83 1769 : nincols = noutcols = 0; /* these count non-dropped attributes */
84 1769 : same = true;
85 8382 : for (i = 0; i < n; i++)
86 : {
87 6614 : Form_pg_attribute att = TupleDescAttr(outdesc, i);
88 : Oid atttypid;
89 : int32 atttypmod;
90 :
91 6614 : if (att->attisdropped)
92 19 : continue; /* attrMap[i] is already 0 */
93 6595 : noutcols++;
94 6595 : atttypid = att->atttypid;
95 6595 : atttypmod = att->atttypmod;
96 13194 : for (; j < indesc->natts; j++)
97 : {
98 6597 : att = TupleDescAttr(indesc, j);
99 6597 : if (att->attisdropped)
100 2 : continue;
101 6595 : nincols++;
102 : /* Found matching column, check type */
103 13189 : if (atttypid != att->atttypid ||
104 6594 : (atttypmod != att->atttypmod && atttypmod >= 0))
105 1 : ereport(ERROR,
106 : (errcode(ERRCODE_DATATYPE_MISMATCH),
107 : errmsg_internal("%s", _(msg)),
108 : errdetail("Returned type %s does not match expected type %s in column %d.",
109 : format_type_with_typemod(att->atttypid,
110 : att->atttypmod),
111 : format_type_with_typemod(atttypid,
112 : atttypmod),
113 : noutcols)));
114 6594 : attrMap[i] = (AttrNumber) (j + 1);
115 6594 : j++;
116 6594 : break;
117 : }
118 6594 : if (attrMap[i] == 0)
119 0 : same = false; /* we'll complain below */
120 : }
121 :
122 : /* Check for unused input columns */
123 1768 : for (; j < indesc->natts; j++)
124 : {
125 0 : if (TupleDescAttr(indesc, j)->attisdropped)
126 0 : continue;
127 0 : nincols++;
128 0 : same = false; /* we'll complain below */
129 : }
130 :
131 : /* Report column count mismatch using the non-dropped-column counts */
132 1768 : if (!same)
133 0 : ereport(ERROR,
134 : (errcode(ERRCODE_DATATYPE_MISMATCH),
135 : errmsg_internal("%s", _(msg)),
136 : errdetail("Number of returned columns (%d) does not match "
137 : "expected column count (%d).",
138 : nincols, noutcols)));
139 :
140 : /*
141 : * Check to see if the map is one-to-one, in which case we need not do a
142 : * tuple conversion. We must also insist that both tupdescs either
143 : * specify or don't specify an OID column, else we need a conversion to
144 : * add/remove space for that. (For some callers, presence or absence of
145 : * an OID column perhaps would not really matter, but let's be safe.)
146 : */
147 5282 : if (indesc->natts == outdesc->natts &&
148 1757 : indesc->tdhasoid == outdesc->tdhasoid)
149 : {
150 8323 : for (i = 0; i < n; i++)
151 : {
152 : Form_pg_attribute inatt;
153 : Form_pg_attribute outatt;
154 :
155 6566 : if (attrMap[i] == (i + 1))
156 6564 : continue;
157 :
158 : /*
159 : * If it's a dropped column and the corresponding input column is
160 : * also dropped, we needn't convert. However, attlen and attalign
161 : * must agree.
162 : */
163 2 : inatt = TupleDescAttr(indesc, i);
164 2 : outatt = TupleDescAttr(outdesc, i);
165 4 : if (attrMap[i] == 0 &&
166 4 : inatt->attisdropped &&
167 4 : inatt->attlen == outatt->attlen &&
168 2 : inatt->attalign == outatt->attalign)
169 2 : continue;
170 :
171 0 : same = false;
172 0 : break;
173 : }
174 : }
175 : else
176 11 : same = false;
177 :
178 1768 : if (same)
179 : {
180 : /* Runtime conversion is not needed */
181 1757 : pfree(attrMap);
182 1757 : return NULL;
183 : }
184 :
185 : /* Prepare the map structure */
186 11 : map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap));
187 11 : map->indesc = indesc;
188 11 : map->outdesc = outdesc;
189 11 : map->attrMap = attrMap;
190 : /* preallocate workspace for Datum arrays */
191 11 : map->outvalues = (Datum *) palloc(n * sizeof(Datum));
192 11 : map->outisnull = (bool *) palloc(n * sizeof(bool));
193 11 : n = indesc->natts + 1; /* +1 for NULL */
194 11 : map->invalues = (Datum *) palloc(n * sizeof(Datum));
195 11 : map->inisnull = (bool *) palloc(n * sizeof(bool));
196 11 : map->invalues[0] = (Datum) 0; /* set up the NULL entry */
197 11 : map->inisnull[0] = true;
198 :
199 11 : return map;
200 : }
201 :
202 : /*
203 : * Set up for tuple conversion, matching input and output columns by name.
204 : * (Dropped columns are ignored in both input and output.) This is intended
205 : * for use when the rowtypes are related by inheritance, so we expect an exact
206 : * match of both type and typmod. The error messages will be a bit unhelpful
207 : * unless both rowtypes are named composite types.
208 : */
209 : TupleConversionMap *
210 390 : convert_tuples_by_name(TupleDesc indesc,
211 : TupleDesc outdesc,
212 : const char *msg)
213 : {
214 : TupleConversionMap *map;
215 : AttrNumber *attrMap;
216 390 : int n = outdesc->natts;
217 : int i;
218 : bool same;
219 :
220 : /* Verify compatibility and prepare attribute-number map */
221 390 : attrMap = convert_tuples_by_name_map(indesc, outdesc, msg);
222 :
223 : /*
224 : * Check to see if the map is one-to-one, in which case we need not do a
225 : * tuple conversion. We must also insist that both tupdescs either
226 : * specify or don't specify an OID column, else we need a conversion to
227 : * add/remove space for that. (For some callers, presence or absence of
228 : * an OID column perhaps would not really matter, but let's be safe.)
229 : */
230 1054 : if (indesc->natts == outdesc->natts &&
231 332 : indesc->tdhasoid == outdesc->tdhasoid)
232 : {
233 332 : same = true;
234 1004 : for (i = 0; i < n; i++)
235 : {
236 : Form_pg_attribute inatt;
237 : Form_pg_attribute outatt;
238 :
239 717 : if (attrMap[i] == (i + 1))
240 669 : continue;
241 :
242 : /*
243 : * If it's a dropped column and the corresponding input column is
244 : * also dropped, we needn't convert. However, attlen and attalign
245 : * must agree.
246 : */
247 48 : inatt = TupleDescAttr(indesc, i);
248 48 : outatt = TupleDescAttr(outdesc, i);
249 57 : if (attrMap[i] == 0 &&
250 12 : inatt->attisdropped &&
251 6 : inatt->attlen == outatt->attlen &&
252 3 : inatt->attalign == outatt->attalign)
253 3 : continue;
254 :
255 45 : same = false;
256 45 : break;
257 : }
258 : }
259 : else
260 58 : same = false;
261 :
262 390 : if (same)
263 : {
264 : /* Runtime conversion is not needed */
265 287 : pfree(attrMap);
266 287 : return NULL;
267 : }
268 :
269 : /* Prepare the map structure */
270 103 : map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap));
271 103 : map->indesc = indesc;
272 103 : map->outdesc = outdesc;
273 103 : map->attrMap = attrMap;
274 : /* preallocate workspace for Datum arrays */
275 103 : map->outvalues = (Datum *) palloc(n * sizeof(Datum));
276 103 : map->outisnull = (bool *) palloc(n * sizeof(bool));
277 103 : n = indesc->natts + 1; /* +1 for NULL */
278 103 : map->invalues = (Datum *) palloc(n * sizeof(Datum));
279 103 : map->inisnull = (bool *) palloc(n * sizeof(bool));
280 103 : map->invalues[0] = (Datum) 0; /* set up the NULL entry */
281 103 : map->inisnull[0] = true;
282 :
283 103 : return map;
284 : }
285 :
286 : /*
287 : * Return a palloc'd bare attribute map for tuple conversion, matching input
288 : * and output columns by name. (Dropped columns are ignored in both input and
289 : * output.) This is normally a subroutine for convert_tuples_by_name, but can
290 : * be used standalone.
291 : */
292 : AttrNumber *
293 609 : convert_tuples_by_name_map(TupleDesc indesc,
294 : TupleDesc outdesc,
295 : const char *msg)
296 : {
297 : AttrNumber *attrMap;
298 : int n;
299 : int i;
300 :
301 609 : n = outdesc->natts;
302 609 : attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
303 2071 : for (i = 0; i < n; i++)
304 : {
305 1462 : Form_pg_attribute outatt = TupleDescAttr(outdesc, i);
306 : char *attname;
307 : Oid atttypid;
308 : int32 atttypmod;
309 : int j;
310 :
311 1462 : if (outatt->attisdropped)
312 44 : continue; /* attrMap[i] is already 0 */
313 1418 : attname = NameStr(outatt->attname);
314 1418 : atttypid = outatt->atttypid;
315 1418 : atttypmod = outatt->atttypmod;
316 2694 : for (j = 0; j < indesc->natts; j++)
317 : {
318 2694 : Form_pg_attribute inatt = TupleDescAttr(indesc, j);
319 :
320 2694 : if (inatt->attisdropped)
321 104 : continue;
322 2590 : if (strcmp(attname, NameStr(inatt->attname)) == 0)
323 : {
324 : /* Found it, check type */
325 1418 : if (atttypid != inatt->atttypid || atttypmod != inatt->atttypmod)
326 0 : ereport(ERROR,
327 : (errcode(ERRCODE_DATATYPE_MISMATCH),
328 : errmsg_internal("%s", _(msg)),
329 : errdetail("Attribute \"%s\" of type %s does not match corresponding attribute of type %s.",
330 : attname,
331 : format_type_be(outdesc->tdtypeid),
332 : format_type_be(indesc->tdtypeid))));
333 1418 : attrMap[i] = (AttrNumber) (j + 1);
334 1418 : break;
335 : }
336 : }
337 1418 : if (attrMap[i] == 0)
338 0 : ereport(ERROR,
339 : (errcode(ERRCODE_DATATYPE_MISMATCH),
340 : errmsg_internal("%s", _(msg)),
341 : errdetail("Attribute \"%s\" of type %s does not exist in type %s.",
342 : attname,
343 : format_type_be(outdesc->tdtypeid),
344 : format_type_be(indesc->tdtypeid))));
345 : }
346 :
347 609 : return attrMap;
348 : }
349 :
350 : /*
351 : * Perform conversion of a tuple according to the map.
352 : */
353 : HeapTuple
354 165 : do_convert_tuple(HeapTuple tuple, TupleConversionMap *map)
355 : {
356 165 : AttrNumber *attrMap = map->attrMap;
357 165 : Datum *invalues = map->invalues;
358 165 : bool *inisnull = map->inisnull;
359 165 : Datum *outvalues = map->outvalues;
360 165 : bool *outisnull = map->outisnull;
361 165 : int outnatts = map->outdesc->natts;
362 : int i;
363 :
364 : /*
365 : * Extract all the values of the old tuple, offsetting the arrays so that
366 : * invalues[0] is left NULL and invalues[1] is the first source attribute;
367 : * this exactly matches the numbering convention in attrMap.
368 : */
369 165 : heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1);
370 :
371 : /*
372 : * Transpose into proper fields of the new tuple.
373 : */
374 657 : for (i = 0; i < outnatts; i++)
375 : {
376 492 : int j = attrMap[i];
377 :
378 492 : outvalues[i] = invalues[j];
379 492 : outisnull[i] = inisnull[j];
380 : }
381 :
382 : /*
383 : * Now form the new tuple.
384 : */
385 165 : return heap_form_tuple(map->outdesc, outvalues, outisnull);
386 : }
387 :
388 : /*
389 : * Free a TupleConversionMap structure.
390 : */
391 : void
392 5 : free_conversion_map(TupleConversionMap *map)
393 : {
394 : /* indesc and outdesc are not ours to free */
395 5 : pfree(map->attrMap);
396 5 : pfree(map->invalues);
397 5 : pfree(map->inisnull);
398 5 : pfree(map->outvalues);
399 5 : pfree(map->outisnull);
400 5 : pfree(map);
401 5 : }
|