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

462 lines
12 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* format_type.c
* Display type names "nicely".
*
*
* Portions Copyright (c) 1996-2015, 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 "access/htup_details.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,
bool force_qualify);
static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
/*
* 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
* 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.
*
* 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, false);
else
{
typemod = PG_GETARG_INT32(1);
result = format_type_internal(type_oid, typemod, true, true, false);
}
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, false);
}
Allow on-the-fly capture of DDL event details This feature lets user code inspect and take action on DDL events. Whenever a ddl_command_end event trigger is installed, DDL actions executed are saved to a list which can be inspected during execution of a function attached to ddl_command_end. The set-returning function pg_event_trigger_ddl_commands can be used to list actions so captured; it returns data about the type of command executed, as well as the affected object. This is sufficient for many uses of this feature. For the cases where it is not, we also provide a "command" column of a new pseudo-type pg_ddl_command, which is a pointer to a C structure that can be accessed by C code. The struct contains all the info necessary to completely inspect and even reconstruct the executed command. There is no actual deparse code here; that's expected to come later. What we have is enough infrastructure that the deparsing can be done in an external extension. The intention is that we will add some deparsing code in a later release, as an in-core extension. A new test module is included. It's probably insufficient as is, but it should be sufficient as a starting point for a more complete and future-proof approach. Authors: Álvaro Herrera, with some help from Andres Freund, Ian Barwick, Abhijit Menon-Sen. Reviews by Andres Freund, Robert Haas, Amit Kapila, Michael Paquier, Craig Ringer, David Steele. Additional input from Chris Browne, Dimitri Fontaine, Stephen Frost, Petr Jelínek, Tom Lane, Jim Nasby, Steven Singer, Pavel Stěhule. Based on original work by Dimitri Fontaine, though I didn't use his code. Discussion: https://www.postgresql.org/message-id/m2txrsdzxa.fsf@2ndQuadrant.fr https://www.postgresql.org/message-id/20131108153322.GU5809@eldon.alvh.no-ip.org https://www.postgresql.org/message-id/20150215044814.GL3391@alvh.no-ip.org
2015-05-12 00:14:31 +02:00
/*
* 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).
Allow on-the-fly capture of DDL event details This feature lets user code inspect and take action on DDL events. Whenever a ddl_command_end event trigger is installed, DDL actions executed are saved to a list which can be inspected during execution of a function attached to ddl_command_end. The set-returning function pg_event_trigger_ddl_commands can be used to list actions so captured; it returns data about the type of command executed, as well as the affected object. This is sufficient for many uses of this feature. For the cases where it is not, we also provide a "command" column of a new pseudo-type pg_ddl_command, which is a pointer to a C structure that can be accessed by C code. The struct contains all the info necessary to completely inspect and even reconstruct the executed command. There is no actual deparse code here; that's expected to come later. What we have is enough infrastructure that the deparsing can be done in an external extension. The intention is that we will add some deparsing code in a later release, as an in-core extension. A new test module is included. It's probably insufficient as is, but it should be sufficient as a starting point for a more complete and future-proof approach. Authors: Álvaro Herrera, with some help from Andres Freund, Ian Barwick, Abhijit Menon-Sen. Reviews by Andres Freund, Robert Haas, Amit Kapila, Michael Paquier, Craig Ringer, David Steele. Additional input from Chris Browne, Dimitri Fontaine, Stephen Frost, Petr Jelínek, Tom Lane, Jim Nasby, Steven Singer, Pavel Stěhule. Based on original work by Dimitri Fontaine, though I didn't use his code. Discussion: https://www.postgresql.org/message-id/m2txrsdzxa.fsf@2ndQuadrant.fr https://www.postgresql.org/message-id/20131108153322.GU5809@eldon.alvh.no-ip.org https://www.postgresql.org/message-id/20150215044814.GL3391@alvh.no-ip.org
2015-05-12 00:14:31 +02:00
*/
char *
format_type_be_qualified(Oid type_oid)
{
return format_type_internal(type_oid, -1, false, false, true);
}
/*
* 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, false);
}
/*
* This version allows a nondefault typemod to be specified, and forces
* qualification of normal type names.
*/
char *
format_type_with_typemod_qualified(Oid type_oid, int32 typemod)
{
return format_type_internal(type_oid, typemod, true, false, true);
}
/*
* Common workhorse.
*/
static char *
format_type_internal(Oid type_oid, int32 typemod,
bool typemod_given, bool allow_invalid,
bool force_qualify)
{
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 (!force_qualify && 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 = psprintf("%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 = psprintf("%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 = psprintf("%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, false);
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));
}