mirror of
https://github.com/postgres/postgres.git
synced 2025-04-21 12:05:57 +03:00
indexes. Extend the macros in include/catalog/*.h to carry the info about hand-assigned OIDs, and adjust the genbki script and bootstrap code to make the relations actually get those OIDs. Remove the small number of RelOid_pg_foo macros that we had in favor of a complete set named like the catname.h and indexing.h macros. Next phase will get rid of internal use of names for looking up catalogs and indexes; but this completes the changes forcing an initdb, so it looks like a good place to commit. Along the way, I made the shared relations (pg_database etc) not be 'bootstrap' relations any more, so as to reduce the number of hardwired entries and simplify changing those relations in future. I'm not sure whether they ever really needed to be handled as bootstrap relations, but it seems to work fine to not do so now.
1417 lines
39 KiB
C
1417 lines
39 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* functioncmds.c
|
|
*
|
|
* Routines for CREATE and DROP FUNCTION commands and CREATE and DROP
|
|
* CAST commands.
|
|
*
|
|
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.60 2005/04/14 01:38:16 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 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 parameter list of the CREATE FUNCTION statement.
|
|
*
|
|
* Results are stored into output parameters. parameterTypes must always
|
|
* be created, but the other arrays are set to NULL if not needed.
|
|
* requiredResultType is set to InvalidOid if there are no OUT parameters,
|
|
* else it is set to the OID of the implied result type.
|
|
*/
|
|
static void
|
|
examine_parameter_list(List *parameters, Oid languageOid,
|
|
oidvector **parameterTypes,
|
|
ArrayType **allParameterTypes,
|
|
ArrayType **parameterModes,
|
|
ArrayType **parameterNames,
|
|
Oid *requiredResultType)
|
|
{
|
|
int parameterCount = list_length(parameters);
|
|
Oid *inTypes;
|
|
int inCount = 0;
|
|
Datum *allTypes;
|
|
Datum *paramModes;
|
|
Datum *paramNames;
|
|
int outCount = 0;
|
|
bool have_names = false;
|
|
ListCell *x;
|
|
int i;
|
|
|
|
inTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
|
|
allTypes = (Datum *) palloc(parameterCount * sizeof(Datum));
|
|
paramModes = (Datum *) palloc(parameterCount * sizeof(Datum));
|
|
paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum));
|
|
|
|
/* Scan the list and extract data into work arrays */
|
|
i = 0;
|
|
foreach(x, parameters)
|
|
{
|
|
FunctionParameter *fp = (FunctionParameter *) lfirst(x);
|
|
TypeName *t = fp->argType;
|
|
Oid toid;
|
|
|
|
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")));
|
|
|
|
if (fp->mode != FUNC_PARAM_OUT)
|
|
inTypes[inCount++] = toid;
|
|
|
|
if (fp->mode != FUNC_PARAM_IN)
|
|
{
|
|
if (outCount == 0) /* save first OUT param's type */
|
|
*requiredResultType = toid;
|
|
outCount++;
|
|
}
|
|
|
|
allTypes[i] = ObjectIdGetDatum(toid);
|
|
|
|
paramModes[i] = CharGetDatum(fp->mode);
|
|
|
|
if (fp->name && fp->name[0])
|
|
{
|
|
paramNames[i] = DirectFunctionCall1(textin,
|
|
CStringGetDatum(fp->name));
|
|
have_names = true;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
/* Now construct the proper outputs as needed */
|
|
*parameterTypes = buildoidvector(inTypes, inCount);
|
|
|
|
if (outCount > 0)
|
|
{
|
|
*allParameterTypes = construct_array(allTypes, parameterCount, OIDOID,
|
|
sizeof(Oid), true, 'i');
|
|
*parameterModes = construct_array(paramModes, parameterCount, CHAROID,
|
|
1, true, 'c');
|
|
if (outCount > 1)
|
|
*requiredResultType = RECORDOID;
|
|
/* otherwise we set requiredResultType correctly above */
|
|
}
|
|
else
|
|
{
|
|
*allParameterTypes = NULL;
|
|
*parameterModes = NULL;
|
|
*requiredResultType = InvalidOid;
|
|
}
|
|
|
|
if (have_names)
|
|
{
|
|
for (i = 0; i < parameterCount; i++)
|
|
{
|
|
if (paramNames[i] == PointerGetDatum(NULL))
|
|
paramNames[i] = DirectFunctionCall1(textin,
|
|
CStringGetDatum(""));
|
|
}
|
|
*parameterNames = construct_array(paramNames, parameterCount, TEXTOID,
|
|
-1, false, 'i');
|
|
}
|
|
else
|
|
*parameterNames = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Recognize one of the options that can be passed to both CREATE
|
|
* FUNCTION and ALTER FUNCTION and return it via one of the out
|
|
* parameters. Returns true if the passed option was recognized. If
|
|
* the out parameter we were going to assign to points to non-NULL,
|
|
* raise a duplicate error.
|
|
*/
|
|
static bool
|
|
compute_common_attribute(DefElem *defel,
|
|
DefElem **volatility_item,
|
|
DefElem **strict_item,
|
|
DefElem **security_item)
|
|
{
|
|
if (strcmp(defel->defname, "volatility") == 0)
|
|
{
|
|
if (*volatility_item)
|
|
goto duplicate_error;
|
|
|
|
*volatility_item = defel;
|
|
}
|
|
else if (strcmp(defel->defname, "strict") == 0)
|
|
{
|
|
if (*strict_item)
|
|
goto duplicate_error;
|
|
|
|
*strict_item = defel;
|
|
}
|
|
else if (strcmp(defel->defname, "security") == 0)
|
|
{
|
|
if (*security_item)
|
|
goto duplicate_error;
|
|
|
|
*security_item = defel;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
/* Recognized an option */
|
|
return true;
|
|
|
|
duplicate_error:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("conflicting or redundant options")));
|
|
return false; /* keep compiler quiet */
|
|
}
|
|
|
|
static char
|
|
interpret_func_volatility(DefElem *defel)
|
|
{
|
|
char *str = strVal(defel->arg);
|
|
|
|
if (strcmp(str, "immutable") == 0)
|
|
return PROVOLATILE_IMMUTABLE;
|
|
else if (strcmp(str, "stable") == 0)
|
|
return PROVOLATILE_STABLE;
|
|
else if (strcmp(str, "volatile") == 0)
|
|
return PROVOLATILE_VOLATILE;
|
|
else
|
|
{
|
|
elog(ERROR, "invalid volatility \"%s\"", str);
|
|
return 0; /* keep compiler quiet */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Dissect the list of options assembled in gram.y into function
|
|
* attributes.
|
|
*/
|
|
static void
|
|
compute_attributes_sql_style(List *options,
|
|
List **as,
|
|
char **language,
|
|
char *volatility_p,
|
|
bool *strict_p,
|
|
bool *security_definer)
|
|
{
|
|
ListCell *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 (compute_common_attribute(defel,
|
|
&volatility_item,
|
|
&strict_item,
|
|
&security_item))
|
|
{
|
|
/* recognized common option */
|
|
continue;
|
|
}
|
|
else
|
|
elog(ERROR, "option \"%s\" not recognized",
|
|
defel->defname);
|
|
}
|
|
|
|
/* process required items */
|
|
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")));
|
|
|
|
/* process optional items */
|
|
if (volatility_item)
|
|
*volatility_p = interpret_func_volatility(volatility_item);
|
|
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 via
|
|
* *isStrict_p and *volatility_p.
|
|
*
|
|
* These parameters supply optional information about a function.
|
|
* All have defaults if not specified. Parameters:
|
|
*
|
|
* * 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.
|
|
*------------
|
|
*/
|
|
static void
|
|
compute_attributes_with_style(List *parameters, bool *isStrict_p, char *volatility_p)
|
|
{
|
|
ListCell *pl;
|
|
|
|
foreach(pl, parameters)
|
|
{
|
|
DefElem *param = (DefElem *) lfirst(pl);
|
|
|
|
if (pg_strcasecmp(param->defname, "isstrict") == 0)
|
|
*isStrict_p = defGetBoolean(param);
|
|
else if (pg_strcasecmp(param->defname, "iscachable") == 0)
|
|
{
|
|
/* obsolete spelling of isImmutable */
|
|
if (defGetBoolean(param))
|
|
*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 <object file name> [, <link symbol name> ]
|
|
*
|
|
* In all other cases
|
|
*
|
|
* AS <object reference, or sql code>
|
|
*/
|
|
static void
|
|
interpret_AS_clause(Oid languageOid, const char *languageName, 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(linitial(as));
|
|
if (list_length(as) == 1)
|
|
*prosrc_str_p = "-";
|
|
else
|
|
*prosrc_str_p = strVal(lsecond(as));
|
|
}
|
|
else
|
|
{
|
|
/* Everything else wants the given string in prosrc. */
|
|
*prosrc_str_p = strVal(linitial(as));
|
|
*probin_str_p = "-";
|
|
|
|
if (list_length(as) != 1)
|
|
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;
|
|
Oid languageOid;
|
|
Oid languageValidator;
|
|
char *funcname;
|
|
Oid namespaceId;
|
|
AclResult aclresult;
|
|
oidvector *parameterTypes;
|
|
ArrayType *allParameterTypes;
|
|
ArrayType *parameterModes;
|
|
ArrayType *parameterNames;
|
|
Oid requiredResultType;
|
|
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));
|
|
|
|
/* default 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 */
|
|
languageName = case_translate_language_name(language);
|
|
|
|
/* 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.
|
|
*/
|
|
examine_parameter_list(stmt->parameters, languageOid,
|
|
¶meterTypes,
|
|
&allParameterTypes,
|
|
¶meterModes,
|
|
¶meterNames,
|
|
&requiredResultType);
|
|
|
|
if (stmt->returnType)
|
|
{
|
|
/* explicit RETURNS clause */
|
|
compute_return_type(stmt->returnType, languageOid,
|
|
&prorettype, &returnsSet);
|
|
if (OidIsValid(requiredResultType) && prorettype != requiredResultType)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
|
errmsg("function result type must be %s because of OUT parameters",
|
|
format_type_be(requiredResultType))));
|
|
}
|
|
else if (OidIsValid(requiredResultType))
|
|
{
|
|
/* default RETURNS clause from OUT parameters */
|
|
prorettype = requiredResultType;
|
|
returnsSet = false;
|
|
}
|
|
else
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
|
errmsg("function result type must be specified")));
|
|
/* Alternative possibility: default to RETURNS VOID */
|
|
prorettype = VOIDOID;
|
|
returnsSet = false;
|
|
}
|
|
|
|
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,
|
|
parameterTypes,
|
|
PointerGetDatum(allParameterTypes),
|
|
PointerGetDatum(parameterModes),
|
|
PointerGetDatum(parameterNames));
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 = ProcedureRelationId;
|
|
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(PROCNAMEARGSNSP,
|
|
CStringGetDatum(newname),
|
|
PointerGetDatum(&procForm->proargtypes),
|
|
ObjectIdGetDatum(namespaceOid),
|
|
0))
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_FUNCTION),
|
|
errmsg("function %s already exists in schema \"%s\"",
|
|
funcname_signature_string(newname,
|
|
procForm->pronargs,
|
|
procForm->proargtypes.values),
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* Change function owner
|
|
*/
|
|
void
|
|
AlterFunctionOwner(List *name, List *argtypes, AclId newOwnerSysId)
|
|
{
|
|
Oid procOid;
|
|
HeapTuple tup;
|
|
Form_pg_proc procForm;
|
|
Relation rel;
|
|
|
|
rel = heap_openr(ProcedureRelationName, RowExclusiveLock);
|
|
|
|
procOid = LookupFuncNameTypeNames(name, argtypes, false);
|
|
|
|
tup = SearchSysCache(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 change owner of aggregate functions.")));
|
|
|
|
/*
|
|
* If the new owner is the same as the existing owner, consider the
|
|
* command to have succeeded. This is for dump restoration purposes.
|
|
*/
|
|
if (procForm->proowner != newOwnerSysId)
|
|
{
|
|
Datum repl_val[Natts_pg_proc];
|
|
char repl_null[Natts_pg_proc];
|
|
char repl_repl[Natts_pg_proc];
|
|
Acl *newAcl;
|
|
Datum aclDatum;
|
|
bool isNull;
|
|
HeapTuple newtuple;
|
|
|
|
/* Otherwise, must be superuser to change object ownership */
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to change owner")));
|
|
|
|
memset(repl_null, ' ', sizeof(repl_null));
|
|
memset(repl_repl, ' ', sizeof(repl_repl));
|
|
|
|
repl_repl[Anum_pg_proc_proowner - 1] = 'r';
|
|
repl_val[Anum_pg_proc_proowner - 1] = Int32GetDatum(newOwnerSysId);
|
|
|
|
/*
|
|
* Determine the modified ACL for the new owner. This is only
|
|
* necessary when the ACL is non-null.
|
|
*/
|
|
aclDatum = SysCacheGetAttr(PROCOID, tup,
|
|
Anum_pg_proc_proacl,
|
|
&isNull);
|
|
if (!isNull)
|
|
{
|
|
newAcl = aclnewowner(DatumGetAclP(aclDatum),
|
|
procForm->proowner, newOwnerSysId);
|
|
repl_repl[Anum_pg_proc_proacl - 1] = 'r';
|
|
repl_val[Anum_pg_proc_proacl - 1] = PointerGetDatum(newAcl);
|
|
}
|
|
|
|
newtuple = heap_modifytuple(tup, RelationGetDescr(rel), repl_val, repl_null, repl_repl);
|
|
|
|
simple_heap_update(rel, &newtuple->t_self, newtuple);
|
|
CatalogUpdateIndexes(rel, newtuple);
|
|
|
|
heap_freetuple(newtuple);
|
|
}
|
|
|
|
ReleaseSysCache(tup);
|
|
heap_close(rel, NoLock);
|
|
}
|
|
|
|
/*
|
|
* Implements the ALTER FUNCTION utility command (except for the
|
|
* RENAME and OWNER clauses, which are handled as part of the generic
|
|
* ALTER framework).
|
|
*/
|
|
void
|
|
AlterFunction(AlterFunctionStmt *stmt)
|
|
{
|
|
HeapTuple tup;
|
|
Oid funcOid;
|
|
Form_pg_proc procForm;
|
|
Relation rel;
|
|
ListCell *l;
|
|
DefElem *volatility_item = NULL;
|
|
DefElem *strict_item = NULL;
|
|
DefElem *security_def_item = NULL;
|
|
|
|
rel = heap_openr(ProcedureRelationName, RowExclusiveLock);
|
|
|
|
funcOid = LookupFuncNameTypeNames(stmt->func->funcname,
|
|
stmt->func->funcargs,
|
|
false);
|
|
|
|
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);
|
|
|
|
/* Permission check: must own function */
|
|
if (!pg_proc_ownercheck(funcOid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC,
|
|
NameListToString(stmt->func->funcname));
|
|
|
|
if (procForm->proisagg)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is an aggregate function",
|
|
NameListToString(stmt->func->funcname))));
|
|
|
|
/* Examine requested actions. */
|
|
foreach (l, stmt->actions)
|
|
{
|
|
DefElem *defel = (DefElem *) lfirst(l);
|
|
|
|
if (compute_common_attribute(defel,
|
|
&volatility_item,
|
|
&strict_item,
|
|
&security_def_item) == false)
|
|
elog(ERROR, "option \"%s\" not recognized", defel->defname);
|
|
}
|
|
|
|
if (volatility_item)
|
|
procForm->provolatile = interpret_func_volatility(volatility_item);
|
|
if (strict_item)
|
|
procForm->proisstrict = intVal(strict_item->arg);
|
|
if (security_def_item)
|
|
procForm->prosecdef = intVal(security_def_item->arg);
|
|
|
|
/* Do the update */
|
|
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.values[argIndex] != OPAQUEOID)
|
|
elog(ERROR, "function %u doesn't take OPAQUE", funcOid);
|
|
|
|
/* okay to overwrite copied tuple */
|
|
procForm->proargtypes.values[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;
|
|
int nargs;
|
|
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))));
|
|
|
|
/* 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);
|
|
nargs = procstruct->pronargs;
|
|
if (nargs < 1 || nargs > 3)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("cast function must take one to three arguments")));
|
|
if (procstruct->proargtypes.values[0] != sourcetypeid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("argument of cast function must match source data type")));
|
|
if (nargs > 1 && procstruct->proargtypes.values[1] != INT4OID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("second argument of cast function must be type integer")));
|
|
if (nargs > 2 && procstruct->proargtypes.values[2] != BOOLOID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("third argument of cast function must be type boolean")));
|
|
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;
|
|
nargs = 0;
|
|
|
|
/*
|
|
* 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")));
|
|
}
|
|
|
|
/*
|
|
* Allow source and target types to be same only for length coercion
|
|
* functions. We assume a multi-arg function does length coercion.
|
|
*/
|
|
if (sourcetypeid == targettypeid && nargs < 2)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("source data type and target data type are the same")));
|
|
|
|
/* 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 = TypeRelationId;
|
|
referenced.objectId = sourcetypeid;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
/* dependency on target type */
|
|
referenced.classId = TypeRelationId;
|
|
referenced.objectId = targettypeid;
|
|
referenced.objectSubId = 0;
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
/* dependency on function */
|
|
if (OidIsValid(funcid))
|
|
{
|
|
referenced.classId = ProcedureRelationId;
|
|
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);
|
|
}
|