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-2016, 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 *
convert_tuples_by_position(TupleDesc indesc,TupleDesc outdesc,const char * msg)66 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 n = outdesc->natts;
81 attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
82 j = 0; /* j is next physical input attribute */
83 nincols = noutcols = 0; /* these count non-dropped attributes */
84 same = true;
85 for (i = 0; i < n; i++)
86 {
87 Form_pg_attribute att = outdesc->attrs[i];
88 Oid atttypid;
89 int32 atttypmod;
90
91 if (att->attisdropped)
92 continue; /* attrMap[i] is already 0 */
93 noutcols++;
94 atttypid = att->atttypid;
95 atttypmod = att->atttypmod;
96 for (; j < indesc->natts; j++)
97 {
98 att = indesc->attrs[j];
99 if (att->attisdropped)
100 continue;
101 nincols++;
102 /* Found matching column, check type */
103 if (atttypid != att->atttypid ||
104 (atttypmod != att->atttypmod && atttypmod >= 0))
105 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 attrMap[i] = (AttrNumber) (j + 1);
115 j++;
116 break;
117 }
118 if (attrMap[i] == 0)
119 same = false; /* we'll complain below */
120 }
121
122 /* Check for unused input columns */
123 for (; j < indesc->natts; j++)
124 {
125 if (indesc->attrs[j]->attisdropped)
126 continue;
127 nincols++;
128 same = false; /* we'll complain below */
129 }
130
131 /* Report column count mismatch using the non-dropped-column counts */
132 if (!same)
133 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 and the tuple types are the same.
142 * (We check the latter because if they're not, we want to do conversion
143 * to inject the right OID into the tuple datum.)
144 */
145 if (indesc->natts == outdesc->natts &&
146 indesc->tdtypeid == outdesc->tdtypeid)
147 {
148 for (i = 0; i < n; i++)
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 if (attrMap[i] == 0 &&
159 indesc->attrs[i]->attisdropped &&
160 indesc->attrs[i]->attlen == outdesc->attrs[i]->attlen &&
161 indesc->attrs[i]->attalign == outdesc->attrs[i]->attalign)
162 continue;
163
164 same = false;
165 break;
166 }
167 }
168 else
169 same = false;
170
171 if (same)
172 {
173 /* Runtime conversion is not needed */
174 pfree(attrMap);
175 return NULL;
176 }
177
178 /* Prepare the map structure */
179 map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap));
180 map->indesc = indesc;
181 map->outdesc = outdesc;
182 map->attrMap = attrMap;
183 /* preallocate workspace for Datum arrays */
184 map->outvalues = (Datum *) palloc(n * sizeof(Datum));
185 map->outisnull = (bool *) palloc(n * sizeof(bool));
186 n = indesc->natts + 1; /* +1 for NULL */
187 map->invalues = (Datum *) palloc(n * sizeof(Datum));
188 map->inisnull = (bool *) palloc(n * sizeof(bool));
189 map->invalues[0] = (Datum) 0; /* set up the NULL entry */
190 map->inisnull[0] = true;
191
192 return map;
193 }
194
195 /*
196 * Set up for tuple conversion, matching input and output columns by name.
197 * (Dropped columns are ignored in both input and output.) This is intended
198 * for use when the rowtypes are related by inheritance, so we expect an exact
199 * match of both type and typmod. The error messages will be a bit unhelpful
200 * unless both rowtypes are named composite types.
201 */
202 TupleConversionMap *
convert_tuples_by_name(TupleDesc indesc,TupleDesc outdesc,const char * msg)203 convert_tuples_by_name(TupleDesc indesc,
204 TupleDesc outdesc,
205 const char *msg)
206 {
207 TupleConversionMap *map;
208 AttrNumber *attrMap;
209 int n = outdesc->natts;
210 int i;
211 bool same;
212
213 /* Verify compatibility and prepare attribute-number map */
214 attrMap = convert_tuples_by_name_map(indesc, outdesc, msg);
215
216 /*
217 * Check to see if the map is one-to-one and the tuple types are the same.
218 * (We check the latter because if they're not, we want to do conversion
219 * to inject the right OID into the tuple datum.)
220 */
221 if (indesc->natts == outdesc->natts &&
222 indesc->tdtypeid == outdesc->tdtypeid)
223 {
224 same = true;
225 for (i = 0; i < n; i++)
226 {
227 if (attrMap[i] == (i + 1))
228 continue;
229
230 /*
231 * If it's a dropped column and the corresponding input column is
232 * also dropped, we needn't convert. However, attlen and attalign
233 * must agree.
234 */
235 if (attrMap[i] == 0 &&
236 indesc->attrs[i]->attisdropped &&
237 indesc->attrs[i]->attlen == outdesc->attrs[i]->attlen &&
238 indesc->attrs[i]->attalign == outdesc->attrs[i]->attalign)
239 continue;
240
241 same = false;
242 break;
243 }
244 }
245 else
246 same = false;
247
248 if (same)
249 {
250 /* Runtime conversion is not needed */
251 pfree(attrMap);
252 return NULL;
253 }
254
255 /* Prepare the map structure */
256 map = (TupleConversionMap *) palloc(sizeof(TupleConversionMap));
257 map->indesc = indesc;
258 map->outdesc = outdesc;
259 map->attrMap = attrMap;
260 /* preallocate workspace for Datum arrays */
261 map->outvalues = (Datum *) palloc(n * sizeof(Datum));
262 map->outisnull = (bool *) palloc(n * sizeof(bool));
263 n = indesc->natts + 1; /* +1 for NULL */
264 map->invalues = (Datum *) palloc(n * sizeof(Datum));
265 map->inisnull = (bool *) palloc(n * sizeof(bool));
266 map->invalues[0] = (Datum) 0; /* set up the NULL entry */
267 map->inisnull[0] = true;
268
269 return map;
270 }
271
272 /*
273 * Return a palloc'd bare attribute map for tuple conversion, matching input
274 * and output columns by name. (Dropped columns are ignored in both input and
275 * output.) This is normally a subroutine for convert_tuples_by_name, but can
276 * be used standalone.
277 */
278 AttrNumber *
convert_tuples_by_name_map(TupleDesc indesc,TupleDesc outdesc,const char * msg)279 convert_tuples_by_name_map(TupleDesc indesc,
280 TupleDesc outdesc,
281 const char *msg)
282 {
283 AttrNumber *attrMap;
284 int n;
285 int i;
286
287 n = outdesc->natts;
288 attrMap = (AttrNumber *) palloc0(n * sizeof(AttrNumber));
289 for (i = 0; i < n; i++)
290 {
291 Form_pg_attribute att = outdesc->attrs[i];
292 char *attname;
293 Oid atttypid;
294 int32 atttypmod;
295 int j;
296
297 if (att->attisdropped)
298 continue; /* attrMap[i] is already 0 */
299 attname = NameStr(att->attname);
300 atttypid = att->atttypid;
301 atttypmod = att->atttypmod;
302 for (j = 0; j < indesc->natts; j++)
303 {
304 att = indesc->attrs[j];
305 if (att->attisdropped)
306 continue;
307 if (strcmp(attname, NameStr(att->attname)) == 0)
308 {
309 /* Found it, check type */
310 if (atttypid != att->atttypid || atttypmod != att->atttypmod)
311 ereport(ERROR,
312 (errcode(ERRCODE_DATATYPE_MISMATCH),
313 errmsg_internal("%s", _(msg)),
314 errdetail("Attribute \"%s\" of type %s does not match corresponding attribute of type %s.",
315 attname,
316 format_type_be(outdesc->tdtypeid),
317 format_type_be(indesc->tdtypeid))));
318 attrMap[i] = (AttrNumber) (j + 1);
319 break;
320 }
321 }
322 if (attrMap[i] == 0)
323 ereport(ERROR,
324 (errcode(ERRCODE_DATATYPE_MISMATCH),
325 errmsg_internal("%s", _(msg)),
326 errdetail("Attribute \"%s\" of type %s does not exist in type %s.",
327 attname,
328 format_type_be(outdesc->tdtypeid),
329 format_type_be(indesc->tdtypeid))));
330 }
331
332 return attrMap;
333 }
334
335 /*
336 * Perform conversion of a tuple according to the map.
337 */
338 HeapTuple
do_convert_tuple(HeapTuple tuple,TupleConversionMap * map)339 do_convert_tuple(HeapTuple tuple, TupleConversionMap *map)
340 {
341 AttrNumber *attrMap = map->attrMap;
342 Datum *invalues = map->invalues;
343 bool *inisnull = map->inisnull;
344 Datum *outvalues = map->outvalues;
345 bool *outisnull = map->outisnull;
346 int outnatts = map->outdesc->natts;
347 int i;
348
349 /*
350 * Extract all the values of the old tuple, offsetting the arrays so that
351 * invalues[0] is left NULL and invalues[1] is the first source attribute;
352 * this exactly matches the numbering convention in attrMap.
353 */
354 heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1);
355
356 /*
357 * Transpose into proper fields of the new tuple.
358 */
359 for (i = 0; i < outnatts; i++)
360 {
361 int j = attrMap[i];
362
363 outvalues[i] = invalues[j];
364 outisnull[i] = inisnull[j];
365 }
366
367 /*
368 * Now form the new tuple.
369 */
370 return heap_form_tuple(map->outdesc, outvalues, outisnull);
371 }
372
373 /*
374 * Free a TupleConversionMap structure.
375 */
376 void
free_conversion_map(TupleConversionMap * map)377 free_conversion_map(TupleConversionMap *map)
378 {
379 /* indesc and outdesc are not ours to free */
380 pfree(map->attrMap);
381 pfree(map->invalues);
382 pfree(map->inisnull);
383 pfree(map->outvalues);
384 pfree(map->outisnull);
385 pfree(map);
386 }
387