* Display type names "nicely".
*
*
- * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/format_type.c,v 1.22 2001/11/12 21:04:46 tgl Exp $
+ * src/backend/utils/adt/format_type.c
*
*-------------------------------------------------------------------------
*/
#include <ctype.h>
-#include "fmgr.h"
+#include "access/htup_details.h"
+#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
-#include "utils/datetime.h"
+#include "utils/lsyscache.h"
#include "utils/numeric.h"
#include "utils/syscache.h"
-#ifdef MULTIBYTE
#include "mb/pg_wchar.h"
-#endif
-#define MASK(b) (1 << (b))
-
-#define MAX_INT32_LEN 11
-#define _textin(str) DirectFunctionCall1(textin, CStringGetDatum(str))
-
-
-static char *format_type_internal(Oid type_oid, int32 typemod, bool allow_invalid);
-
-
-static char *
-psnprintf(size_t len, const char *fmt,...)
-{
- va_list ap;
- char *buf;
-
- buf = palloc(len);
-
- va_start(ap, fmt);
- vsnprintf(buf, len, fmt, ap);
- va_end(ap);
-
- return buf;
-}
+static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
/*
* pg_attribute.atttypmod. This function will get the type name and
* format it and the modifier to canonical SQL format, if the type is
* a standard type. Otherwise you just get pg_type.typname back,
- * double quoted if it contains funny characters.
+ * double quoted if it contains funny characters or matches a keyword.
+ *
+ * If typemod is NULL then we are formatting a type name in a context where
+ * no typemod is available, eg a function argument or result type. This
+ * yields a slightly different result from specifying typemod = -1 in some
+ * cases. Given typemod = -1 we feel compelled to produce an output that
+ * the parser will interpret as having typemod -1, so that pg_dump will
+ * produce CREATE TABLE commands that recreate the original state. But
+ * given NULL typemod, we assume that the parser's interpretation of
+ * typemod doesn't matter, and so we are willing to output a slightly
+ * "prettier" representation of the same type. For example, type = bpchar
+ * and typemod = NULL gets you "character", whereas typemod = -1 gets you
+ * "bpchar" --- the former will be interpreted as character(1) by the
+ * parser, which does not yield typemod -1.
*
- * If typemod is null (in the SQL sense) then you won't get any
- * "..(x)" type qualifiers. The result is not technically correct,
- * because the various types interpret missing type modifiers
- * differently, but it can be used as a convenient way to format
- * system catalogs, e.g., pg_aggregate, in psql.
+ * XXX encoding a meaning in typemod = NULL is ugly; it'd have been
+ * cleaner to make two functions of one and two arguments respectively.
+ * Not worth changing it now, however.
*/
Datum
format_type(PG_FUNCTION_ARGS)
Oid type_oid;
int32 typemod;
char *result;
+ bits16 flags = FORMAT_TYPE_ALLOW_INVALID;
+ /* Since this function is not strict, we must test for null args */
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
type_oid = PG_GETARG_OID(0);
- if (!PG_ARGISNULL(1))
- typemod = PG_GETARG_INT32(1);
+ if (PG_ARGISNULL(1))
+ typemod = -1;
else
- typemod = -1; /* default typmod */
+ {
+ typemod = PG_GETARG_INT32(1);
+ flags |= FORMAT_TYPE_TYPEMOD_GIVEN;
+ }
- result = format_type_internal(type_oid, typemod, true);
+ result = format_type_extended(type_oid, typemod, flags);
- PG_RETURN_DATUM(_textin(result));
+ PG_RETURN_TEXT_P(cstring_to_text(result));
}
/*
- * This version is for use within the backend in error messages, etc.
- * One difference is that it will fail for an invalid type.
+ * format_type_extended
+ * Generate a possibly-qualified type name.
*
- * The result is always a palloc'd string.
- */
-char *
-format_type_be(Oid type_oid)
-{
- return format_type_internal(type_oid, -1, false);
-}
-
-/*
- * This version allows a nondefault typemod to be specified.
+ * The default behavior is to only qualify if the type is not in the search
+ * path, to ignore the given typmod, and to raise an error if a non-existent
+ * type_oid is given.
+ *
+ * The following bits in 'flags' modify the behavior:
+ * - FORMAT_TYPE_TYPEMOD_GIVEN
+ * include the typmod in the output (typmod could still be -1 though)
+ * - FORMAT_TYPE_ALLOW_INVALID
+ * if the type OID is invalid or unknown, return ??? or such instead
+ * of failing
+ * - FORMAT_TYPE_FORCE_QUALIFY
+ * always schema-qualify type names, regardless of search_path
+ *
+ * Note that TYPEMOD_GIVEN is not interchangeable with "typemod == -1";
+ * see the comments above for format_type().
+ *
+ * Returns a palloc'd string.
*/
char *
-format_type_with_typemod(Oid type_oid, int32 typemod)
-{
- return format_type_internal(type_oid, typemod, false);
-}
-
-
-
-static char *
-format_type_internal(Oid type_oid, int32 typemod, bool allow_invalid)
+format_type_extended(Oid type_oid, int32 typemod, bits16 flags)
{
- bool with_typemod = (typemod >= 0);
HeapTuple tuple;
+ Form_pg_type typeform;
Oid array_base_type;
- int16 typlen;
bool is_array;
- char *name;
char *buf;
+ bool with_typemod;
- if (type_oid == InvalidOid && allow_invalid)
+ if (type_oid == InvalidOid && (flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
return pstrdup("-");
- tuple = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(type_oid),
- 0, 0, 0);
+ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
if (!HeapTupleIsValid(tuple))
{
- if (allow_invalid)
+ if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
return pstrdup("???");
else
- elog(ERROR, "could not locate data type with oid %u in catalog",
- type_oid);
+ elog(ERROR, "cache lookup failed for type %u", type_oid);
}
-
- array_base_type = ((Form_pg_type) GETSTRUCT(tuple))->typelem;
- typlen = ((Form_pg_type) GETSTRUCT(tuple))->typlen;
- if (array_base_type != 0 && typlen < 0)
+ typeform = (Form_pg_type) GETSTRUCT(tuple);
+
+ /*
+ * Check if it's a regular (variable length) array type. Fixed-length
+ * array types such as "name" shouldn't get deconstructed. As of Postgres
+ * 8.1, rather than checking typlen we check the toast property, and don't
+ * deconstruct "plain storage" array types --- this is because we don't
+ * want to show oidvector as oid[].
+ */
+ array_base_type = typeform->typelem;
+
+ if (array_base_type != InvalidOid && typeform->typstorage != 'p')
{
/* Switch our attention to the array element type */
ReleaseSysCache(tuple);
- tuple = SearchSysCache(TYPEOID,
- ObjectIdGetDatum(array_base_type),
- 0, 0, 0);
+ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
if (!HeapTupleIsValid(tuple))
{
- if (allow_invalid)
+ if ((flags & FORMAT_TYPE_ALLOW_INVALID) != 0)
return pstrdup("???[]");
else
- elog(ERROR, "could not locate data type with oid %u in catalog",
- type_oid);
+ elog(ERROR, "cache lookup failed for type %u", type_oid);
}
- is_array = true;
+ typeform = (Form_pg_type) GETSTRUCT(tuple);
type_oid = array_base_type;
+ is_array = true;
}
else
is_array = false;
+ with_typemod = (flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0 && (typemod >= 0);
+
+ /*
+ * See if we want to special-case the output for certain built-in types.
+ * Note that these special cases should all correspond to special
+ * productions in gram.y, to ensure that the type name will be taken as a
+ * system type, not a user type of the same name.
+ *
+ * If we do not provide a special-case output here, the type name will be
+ * handled the same way as a user type name --- in particular, it will be
+ * double-quoted if it matches any lexer keyword. This behavior is
+ * essential for some cases, such as types "bit" and "char".
+ */
+ buf = NULL; /* flag for no special case */
+
switch (type_oid)
{
case BITOID:
if (with_typemod)
- buf = psnprintf(5 + MAX_INT32_LEN + 1, "bit(%d)",
- (int) typemod);
- else
+ buf = printTypmod("bit", typemod, typeform->typmodout);
+ else if ((flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0)
{
/*
- * bit with no typmod is not the same as BIT, which means
- * BIT(1) per SQL spec. Report it as the quoted typename
- * so that parser will not assign a bogus typmod.
+ * bit with typmod -1 is not the same as BIT, which means
+ * BIT(1) per SQL spec. Report it as the quoted typename so
+ * that parser will not assign a bogus typmod.
*/
- buf = pstrdup("\"bit\"");
}
+ else
+ buf = pstrdup("bit");
break;
case BOOLOID:
case BPCHAROID:
if (with_typemod)
- buf = psnprintf(11 + MAX_INT32_LEN + 1, "character(%d)",
- (int) (typemod - VARHDRSZ));
- else
+ buf = printTypmod("character", typemod, typeform->typmodout);
+ else if ((flags & FORMAT_TYPE_TYPEMOD_GIVEN) != 0)
{
/*
- * bpchar with no typmod is not the same as CHARACTER,
- * which means CHARACTER(1) per SQL spec. Report it as
- * bpchar so that parser will not assign a bogus typmod.
+ * bpchar with typmod -1 is not the same as CHARACTER, which
+ * means CHARACTER(1) per SQL spec. Report it as bpchar so
+ * that parser will not assign a bogus typmod.
*/
- buf = pstrdup("bpchar");
}
- break;
-
- case CHAROID:
- /*
- * This char type is the single-byte version. You have to
- * double-quote it to get at it in the parser.
- */
- buf = pstrdup("\"char\"");
+ else
+ buf = pstrdup("character");
break;
case FLOAT4OID:
case NUMERICOID:
if (with_typemod)
- buf = psnprintf(10 + 2 * MAX_INT32_LEN + 1, "numeric(%d,%d)",
- ((typemod - VARHDRSZ) >> 16) & 0xffff,
- (typemod - VARHDRSZ) & 0xffff);
+ buf = printTypmod("numeric", typemod, typeform->typmodout);
else
buf = pstrdup("numeric");
break;
case INTERVALOID:
if (with_typemod)
- {
- int fields = typemod >> 16;
- int precision = typemod & 0xFFFF;
- const char *fieldstr;
-
- switch (fields)
- {
- case MASK(YEAR):
- fieldstr = " year";
- break;
- case MASK(MONTH):
- fieldstr = " month";
- break;
- case MASK(DAY):
- fieldstr = " day";
- break;
- case MASK(HOUR):
- fieldstr = " hour";
- break;
- case MASK(MINUTE):
- fieldstr = " minute";
- break;
- case MASK(SECOND):
- fieldstr = " second";
- break;
- case MASK(YEAR) | MASK(MONTH):
- fieldstr = " year to month";
- break;
- case MASK(DAY) | MASK(HOUR):
- fieldstr = " day to hour";
- break;
- case MASK(DAY) | MASK(HOUR) | MASK(MINUTE):
- fieldstr = " day to minute";
- break;
- case MASK(DAY) | MASK(HOUR) | MASK(MINUTE) | MASK(SECOND):
- fieldstr = " day to second";
- break;
- case MASK(HOUR) | MASK(MINUTE):
- fieldstr = " hour to minute";
- break;
- case MASK(HOUR) | MASK(MINUTE) | MASK(SECOND):
- fieldstr = " hour to second";
- break;
- case MASK(MINUTE) | MASK(SECOND):
- fieldstr = " minute to second";
- break;
- case 0x7FFF:
- fieldstr = "";
- break;
- default:
- elog(DEBUG, "Invalid INTERVAL typmod 0x%x", typemod);
- fieldstr = "";
- break;
- }
- if (precision != 0xFFFF)
- buf = psnprintf(100, "interval(%d)%s",
- precision, fieldstr);
- else
- buf = psnprintf(100, "interval%s",
- fieldstr);
- }
+ buf = printTypmod("interval", typemod, typeform->typmodout);
else
buf = pstrdup("interval");
break;
case TIMEOID:
if (with_typemod)
- buf = psnprintf(50, "time(%d) without time zone",
- typemod);
+ buf = printTypmod("time", typemod, typeform->typmodout);
else
buf = pstrdup("time without time zone");
break;
case TIMETZOID:
if (with_typemod)
- buf = psnprintf(50, "time(%d) with time zone",
- typemod);
+ buf = printTypmod("time", typemod, typeform->typmodout);
else
buf = pstrdup("time with time zone");
break;
case TIMESTAMPOID:
if (with_typemod)
- buf = psnprintf(50, "timestamp(%d) without time zone",
- typemod);
+ buf = printTypmod("timestamp", typemod, typeform->typmodout);
else
buf = pstrdup("timestamp without time zone");
break;
case TIMESTAMPTZOID:
if (with_typemod)
- buf = psnprintf(50, "timestamp(%d) with time zone",
- typemod);
+ buf = printTypmod("timestamp", typemod, typeform->typmodout);
else
buf = pstrdup("timestamp with time zone");
break;
case VARBITOID:
if (with_typemod)
- buf = psnprintf(13 + MAX_INT32_LEN + 1, "bit varying(%d)",
- (int) typemod);
+ buf = printTypmod("bit varying", typemod, typeform->typmodout);
else
buf = pstrdup("bit varying");
break;
case VARCHAROID:
if (with_typemod)
- buf = psnprintf(19 + MAX_INT32_LEN + 1,
- "character varying(%d)",
- (int) (typemod - VARHDRSZ));
+ buf = printTypmod("character varying", typemod, typeform->typmodout);
else
buf = pstrdup("character varying");
break;
+ }
- default:
- name = NameStr(((Form_pg_type) GETSTRUCT(tuple))->typname);
- if (strspn(name, "abcdefghijklmnopqrstuvwxyz0123456789_") != strlen(name)
- || isdigit((unsigned char) name[0]))
- buf = psnprintf(strlen(name) + 3, "\"%s\"", name);
- else
- buf = pstrdup(name);
- break;
+ if (buf == NULL)
+ {
+ /*
+ * Default handling: report the name as it appears in the catalog.
+ * Here, we must qualify the name if it is not visible in the search
+ * path or if caller requests it; and we must double-quote it if it's
+ * not a standard identifier or if it matches any keyword.
+ */
+ char *nspname;
+ char *typname;
+
+ if ((flags & FORMAT_TYPE_FORCE_QUALIFY) == 0 &&
+ TypeIsVisible(type_oid))
+ nspname = NULL;
+ else
+ nspname = get_namespace_name_or_temp(typeform->typnamespace);
+
+ typname = NameStr(typeform->typname);
+
+ buf = quote_qualified_identifier(nspname, typname);
+
+ if (with_typemod)
+ buf = printTypmod(buf, typemod, typeform->typmodout);
}
if (is_array)
- buf = psnprintf(strlen(buf) + 3, "%s[]", buf);
+ buf = psprintf("%s[]", buf);
ReleaseSysCache(tuple);
return buf;
}
+/*
+ * This version is for use within the backend in error messages, etc.
+ * One difference is that it will fail for an invalid type.
+ *
+ * The result is always a palloc'd string.
+ */
+char *
+format_type_be(Oid type_oid)
+{
+ return format_type_extended(type_oid, -1, 0);
+}
+
+/*
+ * This version returns a name that is always qualified (unless it's one
+ * of the SQL-keyword type names, such as TIMESTAMP WITH TIME ZONE).
+ */
+char *
+format_type_be_qualified(Oid type_oid)
+{
+ return format_type_extended(type_oid, -1, FORMAT_TYPE_FORCE_QUALIFY);
+}
+
+/*
+ * This version allows a nondefault typemod to be specified.
+ */
+char *
+format_type_with_typemod(Oid type_oid, int32 typemod)
+{
+ return format_type_extended(type_oid, typemod, FORMAT_TYPE_TYPEMOD_GIVEN);
+}
+
+/*
+ * Add typmod decoration to the basic type name
+ */
+static char *
+printTypmod(const char *typname, int32 typmod, Oid typmodout)
+{
+ char *res;
+
+ /* Shouldn't be called if typmod is -1 */
+ Assert(typmod >= 0);
+
+ if (typmodout == InvalidOid)
+ {
+ /* Default behavior: just print the integer typmod with parens */
+ res = psprintf("%s(%d)", typname, (int) typmod);
+ }
+ else
+ {
+ /* Use the type-specific typmodout procedure */
+ char *tmstr;
+
+ tmstr = DatumGetCString(OidFunctionCall1(typmodout,
+ Int32GetDatum(typmod)));
+ res = psprintf("%s%s", typname, tmstr);
+ }
+
+ return res;
+}
+
/*
- * type_maximum_size --- determine maximum width of a varlena column
+ * type_maximum_size --- determine maximum width of a variable-width column
*
* If the max width is indeterminate, return -1. In particular, we return
* -1 for any type not known to this routine. We assume the caller has
- * already determined that the type is a varlena type, so it's not
+ * already determined that the type is a variable-width type, so it's not
* necessary to look up the type's pg_type tuple here.
*
* This may appear unrelated to format_type(), but in fact the two routines
* share knowledge of the encoding of typmod for different types, so it's
- * convenient to keep them together.
+ * convenient to keep them together. (XXX now that most of this knowledge
+ * has been pushed out of format_type into the typmodout functions, it's
+ * interesting to wonder if it's worth trying to factor this code too...)
*/
int32
type_maximum_size(Oid type_oid, int32 typemod)
case BPCHAROID:
case VARCHAROID:
/* typemod includes varlena header */
-#ifdef MULTIBYTE
+
/* typemod is in characters not bytes */
return (typemod - VARHDRSZ) *
pg_encoding_max_length(GetDatabaseEncoding())
+ VARHDRSZ;
-#else
- return typemod;
-#endif
case NUMERICOID:
- /* precision (ie, max # of digits) is in upper bits of typmod */
- if (typemod > VARHDRSZ)
- {
- int precision = ((typemod - VARHDRSZ) >> 16) & 0xffff;
-
- /* Numeric stores 2 decimal digits/byte, plus header */
- return (precision + 1) / 2 + NUMERIC_HDRSZ;
- }
- break;
+ return numeric_maximum_size(typemod);
case VARBITOID:
case BITOID:
/*
* oidvectortypes - converts a vector of type OIDs to "typname" list
- *
- * The interface for this function is wrong: it should be told how many
- * OIDs are significant in the input vector, so that trailing InvalidOid
- * argument types can be recognized.
*/
Datum
oidvectortypes(PG_FUNCTION_ARGS)
{
- Oid *oidArray = (Oid *) PG_GETARG_POINTER(0);
+ oidvector *oidArray = (oidvector *) PG_GETARG_POINTER(0);
char *result;
- int numargs;
+ int numargs = oidArray->dim1;
int num;
size_t total;
size_t left;
- /* Try to guess how many args there are :-( */
- numargs = 0;
- for (num = 0; num < FUNC_MAX_ARGS; num++)
- {
- if (oidArray[num] != InvalidOid)
- numargs = num + 1;
- }
-
total = 20 * numargs + 1;
result = palloc(total);
result[0] = '\0';
for (num = 0; num < numargs; num++)
{
- char *typename = format_type_internal(oidArray[num], -1, true);
+ char *typename = format_type_extended(oidArray->values[num], -1,
+ FORMAT_TYPE_ALLOW_INVALID);
+ size_t slen = strlen(typename);
- if (left < strlen(typename) + 2)
+ if (left < (slen + 2))
{
- total += strlen(typename) + 2;
+ total += slen + 2;
result = repalloc(result, total);
- left += strlen(typename) + 2;
+ left += slen + 2;
}
if (num > 0)
left -= 2;
}
strcat(result, typename);
- left -= strlen(typename);
+ left -= slen;
}
- PG_RETURN_DATUM(_textin(result));
+ PG_RETURN_TEXT_P(cstring_to_text(result));
}