/*------------------------------------------------------------------------- * * functioncmds.c * * Routines for CREATE and DROP FUNCTION commands * * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /cvsroot/pgsql/src/backend/commands/functioncmds.c,v 1.40 2003/11/12 21:15:50 tgl Exp $ * * DESCRIPTION * These routines take the parse tree and pick out the * appropriate arguments/flags, and pass the results to the * corresponding "FooDefine" routines (in src/catalog) that do * the actual catalog-munging. These routines also verify permission * of the user to execute the command. * * NOTES * These things must be defined and committed in the following order: * "create function": * input/output, recv/send procedures * "create type": * type * "create operator": * operators * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/genam.h" #include "access/heapam.h" #include "catalog/catname.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_cast.h" #include "catalog/pg_language.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "miscadmin.h" #include "optimizer/cost.h" #include "parser/parse_func.h" #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" /* * Examine the "returns" clause returnType of the CREATE FUNCTION statement * and return information about it as *prorettype_p and *returnsSet. * * This is more complex than the average typename lookup because we want to * allow a shell type to be used, or even created if the specified return type * doesn't exist yet. (Without this, there's no way to define the I/O procs * for a new type.) But SQL function creation won't cope, so error out if * the target language is SQL. (We do this here, not in the SQL-function * validator, so as not to produce a NOTICE and then an ERROR for the same * condition.) */ static void compute_return_type(TypeName *returnType, Oid languageOid, Oid *prorettype_p, bool *returnsSet_p) { Oid rettype; rettype = LookupTypeName(returnType); if (OidIsValid(rettype)) { if (!get_typisdefined(rettype)) { if (languageOid == SQLlanguageId) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("SQL function cannot return shell type %s", TypeNameToString(returnType)))); else ereport(NOTICE, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("return type %s is only a shell", TypeNameToString(returnType)))); } } else { char *typnam = TypeNameToString(returnType); Oid namespaceId; AclResult aclresult; char *typname; /* * Only C-coded functions can be I/O functions. We enforce this * restriction here mainly to prevent littering the catalogs with * shell types due to simple typos in user-defined function * definitions. */ if (languageOid != INTERNALlanguageId && languageOid != ClanguageId) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("type \"%s\" does not exist", typnam))); /* Otherwise, go ahead and make a shell type */ ereport(NOTICE, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("type \"%s\" is not yet defined", typnam), errdetail("Creating a shell type definition."))); namespaceId = QualifiedNameGetCreationNamespace(returnType->names, &typname); aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceId)); rettype = TypeShellMake(typname, namespaceId); Assert(OidIsValid(rettype)); } *prorettype_p = rettype; *returnsSet_p = returnType->setof; } /* * Interpret the argument-types list of the CREATE FUNCTION statement. */ static int compute_parameter_types(List *argTypes, Oid languageOid, Oid *parameterTypes) { int parameterCount = 0; List *x; MemSet(parameterTypes, 0, FUNC_MAX_ARGS * sizeof(Oid)); foreach(x, argTypes) { TypeName *t = (TypeName *) lfirst(x); Oid toid; if (parameterCount >= FUNC_MAX_ARGS) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg("functions cannot have more than %d arguments", FUNC_MAX_ARGS))); toid = LookupTypeName(t); if (OidIsValid(toid)) { if (!get_typisdefined(toid)) { /* As above, hard error if language is SQL */ if (languageOid == SQLlanguageId) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("SQL function cannot accept shell type %s", TypeNameToString(t)))); else ereport(NOTICE, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("argument type %s is only a shell", TypeNameToString(t)))); } } else { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("type %s does not exist", TypeNameToString(t)))); } if (t->setof) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("functions cannot accept set arguments"))); parameterTypes[parameterCount++] = toid; } return parameterCount; } /* * Dissect the list of options assembled in gram.y into function * attributes. */ static void compute_attributes_sql_style(const List *options, List **as, char **language, char *volatility_p, bool *strict_p, bool *security_definer) { const List *option; DefElem *as_item = NULL; DefElem *language_item = NULL; DefElem *volatility_item = NULL; DefElem *strict_item = NULL; DefElem *security_item = NULL; foreach(option, options) { DefElem *defel = (DefElem *) lfirst(option); if (strcmp(defel->defname, "as") == 0) { if (as_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); as_item = defel; } else if (strcmp(defel->defname, "language") == 0) { if (language_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); language_item = defel; } else if (strcmp(defel->defname, "volatility") == 0) { if (volatility_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); volatility_item = defel; } else if (strcmp(defel->defname, "strict") == 0) { if (strict_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); strict_item = defel; } else if (strcmp(defel->defname, "security") == 0) { if (security_item) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); security_item = defel; } else elog(ERROR, "option \"%s\" not recognized", defel->defname); } if (as_item) *as = (List *) as_item->arg; else ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("no function body specified"))); if (language_item) *language = strVal(language_item->arg); else ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("no language specified"))); if (volatility_item) { if (strcmp(strVal(volatility_item->arg), "immutable") == 0) *volatility_p = PROVOLATILE_IMMUTABLE; else if (strcmp(strVal(volatility_item->arg), "stable") == 0) *volatility_p = PROVOLATILE_STABLE; else if (strcmp(strVal(volatility_item->arg), "volatile") == 0) *volatility_p = PROVOLATILE_VOLATILE; else elog(ERROR, "invalid volatility \"%s\"", strVal(volatility_item->arg)); } if (strict_item) *strict_p = intVal(strict_item->arg); if (security_item) *security_definer = intVal(security_item->arg); } /*------------- * Interpret the parameters *parameters and return their contents as * *byte_pct_p, etc. * * These parameters supply optional information about a function. * All have defaults if not specified. * * Note: currently, only two of these parameters actually do anything: * * * isStrict means the function should not be called when any NULL * inputs are present; instead a NULL result value should be assumed. * * * volatility tells the optimizer whether the function's result can * be assumed to be repeatable over multiple evaluations. * * The other four parameters are not used anywhere. They used to be * used in the "expensive functions" optimizer, but that's been dead code * for a long time. *------------ */ static void compute_attributes_with_style(List *parameters, bool *isStrict_p, char *volatility_p) { List *pl; foreach(pl, parameters) { DefElem *param = (DefElem *) lfirst(pl); if (strcasecmp(param->defname, "isstrict") == 0) *isStrict_p = true; else if (strcasecmp(param->defname, "iscachable") == 0) { /* obsolete spelling of isImmutable */ *volatility_p = PROVOLATILE_IMMUTABLE; } else ereport(WARNING, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unrecognized function attribute \"%s\" ignored", param->defname))); } } /* * For a dynamically linked C language object, the form of the clause is * * AS [, ] * * In all other cases * * AS * */ static void interpret_AS_clause(Oid languageOid, const char *languageName, const List *as, char **prosrc_str_p, char **probin_str_p) { Assert(as != NIL); if (languageOid == ClanguageId) { /* * For "C" language, store the file name in probin and, when * given, the link symbol name in prosrc. */ *probin_str_p = strVal(lfirst(as)); if (lnext(as) == NULL) *prosrc_str_p = "-"; else *prosrc_str_p = strVal(lsecond(as)); } else { /* Everything else wants the given string in prosrc. */ *prosrc_str_p = strVal(lfirst(as)); *probin_str_p = "-"; if (lnext(as) != NIL) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("only one AS item needed for language \"%s\"", languageName))); } } /* * CreateFunction * Execute a CREATE FUNCTION utility statement. */ void CreateFunction(CreateFunctionStmt *stmt) { char *probin_str; char *prosrc_str; Oid prorettype; bool returnsSet; char *language; char languageName[NAMEDATALEN]; Oid languageOid; Oid languageValidator; char *funcname; Oid namespaceId; AclResult aclresult; int parameterCount; Oid parameterTypes[FUNC_MAX_ARGS]; bool isStrict, security; char volatility; HeapTuple languageTuple; Form_pg_language languageStruct; List *as_clause; /* Convert list of names to a name and namespace */ namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname, &funcname); /* Check we have creation rights in target namespace */ aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceId)); /* defaults attributes */ isStrict = false; security = false; volatility = PROVOLATILE_VOLATILE; /* override attributes from explicit list */ compute_attributes_sql_style(stmt->options, &as_clause, &language, &volatility, &isStrict, &security); /* Convert language name to canonical case */ case_translate_language_name(language, languageName); /* Look up the language and validate permissions */ languageTuple = SearchSysCache(LANGNAME, PointerGetDatum(languageName), 0, 0, 0); if (!HeapTupleIsValid(languageTuple)) /* Add any new languages to this list to invoke the hint. */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("language \"%s\" does not exist", languageName), (strcmp(languageName, "plperl") == 0 || strcmp(languageName, "plperlu") == 0 || strcmp(languageName, "plpgsql") == 0 || strcmp(languageName, "plpythonu") == 0 || strcmp(languageName, "pltcl") == 0 || strcmp(languageName, "pltclu") == 0) ? errhint("You need to use \"createlang\" to load the language into the database.") : 0)); languageOid = HeapTupleGetOid(languageTuple); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); if (languageStruct->lanpltrusted) { /* if trusted language, need USAGE privilege */ AclResult aclresult; aclresult = pg_language_aclcheck(languageOid, GetUserId(), ACL_USAGE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_LANGUAGE, NameStr(languageStruct->lanname)); } else { /* if untrusted language, must be superuser */ if (!superuser()) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE, NameStr(languageStruct->lanname)); } languageValidator = languageStruct->lanvalidator; ReleaseSysCache(languageTuple); /* * Convert remaining parameters of CREATE to form wanted by * ProcedureCreate. */ compute_return_type(stmt->returnType, languageOid, &prorettype, &returnsSet); parameterCount = compute_parameter_types(stmt->argTypes, languageOid, parameterTypes); compute_attributes_with_style(stmt->withClause, &isStrict, &volatility); interpret_AS_clause(languageOid, languageName, as_clause, &prosrc_str, &probin_str); if (languageOid == INTERNALlanguageId) { /* * In PostgreSQL versions before 6.5, the SQL name of the created * function could not be different from the internal name, and * "prosrc" wasn't used. So there is code out there that does * CREATE FUNCTION xyz AS '' LANGUAGE 'internal'. To preserve some * modicum of backwards compatibility, accept an empty "prosrc" * value as meaning the supplied SQL function name. */ if (strlen(prosrc_str) == 0) prosrc_str = funcname; } if (languageOid == ClanguageId) { /* If link symbol is specified as "-", substitute procedure name */ if (strcmp(prosrc_str, "-") == 0) prosrc_str = funcname; } /* * And now that we have all the parameters, and know we're permitted * to do so, go ahead and create the function. */ ProcedureCreate(funcname, namespaceId, stmt->replace, returnsSet, prorettype, languageOid, languageValidator, prosrc_str, /* converted to text later */ probin_str, /* converted to text later */ false, /* not an aggregate */ security, isStrict, volatility, parameterCount, parameterTypes); } /* * RemoveFunction * Deletes a function. */ void RemoveFunction(RemoveFuncStmt *stmt) { List *functionName = stmt->funcname; List *argTypes = stmt->args; /* list of TypeName nodes */ Oid funcOid; HeapTuple tup; ObjectAddress object; /* * Find the function, do permissions and validity checks */ funcOid = LookupFuncNameTypeNames(functionName, argTypes, false); tup = SearchSysCache(PROCOID, ObjectIdGetDatum(funcOid), 0, 0, 0); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for function %u", funcOid); /* Permission check: must own func or its namespace */ if (!pg_proc_ownercheck(funcOid, GetUserId()) && !pg_namespace_ownercheck(((Form_pg_proc) GETSTRUCT(tup))->pronamespace, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(functionName)); if (((Form_pg_proc) GETSTRUCT(tup))->proisagg) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is an aggregate function", NameListToString(functionName)), errhint("Use DROP AGGREGATE to drop aggregate functions."))); if (((Form_pg_proc) GETSTRUCT(tup))->prolang == INTERNALlanguageId) { /* "Helpful" NOTICE when removing a builtin function ... */ ereport(NOTICE, (errcode(ERRCODE_WARNING), errmsg("removing built-in function \"%s\"", NameListToString(functionName)))); } ReleaseSysCache(tup); /* * Do the deletion */ object.classId = RelOid_pg_proc; object.objectId = funcOid; object.objectSubId = 0; performDeletion(&object, stmt->behavior); } /* * Guts of function deletion. * * Note: this is also used for aggregate deletion, since the OIDs of * both functions and aggregates point to pg_proc. */ void RemoveFunctionById(Oid funcOid) { Relation relation; HeapTuple tup; bool isagg; /* * Delete the pg_proc tuple. */ relation = heap_openr(ProcedureRelationName, RowExclusiveLock); tup = SearchSysCache(PROCOID, ObjectIdGetDatum(funcOid), 0, 0, 0); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for function %u", funcOid); isagg = ((Form_pg_proc) GETSTRUCT(tup))->proisagg; simple_heap_delete(relation, &tup->t_self); ReleaseSysCache(tup); heap_close(relation, RowExclusiveLock); /* * If there's a pg_aggregate tuple, delete that too. */ if (isagg) { relation = heap_openr(AggregateRelationName, RowExclusiveLock); tup = SearchSysCache(AGGFNOID, ObjectIdGetDatum(funcOid), 0, 0, 0); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for pg_aggregate tuple for function %u", funcOid); simple_heap_delete(relation, &tup->t_self); ReleaseSysCache(tup); heap_close(relation, RowExclusiveLock); } } /* * Rename function */ void RenameFunction(List *name, List *argtypes, const char *newname) { Oid procOid; Oid namespaceOid; HeapTuple tup; Form_pg_proc procForm; Relation rel; AclResult aclresult; rel = heap_openr(ProcedureRelationName, RowExclusiveLock); procOid = LookupFuncNameTypeNames(name, argtypes, false); tup = SearchSysCacheCopy(PROCOID, ObjectIdGetDatum(procOid), 0, 0, 0); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for function %u", procOid); procForm = (Form_pg_proc) GETSTRUCT(tup); if (procForm->proisagg) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is an aggregate function", NameListToString(name)), errhint("Use ALTER AGGREGATE to rename aggregate functions."))); namespaceOid = procForm->pronamespace; /* make sure the new name doesn't exist */ if (SearchSysCacheExists(PROCNAMENSP, CStringGetDatum(newname), Int16GetDatum(procForm->pronargs), PointerGetDatum(procForm->proargtypes), ObjectIdGetDatum(namespaceOid))) { ereport(ERROR, (errcode(ERRCODE_DUPLICATE_FUNCTION), errmsg("function %s already exists in schema \"%s\"", funcname_signature_string(newname, procForm->pronargs, procForm->proargtypes), get_namespace_name(namespaceOid)))); } /* must be owner */ if (!pg_proc_ownercheck(procOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(name)); /* must have CREATE privilege on namespace */ aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); /* rename */ namestrcpy(&(procForm->proname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); heap_close(rel, NoLock); heap_freetuple(tup); } /* * SetFunctionReturnType - change declared return type of a function * * This is presently only used for adjusting legacy functions that return * OPAQUE to return whatever we find their correct definition should be. * The caller should emit a suitable warning explaining what we did. */ void SetFunctionReturnType(Oid funcOid, Oid newRetType) { Relation pg_proc_rel; HeapTuple tup; Form_pg_proc procForm; pg_proc_rel = heap_openr(ProcedureRelationName, RowExclusiveLock); tup = SearchSysCacheCopy(PROCOID, ObjectIdGetDatum(funcOid), 0, 0, 0); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for function %u", funcOid); procForm = (Form_pg_proc) GETSTRUCT(tup); if (procForm->prorettype != OPAQUEOID) /* caller messed up */ elog(ERROR, "function %u doesn't return OPAQUE", funcOid); /* okay to overwrite copied tuple */ procForm->prorettype = newRetType; /* update the catalog and its indexes */ simple_heap_update(pg_proc_rel, &tup->t_self, tup); CatalogUpdateIndexes(pg_proc_rel, tup); heap_close(pg_proc_rel, RowExclusiveLock); } /* * SetFunctionArgType - change declared argument type of a function * * As above, but change an argument's type. */ void SetFunctionArgType(Oid funcOid, int argIndex, Oid newArgType) { Relation pg_proc_rel; HeapTuple tup; Form_pg_proc procForm; pg_proc_rel = heap_openr(ProcedureRelationName, RowExclusiveLock); tup = SearchSysCacheCopy(PROCOID, ObjectIdGetDatum(funcOid), 0, 0, 0); if (!HeapTupleIsValid(tup)) /* should not happen */ elog(ERROR, "cache lookup failed for function %u", funcOid); procForm = (Form_pg_proc) GETSTRUCT(tup); if (argIndex < 0 || argIndex >= procForm->pronargs || procForm->proargtypes[argIndex] != OPAQUEOID) elog(ERROR, "function %u doesn't take OPAQUE", funcOid); /* okay to overwrite copied tuple */ procForm->proargtypes[argIndex] = newArgType; /* update the catalog and its indexes */ simple_heap_update(pg_proc_rel, &tup->t_self, tup); CatalogUpdateIndexes(pg_proc_rel, tup); heap_close(pg_proc_rel, RowExclusiveLock); } /* * CREATE CAST */ void CreateCast(CreateCastStmt *stmt) { Oid sourcetypeid; Oid targettypeid; Oid funcid; char castcontext; Relation relation; HeapTuple tuple; Datum values[Natts_pg_cast]; char nulls[Natts_pg_cast]; ObjectAddress myself, referenced; sourcetypeid = LookupTypeName(stmt->sourcetype); if (!OidIsValid(sourcetypeid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("source data type %s does not exist", TypeNameToString(stmt->sourcetype)))); targettypeid = LookupTypeName(stmt->targettype); if (!OidIsValid(targettypeid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("target data type %s does not exist", TypeNameToString(stmt->targettype)))); if (sourcetypeid == targettypeid) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("source data type and target data type are the same"))); /* No shells, no pseudo-types allowed */ if (!get_typisdefined(sourcetypeid)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("source data type %s is only a shell", TypeNameToString(stmt->sourcetype)))); if (!get_typisdefined(targettypeid)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("target data type %s is only a shell", TypeNameToString(stmt->targettype)))); if (get_typtype(sourcetypeid) == 'p') ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("source data type %s is a pseudo-type", TypeNameToString(stmt->sourcetype)))); if (get_typtype(targettypeid) == 'p') ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("target data type %s is a pseudo-type", TypeNameToString(stmt->targettype)))); /* Permission check */ if (!pg_type_ownercheck(sourcetypeid, GetUserId()) && !pg_type_ownercheck(targettypeid, GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be owner of type %s or type %s", TypeNameToString(stmt->sourcetype), TypeNameToString(stmt->targettype)))); if (stmt->func != NULL) { Form_pg_proc procstruct; funcid = LookupFuncNameTypeNames(stmt->func->funcname, stmt->func->funcargs, false); tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(funcid), 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcid); procstruct = (Form_pg_proc) GETSTRUCT(tuple); if (procstruct->pronargs != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("cast function must take one argument"))); if (procstruct->proargtypes[0] != sourcetypeid) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("argument of cast function must match source data type"))); if (procstruct->prorettype != targettypeid) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("return data type of cast function must match target data type"))); /* * Restricting the volatility of a cast function may or may not be * a good idea in the abstract, but it definitely breaks many old * user-defined types. Disable this check --- tgl 2/1/03 */ #ifdef NOT_USED if (procstruct->provolatile == PROVOLATILE_VOLATILE) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("cast function must not be volatile"))); #endif if (procstruct->proisagg) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("cast function must not be an aggregate function"))); if (procstruct->proretset) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("cast function must not return a set"))); ReleaseSysCache(tuple); } else { int16 typ1len; int16 typ2len; bool typ1byval; bool typ2byval; char typ1align; char typ2align; /* indicates binary coercibility */ funcid = InvalidOid; /* * Must be superuser to create binary-compatible casts, since * erroneous casts can easily crash the backend. */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create a cast WITHOUT FUNCTION"))); /* * Also, insist that the types match as to size, alignment, and * pass-by-value attributes; this provides at least a crude check * that they have similar representations. A pair of types that * fail this test should certainly not be equated. */ get_typlenbyvalalign(sourcetypeid, &typ1len, &typ1byval, &typ1align); get_typlenbyvalalign(targettypeid, &typ2len, &typ2byval, &typ2align); if (typ1len != typ2len || typ1byval != typ2byval || typ1align != typ2align) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("source and target data types are not physically compatible"))); } /* convert CoercionContext enum to char value for castcontext */ switch (stmt->context) { case COERCION_IMPLICIT: castcontext = COERCION_CODE_IMPLICIT; break; case COERCION_ASSIGNMENT: castcontext = COERCION_CODE_ASSIGNMENT; break; case COERCION_EXPLICIT: castcontext = COERCION_CODE_EXPLICIT; break; default: elog(ERROR, "unrecognized CoercionContext: %d", stmt->context); castcontext = 0; /* keep compiler quiet */ break; } relation = heap_openr(CastRelationName, RowExclusiveLock); /* * Check for duplicate. This is just to give a friendly error * message, the unique index would catch it anyway (so no need to * sweat about race conditions). */ tuple = SearchSysCache(CASTSOURCETARGET, ObjectIdGetDatum(sourcetypeid), ObjectIdGetDatum(targettypeid), 0, 0); if (HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("cast from type %s to type %s already exists", TypeNameToString(stmt->sourcetype), TypeNameToString(stmt->targettype)))); /* ready to go */ values[Anum_pg_cast_castsource - 1] = ObjectIdGetDatum(sourcetypeid); values[Anum_pg_cast_casttarget - 1] = ObjectIdGetDatum(targettypeid); values[Anum_pg_cast_castfunc - 1] = ObjectIdGetDatum(funcid); values[Anum_pg_cast_castcontext - 1] = CharGetDatum(castcontext); MemSet(nulls, ' ', Natts_pg_cast); tuple = heap_formtuple(RelationGetDescr(relation), values, nulls); simple_heap_insert(relation, tuple); CatalogUpdateIndexes(relation, tuple); /* make dependency entries */ myself.classId = RelationGetRelid(relation); myself.objectId = HeapTupleGetOid(tuple); myself.objectSubId = 0; /* dependency on source type */ referenced.classId = RelOid_pg_type; referenced.objectId = sourcetypeid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); /* dependency on target type */ referenced.classId = RelOid_pg_type; referenced.objectId = targettypeid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); /* dependency on function */ if (OidIsValid(funcid)) { referenced.classId = RelOid_pg_proc; referenced.objectId = funcid; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } heap_freetuple(tuple); heap_close(relation, RowExclusiveLock); } /* * DROP CAST */ void DropCast(DropCastStmt *stmt) { Oid sourcetypeid; Oid targettypeid; HeapTuple tuple; ObjectAddress object; sourcetypeid = LookupTypeName(stmt->sourcetype); if (!OidIsValid(sourcetypeid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("source data type %s does not exist", TypeNameToString(stmt->sourcetype)))); targettypeid = LookupTypeName(stmt->targettype); if (!OidIsValid(targettypeid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("target data type %s does not exist", TypeNameToString(stmt->targettype)))); tuple = SearchSysCache(CASTSOURCETARGET, ObjectIdGetDatum(sourcetypeid), ObjectIdGetDatum(targettypeid), 0, 0); if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("cast from type %s to type %s does not exist", TypeNameToString(stmt->sourcetype), TypeNameToString(stmt->targettype)))); /* Permission check */ if (!pg_type_ownercheck(sourcetypeid, GetUserId()) && !pg_type_ownercheck(targettypeid, GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be owner of type %s or type %s", TypeNameToString(stmt->sourcetype), TypeNameToString(stmt->targettype)))); /* * Do the deletion */ object.classId = get_system_catalog_relid(CastRelationName); object.objectId = HeapTupleGetOid(tuple); object.objectSubId = 0; ReleaseSysCache(tuple); performDeletion(&object, stmt->behavior); } void DropCastById(Oid castOid) { Relation relation; ScanKeyData scankey; SysScanDesc scan; HeapTuple tuple; relation = heap_openr(CastRelationName, RowExclusiveLock); ScanKeyInit(&scankey, ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(castOid)); scan = systable_beginscan(relation, CastOidIndex, true, SnapshotNow, 1, &scankey); tuple = systable_getnext(scan); if (!HeapTupleIsValid(tuple)) elog(ERROR, "could not find tuple for cast %u", castOid); simple_heap_delete(relation, &tuple->t_self); systable_endscan(scan); heap_close(relation, RowExclusiveLock); }