1 /*-------------------------------------------------------------------------
2  *
3  * format_type.c
4  *	  Display type names "nicely".
5  *
6  *
7  * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
8  * Portions Copyright (c) 1994, Regents of the University of California
9  *
10  * IDENTIFICATION
11  *	  src/backend/utils/adt/format_type.c
12  *
13  *-------------------------------------------------------------------------
14  */
15 
16 #include "postgres.h"
17 
18 #include <ctype.h>
19 
20 #include "access/htup_details.h"
21 #include "catalog/namespace.h"
22 #include "catalog/pg_type.h"
23 #include "utils/builtins.h"
24 #include "utils/lsyscache.h"
25 #include "utils/numeric.h"
26 #include "utils/syscache.h"
27 #include "mb/pg_wchar.h"
28 
29 #define MAX_INT32_LEN 11
30 
31 static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
32 
33 
34 /*
35  * SQL function: format_type(type_oid, typemod)
36  *
37  * `type_oid' is from pg_type.oid, `typemod' is from
38  * pg_attribute.atttypmod. This function will get the type name and
39  * format it and the modifier to canonical SQL format, if the type is
40  * a standard type. Otherwise you just get pg_type.typname back,
41  * double quoted if it contains funny characters or matches a keyword.
42  *
43  * If typemod is NULL then we are formatting a type name in a context where
44  * no typemod is available, eg a function argument or result type.  This
45  * yields a slightly different result from specifying typemod = -1 in some
46  * cases.  Given typemod = -1 we feel compelled to produce an output that
47  * the parser will interpret as having typemod -1, so that pg_dump will
48  * produce CREATE TABLE commands that recreate the original state.  But
49  * given NULL typemod, we assume that the parser's interpretation of
50  * typemod doesn't matter, and so we are willing to output a slightly
51  * "prettier" representation of the same type.  For example, type = bpchar
52  * and typemod = NULL gets you "character", whereas typemod = -1 gets you
53  * "bpchar" --- the former will be interpreted as character(1) by the
54  * parser, which does not yield typemod -1.
55  *
56  * XXX encoding a meaning in typemod = NULL is ugly; it'd have been
57  * cleaner to make two functions of one and two arguments respectively.
58  * Not worth changing it now, however.
59  */
60 Datum
format_type(PG_FUNCTION_ARGS)61 format_type(PG_FUNCTION_ARGS)
62 {
63 	Oid			type_oid;
64 	int32		typemod;
65 	char	   *result;
66 	bits16		flags = FORMAT_TYPE_ALLOW_INVALID;
67 
68 	/* Since this function is not strict, we must test for null args */
69 	if (PG_ARGISNULL(0))
70 		PG_RETURN_NULL();
71 
72 	type_oid = PG_GETARG_OID(0);
73 
74 	if (PG_ARGISNULL(1))
75 		typemod = -1;
76 	else
77 	{
78 		typemod = PG_GETARG_INT32(1);
79 		flags |= FORMAT_TYPE_TYPEMOD_GIVEN;
80 	}
81 
82 	result = format_type_extended(type_oid, typemod, flags);
83 
84 	PG_RETURN_TEXT_P(cstring_to_text(result));
85 }
86 
87 /*
88  * format_type_extended
89  *		Generate a possibly-qualified type name.
90  *
91  * The default behavior is to only qualify if the type is not in the search
92  * path, to ignore the given typmod, and to raise an error if a non-existent
93  * type_oid is given.
94  *
95  * The following bits in 'flags' modify the behavior:
96  * - FORMAT_TYPE_TYPEMOD_GIVEN
97  *			include the typmod in the output (typmod could still be -1 though)
98  * - FORMAT_TYPE_ALLOW_INVALID
99  *			if the type OID is invalid or unknown, return ??? or such instead
100  *			of failing
101  * - FORMAT_TYPE_FORCE_QUALIFY
102  *			always schema-qualify type names, regardless of search_path
103  *
104  * Note that TYPEMOD_GIVEN is not interchangeable with "typemod == -1";
105  * see the comments above for format_type().
106  *
107  * Returns a palloc'd string.
108  */
109 char *
format_type_extended(Oid type_oid,int32 typemod,bits16 flags)110 format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
111 {
112 	HeapTuple	tuple;
113 	Form_pg_type typeform;
114 	Oid			array_base_type;
115 	bool		is_array;
116 	char	   *buf;
117 	bool		with_typemod;
118 
119 	if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
120 		return pstrdup("-");
121 
122 	tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
123 	if (!HeapTupleIsValid(tuple))
124 	{
125 		if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
126 			return pstrdup("???");
127 		else
128 			elog(ERROR, "cache lookup failed for type %u", type_oid);
129 	}
130 	typeform = (Form_pg_type) GETSTRUCT(tuple);
131 
132 	/*
133 	 * Check if it's a regular (variable length) array type.  Fixed-length
134 	 * array types such as "name" shouldn't get deconstructed.  As of Postgres
135 	 * 8.1, rather than checking typlen we check the toast property, and don't
136 	 * deconstruct "plain storage" array types --- this is because we don't
137 	 * want to show oidvector as oid[].
138 	 */
139 	array_base_type = typeform->typelem;
140 
141 	if (array_base_type != InvalidOid && typeform->typstorage != 'p')
142 	{
143 		/* Switch our attention to the array element type */
144 		ReleaseSysCache(tuple);
145 		tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
146 		if (!HeapTupleIsValid(tuple))
147 		{
148 			if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
149 				return pstrdup("???[]");
150 			else
151 				elog(ERROR, "cache lookup failed for type %u", type_oid);
152 		}
153 		typeform = (Form_pg_type) GETSTRUCT(tuple);
154 		type_oid = array_base_type;
155 		is_array = true;
156 	}
157 	else
158 		is_array = false;
159 
160 	with_typemod = (flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0 && (typemod >= 0);
161 
162 	/*
163 	 * See if we want to special-case the output for certain built-in types.
164 	 * Note that these special cases should all correspond to special
165 	 * productions in gram.y, to ensure that the type name will be taken as a
166 	 * system type, not a user type of the same name.
167 	 *
168 	 * If we do not provide a special-case output here, the type name will be
169 	 * handled the same way as a user type name --- in particular, it will be
170 	 * double-quoted if it matches any lexer keyword.  This behavior is
171 	 * essential for some cases, such as types "bit" and "char".
172 	 */
173 	buf = NULL;					/* flag for no special case */
174 
175 	switch (type_oid)
176 	{
177 		case BITOID:
178 			if (with_typemod)
179 				buf = printTypmod("bit", typemod, typeform->typmodout);
180 			else if ((flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0)
181 			{
182 				/*
183 				 * bit with typmod -1 is not the same as BIT, which means
184 				 * BIT(1) per SQL spec.  Report it as the quoted typename so
185 				 * that parser will not assign a bogus typmod.
186 				 */
187 			}
188 			else
189 				buf = pstrdup("bit");
190 			break;
191 
192 		case BOOLOID:
193 			buf = pstrdup("boolean");
194 			break;
195 
196 		case BPCHAROID:
197 			if (with_typemod)
198 				buf = printTypmod("character", typemod, typeform->typmodout);
199 			else if ((flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0)
200 			{
201 				/*
202 				 * bpchar with typmod -1 is not the same as CHARACTER, which
203 				 * means CHARACTER(1) per SQL spec.  Report it as bpchar so
204 				 * that parser will not assign a bogus typmod.
205 				 */
206 			}
207 			else
208 				buf = pstrdup("character");
209 			break;
210 
211 		case FLOAT4OID:
212 			buf = pstrdup("real");
213 			break;
214 
215 		case FLOAT8OID:
216 			buf = pstrdup("double precision");
217 			break;
218 
219 		case INT2OID:
220 			buf = pstrdup("smallint");
221 			break;
222 
223 		case INT4OID:
224 			buf = pstrdup("integer");
225 			break;
226 
227 		case INT8OID:
228 			buf = pstrdup("bigint");
229 			break;
230 
231 		case NUMERICOID:
232 			if (with_typemod)
233 				buf = printTypmod("numeric", typemod, typeform->typmodout);
234 			else
235 				buf = pstrdup("numeric");
236 			break;
237 
238 		case INTERVALOID:
239 			if (with_typemod)
240 				buf = printTypmod("interval", typemod, typeform->typmodout);
241 			else
242 				buf = pstrdup("interval");
243 			break;
244 
245 		case TIMEOID:
246 			if (with_typemod)
247 				buf = printTypmod("time", typemod, typeform->typmodout);
248 			else
249 				buf = pstrdup("time without time zone");
250 			break;
251 
252 		case TIMETZOID:
253 			if (with_typemod)
254 				buf = printTypmod("time", typemod, typeform->typmodout);
255 			else
256 				buf = pstrdup("time with time zone");
257 			break;
258 
259 		case TIMESTAMPOID:
260 			if (with_typemod)
261 				buf = printTypmod("timestamp", typemod, typeform->typmodout);
262 			else
263 				buf = pstrdup("timestamp without time zone");
264 			break;
265 
266 		case TIMESTAMPTZOID:
267 			if (with_typemod)
268 				buf = printTypmod("timestamp", typemod, typeform->typmodout);
269 			else
270 				buf = pstrdup("timestamp with time zone");
271 			break;
272 
273 		case VARBITOID:
274 			if (with_typemod)
275 				buf = printTypmod("bit varying", typemod, typeform->typmodout);
276 			else
277 				buf = pstrdup("bit varying");
278 			break;
279 
280 		case VARCHAROID:
281 			if (with_typemod)
282 				buf = printTypmod("character varying", typemod, typeform->typmodout);
283 			else
284 				buf = pstrdup("character varying");
285 			break;
286 	}
287 
288 	if (buf == NULL)
289 	{
290 		/*
291 		 * Default handling: report the name as it appears in the catalog.
292 		 * Here, we must qualify the name if it is not visible in the search
293 		 * path or if caller requests it; and we must double-quote it if it's
294 		 * not a standard identifier or if it matches any keyword.
295 		 */
296 		char	   *nspname;
297 		char	   *typname;
298 
299 		if ((flags & FORMAT_TYPE_FORCE_QUALIFY) == 0 &&
300 			TypeIsVisible(type_oid))
301 			nspname = NULL;
302 		else
303 			nspname = get_namespace_name_or_temp(typeform->typnamespace);
304 
305 		typname = NameStr(typeform->typname);
306 
307 		buf = quote_qualified_identifier(nspname, typname);
308 
309 		if (with_typemod)
310 			buf = printTypmod(buf, typemod, typeform->typmodout);
311 	}
312 
313 	if (is_array)
314 		buf = psprintf("%s[]", buf);
315 
316 	ReleaseSysCache(tuple);
317 
318 	return buf;
319 }
320 
321 /*
322  * This version is for use within the backend in error messages, etc.
323  * One difference is that it will fail for an invalid type.
324  *
325  * The result is always a palloc'd string.
326  */
327 char *
format_type_be(Oid type_oid)328 format_type_be(Oid type_oid)
329 {
330 	return format_type_extended(type_oid, -1, 0);
331 }
332 
333 /*
334  * This version returns a name that is always qualified (unless it's one
335  * of the SQL-keyword type names, such as TIMESTAMP WITH TIME ZONE).
336  */
337 char *
format_type_be_qualified(Oid type_oid)338 format_type_be_qualified(Oid type_oid)
339 {
340 	return format_type_extended(type_oid, -1, FORMAT_TYPE_FORCE_QUALIFY);
341 }
342 
343 /*
344  * This version allows a nondefault typemod to be specified.
345  */
346 char *
format_type_with_typemod(Oid type_oid,int32 typemod)347 format_type_with_typemod(Oid type_oid, int32 typemod)
348 {
349 	return format_type_extended(type_oid, typemod, FORMAT_TYPE_TYPEMOD_GIVEN);
350 }
351 
352 /*
353  * Add typmod decoration to the basic type name
354  */
355 static char *
printTypmod(const char * typname,int32 typmod,Oid typmodout)356 printTypmod(const char *typname, int32 typmod, Oid typmodout)
357 {
358 	char	   *res;
359 
360 	/* Shouldn't be called if typmod is -1 */
361 	Assert(typmod >= 0);
362 
363 	if (typmodout == InvalidOid)
364 	{
365 		/* Default behavior: just print the integer typmod with parens */
366 		res = psprintf("%s(%d)", typname, (int) typmod);
367 	}
368 	else
369 	{
370 		/* Use the type-specific typmodout procedure */
371 		char	   *tmstr;
372 
373 		tmstr = DatumGetCString(OidFunctionCall1(typmodout,
374 												 Int32GetDatum(typmod)));
375 		res = psprintf("%s%s", typname, tmstr);
376 	}
377 
378 	return res;
379 }
380 
381 
382 /*
383  * type_maximum_size --- determine maximum width of a variable-width column
384  *
385  * If the max width is indeterminate, return -1.  In particular, we return
386  * -1 for any type not known to this routine.  We assume the caller has
387  * already determined that the type is a variable-width type, so it's not
388  * necessary to look up the type's pg_type tuple here.
389  *
390  * This may appear unrelated to format_type(), but in fact the two routines
391  * share knowledge of the encoding of typmod for different types, so it's
392  * convenient to keep them together.  (XXX now that most of this knowledge
393  * has been pushed out of format_type into the typmodout functions, it's
394  * interesting to wonder if it's worth trying to factor this code too...)
395  */
396 int32
type_maximum_size(Oid type_oid,int32 typemod)397 type_maximum_size(Oid type_oid, int32 typemod)
398 {
399 	if (typemod < 0)
400 		return -1;
401 
402 	switch (type_oid)
403 	{
404 		case BPCHAROID:
405 		case VARCHAROID:
406 			/* typemod includes varlena header */
407 
408 			/* typemod is in characters not bytes */
409 			return (typemod - VARHDRSZ) *
410 				pg_encoding_max_length(GetDatabaseEncoding())
411 				+ VARHDRSZ;
412 
413 		case NUMERICOID:
414 			return numeric_maximum_size(typemod);
415 
416 		case VARBITOID:
417 		case BITOID:
418 			/* typemod is the (max) number of bits */
419 			return (typemod + (BITS_PER_BYTE - 1)) / BITS_PER_BYTE
420 				+ 2 * sizeof(int32);
421 	}
422 
423 	/* Unknown type, or unlimited-width type such as 'text' */
424 	return -1;
425 }
426 
427 
428 /*
429  * oidvectortypes			- converts a vector of type OIDs to "typname" list
430  */
431 Datum
oidvectortypes(PG_FUNCTION_ARGS)432 oidvectortypes(PG_FUNCTION_ARGS)
433 {
434 	oidvector  *oidArray = (oidvector *) PG_GETARG_POINTER(0);
435 	char	   *result;
436 	int			numargs = oidArray->dim1;
437 	int			num;
438 	size_t		total;
439 	size_t		left;
440 
441 	total = 20 * numargs + 1;
442 	result = palloc(total);
443 	result[0] = '\0';
444 	left = total - 1;
445 
446 	for (num = 0; num < numargs; num++)
447 	{
448 		char	   *typename = format_type_extended(oidArray->values[num], -1,
449 													FORMAT_TYPE_ALLOW_INVALID);
450 		size_t		slen = strlen(typename);
451 
452 		if (left < (slen + 2))
453 		{
454 			total += slen + 2;
455 			result = repalloc(result, total);
456 			left += slen + 2;
457 		}
458 
459 		if (num > 0)
460 		{
461 			strcat(result, ", ");
462 			left -= 2;
463 		}
464 		strcat(result, typename);
465 		left -= slen;
466 	}
467 
468 	PG_RETURN_TEXT_P(cstring_to_text(result));
469 }
470