postgresql/src/backend/utils/adt/format_type.c

461 lines
12 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* format_type.c
* Display type names "nicely".
*
*
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/backend/utils/adt/format_type.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <ctype.h>
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/numeric.h"
#include "utils/syscache.h"
#include "mb/pg_wchar.h"
#define MAX_INT32_LEN 11
static char *format_type_internal(Oid type_oid, int32 typemod,
bool typemod_given, bool allow_invalid);
static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
2002-09-04 22:31:48 +02:00
static char *
psnprintf(size_t len, const char *fmt,...)
/* This lets gcc check the format string for consistency. */
__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
/*
* SQL function: format_type(type_oid, typemod)
*
* `type_oid' is from pg_type.oid, `typemod' is from
* 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 or matches a keyword.
*
* If typemod is NULL then we are formatting a type name in a context where
2002-09-04 22:31:48 +02:00
* 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
2002-09-04 22:31:48 +02:00
* 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
2002-09-04 22:31:48 +02:00
* "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.
*
* 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;
/* 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))
result = format_type_internal(type_oid, -1, false, true);
else
{
typemod = PG_GETARG_INT32(1);
result = format_type_internal(type_oid, typemod, true, true);
}
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.
*
* The result is always a palloc'd string.
*/
char *
format_type_be(Oid type_oid)
{
return format_type_internal(type_oid, -1, false, false);
}
/*
* This version allows a nondefault typemod to be specified.
*/
char *
format_type_with_typemod(Oid type_oid, int32 typemod)
{
return format_type_internal(type_oid, typemod, true, false);
}
static char *
format_type_internal(Oid type_oid, int32 typemod,
bool typemod_given, bool allow_invalid)
{
bool with_typemod = typemod_given && (typemod >= 0);
HeapTuple tuple;
Form_pg_type typeform;
Oid array_base_type;
bool is_array;
char *buf;
if (type_oid == InvalidOid && allow_invalid)
return pstrdup("-");
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_oid));
if (!HeapTupleIsValid(tuple))
{
if (allow_invalid)
return pstrdup("???");
else
elog(ERROR, "cache lookup failed for type %u", type_oid);
}
typeform = (Form_pg_type) GETSTRUCT(tuple);
2002-03-20 20:45:13 +01:00
/*
Improve handling of domains over arrays. This patch eliminates various bizarre behaviors caused by sloppy thinking about the difference between a domain type and its underlying array type. In particular, the operation of updating one element of such an array has to be considered as yielding a value of the underlying array type, *not* a value of the domain, because there's no assurance that the domain's CHECK constraints are still satisfied. If we're intending to store the result back into a domain column, we have to re-cast to the domain type so that constraints are re-checked. For similar reasons, such a domain can't be blindly matched to an ANYARRAY polymorphic parameter, because the polymorphic function is likely to apply array-ish operations that could invalidate the domain constraints. For the moment, we just forbid such matching. We might later wish to insert an automatic downcast to the underlying array type, but such a change should also change matching of domains to ANYELEMENT for consistency. To ensure that all such logic is rechecked, this patch removes the original hack of setting a domain's pg_type.typelem field to match its base type; the typelem will always be zero instead. In those places where it's really okay to look through the domain type with no other logic changes, use the newly added get_base_element_type function in place of get_element_type. catversion bumped due to change in pg_type contents. Per bug #5717 from Richard Huxton and subsequent discussion.
2010-10-21 22:07:17 +02:00
* 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[].
2002-03-20 20:45:13 +01:00
*/
array_base_type = typeform->typelem;
if (array_base_type != InvalidOid &&
Improve handling of domains over arrays. This patch eliminates various bizarre behaviors caused by sloppy thinking about the difference between a domain type and its underlying array type. In particular, the operation of updating one element of such an array has to be considered as yielding a value of the underlying array type, *not* a value of the domain, because there's no assurance that the domain's CHECK constraints are still satisfied. If we're intending to store the result back into a domain column, we have to re-cast to the domain type so that constraints are re-checked. For similar reasons, such a domain can't be blindly matched to an ANYARRAY polymorphic parameter, because the polymorphic function is likely to apply array-ish operations that could invalidate the domain constraints. For the moment, we just forbid such matching. We might later wish to insert an automatic downcast to the underlying array type, but such a change should also change matching of domains to ANYELEMENT for consistency. To ensure that all such logic is rechecked, this patch removes the original hack of setting a domain's pg_type.typelem field to match its base type; the typelem will always be zero instead. In those places where it's really okay to look through the domain type with no other logic changes, use the newly added get_base_element_type function in place of get_element_type. catversion bumped due to change in pg_type contents. Per bug #5717 from Richard Huxton and subsequent discussion.
2010-10-21 22:07:17 +02:00
typeform->typstorage != 'p')
{
/* Switch our attention to the array element type */
ReleaseSysCache(tuple);
tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(array_base_type));
if (!HeapTupleIsValid(tuple))
{
if (allow_invalid)
return pstrdup("???[]");
else
elog(ERROR, "cache lookup failed for type %u", type_oid);
}
typeform = (Form_pg_type) GETSTRUCT(tuple);
type_oid = array_base_type;
is_array = true;
}
else
is_array = false;
/*
2005-10-15 04:49:52 +02:00
* 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.
*
2002-09-04 22:31:48 +02:00
* If we do not provide a special-case output here, the type name will be
2005-10-15 04:49:52 +02:00
* 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
2002-09-04 22:31:48 +02:00
* 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 = printTypmod("bit", typemod, typeform->typmodout);
else if (typemod_given)
{
/*
* bit with typmod -1 is not the same as BIT, which means
2005-10-15 04:49:52 +02:00
* BIT(1) per SQL spec. Report it as the quoted typename so
* that parser will not assign a bogus typmod.
*/
}
else
buf = pstrdup("bit");
break;
case BOOLOID:
buf = pstrdup("boolean");
break;
case BPCHAROID:
if (with_typemod)
buf = printTypmod("character", typemod, typeform->typmodout);
else if (typemod_given)
{
/*
2005-10-15 04:49:52 +02:00
* 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.
*/
}
else
buf = pstrdup("character");
break;
case FLOAT4OID:
buf = pstrdup("real");
break;
case FLOAT8OID:
buf = pstrdup("double precision");
break;
case INT2OID:
buf = pstrdup("smallint");
break;
case INT4OID:
buf = pstrdup("integer");
break;
case INT8OID:
buf = pstrdup("bigint");
break;
case NUMERICOID:
if (with_typemod)
buf = printTypmod("numeric", typemod, typeform->typmodout);
else
buf = pstrdup("numeric");
break;
case INTERVALOID:
if (with_typemod)
buf = printTypmod("interval", typemod, typeform->typmodout);
else
buf = pstrdup("interval");
break;
case TIMEOID:
if (with_typemod)
buf = printTypmod("time", typemod, typeform->typmodout);
else
buf = pstrdup("time without time zone");
break;
case TIMETZOID:
if (with_typemod)
buf = printTypmod("time", typemod, typeform->typmodout);
else
buf = pstrdup("time with time zone");
break;
case TIMESTAMPOID:
if (with_typemod)
buf = printTypmod("timestamp", typemod, typeform->typmodout);
else
buf = pstrdup("timestamp without time zone");
break;
case TIMESTAMPTZOID:
if (with_typemod)
buf = printTypmod("timestamp", typemod, typeform->typmodout);
else
buf = pstrdup("timestamp with time zone");
break;
case VARBITOID:
if (with_typemod)
buf = printTypmod("bit varying", typemod, typeform->typmodout);
else
buf = pstrdup("bit varying");
break;
case VARCHAROID:
if (with_typemod)
buf = printTypmod("character varying", typemod, typeform->typmodout);
else
buf = pstrdup("character varying");
break;
}
if (buf == NULL)
{
/*
* Default handling: report the name as it appears in the catalog.
2005-10-15 04:49:52 +02:00
* Here, we must qualify the name if it is not visible in the search
* path, and we must double-quote it if it's not a standard identifier
* or if it matches any keyword.
*/
char *nspname;
char *typname;
if (TypeIsVisible(type_oid))
nspname = NULL;
else
nspname = get_namespace_name(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);
ReleaseSysCache(tuple);
return buf;
}
/*
* Add typmod decoration to the basic type name
*/
static char *
printTypmod(const char *typname, int32 typmod, Oid typmodout)
{
2007-11-15 22:14:46 +01:00
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 = psnprintf(strlen(typname) + MAX_INT32_LEN + 3, "%s(%d)",
typname, (int) typmod);
}
else
{
/* Use the type-specific typmodout procedure */
2007-11-15 22:14:46 +01:00
char *tmstr;
tmstr = DatumGetCString(OidFunctionCall1(typmodout,
Int32GetDatum(typmod)));
res = psnprintf(strlen(typname) + strlen(tmstr) + 1, "%s%s",
typname, tmstr);
}
return res;
}
/*
* 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 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. (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)
{
if (typemod < 0)
return -1;
switch (type_oid)
{
case BPCHAROID:
case VARCHAROID:
/* typemod includes varlena header */
/* typemod is in characters not bytes */
return (typemod - VARHDRSZ) *
pg_encoding_max_length(GetDatabaseEncoding())
+ VARHDRSZ;
case NUMERICOID:
return numeric_maximum_size(typemod);
case VARBITOID:
case BITOID:
/* typemod is the (max) number of bits */
2001-03-22 05:01:46 +01:00
return (typemod + (BITS_PER_BYTE - 1)) / BITS_PER_BYTE
+ 2 * sizeof(int32);
}
/* Unknown type, or unlimited-width type such as 'text' */
return -1;
}
/*
* oidvectortypes - converts a vector of type OIDs to "typname" list
*/
Datum
oidvectortypes(PG_FUNCTION_ARGS)
{
oidvector *oidArray = (oidvector *) PG_GETARG_POINTER(0);
char *result;
int numargs = oidArray->dim1;
int num;
size_t total;
size_t left;
total = 20 * numargs + 1;
result = palloc(total);
result[0] = '\0';
left = total - 1;
2001-03-22 05:01:46 +01:00
for (num = 0; num < numargs; num++)
{
char *typename = format_type_internal(oidArray->values[num], -1,
false, true);
size_t slen = strlen(typename);
if (left < (slen + 2))
{
total += slen + 2;
result = repalloc(result, total);
left += slen + 2;
}
if (num > 0)
{
strcat(result, ", ");
left -= 2;
}
strcat(result, typename);
left -= slen;
}
PG_RETURN_TEXT_P(cstring_to_text(result));
}
/* snprintf into a palloc'd string */
static char *
2002-09-04 22:31:48 +02:00
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;
}