postgresql/src/backend/parser/parse_type.c
Tom Lane a051ef699c Remove collation information from TypeName, where it does not belong.
The initial collations patch treated a COLLATE spec as part of a TypeName,
following what can only be described as brain fade on the part of the SQL
committee.  It's a lot more reasonable to treat COLLATE as a syntactically
separate object, so that it can be added in only the productions where it
actually belongs, rather than needing to reject it in a boatload of places
where it doesn't belong (something the original patch mostly failed to do).
In addition this change lets us meet the spec's requirement to allow
COLLATE anywhere in the clauses of a ColumnDef, and it avoids unfriendly
behavior for constructs such as "foo::type COLLATE collation".

To do this, pull collation information out of TypeName and put it in
ColumnDef instead, thus reverting most of the collation-related changes in
parse_type.c's API.  I made one additional structural change, which was to
use a ColumnDef as an intermediate node in AT_AlterColumnType AlterTableCmd
nodes.  This provides enough room to get rid of the "transform" wart in
AlterTableCmd too, since the ColumnDef can carry the USING expression
easily enough.

Also fix some other minor bugs that have crept in in the same areas,
like failure to copy recently-added fields of ColumnDef in copyfuncs.c.

While at it, document the formerly secret ability to specify a collation
in ALTER TABLE ALTER COLUMN TYPE, ALTER TYPE ADD ATTRIBUTE, and
ALTER TYPE ALTER ATTRIBUTE TYPE; and correct some misstatements about
what the default collation selection will be when COLLATE is omitted.

BTW, the three-parameter form of format_type() should go away too,
since it just contributes to the confusion in this area; but I'll do
that in a separate patch.
2011-03-09 22:39:20 -05:00

732 lines
19 KiB
C

/*-------------------------------------------------------------------------
*
* parse_type.c
* handle type operations for parser
*
* Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/parser/parse_type.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "lib/stringinfo.h"
#include "nodes/makefuncs.h"
#include "parser/parser.h"
#include "parser/parse_type.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
static int32 typenameTypeMod(ParseState *pstate, const TypeName *typeName,
Type typ);
/*
* LookupTypeName
* Given a TypeName object, lookup the pg_type syscache entry of the type.
* Returns NULL if no such type can be found. If the type is found,
* the typmod value represented in the TypeName struct is computed and
* stored into *typmod_p.
*
* NB: on success, the caller must ReleaseSysCache the type tuple when done
* with it.
*
* NB: direct callers of this function MUST check typisdefined before assuming
* that the type is fully valid. Most code should go through typenameType
* or typenameTypeId instead.
*
* typmod_p can be passed as NULL if the caller does not care to know the
* typmod value, but the typmod decoration (if any) will be validated anyway,
* except in the case where the type is not found. Note that if the type is
* found but is a shell, and there is typmod decoration, an error will be
* thrown --- this is intentional.
*
* pstate is only used for error location info, and may be NULL.
*/
Type
LookupTypeName(ParseState *pstate, const TypeName *typeName,
int32 *typmod_p)
{
Oid typoid;
HeapTuple tup;
int32 typmod;
if (typeName->names == NIL)
{
/* We have the OID already if it's an internally generated TypeName */
typoid = typeName->typeOid;
}
else if (typeName->pct_type)
{
/* Handle %TYPE reference to type of an existing field */
RangeVar *rel = makeRangeVar(NULL, NULL, typeName->location);
char *field = NULL;
Oid relid;
AttrNumber attnum;
/* deconstruct the name list */
switch (list_length(typeName->names))
{
case 1:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper %%TYPE reference (too few dotted names): %s",
NameListToString(typeName->names)),
parser_errposition(pstate, typeName->location)));
break;
case 2:
rel->relname = strVal(linitial(typeName->names));
field = strVal(lsecond(typeName->names));
break;
case 3:
rel->schemaname = strVal(linitial(typeName->names));
rel->relname = strVal(lsecond(typeName->names));
field = strVal(lthird(typeName->names));
break;
case 4:
rel->catalogname = strVal(linitial(typeName->names));
rel->schemaname = strVal(lsecond(typeName->names));
rel->relname = strVal(lthird(typeName->names));
field = strVal(lfourth(typeName->names));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("improper %%TYPE reference (too many dotted names): %s",
NameListToString(typeName->names)),
parser_errposition(pstate, typeName->location)));
break;
}
/* look up the field */
relid = RangeVarGetRelid(rel, false);
attnum = get_attnum(relid, field);
if (attnum == InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
field, rel->relname),
parser_errposition(pstate, typeName->location)));
typoid = get_atttype(relid, attnum);
/* this construct should never have an array indicator */
Assert(typeName->arrayBounds == NIL);
/* emit nuisance notice (intentionally not errposition'd) */
ereport(NOTICE,
(errmsg("type reference %s converted to %s",
TypeNameToString(typeName),
format_type_be(typoid))));
}
else
{
/* Normal reference to a type name */
char *schemaname;
char *typname;
/* deconstruct the name list */
DeconstructQualifiedName(typeName->names, &schemaname, &typname);
if (schemaname)
{
/* Look in specific schema only */
Oid namespaceId;
namespaceId = LookupExplicitNamespace(schemaname);
typoid = GetSysCacheOid2(TYPENAMENSP,
PointerGetDatum(typname),
ObjectIdGetDatum(namespaceId));
}
else
{
/* Unqualified type name, so search the search path */
typoid = TypenameGetTypid(typname);
}
/* If an array reference, return the array type instead */
if (typeName->arrayBounds != NIL)
typoid = get_array_type(typoid);
}
if (!OidIsValid(typoid))
{
if (typmod_p)
*typmod_p = -1;
return NULL;
}
tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typoid));
if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for type %u", typoid);
typmod = typenameTypeMod(pstate, typeName, (Type) tup);
if (typmod_p)
*typmod_p = typmod;
return (Type) tup;
}
/*
* typenameType - given a TypeName, return a Type structure and typmod
*
* This is equivalent to LookupTypeName, except that this will report
* a suitable error message if the type cannot be found or is not defined.
* Callers of this can therefore assume the result is a fully valid type.
*/
Type
typenameType(ParseState *pstate, const TypeName *typeName, int32 *typmod_p)
{
Type tup;
tup = LookupTypeName(pstate, typeName, typmod_p);
if (tup == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("type \"%s\" does not exist",
TypeNameToString(typeName)),
parser_errposition(pstate, typeName->location)));
if (!((Form_pg_type) GETSTRUCT(tup))->typisdefined)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("type \"%s\" is only a shell",
TypeNameToString(typeName)),
parser_errposition(pstate, typeName->location)));
return tup;
}
/*
* typenameTypeId - given a TypeName, return the type's OID
*
* This is similar to typenameType, but we only hand back the type OID
* not the syscache entry.
*/
Oid
typenameTypeId(ParseState *pstate, const TypeName *typeName)
{
Oid typoid;
Type tup;
tup = typenameType(pstate, typeName, NULL);
typoid = HeapTupleGetOid(tup);
ReleaseSysCache(tup);
return typoid;
}
/*
* typenameTypeIdAndMod - given a TypeName, return the type's OID and typmod
*
* This is equivalent to typenameType, but we only hand back the type OID
* and typmod, not the syscache entry.
*/
void
typenameTypeIdAndMod(ParseState *pstate, const TypeName *typeName,
Oid *typeid_p, int32 *typmod_p)
{
Type tup;
tup = typenameType(pstate, typeName, typmod_p);
*typeid_p = HeapTupleGetOid(tup);
ReleaseSysCache(tup);
}
/*
* typenameTypeMod - given a TypeName, return the internal typmod value
*
* This will throw an error if the TypeName includes type modifiers that are
* illegal for the data type.
*
* The actual type OID represented by the TypeName must already have been
* looked up, and is passed as "typ".
*
* pstate is only used for error location info, and may be NULL.
*/
static int32
typenameTypeMod(ParseState *pstate, const TypeName *typeName, Type typ)
{
int32 result;
Oid typmodin;
Datum *datums;
int n;
ListCell *l;
ArrayType *arrtypmod;
ParseCallbackState pcbstate;
/* Return prespecified typmod if no typmod expressions */
if (typeName->typmods == NIL)
return typeName->typemod;
/*
* Else, type had better accept typmods. We give a special error message
* for the shell-type case, since a shell couldn't possibly have a
* typmodin function.
*/
if (!((Form_pg_type) GETSTRUCT(typ))->typisdefined)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("type modifier cannot be specified for shell type \"%s\"",
TypeNameToString(typeName)),
parser_errposition(pstate, typeName->location)));
typmodin = ((Form_pg_type) GETSTRUCT(typ))->typmodin;
if (typmodin == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("type modifier is not allowed for type \"%s\"",
TypeNameToString(typeName)),
parser_errposition(pstate, typeName->location)));
/*
* Convert the list of raw-grammar-output expressions to a cstring array.
* Currently, we allow simple numeric constants, string literals, and
* identifiers; possibly this list could be extended.
*/
datums = (Datum *) palloc(list_length(typeName->typmods) * sizeof(Datum));
n = 0;
foreach(l, typeName->typmods)
{
Node *tm = (Node *) lfirst(l);
char *cstr = NULL;
if (IsA(tm, A_Const))
{
A_Const *ac = (A_Const *) tm;
if (IsA(&ac->val, Integer))
{
cstr = (char *) palloc(32);
snprintf(cstr, 32, "%ld", (long) ac->val.val.ival);
}
else if (IsA(&ac->val, Float) ||
IsA(&ac->val, String))
{
/* we can just use the str field directly. */
cstr = ac->val.val.str;
}
}
else if (IsA(tm, ColumnRef))
{
ColumnRef *cr = (ColumnRef *) tm;
if (list_length(cr->fields) == 1 &&
IsA(linitial(cr->fields), String))
cstr = strVal(linitial(cr->fields));
}
if (!cstr)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("type modifiers must be simple constants or identifiers"),
parser_errposition(pstate, typeName->location)));
datums[n++] = CStringGetDatum(cstr);
}
/* hardwired knowledge about cstring's representation details here */
arrtypmod = construct_array(datums, n, CSTRINGOID,
-2, false, 'c');
/* arrange to report location if type's typmodin function fails */
setup_parser_errposition_callback(&pcbstate, pstate, typeName->location);
result = DatumGetInt32(OidFunctionCall1(typmodin,
PointerGetDatum(arrtypmod)));
cancel_parser_errposition_callback(&pcbstate);
pfree(datums);
pfree(arrtypmod);
return result;
}
/*
* appendTypeNameToBuffer
* Append a string representing the name of a TypeName to a StringInfo.
* This is the shared guts of TypeNameToString and TypeNameListToString.
*
* NB: this must work on TypeNames that do not describe any actual type;
* it is mostly used for reporting lookup errors.
*/
static void
appendTypeNameToBuffer(const TypeName *typeName, StringInfo string)
{
if (typeName->names != NIL)
{
/* Emit possibly-qualified name as-is */
ListCell *l;
foreach(l, typeName->names)
{
if (l != list_head(typeName->names))
appendStringInfoChar(string, '.');
appendStringInfoString(string, strVal(lfirst(l)));
}
}
else
{
/* Look up internally-specified type */
appendStringInfoString(string, format_type_be(typeName->typeOid));
}
/*
* Add decoration as needed, but only for fields considered by
* LookupTypeName
*/
if (typeName->pct_type)
appendStringInfoString(string, "%TYPE");
if (typeName->arrayBounds != NIL)
appendStringInfoString(string, "[]");
}
/*
* TypeNameToString
* Produce a string representing the name of a TypeName.
*
* NB: this must work on TypeNames that do not describe any actual type;
* it is mostly used for reporting lookup errors.
*/
char *
TypeNameToString(const TypeName *typeName)
{
StringInfoData string;
initStringInfo(&string);
appendTypeNameToBuffer(typeName, &string);
return string.data;
}
/*
* TypeNameListToString
* Produce a string representing the name(s) of a List of TypeNames
*/
char *
TypeNameListToString(List *typenames)
{
StringInfoData string;
ListCell *l;
initStringInfo(&string);
foreach(l, typenames)
{
TypeName *typeName = (TypeName *) lfirst(l);
Assert(IsA(typeName, TypeName));
if (l != list_head(typenames))
appendStringInfoChar(&string, ',');
appendTypeNameToBuffer(typeName, &string);
}
return string.data;
}
/*
* LookupCollation
*
* Look up collation by name, return OID, with support for error location.
*/
Oid
LookupCollation(ParseState *pstate, List *collnames, int location)
{
Oid colloid;
ParseCallbackState pcbstate;
if (pstate)
setup_parser_errposition_callback(&pcbstate, pstate, location);
colloid = get_collation_oid(collnames, false);
if (pstate)
cancel_parser_errposition_callback(&pcbstate);
return colloid;
}
/*
* GetColumnDefCollation
*
* Get the collation to be used for a column being defined, given the
* ColumnDef node and the previously-determined column type OID.
*
* pstate is only used for error location purposes, and can be NULL.
*/
Oid
GetColumnDefCollation(ParseState *pstate, ColumnDef *coldef, Oid typeOid)
{
Oid result;
Oid typcollation = get_typcollation(typeOid);
int location = -1;
if (coldef->collClause)
{
/* We have a raw COLLATE clause, so look up the collation */
location = coldef->collClause->location;
result = LookupCollation(pstate, coldef->collClause->collnames,
location);
}
else if (OidIsValid(coldef->collOid))
{
/* Precooked collation spec, use that */
result = coldef->collOid;
}
else
{
/* Use the type's default collation if any */
result = typcollation;
}
/* Complain if COLLATE is applied to an uncollatable type */
if (OidIsValid(result) && !OidIsValid(typcollation))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("collations are not supported by type %s",
format_type_be(typeOid)),
parser_errposition(pstate, location)));
return result;
}
/* return a Type structure, given a type id */
/* NB: caller must ReleaseSysCache the type tuple when done with it */
Type
typeidType(Oid id)
{
HeapTuple tup;
tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(id));
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for type %u", id);
return (Type) tup;
}
/* given type (as type struct), return the type OID */
Oid
typeTypeId(Type tp)
{
if (tp == NULL) /* probably useless */
elog(ERROR, "typeTypeId() called with NULL type struct");
return HeapTupleGetOid(tp);
}
/* given type (as type struct), return the length of type */
int16
typeLen(Type t)
{
Form_pg_type typ;
typ = (Form_pg_type) GETSTRUCT(t);
return typ->typlen;
}
/* given type (as type struct), return the value of its 'byval' attribute.*/
bool
typeByVal(Type t)
{
Form_pg_type typ;
typ = (Form_pg_type) GETSTRUCT(t);
return typ->typbyval;
}
/* given type (as type struct), return the name of type */
char *
typeTypeName(Type t)
{
Form_pg_type typ;
typ = (Form_pg_type) GETSTRUCT(t);
/* pstrdup here because result may need to outlive the syscache entry */
return pstrdup(NameStr(typ->typname));
}
Oid
typeTypeRelid(Type typ)
{
Form_pg_type typtup;
typtup = (Form_pg_type) GETSTRUCT(typ);
return typtup->typrelid;
}
/*
* Given a type structure and a string, returns the internal representation
* of that string. The "string" can be NULL to perform conversion of a NULL
* (which might result in failure, if the input function rejects NULLs).
*/
Datum
stringTypeDatum(Type tp, char *string, int32 atttypmod)
{
Form_pg_type typform = (Form_pg_type) GETSTRUCT(tp);
Oid typinput = typform->typinput;
Oid typioparam = getTypeIOParam(tp);
Datum result;
result = OidInputFunctionCall(typinput, string,
typioparam, atttypmod);
#ifdef RANDOMIZE_ALLOCATED_MEMORY
/*
* For pass-by-reference data types, repeat the conversion to see if the
* input function leaves any uninitialized bytes in the result. We can
* only detect that reliably if RANDOMIZE_ALLOCATED_MEMORY is enabled, so
* we don't bother testing otherwise. The reason we don't want any
* instability in the input function is that comparison of Const nodes
* relies on bytewise comparison of the datums, so if the input function
* leaves garbage then subexpressions that should be identical may not get
* recognized as such. See pgsql-hackers discussion of 2008-04-04.
*/
if (string && !typform->typbyval)
{
Datum result2;
result2 = OidInputFunctionCall(typinput, string,
typioparam, atttypmod);
if (!datumIsEqual(result, result2, typform->typbyval, typform->typlen))
elog(WARNING, "type %s has unstable input conversion for \"%s\"",
NameStr(typform->typname), string);
}
#endif
return result;
}
/* given a typeid, return the type's typrelid (associated relation, if any) */
Oid
typeidTypeRelid(Oid type_id)
{
HeapTuple typeTuple;
Form_pg_type type;
Oid result;
typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
if (!HeapTupleIsValid(typeTuple))
elog(ERROR, "cache lookup failed for type %u", type_id);
type = (Form_pg_type) GETSTRUCT(typeTuple);
result = type->typrelid;
ReleaseSysCache(typeTuple);
return result;
}
/*
* error context callback for parse failure during parseTypeString()
*/
static void
pts_error_callback(void *arg)
{
const char *str = (const char *) arg;
errcontext("invalid type name \"%s\"", str);
/*
* Currently we just suppress any syntax error position report, rather
* than transforming to an "internal query" error. It's unlikely that a
* type name is complex enough to need positioning.
*/
errposition(0);
}
/*
* Given a string that is supposed to be a SQL-compatible type declaration,
* such as "int4" or "integer" or "character varying(32)", parse
* the string and convert it to a type OID and type modifier.
*/
void
parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p)
{
StringInfoData buf;
List *raw_parsetree_list;
SelectStmt *stmt;
ResTarget *restarget;
TypeCast *typecast;
TypeName *typeName;
ErrorContextCallback ptserrcontext;
/* make sure we give useful error for empty input */
if (strspn(str, " \t\n\r\f") == strlen(str))
goto fail;
initStringInfo(&buf);
appendStringInfo(&buf, "SELECT NULL::%s", str);
/*
* Setup error traceback support in case of ereport() during parse
*/
ptserrcontext.callback = pts_error_callback;
ptserrcontext.arg = (void *) str;
ptserrcontext.previous = error_context_stack;
error_context_stack = &ptserrcontext;
raw_parsetree_list = raw_parser(buf.data);
error_context_stack = ptserrcontext.previous;
/*
* Make sure we got back exactly what we expected and no more; paranoia is
* justified since the string might contain anything.
*/
if (list_length(raw_parsetree_list) != 1)
goto fail;
stmt = (SelectStmt *) linitial(raw_parsetree_list);
if (stmt == NULL ||
!IsA(stmt, SelectStmt) ||
stmt->distinctClause != NIL ||
stmt->intoClause != NULL ||
stmt->fromClause != NIL ||
stmt->whereClause != NULL ||
stmt->groupClause != NIL ||
stmt->havingClause != NULL ||
stmt->windowClause != NIL ||
stmt->withClause != NULL ||
stmt->valuesLists != NIL ||
stmt->sortClause != NIL ||
stmt->limitOffset != NULL ||
stmt->limitCount != NULL ||
stmt->lockingClause != NIL ||
stmt->op != SETOP_NONE)
goto fail;
if (list_length(stmt->targetList) != 1)
goto fail;
restarget = (ResTarget *) linitial(stmt->targetList);
if (restarget == NULL ||
!IsA(restarget, ResTarget) ||
restarget->name != NULL ||
restarget->indirection != NIL)
goto fail;
typecast = (TypeCast *) restarget->val;
if (typecast == NULL ||
!IsA(typecast, TypeCast) ||
typecast->arg == NULL ||
!IsA(typecast->arg, A_Const))
goto fail;
typeName = typecast->typeName;
if (typeName == NULL ||
!IsA(typeName, TypeName))
goto fail;
if (typeName->setof)
goto fail;
typenameTypeIdAndMod(NULL, typeName, typeid_p, typmod_p);
pfree(buf.data);
return;
fail:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("invalid type name \"%s\"", str)));
}