First phase of OUT-parameters project. We can now define and use SQL

functions with OUT parameters.  The various PLs still need work, as does
pg_dump.  Rudimentary docs and regression tests included.
This commit is contained in:
Tom Lane 2005-03-31 22:46:33 +00:00
parent fb13881f42
commit 47888fe842
23 changed files with 1500 additions and 529 deletions

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.64 2005/01/04 00:39:53 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.65 2005/03/31 22:45:59 tgl Exp $
-->
<refentry id="SQL-CREATEFUNCTION">
@ -19,8 +19,9 @@ $PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.64 2005/01/04 00:39
<refsynopsisdiv>
<synopsis>
CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
RETURNS <replaceable class="parameter">rettype</replaceable>
CREATE [ OR REPLACE ] FUNCTION
<replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
[ RETURNS <replaceable class="parameter">rettype</replaceable> ]
{ LANGUAGE <replaceable class="parameter">langname</replaceable>
| IMMUTABLE | STABLE | VOLATILE
| CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
@ -57,7 +58,9 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
tried, you would actually be creating a new, distinct function).
Also, <command>CREATE OR REPLACE FUNCTION</command> will not let
you change the return type of an existing function. To do that,
you must drop and recreate the function.
you must drop and recreate the function. (When using <literal>OUT</>
parameters, that means you can't change the names or types of any
<literal>OUT</> parameters except by dropping the function.)
</para>
<para>
@ -88,6 +91,17 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">argmode</replaceable></term>
<listitem>
<para>
The mode of an argument: either <literal>IN</>, <literal>OUT</>,
or <literal>INOUT</>. If omitted, the default is <literal>IN</>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">argname</replaceable></term>
@ -95,7 +109,10 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
<para>
The name of an argument. Some languages (currently only PL/pgSQL) let
you use the name in the function body. For other languages the
argument name is just extra documentation.
name of an input argument is just extra documentation. But the name
of an output argument is significant, since it defines the column
name in the result row type. (If you omit the name for an output
argument, the system will choose a default column name.)
</para>
</listitem>
</varlistentry>
@ -137,6 +154,13 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
Depending on the implementation language it may also be allowed
to specify <quote>pseudotypes</> such as <type>cstring</>.
</para>
<para>
When there are <literal>OUT</> or <literal>INOUT</> parameters,
the <literal>RETURNS</> clause may be omitted. If present, it
must agree with the result type implied by the output parameters:
<literal>RECORD</> if there are multiple output parameters, or
the same type as the single output parameter.
</para>
<para>
The <literal>SETOF</literal>
modifier indicates that the function will return a set of
@ -361,6 +385,16 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
names).
</para>
<para>
Two functions are considered the same if they have the same names and
<emphasis>input</> argument types, ignoring any <literal>OUT</>
parameters. Thus for example these declarations conflict:
<programlisting>
CREATE FUNCTION foo(int) ...
CREATE FUNCTION foo(int, out text) ...
</programlisting>
</para>
<para>
When repeated <command>CREATE FUNCTION</command> calls refer to
the same object file, the file is only loaded once. To unload and
@ -393,7 +427,7 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
<title>Examples</title>
<para>
Here is a trivial example to help you get started. For more
Here are some trivial examples to help you get started. For more
information and examples, see <xref linkend="xfunc">.
<programlisting>
CREATE FUNCTION add(integer, integer) RETURNS integer
@ -407,13 +441,34 @@ CREATE FUNCTION add(integer, integer) RETURNS integer
<para>
Increment an integer, making use of an argument name, in
<application>PL/pgSQL</application>:
<programlisting>
CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
BEGIN
RETURN i + 1;
END;
$$ LANGUAGE plpgsql;
</programlisting>
</para>
<para>
Return a record containing multiple output parameters:
<programlisting>
CREATE FUNCTION dup(in int, out f1 int, out f2 text)
AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
LANGUAGE SQL;
SELECT * FROM dup(42);
</programlisting>
You can do the same thing more verbosely with an explicitly named
composite type:
<programlisting>
CREATE TYPE dup_result AS (f1 int, f2 text);
CREATE FUNCTION dup(int) RETURNS dup_result
AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
LANGUAGE SQL;
SELECT * FROM dup(42);
</programlisting>
</para>
</refsect1>
@ -428,6 +483,13 @@ $$ LANGUAGE plpgsql;
not fully compatible. The attributes are not portable, neither are the
different available languages.
</para>
<para>
For compatibility with some other database systems,
<replaceable class="parameter">argmode</replaceable> can be written
either before or after <replaceable class="parameter">argname</replaceable>.
But only the first way is standard-compliant.
</para>
</refsect1>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.101 2005/03/16 21:38:04 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.102 2005/03/31 22:46:02 tgl Exp $
-->
<sect1 id="xfunc">
@ -172,7 +172,7 @@ INSERT INTO $1 VALUES (42);
</programlisting>
</para>
<sect2>
<sect2 id="xfunc-sql-base-functions">
<title><acronym>SQL</acronym> Functions on Base Types</title>
<para>
@ -484,7 +484,7 @@ SELECT emp.name, emp.double_salary FROM emp;
</tip>
<para>
Another way to use a function returning a row result is to pass the
Another way to use a function returning a composite type is to pass the
result to another function that accepts the correct row type as input:
<screen>
@ -501,8 +501,89 @@ SELECT getname(new_emp());
</para>
<para>
Another way to use a function that returns a composite type is to
call it as a table function, as described below.
Still another way to use a function that returns a composite type is to
call it as a table function, as described in <xref
linkend="xfunc-sql-table-functions">.
</para>
</sect2>
<sect2 id="xfunc-output-parameters">
<title>Functions with Output Parameters</title>
<indexterm>
<primary>function</primary>
<secondary>output parameter</secondary>
</indexterm>
<para>
An alternative way of describing a function's results is to define it
with <firstterm>output parameters</>, as in this example:
<screen>
CREATE FUNCTION add_em (IN x int, IN y int, OUT sum int)
AS 'SELECT $1 + $2'
LANGUAGE SQL;
SELECT add_em(3,7);
add_em
--------
10
(1 row)
</screen>
This is not essentially different from the version of <literal>add_em</>
shown in <xref linkend="xfunc-sql-base-functions">. The real value of
output parameters is that they provide a convenient way of defining
functions that return several columns. For example,
<screen>
CREATE FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int)
AS 'SELECT $1 + $2, $1 * $2'
LANGUAGE SQL;
SELECT * FROM sum_n_product(11,42);
sum | product
-----+---------
53 | 462
(1 row)
</screen>
What has essentially happened here is that we have created an anonymous
composite type for the result of the function. The above example has
the same end result as
<screen>
CREATE TYPE sum_prod AS (sum int, product int);
CREATE FUNCTION sum_n_product (int, int) RETURNS sum_prod
AS 'SELECT $1 + $2, $1 * $2'
LANGUAGE SQL;
</screen>
but not having to bother with the separate composite type definition
is often handy.
</para>
<para>
Notice that output parameters are not included in the calling argument
list when invoking such a function from SQL. This is because
<productname>PostgreSQL</productname> considers only the input
parameters to define the function's calling signature. That means
also that only the input parameters matter when referencing the function
for purposes such as dropping it. We could drop the above function
with either of
<screen>
DROP FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int);
DROP FUNCTION sum_n_product (int, int);
</screen>
</para>
<para>
Parameters can be marked as <literal>IN</> (the default),
<literal>OUT</>, or <literal>INOUT</>. An <literal>INOUT</>
parameter serves as both an input parameter (part of the calling
argument list) and an output parameter (part of the result record type).
</para>
</sect2>
@ -692,6 +773,21 @@ CREATE FUNCTION invalid_func() RETURNS anyelement AS $$
$$ LANGUAGE SQL;
ERROR: cannot determine result data type
DETAIL: A function returning "anyarray" or "anyelement" must have at least one argument of either type.
</screen>
</para>
<para>
Polymorphism can be used with functions that have output arguments.
For example:
<screen>
CREATE FUNCTION dup (f1 anyelement, OUT f2 anyelement, OUT f3 anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;
SELECT * FROM dup(22);
f2 | f3
----+---------
22 | {22,22}
(1 row)
</screen>
</para>
</sect2>
@ -962,7 +1058,7 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision
<sect1 id="xfunc-c">
<title>C-Language Functions</title>
<indexterm zone="xfunc-sql">
<indexterm zone="xfunc-c">
<primary>function</primary>
<secondary>user-defined</secondary>
<tertiary>in C</tertiary>

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.109 2005/03/07 04:42:16 tgl Exp $
* $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.110 2005/03/31 22:46:04 tgl Exp $
*
* NOTES
* some of the executor utility code such as "ExecTypeFromTL" should be
@ -19,16 +19,11 @@
#include "postgres.h"
#include "funcapi.h"
#include "access/heapam.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "nodes/parsenodes.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
/*
@ -548,122 +543,3 @@ BuildDescForRelation(List *schema)
return desc;
}
/*
* RelationNameGetTupleDesc
*
* Given a (possibly qualified) relation name, build a TupleDesc.
*/
TupleDesc
RelationNameGetTupleDesc(const char *relname)
{
RangeVar *relvar;
Relation rel;
TupleDesc tupdesc;
List *relname_list;
/* Open relation and copy the tuple description */
relname_list = stringToQualifiedNameList(relname, "RelationNameGetTupleDesc");
relvar = makeRangeVarFromNameList(relname_list);
rel = relation_openrv(relvar, AccessShareLock);
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
relation_close(rel, AccessShareLock);
return tupdesc;
}
/*
* TypeGetTupleDesc
*
* Given a type Oid, build a TupleDesc.
*
* If the type is composite, *and* a colaliases List is provided, *and*
* the List is of natts length, use the aliases instead of the relation
* attnames. (NB: this usage is deprecated since it may result in
* creation of unnecessary transient record types.)
*
* If the type is a base type, a single item alias List is required.
*/
TupleDesc
TypeGetTupleDesc(Oid typeoid, List *colaliases)
{
TypeFuncClass functypclass = get_type_func_class(typeoid);
TupleDesc tupdesc = NULL;
/*
* Build a suitable tupledesc representing the output rows
*/
if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(typeoid, -1));
if (colaliases != NIL)
{
int natts = tupdesc->natts;
int varattno;
/* does the list length match the number of attributes? */
if (list_length(colaliases) != natts)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of aliases does not match number of columns")));
/* OK, use the aliases instead */
for (varattno = 0; varattno < natts; varattno++)
{
char *label = strVal(list_nth(colaliases, varattno));
if (label != NULL)
namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
}
/* The tuple type is now an anonymous record type */
tupdesc->tdtypeid = RECORDOID;
tupdesc->tdtypmod = -1;
}
}
else if (functypclass == TYPEFUNC_SCALAR)
{
/* Base data type, i.e. scalar */
char *attname;
/* the alias list is required for base types */
if (colaliases == NIL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("no column alias was provided")));
/* the alias list length must be 1 */
if (list_length(colaliases) != 1)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of aliases does not match number of columns")));
/* OK, get the column alias */
attname = strVal(linitial(colaliases));
tupdesc = CreateTemplateTupleDesc(1, false);
TupleDescInitEntry(tupdesc,
(AttrNumber) 1,
attname,
typeoid,
-1,
0);
}
else if (functypclass == TYPEFUNC_RECORD)
{
/* XXX can't support this because typmod wasn't passed in ... */
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("could not determine row description for function returning record")));
}
else
{
/* crummy error message, but parser should have caught this */
elog(ERROR, "function in FROM has unsupported return type");
}
return tupdesc;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.71 2005/03/29 03:01:30 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.72 2005/03/31 22:46:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -180,7 +180,7 @@ AggregateCreate(const char *aggName,
false, /* doesn't return a set */
finaltype, /* returnType */
INTERNALlanguageId, /* languageObjectId */
0,
InvalidOid, /* no validator */
"aggregate_dummy", /* placeholder proc */
"-", /* probin */
true, /* isAgg */
@ -189,9 +189,10 @@ AggregateCreate(const char *aggName,
false, /* isStrict (not needed for agg) */
PROVOLATILE_IMMUTABLE, /* volatility (not
* needed for agg) */
1, /* parameterCount */
fnArgs, /* parameterTypes */
NULL); /* parameterNames */
buildoidvector(fnArgs, 1), /* paramTypes */
PointerGetDatum(NULL), /* allParamTypes */
PointerGetDatum(NULL), /* parameterModes */
PointerGetDatum(NULL)); /* parameterNames */
/*
* Okay to create the pg_aggregate entry.

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.125 2005/03/29 19:44:23 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.126 2005/03/31 22:46:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -21,6 +21,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "mb/pg_wchar.h"
#include "parser/parse_type.h"
@ -40,8 +41,6 @@ Datum fmgr_internal_validator(PG_FUNCTION_ARGS);
Datum fmgr_c_validator(PG_FUNCTION_ARGS);
Datum fmgr_sql_validator(PG_FUNCTION_ARGS);
static Datum create_parameternames_array(int parameterCount,
const char *parameterNames[]);
static void sql_function_parse_error_callback(void *arg);
static int match_prosrc_to_query(const char *prosrc, const char *queryText,
int cursorpos);
@ -51,6 +50,10 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
/* ----------------------------------------------------------------
* ProcedureCreate
*
* Note: allParameterTypes, parameterModes, parameterNames are either arrays
* of the proper types or NULL. We declare them Datum, not "ArrayType *",
* to avoid importing array.h into pg_proc.h.
* ----------------------------------------------------------------
*/
Oid
@ -67,26 +70,29 @@ ProcedureCreate(const char *procedureName,
bool security_definer,
bool isStrict,
char volatility,
int parameterCount,
const Oid *parameterTypes,
const char *parameterNames[])
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
Datum parameterNames)
{
int i;
Oid retval;
int parameterCount;
int allParamCount;
Oid *allParams;
bool genericInParam = false;
Relation rel;
HeapTuple tup;
HeapTuple oldtup;
char nulls[Natts_pg_proc];
Datum values[Natts_pg_proc];
char replaces[Natts_pg_proc];
oidvector *proargtypes;
Datum namesarray;
Oid relid;
NameData procname;
TupleDesc tupDesc;
Oid retval;
bool is_update;
ObjectAddress myself,
referenced;
int i;
/*
* sanity checks
@ -94,55 +100,88 @@ ProcedureCreate(const char *procedureName,
Assert(PointerIsValid(prosrc));
Assert(PointerIsValid(probin));
parameterCount = parameterTypes->dim1;
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("functions cannot have more than %d arguments",
FUNC_MAX_ARGS)));
/* note: the above is correct, we do NOT count output arguments */
if (allParameterTypes != PointerGetDatum(NULL))
{
/*
* We expect the array to be a 1-D OID array; verify that. We
* don't need to use deconstruct_array() since the array data is
* just going to look like a C array of OID values.
*/
allParamCount = ARR_DIMS(DatumGetPointer(allParameterTypes))[0];
if (ARR_NDIM(DatumGetPointer(allParameterTypes)) != 1 ||
allParamCount <= 0 ||
ARR_ELEMTYPE(DatumGetPointer(allParameterTypes)) != OIDOID)
elog(ERROR, "allParameterTypes is not a 1-D Oid array");
allParams = (Oid *) ARR_DATA_PTR(DatumGetPointer(allParameterTypes));
Assert(allParamCount >= parameterCount);
/* we assume caller got the contents right */
}
else
{
allParamCount = parameterCount;
allParams = parameterTypes->values;
}
/*
* Do not allow return type ANYARRAY or ANYELEMENT unless at least one
* argument is also ANYARRAY or ANYELEMENT
* input argument is also ANYARRAY or ANYELEMENT
*/
if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID)
for (i = 0; i < parameterCount; i++)
{
bool genericParam = false;
for (i = 0; i < parameterCount; i++)
if (parameterTypes->values[i] == ANYARRAYOID ||
parameterTypes->values[i] == ANYELEMENTOID)
{
if (parameterTypes[i] == ANYARRAYOID ||
parameterTypes[i] == ANYELEMENTOID)
genericInParam = true;
break;
}
}
if (!genericInParam)
{
bool genericOutParam = false;
if (allParameterTypes != PointerGetDatum(NULL))
{
for (i = 0; i < allParamCount; i++)
{
genericParam = true;
break;
if (allParams[i] == ANYARRAYOID ||
allParams[i] == ANYELEMENTOID)
{
genericOutParam = true;
break;
}
}
}
if (!genericParam)
if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID ||
genericOutParam)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot determine result data type"),
errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type.")));
}
/* Convert param types to oidvector */
/* (Probably we should make caller pass it this way to start with) */
proargtypes = buildoidvector(parameterTypes, parameterCount);
/* Process param names, if given */
namesarray = create_parameternames_array(parameterCount, parameterNames);
/*
* don't allow functions of complex types that have the same name as
* existing attributes of the type
*/
if (parameterCount == 1 && OidIsValid(parameterTypes[0]) &&
(relid = typeidTypeRelid(parameterTypes[0])) != InvalidOid &&
if (parameterCount == 1 &&
OidIsValid(parameterTypes->values[0]) &&
(relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid &&
get_attnum(relid, procedureName) != InvalidAttrNumber)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_COLUMN),
errmsg("\"%s\" is already an attribute of type %s",
procedureName, format_type_be(parameterTypes[0]))));
procedureName,
format_type_be(parameterTypes->values[0]))));
/*
* All seems OK; prepare the data to be inserted into pg_proc.
@ -167,12 +206,17 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility);
values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(proargtypes);
/* XXX for now, just null out the new columns */
nulls[Anum_pg_proc_proallargtypes - 1] = 'n';
nulls[Anum_pg_proc_proargmodes - 1] = 'n';
if (namesarray != PointerGetDatum(NULL))
values[Anum_pg_proc_proargnames - 1] = namesarray;
values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes);
if (allParameterTypes != PointerGetDatum(NULL))
values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes;
else
nulls[Anum_pg_proc_proallargtypes - 1] = 'n';
if (parameterModes != PointerGetDatum(NULL))
values[Anum_pg_proc_proargmodes - 1] = parameterModes;
else
nulls[Anum_pg_proc_proargmodes - 1] = 'n';
if (parameterNames != PointerGetDatum(NULL))
values[Anum_pg_proc_proargnames - 1] = parameterNames;
else
nulls[Anum_pg_proc_proargnames - 1] = 'n';
values[Anum_pg_proc_prosrc - 1] = DirectFunctionCall1(textin,
@ -188,7 +232,7 @@ ProcedureCreate(const char *procedureName,
/* Check for pre-existing definition */
oldtup = SearchSysCache(PROCNAMEARGSNSP,
PointerGetDatum(procedureName),
PointerGetDatum(proargtypes),
PointerGetDatum(parameterTypes),
ObjectIdGetDatum(procNamespace),
0);
@ -214,9 +258,33 @@ ProcedureCreate(const char *procedureName,
returnsSet != oldproc->proretset)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change return type of existing function"),
errmsg("cannot change return type of existing function"),
errhint("Use DROP FUNCTION first.")));
/*
* If it returns RECORD, check for possible change of record type
* implied by OUT parameters
*/
if (returnType == RECORDOID)
{
TupleDesc olddesc;
TupleDesc newdesc;
olddesc = build_function_result_tupdesc_t(oldtup);
newdesc = build_function_result_tupdesc_d(allParameterTypes,
parameterModes,
parameterNames);
if (olddesc == NULL && newdesc == NULL)
/* ok, both are runtime-defined RECORDs */ ;
else if (olddesc == NULL || newdesc == NULL ||
!equalTupleDescs(olddesc, newdesc))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("cannot change return type of existing function"),
errdetail("Row type defined by OUT parameters is different."),
errhint("Use DROP FUNCTION first.")));
}
/* Can't change aggregate status, either */
if (oldproc->proisagg != isAgg)
{
@ -285,11 +353,11 @@ ProcedureCreate(const char *procedureName,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
/* dependency on input types */
for (i = 0; i < parameterCount; i++)
/* dependency on parameter types */
for (i = 0; i < allParamCount; i++)
{
referenced.classId = RelOid_pg_type;
referenced.objectId = parameterTypes[i];
referenced.objectId = allParams[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
@ -310,42 +378,6 @@ ProcedureCreate(const char *procedureName,
}
/*
* create_parameternames_array - build proargnames value from an array
* of C strings. Returns a NULL pointer if no names provided.
*/
static Datum
create_parameternames_array(int parameterCount, const char *parameterNames[])
{
Datum elems[FUNC_MAX_ARGS];
bool found = false;
ArrayType *names;
int i;
if (!parameterNames)
return PointerGetDatum(NULL);
for (i = 0; i < parameterCount; i++)
{
const char *s = parameterNames[i];
if (s && *s)
found = true;
else
s = "";
elems[i] = DirectFunctionCall1(textin, CStringGetDatum(s));
}
if (!found)
return PointerGetDatum(NULL);
names = construct_array(elems, parameterCount, TEXTOID, -1, false, 'i');
return PointerGetDatum(names);
}
/*
* Validator for internal functions
@ -461,7 +493,6 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
Datum tmp;
char *prosrc;
ErrorContextCallback sqlerrcontext;
char functyptype;
bool haspolyarg;
int i;
@ -472,11 +503,9 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
elog(ERROR, "cache lookup failed for function %u", funcoid);
proc = (Form_pg_proc) GETSTRUCT(tuple);
functyptype = get_typtype(proc->prorettype);
/* Disallow pseudotype result */
/* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */
if (functyptype == 'p' &&
if (get_typtype(proc->prorettype) == 'p' &&
proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID &&
proc->prorettype != ANYARRAYOID &&
@ -535,7 +564,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
querytree_list = pg_parse_and_rewrite(prosrc,
proc->proargtypes.values,
proc->pronargs);
(void) check_sql_fn_retval(proc->prorettype, functyptype,
(void) check_sql_fn_retval(funcoid, proc->prorettype,
querytree_list, NULL);
}
else

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.58 2005/03/29 17:58:49 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.59 2005/03/31 22:46:07 tgl Exp $
*
* DESCRIPTION
* These routines take the parse tree and pick out the
@ -55,7 +55,7 @@
/*
* Examine the "returns" clause returnType of the CREATE FUNCTION statement
* 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
@ -131,38 +131,44 @@ compute_return_type(TypeName *returnType, Oid languageOid,
/*
* 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 int
examine_parameter_list(List *parameter, Oid languageOid,
Oid *parameterTypes, const char *parameterNames[])
static void
examine_parameter_list(List *parameters, Oid languageOid,
oidvector **parameterTypes,
ArrayType **allParameterTypes,
ArrayType **parameterModes,
ArrayType **parameterNames,
Oid *requiredResultType)
{
int parameterCount = 0;
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;
MemSet(parameterTypes, 0, FUNC_MAX_ARGS * sizeof(Oid));
MemSet(parameterNames, 0, FUNC_MAX_ARGS * sizeof(char *));
inTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
allTypes = (Datum *) palloc(parameterCount * sizeof(Datum));
paramModes = (Datum *) palloc(parameterCount * sizeof(Datum));
paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum));
foreach(x, parameter)
/* 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;
if (parameterCount >= FUNC_MAX_ARGS)
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("functions cannot have more than %d arguments",
FUNC_MAX_ARGS)));
if (fp->mode == FUNC_PARAM_OUT)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CREATE FUNCTION / OUT parameters are not implemented")));
if (fp->mode == FUNC_PARAM_INOUT)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("CREATE FUNCTION / INOUT parameters are not implemented")));
toid = LookupTypeName(t);
if (OidIsValid(toid))
{
@ -194,16 +200,66 @@ examine_parameter_list(List *parameter, Oid languageOid,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("functions cannot accept set arguments")));
parameterTypes[parameterCount] = toid;
if (fp->mode != FUNC_PARAM_OUT)
inTypes[inCount++] = toid;
parameterNames[parameterCount] = fp->name;
if (fp->mode != FUNC_PARAM_IN)
{
if (outCount == 0) /* save first OUT param's type */
*requiredResultType = toid;
outCount++;
}
parameterCount++;
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++;
}
return parameterCount;
/* 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
@ -321,6 +377,7 @@ compute_attributes_sql_style(List *options,
defel->defname);
}
/* process required items */
if (as_item)
*as = (List *) as_item->arg;
else
@ -335,6 +392,7 @@ compute_attributes_sql_style(List *options,
(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)
@ -445,9 +503,11 @@ CreateFunction(CreateFunctionStmt *stmt)
char *funcname;
Oid namespaceId;
AclResult aclresult;
int parameterCount;
Oid parameterTypes[FUNC_MAX_ARGS];
const char *parameterNames[FUNC_MAX_ARGS];
oidvector *parameterTypes;
ArrayType *allParameterTypes;
ArrayType *parameterModes;
ArrayType *parameterNames;
Oid requiredResultType;
bool isStrict,
security;
char volatility;
@ -465,7 +525,7 @@ CreateFunction(CreateFunctionStmt *stmt)
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
get_namespace_name(namespaceId));
/* defaults attributes */
/* default attributes */
isStrict = false;
security = false;
volatility = PROVOLATILE_VOLATILE;
@ -523,11 +583,39 @@ CreateFunction(CreateFunctionStmt *stmt)
* Convert remaining parameters of CREATE to form wanted by
* ProcedureCreate.
*/
compute_return_type(stmt->returnType, languageOid,
&prorettype, &returnsSet);
examine_parameter_list(stmt->parameters, languageOid,
&parameterTypes,
&allParameterTypes,
&parameterModes,
&parameterNames,
&requiredResultType);
parameterCount = examine_parameter_list(stmt->parameters, languageOid,
parameterTypes, parameterNames);
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);
@ -572,9 +660,10 @@ CreateFunction(CreateFunctionStmt *stmt)
security,
isStrict,
volatility,
parameterCount,
parameterTypes,
parameterNames);
PointerGetDatum(allParameterTypes),
PointerGetDatum(parameterModes),
PointerGetDatum(parameterNames));
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.94 2005/03/29 00:16:59 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.95 2005/03/31 22:46:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -20,6 +20,7 @@
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_type.h"
@ -277,8 +278,8 @@ init_sql_fcache(FmgrInfo *finfo)
* form.
*/
if (haspolyarg || fcache->returnsTuple)
fcache->returnsTuple = check_sql_fn_retval(rettype,
get_typtype(rettype),
fcache->returnsTuple = check_sql_fn_retval(foid,
rettype,
queryTree_list,
&fcache->junkFilter);
@ -858,7 +859,7 @@ ShutdownSQLFunction(Datum arg)
* tuple result), *junkFilter is set to NULL.
*/
bool
check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
JunkFilter **junkFilter)
{
Query *parse;
@ -866,12 +867,8 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
List *tlist;
ListCell *tlistitem;
int tlistlen;
Oid typerelid;
char fn_typtype;
Oid restype;
Relation reln;
int relnatts; /* physical number of columns in rel */
int rellogcols; /* # of nondeleted columns in rel */
int colindex; /* physical column index */
if (junkFilter)
*junkFilter = NULL; /* default result */
@ -922,13 +919,10 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
*/
tlistlen = ExecCleanTargetListLength(tlist);
typerelid = typeidTypeRelid(rettype);
fn_typtype = get_typtype(rettype);
if (fn_typtype == 'b' || fn_typtype == 'd')
{
/* Shouldn't have a typerelid */
Assert(typerelid == InvalidOid);
/*
* For base-type returns, the target list should have exactly one
* entry, and its type should agree with what the user declared.
@ -950,10 +944,13 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
errdetail("Actual return type is %s.",
format_type_be(restype))));
}
else if (fn_typtype == 'c')
else if (fn_typtype == 'c' || rettype == RECORDOID)
{
/* Must have a typerelid */
Assert(typerelid != InvalidOid);
/* Returns a rowtype */
TupleDesc tupdesc;
int tupnatts; /* physical number of columns in tuple */
int tuplogcols; /* # of nondeleted columns in tuple */
int colindex; /* physical column index */
/*
* If the target list is of length 1, and the type of the varnode
@ -969,16 +966,27 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
return false; /* NOT returning whole tuple */
}
/* Is the rowtype fixed, or determined only at runtime? */
if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
{
/*
* Assume we are returning the whole tuple.
* Crosschecking against what the caller expects will happen at
* runtime.
*/
if (junkFilter)
*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
return true;
}
Assert(tupdesc);
/*
* Otherwise verify that the targetlist matches the return tuple
* type. This part of the typechecking is a hack. We look up the
* relation that is the declared return type, and scan the
* non-deleted attributes to ensure that they match the datatypes
* of the non-resjunk columns.
* Verify that the targetlist matches the return tuple type.
* We scan the non-deleted attributes to ensure that they match the
* datatypes of the non-resjunk columns.
*/
reln = relation_open(typerelid, AccessShareLock);
relnatts = reln->rd_rel->relnatts;
rellogcols = 0; /* we'll count nondeleted cols as we go */
tupnatts = tupdesc->natts;
tuplogcols = 0; /* we'll count nondeleted cols as we go */
colindex = 0;
foreach(tlistitem, tlist)
@ -994,15 +1002,15 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
do
{
colindex++;
if (colindex > relnatts)
if (colindex > tupnatts)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
format_type_be(rettype)),
errdetail("Final SELECT returns too many columns.")));
attr = reln->rd_att->attrs[colindex - 1];
attr = tupdesc->attrs[colindex - 1];
} while (attr->attisdropped);
rellogcols++;
tuplogcols++;
tletype = exprType((Node *) tle->expr);
atttype = attr->atttypid;
@ -1014,19 +1022,19 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
errdetail("Final SELECT returns %s instead of %s at column %d.",
format_type_be(tletype),
format_type_be(atttype),
rellogcols)));
tuplogcols)));
}
for (;;)
{
colindex++;
if (colindex > relnatts)
if (colindex > tupnatts)
break;
if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
rellogcols++;
if (!tupdesc->attrs[colindex - 1]->attisdropped)
tuplogcols++;
}
if (tlistlen != rellogcols)
if (tlistlen != tuplogcols)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
@ -1036,40 +1044,12 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
/* Set up junk filter if needed */
if (junkFilter)
*junkFilter = ExecInitJunkFilterConversion(tlist,
CreateTupleDescCopy(reln->rd_att),
CreateTupleDescCopy(tupdesc),
NULL);
relation_close(reln, AccessShareLock);
/* Report that we are returning entire tuple result */
return true;
}
else if (rettype == RECORDOID)
{
/*
* If the target list is of length 1, and the type of the varnode
* in the target list matches the declared return type, this is
* okay. This can happen, for example, where the body of the
* function is 'SELECT func2()', where func2 has the same return
* type as the function that's calling it.
*/
if (tlistlen == 1)
{
restype = ((TargetEntry *) linitial(tlist))->resdom->restype;
if (IsBinaryCoercible(restype, rettype))
return false; /* NOT returning whole tuple */
}
/*
* Otherwise assume we are returning the whole tuple.
* Crosschecking against what the caller expects will happen at
* runtime.
*/
if (junkFilter)
*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
return true;
}
else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
{
/* This should already have been caught ... */

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.31 2005/03/16 21:38:07 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.32 2005/03/31 22:46:08 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,18 +22,10 @@
*/
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/pg_type.h"
#include "executor/execdebug.h"
#include "executor/execdefs.h"
#include "executor/execdesc.h"
#include "executor/nodeFunctionscan.h"
#include "funcapi.h"
#include "parser/parsetree.h"
#include "parser/parse_expr.h"
#include "parser/parse_type.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"
static TupleTableSlot *FunctionNext(FunctionScanState *node);
@ -180,18 +172,21 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate)
*/
rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
Assert(rte->rtekind == RTE_FUNCTION);
funcrettype = exprType(rte->funcexpr);
/*
* Now determine if the function returns a simple or composite type,
* and build an appropriate tupdesc.
*/
functypclass = get_type_func_class(funcrettype);
functypclass = get_expr_result_type(rte->funcexpr,
&funcrettype,
&tupdesc);
if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(funcrettype, -1));
Assert(tupdesc);
/* Must copy it out of typcache for safety */
tupdesc = CreateTupleDescCopy(tupdesc);
}
else if (functypclass == TYPEFUNC_SCALAR)
{
@ -216,14 +211,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate)
elog(ERROR, "function in FROM has unsupported return type");
}
/*
* For RECORD results, make sure a typmod has been assigned. (The
* function should do this for itself, but let's cover things in case
* it doesn't.)
*/
if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0)
assign_record_type_typmod(tupdesc);
scanstate->tupdesc = tupdesc;
ExecSetSlotDescriptor(scanstate->ss.ss_ScanTupleSlot,
tupdesc, false);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.191 2005/03/29 00:17:02 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.192 2005/03/31 22:46:09 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -2319,8 +2319,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
* probably not important, but let's be careful.)
*/
if (polymorphic)
(void) check_sql_fn_retval(result_type, get_typtype(result_type),
querytree_list, NULL);
(void) check_sql_fn_retval(funcid, result_type, querytree_list, NULL);
/*
* Additional validity checks on the expression. It mustn't return a

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.485 2005/03/29 17:58:50 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.486 2005/03/31 22:46:11 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -2544,7 +2544,7 @@ def_elem: ColLabel '=' def_arg
;
/* Note: any simple identifier will be returned as a type name! */
def_arg: func_return { $$ = (Node *)$1; }
def_arg: func_type { $$ = (Node *)$1; }
| qual_all_Op { $$ = (Node *)$1; }
| NumericOnly { $$ = (Node *)$1; }
| Sconst { $$ = (Node *)makeString($1); }
@ -3282,6 +3282,18 @@ CreateFunctionStmt:
n->withClause = $9;
$$ = (Node *)n;
}
| CREATE opt_or_replace FUNCTION func_name func_args
createfunc_opt_list opt_definition
{
CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
n->replace = $2;
n->funcname = $4;
n->parameters = $5;
n->returnType = NULL;
n->options = $6;
n->withClause = $7;
$$ = (Node *)n;
}
;
opt_or_replace:
@ -3367,7 +3379,7 @@ param_name: function_name
func_return:
func_type
{
/* We can catch over-specified arguments here if we want to,
/* We can catch over-specified results here if we want to,
* but for now better to silently swallow typmod, etc.
* - thomas 2000-03-22
*/
@ -3424,7 +3436,6 @@ common_func_opt_item:
{
$$ = makeDefElem("volatility", (Node *)makeString("volatile"));
}
| EXTERNAL SECURITY DEFINER
{
$$ = makeDefElem("security", (Node *)makeInteger(TRUE));

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.176 2005/03/29 03:01:31 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.177 2005/03/31 22:46:13 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -18,6 +18,7 @@
#include "catalog/catname.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_proc.h"
#include "funcapi.h"
#include "lib/stringinfo.h"
#include "nodes/makefuncs.h"
#include "parser/parse_agg.h"
@ -1154,10 +1155,8 @@ make_fn_arguments(ParseState *pstate,
static Node *
ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg)
{
Oid argtype;
Oid argrelid;
AttrNumber attnum;
FieldSelect *fselect;
TupleDesc tupdesc;
int i;
/*
* Special case for whole-row Vars so that we can resolve (foo.*).bar
@ -1180,27 +1179,31 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg)
/*
* Else do it the hard way. Note that if the arg is of RECORD type,
* we will never recognize a column name, and always assume the item
* must be a function.
* and isn't resolvable as a function with OUT params, we will never
* be able to recognize a column name here.
*/
argtype = exprType(first_arg);
argrelid = typeidTypeRelid(argtype);
if (!argrelid)
return NULL; /* can only happen if RECORD */
if (get_expr_result_type(first_arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
return NULL; /* unresolvable RECORD type */
attnum = get_attnum(argrelid, funcname);
if (attnum == InvalidAttrNumber)
return NULL; /* funcname does not match any column */
for (i = 0; i < tupdesc->natts; i++)
{
Form_pg_attribute att = tupdesc->attrs[i];
/* Success, so generate a FieldSelect expression */
fselect = makeNode(FieldSelect);
fselect->arg = (Expr *) first_arg;
fselect->fieldnum = attnum;
get_atttypetypmod(argrelid, attnum,
&fselect->resulttype,
&fselect->resulttypmod);
if (strcmp(funcname, NameStr(att->attname)) == 0 &&
!att->attisdropped)
{
/* Success, so generate a FieldSelect expression */
FieldSelect *fselect = makeNode(FieldSelect);
return (Node *) fselect;
fselect->arg = (Expr *) first_arg;
fselect->fieldnum = i + 1;
fselect->resulttype = att->atttypid;
fselect->resulttypmod = att->atttypmod;
return (Node *) fselect;
}
}
return NULL; /* funcname does not match any column */
}
/*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.102 2004/12/31 22:00:27 pgsql Exp $
* $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.103 2005/03/31 22:46:13 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -17,21 +17,20 @@
#include <ctype.h>
#include "access/heapam.h"
#include "access/htup.h"
#include "catalog/heap.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
#include "parser/parsetree.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
#include "rewrite/rewriteManip.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
/* GUC parameter */
bool add_missing_from;
@ -46,6 +45,10 @@ static void expandRelation(Oid relid, Alias *eref,
int rtindex, int sublevels_up,
bool include_dropped,
List **colnames, List **colvars);
static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
int rtindex, int sublevels_up,
bool include_dropped,
List **colnames, List **colvars);
static int specialAttNum(const char *attname);
static void warnAutoRange(ParseState *pstate, RangeVar *relation);
@ -965,8 +968,9 @@ addRangeTableEntryForFunction(ParseState *pstate,
bool inFromCl)
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
Oid funcrettype = exprType(funcexpr);
TypeFuncClass functypclass;
Oid funcrettype;
TupleDesc tupdesc;
Alias *alias = rangefunc->alias;
List *coldeflist = rangefunc->coldeflist;
Alias *eref;
@ -982,58 +986,37 @@ addRangeTableEntryForFunction(ParseState *pstate,
rte->eref = eref;
/*
* Now determine if the function returns a simple or composite type,
* and check/add column aliases.
* Now determine if the function returns a simple or composite type.
*/
functypclass = get_expr_result_type(funcexpr,
&funcrettype,
&tupdesc);
/*
* A coldeflist is required if the function returns RECORD and hasn't
* got a predetermined record type, and is prohibited otherwise.
*/
if (coldeflist != NIL)
{
/*
* we *only* allow a coldeflist for functions returning a RECORD
* pseudo-type
*/
if (funcrettype != RECORDOID)
if (functypclass != TYPEFUNC_RECORD)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("a column definition list is only allowed for functions returning \"record\"")));
}
else
{
/*
* ... and a coldeflist is *required* for functions returning a
* RECORD pseudo-type
*/
if (funcrettype == RECORDOID)
if (functypclass == TYPEFUNC_RECORD)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("a column definition list is required for functions returning \"record\"")));
}
functypclass = get_type_func_class(funcrettype);
if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
Oid funcrelid = typeidTypeRelid(funcrettype);
Relation rel;
if (!OidIsValid(funcrelid)) /* shouldn't happen */
elog(ERROR, "invalid typrelid for complex type %u", funcrettype);
/*
* Get the rel's relcache entry. This access ensures that we have
* an up-to-date relcache entry for the rel.
*/
rel = relation_open(funcrelid, AccessShareLock);
Assert(tupdesc);
/* Build the column alias list */
buildRelationAliases(rel->rd_att, alias, eref);
/*
* Drop the rel refcount, but keep the access lock till end of
* transaction so that the table can't be deleted or have its
* schema modified underneath us.
*/
relation_close(rel, NoLock);
buildRelationAliases(tupdesc, alias, eref);
}
else if (functypclass == TYPEFUNC_SCALAR)
{
@ -1308,24 +1291,19 @@ expandRTE(List *rtable, int rtindex, int sublevels_up,
case RTE_FUNCTION:
{
/* Function RTE */
Oid funcrettype = exprType(rte->funcexpr);
TypeFuncClass functypclass = get_type_func_class(funcrettype);
TypeFuncClass functypclass;
Oid funcrettype;
TupleDesc tupdesc;
functypclass = get_expr_result_type(rte->funcexpr,
&funcrettype,
&tupdesc);
if (functypclass == TYPEFUNC_COMPOSITE)
{
/*
* Composite data type, i.e. a table's row type
*
* Same as ordinary relation RTE
*/
Oid funcrelid = typeidTypeRelid(funcrettype);
if (!OidIsValid(funcrelid)) /* shouldn't happen */
elog(ERROR, "invalid typrelid for complex type %u",
funcrettype);
expandRelation(funcrelid, rte->eref, rtindex, sublevels_up,
include_dropped, colnames, colvars);
/* Composite data type, e.g. a table's row type */
Assert(tupdesc);
expandTupleDesc(tupdesc, rte->eref, rtindex, sublevels_up,
include_dropped, colnames, colvars);
}
else if (functypclass == TYPEFUNC_SCALAR)
{
@ -1467,17 +1445,30 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
List **colnames, List **colvars)
{
Relation rel;
int varattno;
int maxattrs;
int numaliases;
/* Get the tupledesc and turn it over to expandTupleDesc */
rel = relation_open(relid, AccessShareLock);
maxattrs = RelationGetNumberOfAttributes(rel);
numaliases = list_length(eref->colnames);
expandTupleDesc(rel->rd_att, eref, rtindex, sublevels_up, include_dropped,
colnames, colvars);
relation_close(rel, AccessShareLock);
}
/*
* expandTupleDesc -- expandRTE subroutine
*/
static void
expandTupleDesc(TupleDesc tupdesc, Alias *eref,
int rtindex, int sublevels_up,
bool include_dropped,
List **colnames, List **colvars)
{
int maxattrs = tupdesc->natts;
int numaliases = list_length(eref->colnames);
int varattno;
for (varattno = 0; varattno < maxattrs; varattno++)
{
Form_pg_attribute attr = rel->rd_att->attrs[varattno];
Form_pg_attribute attr = tupdesc->attrs[varattno];
if (attr->attisdropped)
{
@ -1519,8 +1510,6 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
*colvars = lappend(*colvars, varnode);
}
}
relation_close(rel, AccessShareLock);
}
/*
@ -1662,33 +1651,29 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
case RTE_FUNCTION:
{
/* Function RTE */
Oid funcrettype = exprType(rte->funcexpr);
TypeFuncClass functypclass = get_type_func_class(funcrettype);
List *coldeflist = rte->coldeflist;
TypeFuncClass functypclass;
Oid funcrettype;
TupleDesc tupdesc;
functypclass = get_expr_result_type(rte->funcexpr,
&funcrettype,
&tupdesc);
if (functypclass == TYPEFUNC_COMPOSITE)
{
/*
* Composite data type, i.e. a table's row type
*
* Same as ordinary relation RTE
*/
Oid funcrelid = typeidTypeRelid(funcrettype);
HeapTuple tp;
/* Composite data type, e.g. a table's row type */
Form_pg_attribute att_tup;
if (!OidIsValid(funcrelid)) /* shouldn't happen */
elog(ERROR, "invalid typrelid for complex type %u",
funcrettype);
Assert(tupdesc);
/* this is probably a can't-happen case */
if (attnum < 1 || attnum > tupdesc->natts)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column %d of relation \"%s\" does not exist",
attnum,
rte->eref->aliasname)));
tp = SearchSysCache(ATTNUM,
ObjectIdGetDatum(funcrelid),
Int16GetDatum(attnum),
0, 0);
if (!HeapTupleIsValid(tp)) /* shouldn't happen */
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
attnum, funcrelid);
att_tup = (Form_pg_attribute) GETSTRUCT(tp);
att_tup = tupdesc->attrs[attnum - 1];
/*
* If dropped column, pretend it ain't there. See
@ -1699,10 +1684,9 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" of relation \"%s\" does not exist",
NameStr(att_tup->attname),
get_rel_name(funcrelid))));
rte->eref->aliasname)));
*vartype = att_tup->atttypid;
*vartypmod = att_tup->atttypmod;
ReleaseSysCache(tp);
}
else if (functypclass == TYPEFUNC_SCALAR)
{
@ -1712,7 +1696,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
}
else if (functypclass == TYPEFUNC_RECORD)
{
ColumnDef *colDef = list_nth(coldeflist, attnum - 1);
ColumnDef *colDef = list_nth(rte->coldeflist, attnum - 1);
*vartype = typenameTypeId(colDef->typename);
*vartypmod = -1;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.121 2005/03/29 00:17:11 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.122 2005/03/31 22:46:14 tgl Exp $
*
* NOTES
* Eventually, the index information should go through here, too.
@ -1543,42 +1543,6 @@ get_typtype(Oid typid)
return '\0';
}
/*
* get_type_func_class
*
* Given the type OID, obtain its TYPEFUNC classification.
*
* This is intended to centralize a bunch of formerly ad-hoc code for
* classifying types. The categories used here are useful for deciding
* how to handle functions returning the datatype.
*/
TypeFuncClass
get_type_func_class(Oid typid)
{
switch (get_typtype(typid))
{
case 'c':
return TYPEFUNC_COMPOSITE;
case 'b':
case 'd':
return TYPEFUNC_SCALAR;
case 'p':
if (typid == RECORDOID)
return TYPEFUNC_RECORD;
/*
* We treat VOID and CSTRING as legitimate scalar datatypes,
* mostly for the convenience of the JDBC driver (which wants
* to be able to do "SELECT * FROM foo()" for all legitimately
* user-callable functions).
*/
if (typid == VOIDOID || typid == CSTRINGOID)
return TYPEFUNC_SCALAR;
return TYPEFUNC_OTHER;
}
/* shouldn't get here, probably */
return TYPEFUNC_OTHER;
}
/*
* get_typ_typrelid
*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.92 2005/03/29 03:01:31 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.93 2005/03/31 22:46:16 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -403,7 +403,7 @@ fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple)
* We want to raise an error here only if the info function returns
* something bogus.
*
* This function is broken out of fmgr_info_C_lang() so that ProcedureCreate()
* This function is broken out of fmgr_info_C_lang so that fmgr_c_validator
* can validate the information record for a function not yet entered into
* pg_proc.
*/
@ -576,8 +576,8 @@ fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
/*
* Specialized lookup routine for ProcedureCreate(): given the alleged name
* of an internal function, return the OID of the function.
* Specialized lookup routine for fmgr_internal_validator: given the alleged
* name of an internal function, return the OID of the function.
* If the name is not recognized, return InvalidOid.
*/
Oid
@ -1869,10 +1869,6 @@ get_fn_expr_rettype(FmgrInfo *flinfo)
Oid
get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
{
Node *expr;
List *args;
Oid argtype;
/*
* can't return anything useful if we have no FmgrInfo or if its
* fn_expr node has not been initialized
@ -1880,7 +1876,23 @@ get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
if (!flinfo || !flinfo->fn_expr)
return InvalidOid;
expr = flinfo->fn_expr;
return get_call_expr_argtype(flinfo->fn_expr, argnum);
}
/*
* Get the actual type OID of a specific function argument (counting from 0),
* but working from the calling expression tree instead of FmgrInfo
*
* Returns InvalidOid if information is not available
*/
Oid
get_call_expr_argtype(Node *expr, int argnum)
{
List *args;
Oid argtype;
if (expr == NULL)
return InvalidOid;
if (IsA(expr, FuncExpr))
args = ((FuncExpr *) expr)->args;

View File

@ -7,17 +7,37 @@
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.18 2005/01/01 05:43:08 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.19 2005/03/31 22:46:16 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "funcapi.h"
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
static void shutdown_MultiFuncCall(Datum arg);
static TypeFuncClass internal_get_result_type(Oid funcid,
Node *call_expr,
ReturnSetInfo *rsinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc);
static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
oidvector *declared_args,
Node *call_expr);
static TypeFuncClass get_type_func_class(Oid typid);
/*
* init_MultiFuncCall
@ -156,3 +176,624 @@ shutdown_MultiFuncCall(Datum arg)
pfree(funcctx);
}
/*
* get_call_result_type
* Given a function's call info record, determine the kind of datatype
* it is supposed to return. If resultTypeId isn't NULL, *resultTypeId
* receives the actual datatype OID (this is mainly useful for scalar
* result types). If resultTupleDesc isn't NULL, *resultTupleDesc
* receives a pointer to a TupleDesc when the result is of a composite
* type, or NULL when it's a scalar result. NB: the tupledesc should
* be copied if it is to be accessed over a long period.
*
* One hard case that this handles is resolution of actual rowtypes for
* functions returning RECORD (from either the function's OUT parameter
* list, or a ReturnSetInfo context node). TYPEFUNC_RECORD is returned
* only when we couldn't resolve the actual rowtype for lack of information.
*
* The other hard case that this handles is resolution of polymorphism.
* We will never return ANYELEMENT or ANYARRAY, either as a scalar result
* type or as a component of a rowtype.
*
* This function is relatively expensive --- in a function returning set,
* try to call it only the first time through.
*/
TypeFuncClass
get_call_result_type(FunctionCallInfo fcinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
{
return internal_get_result_type(fcinfo->flinfo->fn_oid,
fcinfo->flinfo->fn_expr,
(ReturnSetInfo *) fcinfo->resultinfo,
resultTypeId,
resultTupleDesc);
}
/*
* get_expr_result_type
* As above, but work from a calling expression node tree
*/
TypeFuncClass
get_expr_result_type(Node *expr,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
{
TypeFuncClass result;
if (expr && IsA(expr, FuncExpr))
result = internal_get_result_type(((FuncExpr *) expr)->funcid,
expr,
NULL,
resultTypeId,
resultTupleDesc);
else
{
/* handle as a generic expression; no chance to resolve RECORD */
Oid typid = exprType(expr);
if (resultTypeId)
*resultTypeId = typid;
if (resultTupleDesc)
*resultTupleDesc = NULL;
result = get_type_func_class(typid);
if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
*resultTupleDesc = lookup_rowtype_tupdesc(typid, -1);
}
return result;
}
/*
* get_expr_result_type
* As above, but work from a function's OID only
*
* This will not be able to resolve pure-RECORD results nor polymorphism.
*/
TypeFuncClass
get_func_result_type(Oid functionId,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
{
return internal_get_result_type(functionId,
NULL,
NULL,
resultTypeId,
resultTupleDesc);
}
/*
* internal_get_result_type -- workhorse code implementing all the above
*
* funcid must always be supplied. call_expr and rsinfo can be NULL if not
* available. We will return TYPEFUNC_RECORD, and store NULL into
* *resultTupleDesc, if we cannot deduce the complete result rowtype from
* the available information.
*/
static TypeFuncClass
internal_get_result_type(Oid funcid,
Node *call_expr,
ReturnSetInfo *rsinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc)
{
TypeFuncClass result;
HeapTuple tp;
Form_pg_proc procform;
Oid rettype;
TupleDesc tupdesc;
/* First fetch the function's pg_proc row to inspect its rettype */
tp = SearchSysCache(PROCOID,
ObjectIdGetDatum(funcid),
0, 0, 0);
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for function %u", funcid);
procform = (Form_pg_proc) GETSTRUCT(tp);
rettype = procform->prorettype;
/* Check for OUT parameters defining a RECORD result */
tupdesc = build_function_result_tupdesc_t(tp);
if (tupdesc)
{
/*
* It has OUT parameters, so it's basically like a regular
* composite type, except we have to be able to resolve any
* polymorphic OUT parameters.
*/
if (resultTypeId)
*resultTypeId = rettype;
if (resolve_polymorphic_tupdesc(tupdesc,
&procform->proargtypes,
call_expr))
{
if (tupdesc->tdtypeid == RECORDOID &&
tupdesc->tdtypmod < 0)
assign_record_type_typmod(tupdesc);
if (resultTupleDesc)
*resultTupleDesc = tupdesc;
result = TYPEFUNC_COMPOSITE;
}
else
{
if (resultTupleDesc)
*resultTupleDesc = NULL;
result = TYPEFUNC_RECORD;
}
ReleaseSysCache(tp);
return result;
}
/*
* If scalar polymorphic result, try to resolve it.
*/
if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
{
Oid newrettype = exprType(call_expr);
if (newrettype == InvalidOid) /* this probably should not happen */
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("could not determine actual result type for function \"%s\" declared to return type %s",
NameStr(procform->proname),
format_type_be(rettype))));
rettype = newrettype;
}
if (resultTypeId)
*resultTypeId = rettype;
if (resultTupleDesc)
*resultTupleDesc = NULL; /* default result */
/* Classify the result type */
result = get_type_func_class(rettype);
switch (result)
{
case TYPEFUNC_COMPOSITE:
if (resultTupleDesc)
*resultTupleDesc = lookup_rowtype_tupdesc(rettype, -1);
/* Named composite types can't have any polymorphic columns */
break;
case TYPEFUNC_SCALAR:
break;
case TYPEFUNC_RECORD:
/* We must get the tupledesc from call context */
if (rsinfo && IsA(rsinfo, ReturnSetInfo) &&
rsinfo->expectedDesc != NULL)
{
result = TYPEFUNC_COMPOSITE;
if (resultTupleDesc)
*resultTupleDesc = rsinfo->expectedDesc;
/* Assume no polymorphic columns here, either */
}
break;
default:
break;
}
ReleaseSysCache(tp);
return result;
}
/*
* Given the result tuple descriptor for a function with OUT parameters,
* replace any polymorphic columns (ANYELEMENT/ANYARRAY) with correct data
* types deduced from the input arguments. Returns TRUE if able to deduce
* all types, FALSE if not.
*/
static bool
resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
Node *call_expr)
{
int natts = tupdesc->natts;
int nargs = declared_args->dim1;
bool have_anyelement_result = false;
bool have_anyarray_result = false;
Oid anyelement_type = InvalidOid;
Oid anyarray_type = InvalidOid;
int i;
/* See if there are any polymorphic outputs; quick out if not */
for (i = 0; i < natts; i++)
{
switch (tupdesc->attrs[i]->atttypid)
{
case ANYELEMENTOID:
have_anyelement_result = true;
break;
case ANYARRAYOID:
have_anyarray_result = true;
break;
default:
break;
}
}
if (!have_anyelement_result && !have_anyarray_result)
return true;
/*
* Otherwise, extract actual datatype(s) from input arguments. (We assume
* the parser already validated consistency of the arguments.)
*/
if (!call_expr)
return false; /* no hope */
for (i = 0; i < nargs; i++)
{
switch (declared_args->values[i])
{
case ANYELEMENTOID:
if (!OidIsValid(anyelement_type))
anyelement_type = get_call_expr_argtype(call_expr, i);
break;
case ANYARRAYOID:
if (!OidIsValid(anyarray_type))
anyarray_type = get_call_expr_argtype(call_expr, i);
break;
default:
break;
}
}
/* If nothing found, parser messed up */
if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type))
return false;
/* If needed, deduce one polymorphic type from the other */
if (have_anyelement_result && !OidIsValid(anyelement_type))
anyelement_type = resolve_generic_type(ANYELEMENTOID,
anyarray_type,
ANYARRAYOID);
if (have_anyarray_result && !OidIsValid(anyarray_type))
anyarray_type = resolve_generic_type(ANYARRAYOID,
anyelement_type,
ANYELEMENTOID);
/* And finally replace the tuple column types as needed */
for (i = 0; i < natts; i++)
{
switch (tupdesc->attrs[i]->atttypid)
{
case ANYELEMENTOID:
TupleDescInitEntry(tupdesc, i+1,
NameStr(tupdesc->attrs[i]->attname),
anyelement_type,
-1,
0);
break;
case ANYARRAYOID:
TupleDescInitEntry(tupdesc, i+1,
NameStr(tupdesc->attrs[i]->attname),
anyarray_type,
-1,
0);
break;
default:
break;
}
}
return true;
}
/*
* get_type_func_class
* Given the type OID, obtain its TYPEFUNC classification.
*
* This is intended to centralize a bunch of formerly ad-hoc code for
* classifying types. The categories used here are useful for deciding
* how to handle functions returning the datatype.
*/
static TypeFuncClass
get_type_func_class(Oid typid)
{
switch (get_typtype(typid))
{
case 'c':
return TYPEFUNC_COMPOSITE;
case 'b':
case 'd':
return TYPEFUNC_SCALAR;
case 'p':
if (typid == RECORDOID)
return TYPEFUNC_RECORD;
/*
* We treat VOID and CSTRING as legitimate scalar datatypes,
* mostly for the convenience of the JDBC driver (which wants
* to be able to do "SELECT * FROM foo()" for all legitimately
* user-callable functions).
*/
if (typid == VOIDOID || typid == CSTRINGOID)
return TYPEFUNC_SCALAR;
return TYPEFUNC_OTHER;
}
/* shouldn't get here, probably */
return TYPEFUNC_OTHER;
}
/*
* build_function_result_tupdesc_t
*
* Given a pg_proc row for a function, return a tuple descriptor for the
* result rowtype, or NULL if the function does not have OUT parameters.
*
* Note that this does not handle resolution of ANYELEMENT/ANYARRAY types;
* that is deliberate.
*/
TupleDesc
build_function_result_tupdesc_t(HeapTuple procTuple)
{
Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(procTuple);
Datum proallargtypes;
Datum proargmodes;
Datum proargnames;
bool isnull;
/* Return NULL if the function isn't declared to return RECORD */
if (procform->prorettype != RECORDOID)
return NULL;
/* If there are no OUT parameters, return NULL */
if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes) ||
heap_attisnull(procTuple, Anum_pg_proc_proargmodes))
return NULL;
/* Get the data out of the tuple */
proallargtypes = SysCacheGetAttr(PROCOID, procTuple,
Anum_pg_proc_proallargtypes,
&isnull);
Assert(!isnull);
proargmodes = SysCacheGetAttr(PROCOID, procTuple,
Anum_pg_proc_proargmodes,
&isnull);
Assert(!isnull);
proargnames = SysCacheGetAttr(PROCOID, procTuple,
Anum_pg_proc_proargnames,
&isnull);
if (isnull)
proargnames = PointerGetDatum(NULL); /* just to be sure */
return build_function_result_tupdesc_d(proallargtypes,
proargmodes,
proargnames);
}
/*
* build_function_result_tupdesc_d
*
* Build a RECORD function's tupledesc from the pg_proc proallargtypes,
* proargmodes, and proargnames arrays. This is split out for the
* convenience of ProcedureCreate, which needs to be able to compute the
* tupledesc before actually creating the function.
*
* Returns NULL if there are not at least two OUT or INOUT arguments.
*/
TupleDesc
build_function_result_tupdesc_d(Datum proallargtypes,
Datum proargmodes,
Datum proargnames)
{
TupleDesc desc;
ArrayType *arr;
int numargs;
Oid *argtypes;
char *argmodes;
Datum *argnames = NULL;
Oid *outargtypes;
char **outargnames;
int numoutargs;
int nargnames;
int i;
/* Can't have output args if columns are null */
if (proallargtypes == PointerGetDatum(NULL) ||
proargmodes == PointerGetDatum(NULL))
return NULL;
/*
* We expect the arrays to be 1-D arrays of the right types; verify that.
* For the OID and char arrays, we don't need to use deconstruct_array()
* since the array data is just going to look like a C array of values.
*/
arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */
numargs = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
numargs < 0 ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "proallargtypes is not a 1-D Oid array");
argtypes = (Oid *) ARR_DATA_PTR(arr);
arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
if (ARR_NDIM(arr) != 1 ||
ARR_DIMS(arr)[0] != numargs ||
ARR_ELEMTYPE(arr) != CHAROID)
elog(ERROR, "proargmodes is not a 1-D char array");
argmodes = (char *) ARR_DATA_PTR(arr);
if (proargnames != PointerGetDatum(NULL))
{
arr = DatumGetArrayTypeP(proargnames); /* ensure not toasted */
if (ARR_NDIM(arr) != 1 ||
ARR_DIMS(arr)[0] != numargs ||
ARR_ELEMTYPE(arr) != TEXTOID)
elog(ERROR, "proargnames is not a 1-D text array");
deconstruct_array(arr, TEXTOID, -1, false, 'i',
&argnames, &nargnames);
Assert(nargnames == numargs);
}
/* zero elements probably shouldn't happen, but handle it gracefully */
if (numargs <= 0)
return NULL;
/* extract output-argument types and names */
outargtypes = (Oid *) palloc(numargs * sizeof(Oid));
outargnames = (char **) palloc(numargs * sizeof(char *));
numoutargs = 0;
for (i = 0; i < numargs; i++)
{
char *pname;
if (argmodes[i] == PROARGMODE_IN)
continue;
Assert(argmodes[i] == PROARGMODE_OUT ||
argmodes[i] == PROARGMODE_INOUT);
outargtypes[numoutargs] = argtypes[i];
if (argnames)
pname = DatumGetCString(DirectFunctionCall1(textout, argnames[i]));
else
pname = NULL;
if (pname == NULL || pname[0] == '\0')
{
/* Parameter is not named, so gin up a column name */
pname = (char *) palloc(32);
snprintf(pname, 32, "column%d", numoutargs + 1);
}
outargnames[numoutargs] = pname;
numoutargs++;
}
/*
* If there is no output argument, or only one, the function does not
* return tuples.
*/
if (numoutargs < 2)
return NULL;
desc = CreateTemplateTupleDesc(numoutargs, false);
for (i = 0; i < numoutargs; i++)
{
TupleDescInitEntry(desc, i+1,
outargnames[i],
outargtypes[i],
-1,
0);
}
return desc;
}
/*
* RelationNameGetTupleDesc
*
* Given a (possibly qualified) relation name, build a TupleDesc.
*/
TupleDesc
RelationNameGetTupleDesc(const char *relname)
{
RangeVar *relvar;
Relation rel;
TupleDesc tupdesc;
List *relname_list;
/* Open relation and copy the tuple description */
relname_list = stringToQualifiedNameList(relname, "RelationNameGetTupleDesc");
relvar = makeRangeVarFromNameList(relname_list);
rel = relation_openrv(relvar, AccessShareLock);
tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
relation_close(rel, AccessShareLock);
return tupdesc;
}
/*
* TypeGetTupleDesc
*
* Given a type Oid, build a TupleDesc.
*
* If the type is composite, *and* a colaliases List is provided, *and*
* the List is of natts length, use the aliases instead of the relation
* attnames. (NB: this usage is deprecated since it may result in
* creation of unnecessary transient record types.)
*
* If the type is a base type, a single item alias List is required.
*/
TupleDesc
TypeGetTupleDesc(Oid typeoid, List *colaliases)
{
TypeFuncClass functypclass = get_type_func_class(typeoid);
TupleDesc tupdesc = NULL;
/*
* Build a suitable tupledesc representing the output rows
*/
if (functypclass == TYPEFUNC_COMPOSITE)
{
/* Composite data type, e.g. a table's row type */
tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(typeoid, -1));
if (colaliases != NIL)
{
int natts = tupdesc->natts;
int varattno;
/* does the list length match the number of attributes? */
if (list_length(colaliases) != natts)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of aliases does not match number of columns")));
/* OK, use the aliases instead */
for (varattno = 0; varattno < natts; varattno++)
{
char *label = strVal(list_nth(colaliases, varattno));
if (label != NULL)
namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
}
/* The tuple type is now an anonymous record type */
tupdesc->tdtypeid = RECORDOID;
tupdesc->tdtypmod = -1;
}
}
else if (functypclass == TYPEFUNC_SCALAR)
{
/* Base data type, i.e. scalar */
char *attname;
/* the alias list is required for base types */
if (colaliases == NIL)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("no column alias was provided")));
/* the alias list length must be 1 */
if (list_length(colaliases) != 1)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of aliases does not match number of columns")));
/* OK, get the column alias */
attname = strVal(linitial(colaliases));
tupdesc = CreateTemplateTupleDesc(1, false);
TupleDescInitEntry(tupdesc,
(AttrNumber) 1,
attname,
typeoid,
-1,
0);
}
else if (functypclass == TYPEFUNC_RECORD)
{
/* XXX can't support this because typmod wasn't passed in ... */
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("could not determine row description for function returning record")));
}
else
{
/* crummy error message, but parser should have caught this */
elog(ERROR, "function in FROM has unsupported return type");
}
return tupdesc;
}

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.356 2005/03/29 19:44:23 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.357 2005/03/31 22:46:18 tgl Exp $
*
* NOTES
* The script catalog/genbki.sh reads this file and generates .bki
@ -3668,9 +3668,10 @@ extern Oid ProcedureCreate(const char *procedureName,
bool security_definer,
bool isStrict,
char volatility,
int parameterCount,
const Oid *parameterTypes,
const char *parameterNames[]);
oidvector *parameterTypes,
Datum allParameterTypes,
Datum parameterModes,
Datum parameterNames);
extern bool function_parse_error_transpose(const char *prosrc);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.24 2004/12/31 22:03:29 pgsql Exp $
* $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.25 2005/03/31 22:46:22 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -20,7 +20,7 @@
extern Datum fmgr_sql(PG_FUNCTION_ARGS);
extern bool check_sql_fn_retval(Oid rettype, char fn_typtype,
extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
List *queryTreeList,
JunkFilter **junkFilter);

View File

@ -11,13 +11,16 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/fmgr.h,v 1.37 2005/03/22 20:13:09 tgl Exp $
* $PostgreSQL: pgsql/src/include/fmgr.h,v 1.38 2005/03/31 22:46:24 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef FMGR_H
#define FMGR_H
/* We don't want to include primnodes.h here, so make a stub reference */
struct Node;
/*
* All functions that can be called directly by fmgr must have this signature.
@ -402,6 +405,7 @@ extern void clear_external_function_hash(void *filehandle);
extern Oid fmgr_internal_function(const char *proname);
extern Oid get_fn_expr_rettype(FmgrInfo *flinfo);
extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
extern Oid get_call_expr_argtype(struct Node *expr, int argnum);
/*
* Routines in dfmgr.c

View File

@ -9,7 +9,7 @@
*
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/include/funcapi.h,v 1.15 2005/01/01 05:43:08 momjian Exp $
* $PostgreSQL: pgsql/src/include/funcapi.h,v 1.16 2005/03/31 22:46:24 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -124,7 +124,57 @@ typedef struct FuncCallContext
} FuncCallContext;
/*----------
* Support to ease writing Functions returning composite types
* Support to ease writing functions returning composite types
*
* External declarations:
* get_call_result_type:
* Given a function's call info record, determine the kind of datatype
* it is supposed to return. If resultTypeId isn't NULL, *resultTypeId
* receives the actual datatype OID (this is mainly useful for scalar
* result types). If resultTupleDesc isn't NULL, *resultTupleDesc
* receives a pointer to a TupleDesc when the result is of a composite
* type, or NULL when it's a scalar result or the rowtype could not be
* determined. NB: the tupledesc should be copied if it is to be
* accessed over a long period.
* get_expr_result_type:
* Given an expression node, return the same info as for
* get_call_result_type. Note: the cases in which rowtypes cannot be
* determined are different from the cases for get_call_result_type.
* get_func_result_type:
* Given only a function's OID, return the same info as for
* get_call_result_type. Note: the cases in which rowtypes cannot be
* determined are different from the cases for get_call_result_type.
* Do *not* use this if you can use one of the others.
*----------
*/
/* Type categories for get_call_result_type and siblings */
typedef enum TypeFuncClass
{
TYPEFUNC_SCALAR, /* scalar result type */
TYPEFUNC_COMPOSITE, /* determinable rowtype result */
TYPEFUNC_RECORD, /* indeterminate rowtype result */
TYPEFUNC_OTHER /* bogus type, eg pseudotype */
} TypeFuncClass;
extern TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
Oid *resultTypeId,
TupleDesc *resultTupleDesc);
extern TypeFuncClass get_expr_result_type(Node *expr,
Oid *resultTypeId,
TupleDesc *resultTupleDesc);
extern TypeFuncClass get_func_result_type(Oid functionId,
Oid *resultTypeId,
TupleDesc *resultTupleDesc);
extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
Datum proargmodes,
Datum proargnames);
extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
/*----------
* Support to ease writing functions returning composite types
*
* External declarations:
* TupleDesc RelationNameGetTupleDesc(const char *relname) - Use to get a
@ -160,7 +210,6 @@ typedef struct FuncCallContext
/* obsolete version of above */
#define TupleGetDatum(_slot, _tuple) PointerGetDatum((_tuple)->t_data)
/* from tupdesc.c */
extern TupleDesc RelationNameGetTupleDesc(const char *relname);
extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.95 2005/03/29 00:17:18 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.96 2005/03/31 22:46:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -24,15 +24,6 @@ typedef enum IOFuncSelector
IOFunc_send
} IOFuncSelector;
/* Type categories for get_type_func_class */
typedef enum TypeFuncClass
{
TYPEFUNC_SCALAR,
TYPEFUNC_COMPOSITE,
TYPEFUNC_RECORD,
TYPEFUNC_OTHER
} TypeFuncClass;
extern bool op_in_opclass(Oid opno, Oid opclass);
extern void get_op_opclass_properties(Oid opno, Oid opclass,
int *strategy, Oid *subtype,
@ -94,7 +85,6 @@ extern char get_typstorage(Oid typid);
extern int32 get_typtypmod(Oid typid);
extern Node *get_typdefault(Oid typid);
extern char get_typtype(Oid typid);
extern TypeFuncClass get_type_func_class(Oid typid);
extern Oid get_typ_typrelid(Oid typid);
extern Oid get_element_type(Oid typid);
extern Oid get_array_type(Oid typid);

View File

@ -396,3 +396,134 @@ DROP FUNCTION foorescan(int,int);
DROP FUNCTION foorescan(int);
DROP TABLE foorescan;
DROP TABLE barrescan;
--
-- Test cases involving OUT parameters
--
CREATE FUNCTION foo(in f1 int, out f2 int)
AS 'select $1+1' LANGUAGE sql;
SELECT foo(42);
foo
-----
43
(1 row)
SELECT * FROM foo(42);
foo
-----
43
(1 row)
SELECT * FROM foo(42) AS p(x);
x
----
43
(1 row)
-- explicit spec of return type is OK
CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS int
AS 'select $1+1' LANGUAGE sql;
-- error, wrong result type
CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS float
AS 'select $1+1' LANGUAGE sql;
ERROR: function result type must be integer because of OUT parameters
-- with multiple OUT params you must get a RECORD result
CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) RETURNS int
AS 'select $1+1' LANGUAGE sql;
ERROR: function result type must be record because of OUT parameters
CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text)
RETURNS record
AS 'select $1+1' LANGUAGE sql;
ERROR: cannot change return type of existing function
HINT: Use DROP FUNCTION first.
CREATE OR REPLACE FUNCTION foor(in f1 int, out f2 int, out text)
AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql;
SELECT f1, foor(f1) FROM int4_tbl;
f1 | foor
-------------+----------------------------
0 | (-1,0z)
123456 | (123455,123456z)
-123456 | (-123457,-123456z)
2147483647 | (2147483646,2147483647z)
-2147483647 | (-2147483648,-2147483647z)
(5 rows)
SELECT * FROM foor(42);
f2 | column2
----+---------
41 | 42z
(1 row)
SELECT * FROM foor(42) AS p(a,b);
a | b
----+-----
41 | 42z
(1 row)
CREATE OR REPLACE FUNCTION foob(in f1 int, inout f2 int, out text)
AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql;
SELECT f1, foob(f1, f1/2) FROM int4_tbl;
f1 | foob
-------------+----------------------------
0 | (-1,0z)
123456 | (61727,123456z)
-123456 | (-61729,-123456z)
2147483647 | (1073741822,2147483647z)
-2147483647 | (-1073741824,-2147483647z)
(5 rows)
SELECT * FROM foob(42, 99);
f2 | column2
----+---------
98 | 42z
(1 row)
SELECT * FROM foob(42, 99) AS p(a,b);
a | b
----+-----
98 | 42z
(1 row)
-- Can reference function with or without OUT params for DROP, etc
DROP FUNCTION foo(int);
DROP FUNCTION foor(in f2 int, out f1 int, out text);
DROP FUNCTION foob(in f1 int, inout f2 int);
--
-- For my next trick, polymorphic OUT parameters
--
CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;
SELECT dup(22);
dup
----------------
(22,"{22,22}")
(1 row)
SELECT dup('xyz'); -- fails
ERROR: could not determine anyarray/anyelement type because input has type "unknown"
SELECT dup('xyz'::text);
dup
-------------------
(xyz,"{xyz,xyz}")
(1 row)
SELECT * FROM dup('xyz'::text);
f2 | f3
-----+-----------
xyz | {xyz,xyz}
(1 row)
-- equivalent specification
CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;
SELECT dup(22);
dup
----------------
(22,"{22,22}")
(1 row)
DROP FUNCTION dup(anyelement);
-- fails, no way to deduce outputs
CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;
ERROR: cannot determine result data type
DETAIL: A function returning "anyarray" or "anyelement" must have at least one argument of either type.

View File

@ -13,8 +13,8 @@ CREATE FUNCTION hobbies_by_name(hobbies_r.name%TYPE)
RETURNS hobbies_r.person%TYPE
AS 'select person from hobbies_r where name = $1'
LANGUAGE 'sql';
NOTICE: type reference hobbies_r.person%TYPE converted to text
NOTICE: type reference hobbies_r.name%TYPE converted to text
NOTICE: type reference hobbies_r.person%TYPE converted to text
CREATE FUNCTION equipment(hobbies_r)
RETURNS setof equipment_r
AS 'select * from equipment_r where hobby = $1.name'

View File

@ -199,3 +199,65 @@ DROP FUNCTION foorescan(int,int);
DROP FUNCTION foorescan(int);
DROP TABLE foorescan;
DROP TABLE barrescan;
--
-- Test cases involving OUT parameters
--
CREATE FUNCTION foo(in f1 int, out f2 int)
AS 'select $1+1' LANGUAGE sql;
SELECT foo(42);
SELECT * FROM foo(42);
SELECT * FROM foo(42) AS p(x);
-- explicit spec of return type is OK
CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS int
AS 'select $1+1' LANGUAGE sql;
-- error, wrong result type
CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS float
AS 'select $1+1' LANGUAGE sql;
-- with multiple OUT params you must get a RECORD result
CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) RETURNS int
AS 'select $1+1' LANGUAGE sql;
CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text)
RETURNS record
AS 'select $1+1' LANGUAGE sql;
CREATE OR REPLACE FUNCTION foor(in f1 int, out f2 int, out text)
AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql;
SELECT f1, foor(f1) FROM int4_tbl;
SELECT * FROM foor(42);
SELECT * FROM foor(42) AS p(a,b);
CREATE OR REPLACE FUNCTION foob(in f1 int, inout f2 int, out text)
AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql;
SELECT f1, foob(f1, f1/2) FROM int4_tbl;
SELECT * FROM foob(42, 99);
SELECT * FROM foob(42, 99) AS p(a,b);
-- Can reference function with or without OUT params for DROP, etc
DROP FUNCTION foo(int);
DROP FUNCTION foor(in f2 int, out f1 int, out text);
DROP FUNCTION foob(in f1 int, inout f2 int);
--
-- For my next trick, polymorphic OUT parameters
--
CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;
SELECT dup(22);
SELECT dup('xyz'); -- fails
SELECT dup('xyz'::text);
SELECT * FROM dup('xyz'::text);
-- equivalent specification
CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;
SELECT dup(22);
DROP FUNCTION dup(anyelement);
-- fails, no way to deduce outputs
CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
AS 'select $1, array[$1,$1]' LANGUAGE sql;