| /*------------------------------------------------------------------------- |
| * |
| * format_type.c |
| * Display type names "nicely". |
| * |
| * |
| * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * $PostgreSQL: pgsql/src/backend/utils/adt/format_type.c,v 1.44 2006/07/14 14:52:24 momjian Exp $ |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres.h" |
| |
| #include <ctype.h> |
| |
| #include "catalog/catquery.h" |
| #include "catalog/namespace.h" |
| #include "catalog/pg_type.h" |
| #include "utils/builtins.h" |
| #include "utils/datetime.h" |
| #include "utils/fmgroids.h" |
| #include "utils/lsyscache.h" |
| #include "utils/numeric.h" |
| #include "utils/syscache.h" |
| #include "mb/pg_wchar.h" |
| |
| #define MAX_INT32_LEN 11 |
| #define _textin(str) DirectFunctionCall1(textin, CStringGetDatum(str)) |
| |
| static char *format_type_internal(Oid type_oid, int32 typemod, |
| bool typemod_given, bool allow_invalid); |
| static char * |
| psnprintf(size_t len, const char *fmt,...) |
| /* This lets gcc check the format string for consistency. */ |
| __attribute__((format(printf, 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 |
| * 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); |
| else |
| { |
| typemod = PG_GETARG_INT32(1); |
| result = format_type_internal(type_oid, typemod, true, true); |
| } |
| |
| PG_RETURN_DATUM(_textin(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; |
| cqContext *typcqCtx; |
| cqContext *basecqCtx; |
| |
| if (type_oid == InvalidOid && allow_invalid) |
| return pstrdup("-"); |
| |
| typcqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(type_oid))); |
| |
| tuple = caql_getnext(typcqCtx); |
| |
| if (!HeapTupleIsValid(tuple)) |
| { |
| caql_endscan(typcqCtx); |
| |
| if (allow_invalid) |
| return pstrdup("???"); |
| else |
| elog(ERROR, "cache lookup failed for type %u", type_oid); |
| } |
| typeform = (Form_pg_type) GETSTRUCT(tuple); |
| |
| /* |
| * Check if it's an array (and not a domain --- we don't want to show the |
| * substructure of a domain type). Fixed-length array types such as |
| * "name" shouldn't get deconstructed either. 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[]. |
| * |
| * *** MPP-5137 patch *** |
| * The intent of this routine is to replace an array type reference with |
| * an array of the underlying base type if, and only if, the array |
| * reference is to the system-defined array type over a base type. |
| * |
| * Checking only typstorage = 'p' causes format_type failures when |
| * processing user-defined array types having typstorage != 'p'. |
| * |
| * To avoid this, at least until 8.3, different checking is in order. |
| * Until 8.3, all system-defined array types have names beginning with an |
| * underscore ('_') with the remainder of the name the same as the base |
| * element type name and have a typinput value of array_in. Base type |
| * array replacement will occur only if both conditions are met. All |
| * other references are left intact. |
| * |
| * User-defined and system-defined fixed-length types (like "name" and |
| * "oidvector") can not use array_in; all of the system-defined |
| * fixed-length types use specialized typinput routines, not array_in, so |
| * checking for array_in clears the system types. (See comments in array.h |
| * about fixed-length arrays.) |
| */ |
| array_base_type = typeform->typelem; |
| |
| if (OidIsValid(array_base_type) && |
| typeform->typtype != TYPTYPE_DOMAIN && |
| /* TODO: Change test to use pg_type.typarray when introducing 8.3 updates */ |
| NameStr(typeform->typname)[0] == '_' && |
| typeform->typinput == F_ARRAY_IN) |
| { |
| HeapTuple base_tuple; |
| Form_pg_type base_typeform; |
| |
| /* Switch our attention to the array element type */ |
| basecqCtx = caql_beginscan( |
| NULL, |
| cql("SELECT * FROM pg_type " |
| " WHERE oid = :1 ", |
| ObjectIdGetDatum(array_base_type))); |
| |
| base_tuple = caql_getnext(basecqCtx); |
| |
| if (!HeapTupleIsValid(base_tuple)) |
| { |
| if (allow_invalid) |
| { |
| caql_endscan(basecqCtx); |
| caql_endscan(typcqCtx); |
| return pstrdup("???[]"); |
| } |
| else |
| { |
| elog(ERROR, "cache lookup failed for type %u", type_oid); |
| } |
| } |
| base_typeform = (Form_pg_type) GETSTRUCT(base_tuple); |
| |
| /* |
| * Now make final checks for system-defined array type before |
| * substituting the array form of a base type for an array type |
| * reference. |
| * |
| * TODO: Eliminate the name check when merging 8.3 updates and typarray checked |
| */ |
| if (strcmp(NameStr(base_typeform->typname), NameStr(typeform->typname)+1) == 0) |
| { |
| /* NOTE: a little grotesque, but since we are switching |
| * the base tuple for the type tuple, free the typcqctx, |
| * then switch the caql context for the base tuple as well |
| * so it gets freed at the end |
| */ |
| |
| caql_endscan(typcqCtx); |
| |
| tuple = base_tuple; |
| |
| typcqCtx = basecqCtx; /* NOTE: switch the caql ctx */ |
| |
| typeform = base_typeform; |
| type_oid = array_base_type; |
| is_array = true; |
| } |
| else |
| { |
| caql_endscan(basecqCtx); |
| is_array = false; |
| } |
| } |
| else |
| { |
| is_array = false; |
| } |
| |
| /* |
| * 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 if (typemod_given) |
| { |
| /* |
| * 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. |
| */ |
| } |
| else |
| buf = pstrdup("bit"); |
| break; |
| |
| case BOOLOID: |
| buf = pstrdup("boolean"); |
| break; |
| |
| case BPCHAROID: |
| if (with_typemod) |
| buf = psnprintf(11 + MAX_INT32_LEN + 1, "character(%d)", |
| (int) (typemod - VARHDRSZ)); |
| else if (typemod_given) |
| { |
| /* |
| * 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 = psnprintf(10 + 2 * MAX_INT32_LEN + 1, "numeric(%d,%d)", |
| ((typemod - VARHDRSZ) >> 16) & 0xffff, |
| (typemod - VARHDRSZ) & 0xffff); |
| else |
| buf = pstrdup("numeric"); |
| break; |
| |
| case INTERVALOID: |
| if (with_typemod) |
| { |
| int fields = INTERVAL_RANGE(typemod); |
| int precision = INTERVAL_PRECISION(typemod); |
| const char *fieldstr; |
| |
| switch (fields) |
| { |
| case INTERVAL_MASK(YEAR): |
| fieldstr = " year"; |
| break; |
| case INTERVAL_MASK(MONTH): |
| fieldstr = " month"; |
| break; |
| case INTERVAL_MASK(DAY): |
| fieldstr = " day"; |
| break; |
| case INTERVAL_MASK(HOUR): |
| fieldstr = " hour"; |
| break; |
| case INTERVAL_MASK(MINUTE): |
| fieldstr = " minute"; |
| break; |
| case INTERVAL_MASK(SECOND): |
| fieldstr = " second"; |
| break; |
| case INTERVAL_MASK(YEAR) |
| | INTERVAL_MASK(MONTH): |
| fieldstr = " year to month"; |
| break; |
| case INTERVAL_MASK(DAY) |
| | INTERVAL_MASK(HOUR): |
| fieldstr = " day to hour"; |
| break; |
| case INTERVAL_MASK(DAY) |
| | INTERVAL_MASK(HOUR) |
| | INTERVAL_MASK(MINUTE): |
| fieldstr = " day to minute"; |
| break; |
| case INTERVAL_MASK(DAY) |
| | INTERVAL_MASK(HOUR) |
| | INTERVAL_MASK(MINUTE) |
| | INTERVAL_MASK(SECOND): |
| fieldstr = " day to second"; |
| break; |
| case INTERVAL_MASK(HOUR) |
| | INTERVAL_MASK(MINUTE): |
| fieldstr = " hour to minute"; |
| break; |
| case INTERVAL_MASK(HOUR) |
| | INTERVAL_MASK(MINUTE) |
| | INTERVAL_MASK(SECOND): |
| fieldstr = " hour to second"; |
| break; |
| case INTERVAL_MASK(MINUTE) |
| | INTERVAL_MASK(SECOND): |
| fieldstr = " minute to second"; |
| break; |
| case INTERVAL_FULL_RANGE: |
| fieldstr = ""; |
| break; |
| default: |
| elog(ERROR, "invalid INTERVAL typmod: 0x%x", typemod); |
| fieldstr = ""; |
| break; |
| } |
| if (precision != INTERVAL_FULL_PRECISION) |
| buf = psnprintf(100, "interval(%d)%s", |
| precision, fieldstr); |
| else |
| buf = psnprintf(100, "interval%s", |
| fieldstr); |
| } |
| else |
| buf = pstrdup("interval"); |
| break; |
| |
| case TIMEOID: |
| if (with_typemod) |
| buf = psnprintf(50, "time(%d) without time zone", |
| typemod); |
| else |
| buf = pstrdup("time without time zone"); |
| break; |
| |
| case TIMETZOID: |
| if (with_typemod) |
| buf = psnprintf(50, "time(%d) with time zone", |
| typemod); |
| else |
| buf = pstrdup("time with time zone"); |
| break; |
| |
| case TIMESTAMPOID: |
| if (with_typemod) |
| buf = psnprintf(50, "timestamp(%d) without time zone", |
| typemod); |
| else |
| buf = pstrdup("timestamp without time zone"); |
| break; |
| |
| case TIMESTAMPTZOID: |
| if (with_typemod) |
| buf = psnprintf(50, "timestamp(%d) with time zone", |
| typemod); |
| 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); |
| else |
| buf = pstrdup("bit varying"); |
| break; |
| |
| case VARCHAROID: |
| if (with_typemod) |
| buf = psnprintf(19 + MAX_INT32_LEN + 1, |
| "character varying(%d)", |
| (int) (typemod - VARHDRSZ)); |
| else |
| buf = pstrdup("character varying"); |
| 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, 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 (is_array) |
| buf = psnprintf(strlen(buf) + 3, "%s[]", buf); |
| |
| caql_endscan(typcqCtx); /* NOTE: this could be the base tuple ctx |
| * if it was an array |
| */ |
| |
| return buf; |
| } |
| |
| |
| /* |
| * 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. |
| */ |
| 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: |
| /* 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; |
| |
| case VARBITOID: |
| case BITOID: |
| /* typemod is the (max) number of bits */ |
| 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; |
| |
| 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_DATUM(_textin(result)); |
| } |
| |
| |
| /* snprintf into a palloc'd string */ |
| 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; |
| } |