postgresql/src/backend/commands/functioncmds.c

2119 lines
60 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* functioncmds.c
*
* Routines for CREATE and DROP FUNCTION commands and CREATE and DROP
* CAST commands.
*
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
2010-09-20 22:08:53 +02:00
* src/backend/commands/functioncmds.c
*
* 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 "access/htup_details.h"
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "commands/alter.h"
#include "commands/defrem.h"
#include "commands/proclang.h"
#include "miscadmin.h"
#include "optimizer/var.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.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/guc.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/tqual.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)
{
2002-09-04 22:31:48 +02:00
Oid rettype;
Type typtup;
AclResult aclresult;
typtup = LookupTypeName(NULL, returnType, NULL, false);
if (typtup)
{
if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined)
{
if (languageOid == SQLlanguageId)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
2005-10-15 04:49:52 +02:00
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))));
}
rettype = typeTypeId(typtup);
ReleaseSysCache(typtup);
}
else
{
2002-09-04 22:31:48 +02:00
char *typnam = TypeNameToString(returnType);
Oid namespaceId;
AclResult aclresult;
char *typname;
ObjectAddress address;
/*
* 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)));
/* Reject if there's typmod decoration, too */
if (returnType->typmods != NIL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
2007-11-15 22:14:46 +01:00
errmsg("type modifier cannot be specified for shell type \"%s\"",
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));
address = TypeShellMake(typname, namespaceId, GetUserId());
rettype = address.objectId;
Assert(OidIsValid(rettype));
}
aclresult = pg_type_aclcheck(rettype, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, rettype);
*prorettype_p = rettype;
*returnsSet_p = returnType->setof;
}
/*
* Interpret the function parameter list of a CREATE FUNCTION or
* CREATE AGGREGATE statement.
*
* Input parameters:
* parameters: list of FunctionParameter structs
* languageOid: OID of function language (InvalidOid if it's CREATE AGGREGATE)
* is_aggregate: needed only to determine error handling
* queryString: likewise, needed only for error handling
*
* Results are stored into output parameters. parameterTypes must always
* be created, but the other arrays are set to NULL if not needed.
Support ordered-set (WITHIN GROUP) aggregates. This patch introduces generic support for ordered-set and hypothetical-set aggregate functions, as well as implementations of the instances defined in SQL:2008 (percentile_cont(), percentile_disc(), rank(), dense_rank(), percent_rank(), cume_dist()). We also added mode() though it is not in the spec, as well as versions of percentile_cont() and percentile_disc() that can compute multiple percentile values in one pass over the data. Unlike the original submission, this patch puts full control of the sorting process in the hands of the aggregate's support functions. To allow the support functions to find out how they're supposed to sort, a new API function AggGetAggref() is added to nodeAgg.c. This allows retrieval of the aggregate call's Aggref node, which may have other uses beyond the immediate need. There is also support for ordered-set aggregates to install cleanup callback functions, so that they can be sure that infrastructure such as tuplesort objects gets cleaned up. In passing, make some fixes in the recently-added support for variadic aggregates, and make some editorial adjustments in the recent FILTER additions for aggregates. Also, simplify use of IsBinaryCoercible() by allowing it to succeed whenever the target type is ANY or ANYELEMENT. It was inconsistent that it dealt with other polymorphic target types but not these. Atri Sharma and Andrew Gierth; reviewed by Pavel Stehule and Vik Fearing, and rather heavily editorialized upon by Tom Lane
2013-12-23 22:11:35 +01:00
* variadicArgType is set to the variadic array type if there's a VARIADIC
* parameter (there can be only one); or to InvalidOid if not.
* requiredResultType is set to InvalidOid if there are no OUT parameters,
* else it is set to the OID of the implied result type.
*/
void
interpret_function_parameter_list(List *parameters,
Oid languageOid,
bool is_aggregate,
const char *queryString,
oidvector **parameterTypes,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
List **parameterDefaults,
Support ordered-set (WITHIN GROUP) aggregates. This patch introduces generic support for ordered-set and hypothetical-set aggregate functions, as well as implementations of the instances defined in SQL:2008 (percentile_cont(), percentile_disc(), rank(), dense_rank(), percent_rank(), cume_dist()). We also added mode() though it is not in the spec, as well as versions of percentile_cont() and percentile_disc() that can compute multiple percentile values in one pass over the data. Unlike the original submission, this patch puts full control of the sorting process in the hands of the aggregate's support functions. To allow the support functions to find out how they're supposed to sort, a new API function AggGetAggref() is added to nodeAgg.c. This allows retrieval of the aggregate call's Aggref node, which may have other uses beyond the immediate need. There is also support for ordered-set aggregates to install cleanup callback functions, so that they can be sure that infrastructure such as tuplesort objects gets cleaned up. In passing, make some fixes in the recently-added support for variadic aggregates, and make some editorial adjustments in the recent FILTER additions for aggregates. Also, simplify use of IsBinaryCoercible() by allowing it to succeed whenever the target type is ANY or ANYELEMENT. It was inconsistent that it dealt with other polymorphic target types but not these. Atri Sharma and Andrew Gierth; reviewed by Pavel Stehule and Vik Fearing, and rather heavily editorialized upon by Tom Lane
2013-12-23 22:11:35 +01:00
Oid *variadicArgType,
Oid *requiredResultType)
{
int parameterCount = list_length(parameters);
Oid *inTypes;
int inCount = 0;
Datum *allTypes;
Datum *paramModes;
Datum *paramNames;
int outCount = 0;
int varCount = 0;
bool have_names = false;
bool have_defaults = false;
ListCell *x;
int i;
ParseState *pstate;
Support ordered-set (WITHIN GROUP) aggregates. This patch introduces generic support for ordered-set and hypothetical-set aggregate functions, as well as implementations of the instances defined in SQL:2008 (percentile_cont(), percentile_disc(), rank(), dense_rank(), percent_rank(), cume_dist()). We also added mode() though it is not in the spec, as well as versions of percentile_cont() and percentile_disc() that can compute multiple percentile values in one pass over the data. Unlike the original submission, this patch puts full control of the sorting process in the hands of the aggregate's support functions. To allow the support functions to find out how they're supposed to sort, a new API function AggGetAggref() is added to nodeAgg.c. This allows retrieval of the aggregate call's Aggref node, which may have other uses beyond the immediate need. There is also support for ordered-set aggregates to install cleanup callback functions, so that they can be sure that infrastructure such as tuplesort objects gets cleaned up. In passing, make some fixes in the recently-added support for variadic aggregates, and make some editorial adjustments in the recent FILTER additions for aggregates. Also, simplify use of IsBinaryCoercible() by allowing it to succeed whenever the target type is ANY or ANYELEMENT. It was inconsistent that it dealt with other polymorphic target types but not these. Atri Sharma and Andrew Gierth; reviewed by Pavel Stehule and Vik Fearing, and rather heavily editorialized upon by Tom Lane
2013-12-23 22:11:35 +01:00
*variadicArgType = InvalidOid; /* default result */
2005-10-15 04:49:52 +02:00
*requiredResultType = InvalidOid; /* default result */
inTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
allTypes = (Datum *) palloc(parameterCount * sizeof(Datum));
paramModes = (Datum *) palloc(parameterCount * sizeof(Datum));
paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum));
*parameterDefaults = NIL;
/* may need a pstate for parse analysis of default exprs */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
/* Scan the list and extract data into work arrays */
i = 0;
foreach(x, parameters)
{
FunctionParameter *fp = (FunctionParameter *) lfirst(x);
TypeName *t = fp->argType;
bool isinput = false;
Oid toid;
Type typtup;
AclResult aclresult;
typtup = LookupTypeName(NULL, t, NULL, false);
if (typtup)
{
if (!((Form_pg_type) GETSTRUCT(typtup))->typisdefined)
{
/* As above, hard error if language is SQL */
if (languageOid == SQLlanguageId)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
2005-10-15 04:49:52 +02:00
errmsg("SQL function cannot accept shell type %s",
TypeNameToString(t))));
/* We don't allow creating aggregates on shell types either */
else if (is_aggregate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("aggregate 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))));
}
toid = typeTypeId(typtup);
ReleaseSysCache(typtup);
}
else
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("type %s does not exist",
TypeNameToString(t))));
toid = InvalidOid; /* keep compiler quiet */
}
aclresult = pg_type_aclcheck(toid, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, toid);
if (t->setof)
{
if (is_aggregate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("aggregates cannot accept set arguments")));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("functions cannot accept set arguments")));
}
/* handle input parameters */
if (fp->mode != FUNC_PARAM_OUT && fp->mode != FUNC_PARAM_TABLE)
{
/* other input parameters can't follow a VARIADIC parameter */
if (varCount > 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be the last input parameter")));
inTypes[inCount++] = toid;
isinput = true;
}
/* handle output parameters */
if (fp->mode != FUNC_PARAM_IN && fp->mode != FUNC_PARAM_VARIADIC)
{
if (outCount == 0) /* save first output param's type */
*requiredResultType = toid;
outCount++;
}
if (fp->mode == FUNC_PARAM_VARIADIC)
{
Support ordered-set (WITHIN GROUP) aggregates. This patch introduces generic support for ordered-set and hypothetical-set aggregate functions, as well as implementations of the instances defined in SQL:2008 (percentile_cont(), percentile_disc(), rank(), dense_rank(), percent_rank(), cume_dist()). We also added mode() though it is not in the spec, as well as versions of percentile_cont() and percentile_disc() that can compute multiple percentile values in one pass over the data. Unlike the original submission, this patch puts full control of the sorting process in the hands of the aggregate's support functions. To allow the support functions to find out how they're supposed to sort, a new API function AggGetAggref() is added to nodeAgg.c. This allows retrieval of the aggregate call's Aggref node, which may have other uses beyond the immediate need. There is also support for ordered-set aggregates to install cleanup callback functions, so that they can be sure that infrastructure such as tuplesort objects gets cleaned up. In passing, make some fixes in the recently-added support for variadic aggregates, and make some editorial adjustments in the recent FILTER additions for aggregates. Also, simplify use of IsBinaryCoercible() by allowing it to succeed whenever the target type is ANY or ANYELEMENT. It was inconsistent that it dealt with other polymorphic target types but not these. Atri Sharma and Andrew Gierth; reviewed by Pavel Stehule and Vik Fearing, and rather heavily editorialized upon by Tom Lane
2013-12-23 22:11:35 +01:00
*variadicArgType = toid;
varCount++;
/* validate variadic parameter type */
switch (toid)
{
case ANYARRAYOID:
case ANYOID:
/* okay */
break;
default:
if (!OidIsValid(get_element_type(toid)))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("VARIADIC parameter must be an array")));
break;
}
}
allTypes[i] = ObjectIdGetDatum(toid);
paramModes[i] = CharGetDatum(fp->mode);
if (fp->name && fp->name[0])
{
ListCell *px;
/*
* As of Postgres 9.0 we disallow using the same name for two
* input or two output function parameters. Depending on the
* function's language, conflicting input and output names might
* be bad too, but we leave it to the PL to complain if so.
*/
foreach(px, parameters)
{
FunctionParameter *prevfp = (FunctionParameter *) lfirst(px);
if (prevfp == fp)
break;
/* pure in doesn't conflict with pure out */
if ((fp->mode == FUNC_PARAM_IN ||
fp->mode == FUNC_PARAM_VARIADIC) &&
(prevfp->mode == FUNC_PARAM_OUT ||
prevfp->mode == FUNC_PARAM_TABLE))
continue;
if ((prevfp->mode == FUNC_PARAM_IN ||
prevfp->mode == FUNC_PARAM_VARIADIC) &&
(fp->mode == FUNC_PARAM_OUT ||
fp->mode == FUNC_PARAM_TABLE))
continue;
if (prevfp->name && prevfp->name[0] &&
strcmp(prevfp->name, fp->name) == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
2010-02-26 03:01:40 +01:00
errmsg("parameter name \"%s\" used more than once",
fp->name)));
}
paramNames[i] = CStringGetTextDatum(fp->name);
have_names = true;
}
if (fp->defexpr)
{
Node *def;
if (!isinput)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only input parameters can have default values")));
def = transformExpr(pstate, fp->defexpr,
EXPR_KIND_FUNCTION_DEFAULT);
def = coerce_to_specific_type(pstate, def, toid, "DEFAULT");
assign_expr_collations(pstate, def);
/*
* Make sure no variables are referred to (this is probably dead
* code now that add_missing_from is history).
*/
if (list_length(pstate->p_rtable) != 0 ||
contain_var_clause(def))
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("cannot use table references in parameter default value")));
/*
* transformExpr() should have already rejected subqueries,
* aggregates, and window functions, based on the EXPR_KIND_ for a
* default expression.
*
* It can't return a set either --- but coerce_to_specific_type
* already checked that for us.
*
* Note: the point of these restrictions is to ensure that an
* expression that, on its face, hasn't got subplans, aggregates,
* etc cannot suddenly have them after function default arguments
* are inserted.
*/
*parameterDefaults = lappend(*parameterDefaults, def);
have_defaults = true;
}
else
{
if (isinput && have_defaults)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("input parameters after one with a default value must also have defaults")));
}
i++;
}
free_parsestate(pstate);
/* Now construct the proper outputs as needed */
*parameterTypes = buildoidvector(inTypes, inCount);
if (outCount > 0 || varCount > 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;
}
if (have_names)
{
for (i = 0; i < parameterCount; i++)
{
if (paramNames[i] == PointerGetDatum(NULL))
paramNames[i] = CStringGetTextDatum("");
}
*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-clause error. (We don't try to detect duplicate
* SET parameters though --- if you're redundant, the last one wins.)
*/
static bool
compute_common_attribute(DefElem *defel,
DefElem **volatility_item,
DefElem **strict_item,
DefElem **security_item,
DefElem **leakproof_item,
List **set_items,
DefElem **cost_item,
DefElem **rows_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 if (strcmp(defel->defname, "leakproof") == 0)
{
if (*leakproof_item)
goto duplicate_error;
*leakproof_item = defel;
}
else if (strcmp(defel->defname, "set") == 0)
{
*set_items = lappend(*set_items, defel->arg);
}
else if (strcmp(defel->defname, "cost") == 0)
{
if (*cost_item)
goto duplicate_error;
*cost_item = defel;
}
else if (strcmp(defel->defname, "rows") == 0)
{
if (*rows_item)
goto duplicate_error;
*rows_item = defel;
}
else
return false;
/* Recognized an option */
return true;
duplicate_error:
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
2005-10-15 04:49:52 +02:00
return false; /* keep compiler quiet */
}
static char
interpret_func_volatility(DefElem *defel)
{
2005-10-15 04:49:52 +02:00
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);
2005-10-15 04:49:52 +02:00
return 0; /* keep compiler quiet */
}
}
/*
* Update a proconfig value according to a list of VariableSetStmt items.
*
* The input and result may be NULL to signify a null entry.
*/
static ArrayType *
update_proconfig_value(ArrayType *a, List *set_items)
{
ListCell *l;
foreach(l, set_items)
{
VariableSetStmt *sstmt = (VariableSetStmt *) lfirst(l);
Assert(IsA(sstmt, VariableSetStmt));
if (sstmt->kind == VAR_RESET_ALL)
a = NULL;
else
{
char *valuestr = ExtractSetVariableArgs(sstmt);
if (valuestr)
a = GUCArrayAdd(a, sstmt->name, valuestr);
2007-11-15 22:14:46 +01:00
else /* RESET */
a = GUCArrayDelete(a, sstmt->name);
}
}
return a;
}
/*
* Dissect the list of options assembled in gram.y into function
* attributes.
*/
static void
compute_attributes_sql_style(List *options,
List **as,
char **language,
Node **transform,
bool *windowfunc_p,
char *volatility_p,
bool *strict_p,
bool *security_definer,
bool *leakproof_p,
ArrayType **proconfig,
float4 *procost,
float4 *prorows)
{
ListCell *option;
2002-09-04 22:31:48 +02:00
DefElem *as_item = NULL;
DefElem *language_item = NULL;
DefElem *transform_item = NULL;
DefElem *windowfunc_item = NULL;
2002-09-04 22:31:48 +02:00
DefElem *volatility_item = NULL;
DefElem *strict_item = NULL;
DefElem *security_item = NULL;
DefElem *leakproof_item = NULL;
List *set_items = NIL;
DefElem *cost_item = NULL;
DefElem *rows_item = NULL;
foreach(option, options)
{
DefElem *defel = (DefElem *) lfirst(option);
2002-09-04 22:31:48 +02:00
if (strcmp(defel->defname, "as") == 0)
{
if (as_item)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
as_item = defel;
}
2002-09-04 22:31:48 +02:00
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, "transform") == 0)
{
if (transform_item)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
transform_item = defel;
}
else if (strcmp(defel->defname, "window") == 0)
{
if (windowfunc_item)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
windowfunc_item = defel;
}
else if (compute_common_attribute(defel,
&volatility_item,
&strict_item,
&security_item,
&leakproof_item,
&set_items,
&cost_item,
&rows_item))
{
/* recognized common option */
continue;
}
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
/* process required items */
if (as_item)
2002-09-04 22:31:48 +02:00
*as = (List *) as_item->arg;
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("no function body specified")));
*as = NIL; /* keep compiler quiet */
}
if (language_item)
*language = strVal(language_item->arg);
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("no language specified")));
*language = NULL; /* keep compiler quiet */
}
/* process optional items */
if (transform_item)
*transform = transform_item->arg;
if (windowfunc_item)
*windowfunc_p = intVal(windowfunc_item->arg);
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);
if (leakproof_item)
*leakproof_p = intVal(leakproof_item->arg);
if (set_items)
*proconfig = update_proconfig_value(NULL, set_items);
if (cost_item)
{
*procost = defGetNumeric(cost_item);
if (*procost <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COST must be positive")));
}
if (rows_item)
{
*prorows = defGetNumeric(rows_item);
if (*prorows <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS must be positive")));
}
}
/*-------------
2005-03-13 06:19:26 +01:00
* Interpret the parameters *parameters and return their contents via
* *isStrict_p and *volatility_p.
*
* These parameters supply optional information about a function.
2005-03-13 06:19:26 +01:00
* 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),
2005-10-15 04:49:52 +02:00
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,
char *funcname, List *as,
char **prosrc_str_p, char **probin_str_p)
{
Assert(as != NIL);
if (languageOid == ClanguageId)
{
/*
2005-10-15 04:49:52 +02:00
* For "C" language, store the file name in probin and, when given,
* the link symbol name in prosrc. If link symbol is omitted,
* substitute procedure name. We also allow link symbol to be
* specified as "-", since that was the habit in PG versions before
* 8.4, and there might be dump files out there that don't translate
* that back to "omitted".
*/
*probin_str_p = strVal(linitial(as));
if (list_length(as) == 1)
*prosrc_str_p = funcname;
else
{
*prosrc_str_p = strVal(lsecond(as));
if (strcmp(*prosrc_str_p, "-") == 0)
*prosrc_str_p = funcname;
}
}
else
{
/* Everything else wants the given string in prosrc. */
*prosrc_str_p = strVal(linitial(as));
*probin_str_p = NULL;
if (list_length(as) != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only one AS item needed for language \"%s\"",
languageName)));
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_p) == 0)
*prosrc_str_p = funcname;
}
}
}
/*
* CreateFunction
* Execute a CREATE FUNCTION utility statement.
*/
ObjectAddress
CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
{
char *probin_str;
char *prosrc_str;
Oid prorettype;
bool returnsSet;
char *language;
Oid languageOid;
Oid languageValidator;
Node *transformDefElem = NULL;
char *funcname;
Oid namespaceId;
AclResult aclresult;
oidvector *parameterTypes;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
List *parameterDefaults;
Support ordered-set (WITHIN GROUP) aggregates. This patch introduces generic support for ordered-set and hypothetical-set aggregate functions, as well as implementations of the instances defined in SQL:2008 (percentile_cont(), percentile_disc(), rank(), dense_rank(), percent_rank(), cume_dist()). We also added mode() though it is not in the spec, as well as versions of percentile_cont() and percentile_disc() that can compute multiple percentile values in one pass over the data. Unlike the original submission, this patch puts full control of the sorting process in the hands of the aggregate's support functions. To allow the support functions to find out how they're supposed to sort, a new API function AggGetAggref() is added to nodeAgg.c. This allows retrieval of the aggregate call's Aggref node, which may have other uses beyond the immediate need. There is also support for ordered-set aggregates to install cleanup callback functions, so that they can be sure that infrastructure such as tuplesort objects gets cleaned up. In passing, make some fixes in the recently-added support for variadic aggregates, and make some editorial adjustments in the recent FILTER additions for aggregates. Also, simplify use of IsBinaryCoercible() by allowing it to succeed whenever the target type is ANY or ANYELEMENT. It was inconsistent that it dealt with other polymorphic target types but not these. Atri Sharma and Andrew Gierth; reviewed by Pavel Stehule and Vik Fearing, and rather heavily editorialized upon by Tom Lane
2013-12-23 22:11:35 +01:00
Oid variadicArgType;
List *trftypes_list = NIL;
ArrayType *trftypes;
Oid requiredResultType;
bool isWindowFunc,
isStrict,
security,
isLeakProof;
char volatility;
ArrayType *proconfig;
float4 procost;
float4 prorows;
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 */
isWindowFunc = false;
isStrict = false;
security = false;
isLeakProof = false;
volatility = PROVOLATILE_VOLATILE;
proconfig = NULL;
procost = -1; /* indicates not set */
prorows = -1; /* indicates not set */
/* override attributes from explicit list */
compute_attributes_sql_style(stmt->options,
&as_clause, &language, &transformDefElem,
&isWindowFunc, &volatility,
&isStrict, &security, &isLeakProof,
&proconfig, &procost, &prorows);
/* Look up the language and validate permissions */
languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
if (!HeapTupleIsValid(languageTuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("language \"%s\" does not exist", language),
(PLTemplateExists(language) ?
errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
2004-08-29 07:07:03 +02:00
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);
/*
2015-05-24 03:35:49 +02:00
* Only superuser is allowed to create leakproof functions because
* leakproof functions can see tuples which have not yet been filtered out
* by security barrier views or row level security policies.
*/
if (isLeakProof && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof function")));
if (transformDefElem)
{
2015-05-24 03:35:49 +02:00
ListCell *lc;
Assert(IsA(transformDefElem, List));
2015-05-24 03:35:49 +02:00
foreach(lc, (List *) transformDefElem)
{
2015-05-24 03:35:49 +02:00
Oid typeid = typenameTypeId(NULL, lfirst(lc));
Oid elt = get_base_element_type(typeid);
typeid = elt ? elt : typeid;
get_transform_oid(typeid, languageOid, false);
trftypes_list = lappend_oid(trftypes_list, typeid);
}
}
/*
* Convert remaining parameters of CREATE to form wanted by
* ProcedureCreate.
*/
interpret_function_parameter_list(stmt->parameters,
languageOid,
false, /* not an aggregate */
queryString,
&parameterTypes,
&allParameterTypes,
&parameterModes,
&parameterNames,
&parameterDefaults,
Support ordered-set (WITHIN GROUP) aggregates. This patch introduces generic support for ordered-set and hypothetical-set aggregate functions, as well as implementations of the instances defined in SQL:2008 (percentile_cont(), percentile_disc(), rank(), dense_rank(), percent_rank(), cume_dist()). We also added mode() though it is not in the spec, as well as versions of percentile_cont() and percentile_disc() that can compute multiple percentile values in one pass over the data. Unlike the original submission, this patch puts full control of the sorting process in the hands of the aggregate's support functions. To allow the support functions to find out how they're supposed to sort, a new API function AggGetAggref() is added to nodeAgg.c. This allows retrieval of the aggregate call's Aggref node, which may have other uses beyond the immediate need. There is also support for ordered-set aggregates to install cleanup callback functions, so that they can be sure that infrastructure such as tuplesort objects gets cleaned up. In passing, make some fixes in the recently-added support for variadic aggregates, and make some editorial adjustments in the recent FILTER additions for aggregates. Also, simplify use of IsBinaryCoercible() by allowing it to succeed whenever the target type is ANY or ANYELEMENT. It was inconsistent that it dealt with other polymorphic target types but not these. Atri Sharma and Andrew Gierth; reviewed by Pavel Stehule and Vik Fearing, and rather heavily editorialized upon by Tom Lane
2013-12-23 22:11:35 +01:00
&variadicArgType,
&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;
}
if (list_length(trftypes_list) > 0)
{
2015-05-24 03:35:49 +02:00
ListCell *lc;
Datum *arr;
int i;
arr = palloc(list_length(trftypes_list) * sizeof(Datum));
i = 0;
2015-05-24 03:35:49 +02:00
foreach(lc, trftypes_list)
arr[i++] = ObjectIdGetDatum(lfirst_oid(lc));
trftypes = construct_array(arr, list_length(trftypes_list),
OIDOID, sizeof(Oid), true, 'i');
}
else
{
/* store SQL NULL instead of emtpy array */
trftypes = NULL;
}
compute_attributes_with_style(stmt->withClause, &isStrict, &volatility);
interpret_AS_clause(languageOid, language, funcname, as_clause,
&prosrc_str, &probin_str);
/*
* Set default values for COST and ROWS depending on other parameters;
* reject ROWS if it's not returnsSet. NB: pg_dump knows these default
* values, keep it in sync if you change them.
*/
if (procost < 0)
{
/* SQL and PL-language functions are assumed more expensive */
if (languageOid == INTERNALlanguageId ||
languageOid == ClanguageId)
procost = 1;
else
procost = 100;
}
if (prorows < 0)
{
if (returnsSet)
prorows = 1000;
else
prorows = 0; /* dummy value if not returnsSet */
}
else if (!returnsSet)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS is not applicable when function does not return a set")));
/*
2005-10-15 04:49:52 +02:00
* And now that we have all the parameters, and know we're permitted to do
* so, go ahead and create the function.
*/
return ProcedureCreate(funcname,
namespaceId,
stmt->replace,
returnsSet,
prorettype,
GetUserId(),
languageOid,
languageValidator,
prosrc_str, /* converted to text later */
probin_str, /* converted to text later */
false, /* not an aggregate */
isWindowFunc,
security,
isLeakProof,
isStrict,
volatility,
parameterTypes,
PointerGetDatum(allParameterTypes),
PointerGetDatum(parameterModes),
PointerGetDatum(parameterNames),
parameterDefaults,
PointerGetDatum(trftypes),
PointerGetDatum(proconfig),
procost,
prorows);
}
/*
* 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_open(ProcedureRelationId, RowExclusiveLock);
tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
2002-09-04 22:31:48 +02:00
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_open(AggregateRelationId, RowExclusiveLock);
tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(funcOid));
2002-09-04 22:31:48 +02:00
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);
}
}
/*
* Implements the ALTER FUNCTION utility command (except for the
* RENAME and OWNER clauses, which are handled as part of the generic
* ALTER framework).
*/
ObjectAddress
AlterFunction(AlterFunctionStmt *stmt)
{
2005-10-15 04:49:52 +02:00
HeapTuple tup;
Oid funcOid;
Form_pg_proc procForm;
2005-10-15 04:49:52 +02:00
Relation rel;
ListCell *l;
DefElem *volatility_item = NULL;
DefElem *strict_item = NULL;
DefElem *security_def_item = NULL;
DefElem *leakproof_item = NULL;
List *set_items = NIL;
DefElem *cost_item = NULL;
DefElem *rows_item = NULL;
ObjectAddress address;
rel = heap_open(ProcedureRelationId, RowExclusiveLock);
funcOid = LookupFuncNameTypeNames(stmt->func->funcname,
stmt->func->funcargs,
false);
tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
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. */
2005-10-15 04:49:52 +02:00
foreach(l, stmt->actions)
{
2005-10-15 04:49:52 +02:00
DefElem *defel = (DefElem *) lfirst(l);
if (compute_common_attribute(defel,
&volatility_item,
&strict_item,
&security_def_item,
&leakproof_item,
&set_items,
&cost_item,
&rows_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);
if (leakproof_item)
{
procForm->proleakproof = intVal(leakproof_item->arg);
if (procForm->proleakproof && !superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof function")));
}
if (cost_item)
{
procForm->procost = defGetNumeric(cost_item);
if (procForm->procost <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("COST must be positive")));
}
if (rows_item)
{
procForm->prorows = defGetNumeric(rows_item);
if (procForm->prorows <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS must be positive")));
if (!procForm->proretset)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS is not applicable when function does not return a set")));
}
if (set_items)
{
Datum datum;
bool isnull;
ArrayType *a;
Datum repl_val[Natts_pg_proc];
bool repl_null[Natts_pg_proc];
bool repl_repl[Natts_pg_proc];
/* extract existing proconfig setting */
datum = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proconfig, &isnull);
a = isnull ? NULL : DatumGetArrayTypeP(datum);
/* update according to each SET or RESET item, left to right */
a = update_proconfig_value(a, set_items);
/* update the tuple */
memset(repl_repl, false, sizeof(repl_repl));
repl_repl[Anum_pg_proc_proconfig - 1] = true;
if (a == NULL)
{
repl_val[Anum_pg_proc_proconfig - 1] = (Datum) 0;
repl_null[Anum_pg_proc_proconfig - 1] = true;
}
else
{
repl_val[Anum_pg_proc_proconfig - 1] = PointerGetDatum(a);
repl_null[Anum_pg_proc_proconfig - 1] = false;
}
tup = heap_modify_tuple(tup, RelationGetDescr(rel),
repl_val, repl_null, repl_repl);
}
/* Do the update */
simple_heap_update(rel, &tup->t_self, tup);
CatalogUpdateIndexes(rel, tup);
InvokeObjectPostAlterHook(ProcedureRelationId, funcOid, 0);
ObjectAddressSet(address, ProcedureRelationId, funcOid);
heap_close(rel, NoLock);
heap_freetuple(tup);
return address;
}
2003-06-27 16:45:32 +02:00
/*
* 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_open(ProcedureRelationId, RowExclusiveLock);
tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for function %u", funcOid);
procForm = (Form_pg_proc) GETSTRUCT(tup);
2003-08-04 02:43:34 +02:00
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_open(ProcedureRelationId, RowExclusiveLock);
tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
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
*/
ObjectAddress
CreateCast(CreateCastStmt *stmt)
{
Oid sourcetypeid;
Oid targettypeid;
char sourcetyptype;
char targettyptype;
Oid funcid;
Oid castid;
int nargs;
char castcontext;
char castmethod;
Relation relation;
HeapTuple tuple;
Datum values[Natts_pg_cast];
bool nulls[Natts_pg_cast];
ObjectAddress myself,
2002-09-04 22:31:48 +02:00
referenced;
AclResult aclresult;
sourcetypeid = typenameTypeId(NULL, stmt->sourcetype);
targettypeid = typenameTypeId(NULL, stmt->targettype);
sourcetyptype = get_typtype(sourcetypeid);
targettyptype = get_typtype(targettypeid);
/* No pseudo-types allowed */
if (sourcetyptype == TYPTYPE_PSEUDO)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("source data type %s is a pseudo-type",
TypeNameToString(stmt->sourcetype))));
if (targettyptype == TYPTYPE_PSEUDO)
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",
format_type_be(sourcetypeid),
format_type_be(targettypeid))));
aclresult = pg_type_aclcheck(sourcetypeid, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, sourcetypeid);
aclresult = pg_type_aclcheck(targettypeid, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, targettypeid);
/* Domains are allowed for historical reasons, but we warn */
if (sourcetyptype == TYPTYPE_DOMAIN)
ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cast will be ignored because the source data type is a domain")));
else if (targettyptype == TYPTYPE_DOMAIN)
ereport(WARNING,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cast will be ignored because the target data type is a domain")));
/* Detemine the cast method */
if (stmt->func != NULL)
castmethod = COERCION_METHOD_FUNCTION;
else if (stmt->inout)
castmethod = COERCION_METHOD_INOUT;
else
castmethod = COERCION_METHOD_BINARY;
if (castmethod == COERCION_METHOD_FUNCTION)
{
Form_pg_proc procstruct;
funcid = LookupFuncNameTypeNames(stmt->func->funcname,
stmt->func->funcargs,
false);
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
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),
2005-10-15 04:49:52 +02:00
errmsg("cast function must take one to three arguments")));
if (!IsBinaryCoercible(sourcetypeid, procstruct->proargtypes.values[0]))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("argument of cast function must match or be binary-coercible from source data type")));
if (nargs > 1 && procstruct->proargtypes.values[1] != INT4OID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2005-10-15 04:49:52 +02:00
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),
2005-10-15 04:49:52 +02:00
errmsg("third argument of cast function must be type boolean")));
if (!IsBinaryCoercible(procstruct->prorettype, targettypeid))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("return data type of cast function must match or be binary-coercible to target data type")));
2003-08-04 02:43:34 +02:00
/*
2005-10-15 04:49:52 +02:00
* 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),
2005-10-15 04:49:52 +02:00
errmsg("cast function must not be an aggregate function")));
if (procstruct->proiswindow)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cast function must not be a window function")));
if (procstruct->proretset)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("cast function must not return a set")));
ReleaseSysCache(tuple);
}
else
{
funcid = InvalidOid;
nargs = 0;
}
if (castmethod == COERCION_METHOD_BINARY)
{
2003-08-04 02:43:34 +02:00
int16 typ1len;
int16 typ2len;
bool typ1byval;
bool typ2byval;
char typ1align;
char typ2align;
/*
* Must be superuser to create binary-compatible casts, since
* erroneous casts can easily crash the backend.
*/
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
2005-10-15 04:49:52 +02:00
errmsg("must be superuser to create a cast WITHOUT FUNCTION")));
/*
* Also, insist that the types match as to size, alignment, and
2005-10-15 04:49:52 +02:00
* 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")));
/*
* We know that composite, enum and array types are never binary-
* compatible with each other. They all have OIDs embedded in them.
*
* Theoretically you could build a user-defined base type that is
* binary-compatible with a composite, enum, or array type. But we
* disallow that too, as in practice such a cast is surely a mistake.
* You can always work around that by writing a cast function.
*/
if (sourcetyptype == TYPTYPE_COMPOSITE ||
targettyptype == TYPTYPE_COMPOSITE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("composite data types are not binary-compatible")));
if (sourcetyptype == TYPTYPE_ENUM ||
targettyptype == TYPTYPE_ENUM)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("enum data types are not binary-compatible")));
if (OidIsValid(get_element_type(sourcetypeid)) ||
OidIsValid(get_element_type(targettypeid)))
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("array data types are not binary-compatible")));
Improve handling of domains over arrays. This patch eliminates various bizarre behaviors caused by sloppy thinking about the difference between a domain type and its underlying array type. In particular, the operation of updating one element of such an array has to be considered as yielding a value of the underlying array type, *not* a value of the domain, because there's no assurance that the domain's CHECK constraints are still satisfied. If we're intending to store the result back into a domain column, we have to re-cast to the domain type so that constraints are re-checked. For similar reasons, such a domain can't be blindly matched to an ANYARRAY polymorphic parameter, because the polymorphic function is likely to apply array-ish operations that could invalidate the domain constraints. For the moment, we just forbid such matching. We might later wish to insert an automatic downcast to the underlying array type, but such a change should also change matching of domains to ANYELEMENT for consistency. To ensure that all such logic is rechecked, this patch removes the original hack of setting a domain's pg_type.typelem field to match its base type; the typelem will always be zero instead. In those places where it's really okay to look through the domain type with no other logic changes, use the newly added get_base_element_type function in place of get_element_type. catversion bumped due to change in pg_type contents. Per bug #5717 from Richard Huxton and subsequent discussion.
2010-10-21 22:07:17 +02:00
/*
* We also disallow creating binary-compatibility casts involving
* domains. Casting from a domain to its base type is already
* allowed, and casting the other way ought to go through domain
* coercion to permit constraint checking. Again, if you're intent on
Improve handling of domains over arrays. This patch eliminates various bizarre behaviors caused by sloppy thinking about the difference between a domain type and its underlying array type. In particular, the operation of updating one element of such an array has to be considered as yielding a value of the underlying array type, *not* a value of the domain, because there's no assurance that the domain's CHECK constraints are still satisfied. If we're intending to store the result back into a domain column, we have to re-cast to the domain type so that constraints are re-checked. For similar reasons, such a domain can't be blindly matched to an ANYARRAY polymorphic parameter, because the polymorphic function is likely to apply array-ish operations that could invalidate the domain constraints. For the moment, we just forbid such matching. We might later wish to insert an automatic downcast to the underlying array type, but such a change should also change matching of domains to ANYELEMENT for consistency. To ensure that all such logic is rechecked, this patch removes the original hack of setting a domain's pg_type.typelem field to match its base type; the typelem will always be zero instead. In those places where it's really okay to look through the domain type with no other logic changes, use the newly added get_base_element_type function in place of get_element_type. catversion bumped due to change in pg_type contents. Per bug #5717 from Richard Huxton and subsequent discussion.
2010-10-21 22:07:17 +02:00
* having your own semantics for that, create a no-op cast function.
*
* NOTE: if we were to relax this, the above checks for composites
* etc. would have to be modified to look through domains to their
* base types.
*/
if (sourcetyptype == TYPTYPE_DOMAIN ||
targettyptype == TYPTYPE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("domain data types must not be marked binary-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),
2005-10-15 04:49:52 +02:00
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_open(CastRelationId, RowExclusiveLock);
/*
2005-10-15 04:49:52 +02:00
* 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 = SearchSysCache2(CASTSOURCETARGET,
ObjectIdGetDatum(sourcetypeid),
ObjectIdGetDatum(targettypeid));
if (HeapTupleIsValid(tuple))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("cast from type %s to type %s already exists",
format_type_be(sourcetypeid),
format_type_be(targettypeid))));
/* ready to go */
2002-09-04 22:31:48 +02:00
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);
values[Anum_pg_cast_castmethod - 1] = CharGetDatum(castmethod);
MemSet(nulls, false, sizeof(nulls));
tuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
castid = simple_heap_insert(relation, tuple);
CatalogUpdateIndexes(relation, tuple);
/* make dependency entries */
myself.classId = CastRelationId;
myself.objectId = castid;
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);
}
/* dependency on extension */
recordDependencyOnCurrentExtension(&myself, false);
/* Post creation hook for new cast */
InvokeObjectPostCreateHook(CastRelationId, castid, 0);
heap_freetuple(tuple);
heap_close(relation, RowExclusiveLock);
return myself;
}
/*
* get_cast_oid - given two type OIDs, look up a cast OID
*
* If missing_ok is false, throw an error if the cast is not found. If
* true, just return InvalidOid.
*/
Oid
get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok)
{
2011-04-10 17:42:00 +02:00
Oid oid;
oid = GetSysCacheOid2(CASTSOURCETARGET,
ObjectIdGetDatum(sourcetypeid),
ObjectIdGetDatum(targettypeid));
if (!OidIsValid(oid) && !missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("cast from type %s to type %s does not exist",
format_type_be(sourcetypeid),
format_type_be(targettypeid))));
return oid;
}
void
DropCastById(Oid castOid)
{
Relation relation;
ScanKeyData scankey;
SysScanDesc scan;
HeapTuple tuple;
relation = heap_open(CastRelationId, RowExclusiveLock);
ScanKeyInit(&scankey,
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(castOid));
scan = systable_beginscan(relation, CastOidIndexId, true,
NULL, 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);
}
static void
check_transform_function(Form_pg_proc procstruct)
{
if (procstruct->provolatile == PROVOLATILE_VOLATILE)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must not be volatile")));
if (procstruct->proisagg)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2015-05-24 03:35:49 +02:00
errmsg("transform function must not be an aggregate function")));
if (procstruct->proiswindow)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must not be a window function")));
if (procstruct->proretset)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must not return a set")));
if (procstruct->pronargs != 1)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("transform function must take one argument")));
if (procstruct->proargtypes.values[0] != INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("first argument of transform function must be type \"internal\"")));
}
/*
* CREATE TRANSFORM
*/
ObjectAddress
CreateTransform(CreateTransformStmt *stmt)
{
Oid typeid;
char typtype;
Oid langid;
Oid fromsqlfuncid;
Oid tosqlfuncid;
AclResult aclresult;
Form_pg_proc procstruct;
Datum values[Natts_pg_transform];
bool nulls[Natts_pg_transform];
bool replaces[Natts_pg_transform];
Oid transformid;
HeapTuple tuple;
HeapTuple newtuple;
Relation relation;
ObjectAddress myself,
referenced;
bool is_replace;
/*
* Get the type
*/
typeid = typenameTypeId(NULL, stmt->type_name);
typtype = get_typtype(typeid);
if (typtype == TYPTYPE_PSEUDO)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("data type %s is a pseudo-type",
TypeNameToString(stmt->type_name))));
if (typtype == TYPTYPE_DOMAIN)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("data type %s is a domain",
TypeNameToString(stmt->type_name))));
if (!pg_type_ownercheck(typeid, GetUserId()))
aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error_type(aclresult, typeid);
/*
* Get the language
*/
langid = get_language_oid(stmt->lang, false);
aclresult = pg_language_aclcheck(langid, GetUserId(), ACL_USAGE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_LANGUAGE, stmt->lang);
/*
* Get the functions
*/
if (stmt->fromsql)
{
fromsqlfuncid = LookupFuncNameTypeNames(stmt->fromsql->funcname, stmt->fromsql->funcargs, false);
if (!pg_proc_ownercheck(fromsqlfuncid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
aclresult = pg_proc_aclcheck(fromsqlfuncid, GetUserId(), ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fromsqlfuncid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", fromsqlfuncid);
procstruct = (Form_pg_proc) GETSTRUCT(tuple);
if (procstruct->prorettype != INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("return data type of FROM SQL function must be \"internal\"")));
check_transform_function(procstruct);
ReleaseSysCache(tuple);
}
else
fromsqlfuncid = InvalidOid;
if (stmt->tosql)
{
tosqlfuncid = LookupFuncNameTypeNames(stmt->tosql->funcname, stmt->tosql->funcargs, false);
if (!pg_proc_ownercheck(tosqlfuncid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
aclresult = pg_proc_aclcheck(tosqlfuncid, GetUserId(), ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(tosqlfuncid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for function %u", tosqlfuncid);
procstruct = (Form_pg_proc) GETSTRUCT(tuple);
if (procstruct->prorettype != typeid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("return data type of TO SQL function must be the transform data type")));
check_transform_function(procstruct);
ReleaseSysCache(tuple);
}
else
tosqlfuncid = InvalidOid;
/*
* Ready to go
*/
values[Anum_pg_transform_trftype - 1] = ObjectIdGetDatum(typeid);
values[Anum_pg_transform_trflang - 1] = ObjectIdGetDatum(langid);
values[Anum_pg_transform_trffromsql - 1] = ObjectIdGetDatum(fromsqlfuncid);
values[Anum_pg_transform_trftosql - 1] = ObjectIdGetDatum(tosqlfuncid);
MemSet(nulls, false, sizeof(nulls));
relation = heap_open(TransformRelationId, RowExclusiveLock);
tuple = SearchSysCache2(TRFTYPELANG,
ObjectIdGetDatum(typeid),
ObjectIdGetDatum(langid));
if (HeapTupleIsValid(tuple))
{
if (!stmt->replace)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
2015-05-24 03:35:49 +02:00
errmsg("transform for type %s language \"%s\" already exists",
format_type_be(typeid),
stmt->lang)));
MemSet(replaces, false, sizeof(replaces));
replaces[Anum_pg_transform_trffromsql - 1] = true;
replaces[Anum_pg_transform_trftosql - 1] = true;
newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
simple_heap_update(relation, &newtuple->t_self, newtuple);
transformid = HeapTupleGetOid(tuple);
ReleaseSysCache(tuple);
is_replace = true;
}
else
{
newtuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
transformid = simple_heap_insert(relation, newtuple);
is_replace = false;
}
CatalogUpdateIndexes(relation, newtuple);
if (is_replace)
deleteDependencyRecordsFor(TransformRelationId, transformid, true);
/* make dependency entries */
myself.classId = TransformRelationId;
myself.objectId = transformid;
myself.objectSubId = 0;
/* dependency on language */
referenced.classId = LanguageRelationId;
referenced.objectId = langid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on type */
referenced.classId = TypeRelationId;
referenced.objectId = typeid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependencies on functions */
if (OidIsValid(fromsqlfuncid))
{
referenced.classId = ProcedureRelationId;
referenced.objectId = fromsqlfuncid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
if (OidIsValid(tosqlfuncid))
{
referenced.classId = ProcedureRelationId;
referenced.objectId = tosqlfuncid;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/* dependency on extension */
recordDependencyOnCurrentExtension(&myself, is_replace);
/* Post creation hook for new transform */
InvokeObjectPostCreateHook(TransformRelationId, transformid, 0);
heap_freetuple(newtuple);
heap_close(relation, RowExclusiveLock);
return myself;
}
/*
* get_transform_oid - given type OID and language OID, look up a transform OID
*
* If missing_ok is false, throw an error if the transform is not found. If
* true, just return InvalidOid.
*/
Oid
get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok)
{
Oid oid;
oid = GetSysCacheOid2(TRFTYPELANG,
ObjectIdGetDatum(type_id),
ObjectIdGetDatum(lang_id));
if (!OidIsValid(oid) && !missing_ok)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
2015-05-24 03:35:49 +02:00
errmsg("transform for type %s language \"%s\" does not exist",
format_type_be(type_id),
get_language_name(lang_id, false))));
return oid;
}
void
DropTransformById(Oid transformOid)
{
Relation relation;
ScanKeyData scankey;
SysScanDesc scan;
HeapTuple tuple;
relation = heap_open(TransformRelationId, RowExclusiveLock);
ScanKeyInit(&scankey,
ObjectIdAttributeNumber,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(transformOid));
scan = systable_beginscan(relation, TransformOidIndexId, true,
NULL, 1, &scankey);
tuple = systable_getnext(scan);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for transform %u", transformOid);
simple_heap_delete(relation, &tuple->t_self);
systable_endscan(scan);
heap_close(relation, RowExclusiveLock);
}
/*
* Subroutine for ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME
*
* Is there a function with the given name and signature already in the given
* namespace? If so, raise an appropriate error message.
*/
void
IsThereFunctionInNamespace(const char *proname, int pronargs,
oidvector *proargtypes, Oid nspOid)
{
/* check for duplicate name (more friendly than unique-index failure) */
if (SearchSysCacheExists3(PROCNAMEARGSNSP,
CStringGetDatum(proname),
PointerGetDatum(proargtypes),
ObjectIdGetDatum(nspOid)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_FUNCTION),
errmsg("function %s already exists in schema \"%s\"",
funcname_signature_string(proname, pronargs,
NIL, proargtypes->values),
get_namespace_name(nspOid))));
}
/*
* ExecuteDoStmt
* Execute inline procedural-language code
*/
void
ExecuteDoStmt(DoStmt *stmt)
{
InlineCodeBlock *codeblock = makeNode(InlineCodeBlock);
ListCell *arg;
DefElem *as_item = NULL;
DefElem *language_item = NULL;
char *language;
Oid laninline;
HeapTuple languageTuple;
Form_pg_language languageStruct;
/* Process options we got from gram.y */
foreach(arg, stmt->args)
{
DefElem *defel = (DefElem *) lfirst(arg);
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
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
if (as_item)
codeblock->source_text = strVal(as_item->arg);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("no inline code specified")));
/* if LANGUAGE option wasn't specified, use the default */
if (language_item)
language = strVal(language_item->arg);
else
language = "plpgsql";
/* Look up the language and validate permissions */
languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
if (!HeapTupleIsValid(languageTuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("language \"%s\" does not exist", language),
(PLTemplateExists(language) ?
errhint("Use CREATE LANGUAGE to load the language into the database.") : 0)));
codeblock->langOid = HeapTupleGetOid(languageTuple);
languageStruct = (Form_pg_language) GETSTRUCT(languageTuple);
codeblock->langIsTrusted = languageStruct->lanpltrusted;
if (languageStruct->lanpltrusted)
{
/* if trusted language, need USAGE privilege */
AclResult aclresult;
aclresult = pg_language_aclcheck(codeblock->langOid, 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));
}
/* get the handler function's OID */
laninline = languageStruct->laninline;
if (!OidIsValid(laninline))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2010-02-26 03:01:40 +01:00
errmsg("language \"%s\" does not support inline code execution",
NameStr(languageStruct->lanname))));
ReleaseSysCache(languageTuple);
/* execute the inline handler */
OidFunctionCall1(laninline, PointerGetDatum(codeblock));
}