mirror of https://github.com/postgres/postgres
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:
parent
fb13881f42
commit
47888fe842
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
¶meterTypes,
|
||||
&allParameterTypes,
|
||||
¶meterModes,
|
||||
¶meterNames,
|
||||
&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));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 */
|
||||
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 */
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue