Commit 47888fe8 authored by Tom Lane's avatar Tom Lane

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.
parent fb13881f
<!--
$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>
......
<!--
$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>
......
......@@ -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;
}
......@@ -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.
......
This diff is collapsed.
......@@ -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;
if (fp->mode != FUNC_PARAM_IN)
{
if (outCount == 0) /* save first OUT param's type */
*requiredResultType = toid;
outCount++;
}
allTypes[i] = ObjectIdGetDatum(toid);
parameterNames[parameterCount] = fp->name;
paramModes[i] = CharGetDatum(fp->mode);
parameterCount++;
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);
parameterCount = examine_parameter_list(stmt->parameters, languageOid,
parameterTypes, parameterNames);
examine_parameter_list(stmt->parameters, languageOid,
&parameterTypes,
&allParameterTypes,
&parameterModes,
&parameterNames,
&requiredResultType);
if (stmt->returnType)
{
/* explicit RETURNS clause */
compute_return_type(stmt->returnType, languageOid,
&prorettype, &returnsSet);
if (OidIsValid(requiredResultType) && prorettype != requiredResultType)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("function result type must be %s because of OUT parameters",
format_type_be(requiredResultType))));
}
else if (OidIsValid(requiredResultType))
{
/* default RETURNS clause from OUT parameters */
prorettype = requiredResultType;
returnsSet = false;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("function result type must be specified")));
/* Alternative possibility: default to RETURNS VOID */
prorettype = VOIDOID;
returnsSet = false;
}
compute_attributes_with_style(stmt->withClause, &isStrict, &volatility);
......@@ -572,9 +660,10 @@ CreateFunction(CreateFunctionStmt *stmt)
security,
isStrict,
volatility,
parameterCount,
parameterTypes,
parameterNames);
PointerGetDatum(allParameterTypes),
PointerGetDatum(parameterModes),
PointerGetDatum(parameterNames));
}
......
......@@ -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 ... */
......
......@@ -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);
......
......@@ -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
......
......@@ -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));
......
......@@ -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 */
attnum = get_attnum(argrelid, funcname);
if (attnum == InvalidAttrNumber)
return NULL; /* funcname does not match any column */
/* 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);
return (Node *) fselect;
if (get_expr_result_type(first_arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
return NULL; /* unresolvable RECORD type */
for (i = 0; i < tupdesc->natts; i++)
{
Form_pg_attribute att = tupdesc->attrs[i];
if (strcmp(funcname, NameStr(att->attname)) == 0 &&
!att->attisdropped)
{
/* Success, so generate a FieldSelect expression */
FieldSelect *fselect = makeNode(FieldSelect);
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 */
}
/*
......
......@@ -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;
......
......@@ -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
*
......
......@@ -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;
......
This diff is collapsed.
......@@ -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);
......
......@@ -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);
......
......@@ -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
......
......@@ -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);
......
......@@ -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);
......
......@@ -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.
......@@ -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'
......
......@@ -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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment