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