mirror of
https://github.com/postgres/postgres.git
synced 2025-06-14 18:42:34 +03:00
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:
@ -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">
|
<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>
|
<refsynopsisdiv>
|
||||||
<synopsis>
|
<synopsis>
|
||||||
CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
|
CREATE [ OR REPLACE ] FUNCTION
|
||||||
RETURNS <replaceable class="parameter">rettype</replaceable>
|
<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>
|
{ LANGUAGE <replaceable class="parameter">langname</replaceable>
|
||||||
| IMMUTABLE | STABLE | VOLATILE
|
| IMMUTABLE | STABLE | VOLATILE
|
||||||
| CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
|
| 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).
|
tried, you would actually be creating a new, distinct function).
|
||||||
Also, <command>CREATE OR REPLACE FUNCTION</command> will not let
|
Also, <command>CREATE OR REPLACE FUNCTION</command> will not let
|
||||||
you change the return type of an existing function. To do that,
|
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>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -88,6 +91,17 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</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>
|
<varlistentry>
|
||||||
<term><replaceable class="parameter">argname</replaceable></term>
|
<term><replaceable class="parameter">argname</replaceable></term>
|
||||||
|
|
||||||
@ -95,7 +109,10 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
|
|||||||
<para>
|
<para>
|
||||||
The name of an argument. Some languages (currently only PL/pgSQL) let
|
The name of an argument. Some languages (currently only PL/pgSQL) let
|
||||||
you use the name in the function body. For other languages the
|
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>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -137,6 +154,13 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
|
|||||||
Depending on the implementation language it may also be allowed
|
Depending on the implementation language it may also be allowed
|
||||||
to specify <quote>pseudotypes</> such as <type>cstring</>.
|
to specify <quote>pseudotypes</> such as <type>cstring</>.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
The <literal>SETOF</literal>
|
The <literal>SETOF</literal>
|
||||||
modifier indicates that the function will return a set of
|
modifier indicates that the function will return a set of
|
||||||
@ -361,6 +385,16 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
|
|||||||
names).
|
names).
|
||||||
</para>
|
</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>
|
<para>
|
||||||
When repeated <command>CREATE FUNCTION</command> calls refer to
|
When repeated <command>CREATE FUNCTION</command> calls refer to
|
||||||
the same object file, the file is only loaded once. To unload and
|
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>
|
<title>Examples</title>
|
||||||
|
|
||||||
<para>
|
<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">.
|
information and examples, see <xref linkend="xfunc">.
|
||||||
<programlisting>
|
<programlisting>
|
||||||
CREATE FUNCTION add(integer, integer) RETURNS integer
|
CREATE FUNCTION add(integer, integer) RETURNS integer
|
||||||
@ -407,13 +441,34 @@ CREATE FUNCTION add(integer, integer) RETURNS integer
|
|||||||
<para>
|
<para>
|
||||||
Increment an integer, making use of an argument name, in
|
Increment an integer, making use of an argument name, in
|
||||||
<application>PL/pgSQL</application>:
|
<application>PL/pgSQL</application>:
|
||||||
|
|
||||||
<programlisting>
|
<programlisting>
|
||||||
CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
|
CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
RETURN i + 1;
|
RETURN i + 1;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ 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>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
@ -428,6 +483,13 @@ $$ LANGUAGE plpgsql;
|
|||||||
not fully compatible. The attributes are not portable, neither are the
|
not fully compatible. The attributes are not portable, neither are the
|
||||||
different available languages.
|
different available languages.
|
||||||
</para>
|
</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>
|
</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">
|
<sect1 id="xfunc">
|
||||||
@ -172,7 +172,7 @@ INSERT INTO $1 VALUES (42);
|
|||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<sect2>
|
<sect2 id="xfunc-sql-base-functions">
|
||||||
<title><acronym>SQL</acronym> Functions on Base Types</title>
|
<title><acronym>SQL</acronym> Functions on Base Types</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@ -484,7 +484,7 @@ SELECT emp.name, emp.double_salary FROM emp;
|
|||||||
</tip>
|
</tip>
|
||||||
|
|
||||||
<para>
|
<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:
|
result to another function that accepts the correct row type as input:
|
||||||
|
|
||||||
<screen>
|
<screen>
|
||||||
@ -501,8 +501,89 @@ SELECT getname(new_emp());
|
|||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
Another way to use a function that returns a composite type is to
|
Still another way to use a function that returns a composite type is to
|
||||||
call it as a table function, as described below.
|
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>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
|
|
||||||
@ -692,6 +773,21 @@ CREATE FUNCTION invalid_func() RETURNS anyelement AS $$
|
|||||||
$$ LANGUAGE SQL;
|
$$ LANGUAGE SQL;
|
||||||
ERROR: cannot determine result data type
|
ERROR: cannot determine result data type
|
||||||
DETAIL: A function returning "anyarray" or "anyelement" must have at least one argument of either 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>
|
</screen>
|
||||||
</para>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
@ -962,7 +1058,7 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision
|
|||||||
<sect1 id="xfunc-c">
|
<sect1 id="xfunc-c">
|
||||||
<title>C-Language Functions</title>
|
<title>C-Language Functions</title>
|
||||||
|
|
||||||
<indexterm zone="xfunc-sql">
|
<indexterm zone="xfunc-c">
|
||||||
<primary>function</primary>
|
<primary>function</primary>
|
||||||
<secondary>user-defined</secondary>
|
<secondary>user-defined</secondary>
|
||||||
<tertiary>in C</tertiary>
|
<tertiary>in C</tertiary>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* NOTES
|
||||||
* some of the executor utility code such as "ExecTypeFromTL" should be
|
* some of the executor utility code such as "ExecTypeFromTL" should be
|
||||||
@ -19,16 +19,11 @@
|
|||||||
|
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
|
|
||||||
#include "funcapi.h"
|
|
||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
#include "catalog/namespace.h"
|
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "nodes/parsenodes.h"
|
|
||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/lsyscache.h"
|
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
#include "utils/typcache.h"
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -548,122 +543,3 @@ BuildDescForRelation(List *schema)
|
|||||||
|
|
||||||
return desc;
|
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
|
* 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 */
|
false, /* doesn't return a set */
|
||||||
finaltype, /* returnType */
|
finaltype, /* returnType */
|
||||||
INTERNALlanguageId, /* languageObjectId */
|
INTERNALlanguageId, /* languageObjectId */
|
||||||
0,
|
InvalidOid, /* no validator */
|
||||||
"aggregate_dummy", /* placeholder proc */
|
"aggregate_dummy", /* placeholder proc */
|
||||||
"-", /* probin */
|
"-", /* probin */
|
||||||
true, /* isAgg */
|
true, /* isAgg */
|
||||||
@ -189,9 +189,10 @@ AggregateCreate(const char *aggName,
|
|||||||
false, /* isStrict (not needed for agg) */
|
false, /* isStrict (not needed for agg) */
|
||||||
PROVOLATILE_IMMUTABLE, /* volatility (not
|
PROVOLATILE_IMMUTABLE, /* volatility (not
|
||||||
* needed for agg) */
|
* needed for agg) */
|
||||||
1, /* parameterCount */
|
buildoidvector(fnArgs, 1), /* paramTypes */
|
||||||
fnArgs, /* parameterTypes */
|
PointerGetDatum(NULL), /* allParamTypes */
|
||||||
NULL); /* parameterNames */
|
PointerGetDatum(NULL), /* parameterModes */
|
||||||
|
PointerGetDatum(NULL)); /* parameterNames */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Okay to create the pg_aggregate entry.
|
* Okay to create the pg_aggregate entry.
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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_proc.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "executor/functions.h"
|
#include "executor/functions.h"
|
||||||
|
#include "funcapi.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "mb/pg_wchar.h"
|
#include "mb/pg_wchar.h"
|
||||||
#include "parser/parse_type.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_c_validator(PG_FUNCTION_ARGS);
|
||||||
Datum fmgr_sql_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 void sql_function_parse_error_callback(void *arg);
|
||||||
static int match_prosrc_to_query(const char *prosrc, const char *queryText,
|
static int match_prosrc_to_query(const char *prosrc, const char *queryText,
|
||||||
int cursorpos);
|
int cursorpos);
|
||||||
@ -51,6 +50,10 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
|
|||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
* ProcedureCreate
|
* 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
|
Oid
|
||||||
@ -67,26 +70,29 @@ ProcedureCreate(const char *procedureName,
|
|||||||
bool security_definer,
|
bool security_definer,
|
||||||
bool isStrict,
|
bool isStrict,
|
||||||
char volatility,
|
char volatility,
|
||||||
int parameterCount,
|
oidvector *parameterTypes,
|
||||||
const Oid *parameterTypes,
|
Datum allParameterTypes,
|
||||||
const char *parameterNames[])
|
Datum parameterModes,
|
||||||
|
Datum parameterNames)
|
||||||
{
|
{
|
||||||
int i;
|
Oid retval;
|
||||||
|
int parameterCount;
|
||||||
|
int allParamCount;
|
||||||
|
Oid *allParams;
|
||||||
|
bool genericInParam = false;
|
||||||
Relation rel;
|
Relation rel;
|
||||||
HeapTuple tup;
|
HeapTuple tup;
|
||||||
HeapTuple oldtup;
|
HeapTuple oldtup;
|
||||||
char nulls[Natts_pg_proc];
|
char nulls[Natts_pg_proc];
|
||||||
Datum values[Natts_pg_proc];
|
Datum values[Natts_pg_proc];
|
||||||
char replaces[Natts_pg_proc];
|
char replaces[Natts_pg_proc];
|
||||||
oidvector *proargtypes;
|
|
||||||
Datum namesarray;
|
|
||||||
Oid relid;
|
Oid relid;
|
||||||
NameData procname;
|
NameData procname;
|
||||||
TupleDesc tupDesc;
|
TupleDesc tupDesc;
|
||||||
Oid retval;
|
|
||||||
bool is_update;
|
bool is_update;
|
||||||
ObjectAddress myself,
|
ObjectAddress myself,
|
||||||
referenced;
|
referenced;
|
||||||
|
int i;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* sanity checks
|
* sanity checks
|
||||||
@ -94,55 +100,88 @@ ProcedureCreate(const char *procedureName,
|
|||||||
Assert(PointerIsValid(prosrc));
|
Assert(PointerIsValid(prosrc));
|
||||||
Assert(PointerIsValid(probin));
|
Assert(PointerIsValid(probin));
|
||||||
|
|
||||||
|
parameterCount = parameterTypes->dim1;
|
||||||
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
|
if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
|
||||||
errmsg("functions cannot have more than %d arguments",
|
errmsg("functions cannot have more than %d arguments",
|
||||||
FUNC_MAX_ARGS)));
|
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
|
* 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;
|
if (parameterTypes->values[i] == ANYARRAYOID ||
|
||||||
|
parameterTypes->values[i] == ANYELEMENTOID)
|
||||||
for (i = 0; i < parameterCount; i++)
|
|
||||||
{
|
{
|
||||||
if (parameterTypes[i] == ANYARRAYOID ||
|
genericInParam = true;
|
||||||
parameterTypes[i] == ANYELEMENTOID)
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!genericInParam)
|
||||||
|
{
|
||||||
|
bool genericOutParam = false;
|
||||||
|
|
||||||
|
if (allParameterTypes != PointerGetDatum(NULL))
|
||||||
|
{
|
||||||
|
for (i = 0; i < allParamCount; i++)
|
||||||
{
|
{
|
||||||
genericParam = true;
|
if (allParams[i] == ANYARRAYOID ||
|
||||||
break;
|
allParams[i] == ANYELEMENTOID)
|
||||||
|
{
|
||||||
|
genericOutParam = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!genericParam)
|
if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID ||
|
||||||
|
genericOutParam)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||||
errmsg("cannot determine result data type"),
|
errmsg("cannot determine result data type"),
|
||||||
errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either 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
|
* don't allow functions of complex types that have the same name as
|
||||||
* existing attributes of the type
|
* existing attributes of the type
|
||||||
*/
|
*/
|
||||||
if (parameterCount == 1 && OidIsValid(parameterTypes[0]) &&
|
if (parameterCount == 1 &&
|
||||||
(relid = typeidTypeRelid(parameterTypes[0])) != InvalidOid &&
|
OidIsValid(parameterTypes->values[0]) &&
|
||||||
|
(relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid &&
|
||||||
get_attnum(relid, procedureName) != InvalidAttrNumber)
|
get_attnum(relid, procedureName) != InvalidAttrNumber)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_DUPLICATE_COLUMN),
|
(errcode(ERRCODE_DUPLICATE_COLUMN),
|
||||||
errmsg("\"%s\" is already an attribute of type %s",
|
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.
|
* 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_provolatile - 1] = CharGetDatum(volatility);
|
||||||
values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
|
values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
|
||||||
values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
|
values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
|
||||||
values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(proargtypes);
|
values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes);
|
||||||
/* XXX for now, just null out the new columns */
|
if (allParameterTypes != PointerGetDatum(NULL))
|
||||||
nulls[Anum_pg_proc_proallargtypes - 1] = 'n';
|
values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes;
|
||||||
nulls[Anum_pg_proc_proargmodes - 1] = 'n';
|
else
|
||||||
if (namesarray != PointerGetDatum(NULL))
|
nulls[Anum_pg_proc_proallargtypes - 1] = 'n';
|
||||||
values[Anum_pg_proc_proargnames - 1] = namesarray;
|
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
|
else
|
||||||
nulls[Anum_pg_proc_proargnames - 1] = 'n';
|
nulls[Anum_pg_proc_proargnames - 1] = 'n';
|
||||||
values[Anum_pg_proc_prosrc - 1] = DirectFunctionCall1(textin,
|
values[Anum_pg_proc_prosrc - 1] = DirectFunctionCall1(textin,
|
||||||
@ -188,7 +232,7 @@ ProcedureCreate(const char *procedureName,
|
|||||||
/* Check for pre-existing definition */
|
/* Check for pre-existing definition */
|
||||||
oldtup = SearchSysCache(PROCNAMEARGSNSP,
|
oldtup = SearchSysCache(PROCNAMEARGSNSP,
|
||||||
PointerGetDatum(procedureName),
|
PointerGetDatum(procedureName),
|
||||||
PointerGetDatum(proargtypes),
|
PointerGetDatum(parameterTypes),
|
||||||
ObjectIdGetDatum(procNamespace),
|
ObjectIdGetDatum(procNamespace),
|
||||||
0);
|
0);
|
||||||
|
|
||||||
@ -214,9 +258,33 @@ ProcedureCreate(const char *procedureName,
|
|||||||
returnsSet != oldproc->proretset)
|
returnsSet != oldproc->proretset)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
(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.")));
|
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 */
|
/* Can't change aggregate status, either */
|
||||||
if (oldproc->proisagg != isAgg)
|
if (oldproc->proisagg != isAgg)
|
||||||
{
|
{
|
||||||
@ -285,11 +353,11 @@ ProcedureCreate(const char *procedureName,
|
|||||||
referenced.objectSubId = 0;
|
referenced.objectSubId = 0;
|
||||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
||||||
|
|
||||||
/* dependency on input types */
|
/* dependency on parameter types */
|
||||||
for (i = 0; i < parameterCount; i++)
|
for (i = 0; i < allParamCount; i++)
|
||||||
{
|
{
|
||||||
referenced.classId = RelOid_pg_type;
|
referenced.classId = RelOid_pg_type;
|
||||||
referenced.objectId = parameterTypes[i];
|
referenced.objectId = allParams[i];
|
||||||
referenced.objectSubId = 0;
|
referenced.objectSubId = 0;
|
||||||
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
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
|
* Validator for internal functions
|
||||||
@ -461,7 +493,6 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
|
|||||||
Datum tmp;
|
Datum tmp;
|
||||||
char *prosrc;
|
char *prosrc;
|
||||||
ErrorContextCallback sqlerrcontext;
|
ErrorContextCallback sqlerrcontext;
|
||||||
char functyptype;
|
|
||||||
bool haspolyarg;
|
bool haspolyarg;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
@ -472,11 +503,9 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
|
|||||||
elog(ERROR, "cache lookup failed for function %u", funcoid);
|
elog(ERROR, "cache lookup failed for function %u", funcoid);
|
||||||
proc = (Form_pg_proc) GETSTRUCT(tuple);
|
proc = (Form_pg_proc) GETSTRUCT(tuple);
|
||||||
|
|
||||||
functyptype = get_typtype(proc->prorettype);
|
|
||||||
|
|
||||||
/* Disallow pseudotype result */
|
/* Disallow pseudotype result */
|
||||||
/* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */
|
/* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */
|
||||||
if (functyptype == 'p' &&
|
if (get_typtype(proc->prorettype) == 'p' &&
|
||||||
proc->prorettype != RECORDOID &&
|
proc->prorettype != RECORDOID &&
|
||||||
proc->prorettype != VOIDOID &&
|
proc->prorettype != VOIDOID &&
|
||||||
proc->prorettype != ANYARRAYOID &&
|
proc->prorettype != ANYARRAYOID &&
|
||||||
@ -535,7 +564,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
|
|||||||
querytree_list = pg_parse_and_rewrite(prosrc,
|
querytree_list = pg_parse_and_rewrite(prosrc,
|
||||||
proc->proargtypes.values,
|
proc->proargtypes.values,
|
||||||
proc->pronargs);
|
proc->pronargs);
|
||||||
(void) check_sql_fn_retval(proc->prorettype, functyptype,
|
(void) check_sql_fn_retval(funcoid, proc->prorettype,
|
||||||
querytree_list, NULL);
|
querytree_list, NULL);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* DESCRIPTION
|
||||||
* These routines take the parse tree and pick out the
|
* 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.
|
* and return information about it as *prorettype_p and *returnsSet.
|
||||||
*
|
*
|
||||||
* This is more complex than the average typename lookup because we want to
|
* 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.
|
* 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
|
static void
|
||||||
examine_parameter_list(List *parameter, Oid languageOid,
|
examine_parameter_list(List *parameters, Oid languageOid,
|
||||||
Oid *parameterTypes, const char *parameterNames[])
|
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;
|
ListCell *x;
|
||||||
|
int i;
|
||||||
|
|
||||||
MemSet(parameterTypes, 0, FUNC_MAX_ARGS * sizeof(Oid));
|
inTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
|
||||||
MemSet(parameterNames, 0, FUNC_MAX_ARGS * sizeof(char *));
|
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);
|
FunctionParameter *fp = (FunctionParameter *) lfirst(x);
|
||||||
TypeName *t = fp->argType;
|
TypeName *t = fp->argType;
|
||||||
Oid toid;
|
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);
|
toid = LookupTypeName(t);
|
||||||
if (OidIsValid(toid))
|
if (OidIsValid(toid))
|
||||||
{
|
{
|
||||||
@ -194,16 +200,66 @@ examine_parameter_list(List *parameter, Oid languageOid,
|
|||||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||||
errmsg("functions cannot accept set arguments")));
|
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
|
* Recognize one of the options that can be passed to both CREATE
|
||||||
* FUNCTION and ALTER FUNCTION and return it via one of the out
|
* FUNCTION and ALTER FUNCTION and return it via one of the out
|
||||||
@ -321,6 +377,7 @@ compute_attributes_sql_style(List *options,
|
|||||||
defel->defname);
|
defel->defname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* process required items */
|
||||||
if (as_item)
|
if (as_item)
|
||||||
*as = (List *) as_item->arg;
|
*as = (List *) as_item->arg;
|
||||||
else
|
else
|
||||||
@ -335,6 +392,7 @@ compute_attributes_sql_style(List *options,
|
|||||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||||
errmsg("no language specified")));
|
errmsg("no language specified")));
|
||||||
|
|
||||||
|
/* process optional items */
|
||||||
if (volatility_item)
|
if (volatility_item)
|
||||||
*volatility_p = interpret_func_volatility(volatility_item);
|
*volatility_p = interpret_func_volatility(volatility_item);
|
||||||
if (strict_item)
|
if (strict_item)
|
||||||
@ -445,9 +503,11 @@ CreateFunction(CreateFunctionStmt *stmt)
|
|||||||
char *funcname;
|
char *funcname;
|
||||||
Oid namespaceId;
|
Oid namespaceId;
|
||||||
AclResult aclresult;
|
AclResult aclresult;
|
||||||
int parameterCount;
|
oidvector *parameterTypes;
|
||||||
Oid parameterTypes[FUNC_MAX_ARGS];
|
ArrayType *allParameterTypes;
|
||||||
const char *parameterNames[FUNC_MAX_ARGS];
|
ArrayType *parameterModes;
|
||||||
|
ArrayType *parameterNames;
|
||||||
|
Oid requiredResultType;
|
||||||
bool isStrict,
|
bool isStrict,
|
||||||
security;
|
security;
|
||||||
char volatility;
|
char volatility;
|
||||||
@ -465,7 +525,7 @@ CreateFunction(CreateFunctionStmt *stmt)
|
|||||||
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
|
||||||
get_namespace_name(namespaceId));
|
get_namespace_name(namespaceId));
|
||||||
|
|
||||||
/* defaults attributes */
|
/* default attributes */
|
||||||
isStrict = false;
|
isStrict = false;
|
||||||
security = false;
|
security = false;
|
||||||
volatility = PROVOLATILE_VOLATILE;
|
volatility = PROVOLATILE_VOLATILE;
|
||||||
@ -523,11 +583,39 @@ CreateFunction(CreateFunctionStmt *stmt)
|
|||||||
* Convert remaining parameters of CREATE to form wanted by
|
* Convert remaining parameters of CREATE to form wanted by
|
||||||
* ProcedureCreate.
|
* ProcedureCreate.
|
||||||
*/
|
*/
|
||||||
compute_return_type(stmt->returnType, languageOid,
|
examine_parameter_list(stmt->parameters, languageOid,
|
||||||
&prorettype, &returnsSet);
|
¶meterTypes,
|
||||||
|
&allParameterTypes,
|
||||||
|
¶meterModes,
|
||||||
|
¶meterNames,
|
||||||
|
&requiredResultType);
|
||||||
|
|
||||||
parameterCount = examine_parameter_list(stmt->parameters, languageOid,
|
if (stmt->returnType)
|
||||||
parameterTypes, parameterNames);
|
{
|
||||||
|
/* 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);
|
compute_attributes_with_style(stmt->withClause, &isStrict, &volatility);
|
||||||
|
|
||||||
@ -572,9 +660,10 @@ CreateFunction(CreateFunctionStmt *stmt)
|
|||||||
security,
|
security,
|
||||||
isStrict,
|
isStrict,
|
||||||
volatility,
|
volatility,
|
||||||
parameterCount,
|
|
||||||
parameterTypes,
|
parameterTypes,
|
||||||
parameterNames);
|
PointerGetDatum(allParameterTypes),
|
||||||
|
PointerGetDatum(parameterModes),
|
||||||
|
PointerGetDatum(parameterNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 "commands/trigger.h"
|
||||||
#include "executor/executor.h"
|
#include "executor/executor.h"
|
||||||
#include "executor/functions.h"
|
#include "executor/functions.h"
|
||||||
|
#include "funcapi.h"
|
||||||
#include "parser/parse_coerce.h"
|
#include "parser/parse_coerce.h"
|
||||||
#include "parser/parse_expr.h"
|
#include "parser/parse_expr.h"
|
||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
@ -277,8 +278,8 @@ init_sql_fcache(FmgrInfo *finfo)
|
|||||||
* form.
|
* form.
|
||||||
*/
|
*/
|
||||||
if (haspolyarg || fcache->returnsTuple)
|
if (haspolyarg || fcache->returnsTuple)
|
||||||
fcache->returnsTuple = check_sql_fn_retval(rettype,
|
fcache->returnsTuple = check_sql_fn_retval(foid,
|
||||||
get_typtype(rettype),
|
rettype,
|
||||||
queryTree_list,
|
queryTree_list,
|
||||||
&fcache->junkFilter);
|
&fcache->junkFilter);
|
||||||
|
|
||||||
@ -858,7 +859,7 @@ ShutdownSQLFunction(Datum arg)
|
|||||||
* tuple result), *junkFilter is set to NULL.
|
* tuple result), *junkFilter is set to NULL.
|
||||||
*/
|
*/
|
||||||
bool
|
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)
|
JunkFilter **junkFilter)
|
||||||
{
|
{
|
||||||
Query *parse;
|
Query *parse;
|
||||||
@ -866,12 +867,8 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
|
|||||||
List *tlist;
|
List *tlist;
|
||||||
ListCell *tlistitem;
|
ListCell *tlistitem;
|
||||||
int tlistlen;
|
int tlistlen;
|
||||||
Oid typerelid;
|
char fn_typtype;
|
||||||
Oid restype;
|
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)
|
if (junkFilter)
|
||||||
*junkFilter = NULL; /* default result */
|
*junkFilter = NULL; /* default result */
|
||||||
@ -922,13 +919,10 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
|
|||||||
*/
|
*/
|
||||||
tlistlen = ExecCleanTargetListLength(tlist);
|
tlistlen = ExecCleanTargetListLength(tlist);
|
||||||
|
|
||||||
typerelid = typeidTypeRelid(rettype);
|
fn_typtype = get_typtype(rettype);
|
||||||
|
|
||||||
if (fn_typtype == 'b' || fn_typtype == 'd')
|
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
|
* For base-type returns, the target list should have exactly one
|
||||||
* entry, and its type should agree with what the user declared.
|
* 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.",
|
errdetail("Actual return type is %s.",
|
||||||
format_type_be(restype))));
|
format_type_be(restype))));
|
||||||
}
|
}
|
||||||
else if (fn_typtype == 'c')
|
else if (fn_typtype == 'c' || rettype == RECORDOID)
|
||||||
{
|
{
|
||||||
/* Must have a typerelid */
|
/* Returns a rowtype */
|
||||||
Assert(typerelid != InvalidOid);
|
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
|
* 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 */
|
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
|
* Verify that the targetlist matches the return tuple type.
|
||||||
* type. This part of the typechecking is a hack. We look up the
|
* We scan the non-deleted attributes to ensure that they match the
|
||||||
* relation that is the declared return type, and scan the
|
* datatypes of the non-resjunk columns.
|
||||||
* non-deleted attributes to ensure that they match the datatypes
|
|
||||||
* of the non-resjunk columns.
|
|
||||||
*/
|
*/
|
||||||
reln = relation_open(typerelid, AccessShareLock);
|
tupnatts = tupdesc->natts;
|
||||||
relnatts = reln->rd_rel->relnatts;
|
tuplogcols = 0; /* we'll count nondeleted cols as we go */
|
||||||
rellogcols = 0; /* we'll count nondeleted cols as we go */
|
|
||||||
colindex = 0;
|
colindex = 0;
|
||||||
|
|
||||||
foreach(tlistitem, tlist)
|
foreach(tlistitem, tlist)
|
||||||
@ -994,15 +1002,15 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
colindex++;
|
colindex++;
|
||||||
if (colindex > relnatts)
|
if (colindex > tupnatts)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||||
errmsg("return type mismatch in function declared to return %s",
|
errmsg("return type mismatch in function declared to return %s",
|
||||||
format_type_be(rettype)),
|
format_type_be(rettype)),
|
||||||
errdetail("Final SELECT returns too many columns.")));
|
errdetail("Final SELECT returns too many columns.")));
|
||||||
attr = reln->rd_att->attrs[colindex - 1];
|
attr = tupdesc->attrs[colindex - 1];
|
||||||
} while (attr->attisdropped);
|
} while (attr->attisdropped);
|
||||||
rellogcols++;
|
tuplogcols++;
|
||||||
|
|
||||||
tletype = exprType((Node *) tle->expr);
|
tletype = exprType((Node *) tle->expr);
|
||||||
atttype = attr->atttypid;
|
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.",
|
errdetail("Final SELECT returns %s instead of %s at column %d.",
|
||||||
format_type_be(tletype),
|
format_type_be(tletype),
|
||||||
format_type_be(atttype),
|
format_type_be(atttype),
|
||||||
rellogcols)));
|
tuplogcols)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
colindex++;
|
colindex++;
|
||||||
if (colindex > relnatts)
|
if (colindex > tupnatts)
|
||||||
break;
|
break;
|
||||||
if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
|
if (!tupdesc->attrs[colindex - 1]->attisdropped)
|
||||||
rellogcols++;
|
tuplogcols++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tlistlen != rellogcols)
|
if (tlistlen != tuplogcols)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
|
||||||
errmsg("return type mismatch in function declared to return %s",
|
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 */
|
/* Set up junk filter if needed */
|
||||||
if (junkFilter)
|
if (junkFilter)
|
||||||
*junkFilter = ExecInitJunkFilterConversion(tlist,
|
*junkFilter = ExecInitJunkFilterConversion(tlist,
|
||||||
CreateTupleDescCopy(reln->rd_att),
|
CreateTupleDescCopy(tupdesc),
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
relation_close(reln, AccessShareLock);
|
|
||||||
|
|
||||||
/* Report that we are returning entire tuple result */
|
/* Report that we are returning entire tuple result */
|
||||||
return true;
|
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)
|
else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
|
||||||
{
|
{
|
||||||
/* This should already have been caught ... */
|
/* This should already have been caught ... */
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 "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 "executor/nodeFunctionscan.h"
|
||||||
|
#include "funcapi.h"
|
||||||
#include "parser/parsetree.h"
|
#include "parser/parsetree.h"
|
||||||
#include "parser/parse_expr.h"
|
|
||||||
#include "parser/parse_type.h"
|
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/lsyscache.h"
|
|
||||||
#include "utils/typcache.h"
|
|
||||||
|
|
||||||
|
|
||||||
static TupleTableSlot *FunctionNext(FunctionScanState *node);
|
static TupleTableSlot *FunctionNext(FunctionScanState *node);
|
||||||
@ -180,18 +172,21 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate)
|
|||||||
*/
|
*/
|
||||||
rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
|
rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
|
||||||
Assert(rte->rtekind == RTE_FUNCTION);
|
Assert(rte->rtekind == RTE_FUNCTION);
|
||||||
funcrettype = exprType(rte->funcexpr);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now determine if the function returns a simple or composite type,
|
* Now determine if the function returns a simple or composite type,
|
||||||
* and build an appropriate tupdesc.
|
* and build an appropriate tupdesc.
|
||||||
*/
|
*/
|
||||||
functypclass = get_type_func_class(funcrettype);
|
functypclass = get_expr_result_type(rte->funcexpr,
|
||||||
|
&funcrettype,
|
||||||
|
&tupdesc);
|
||||||
|
|
||||||
if (functypclass == TYPEFUNC_COMPOSITE)
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
||||||
{
|
{
|
||||||
/* Composite data type, e.g. a table's row type */
|
/* 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)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
@ -216,14 +211,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate)
|
|||||||
elog(ERROR, "function in FROM has unsupported return type");
|
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;
|
scanstate->tupdesc = tupdesc;
|
||||||
ExecSetSlotDescriptor(scanstate->ss.ss_ScanTupleSlot,
|
ExecSetSlotDescriptor(scanstate->ss.ss_ScanTupleSlot,
|
||||||
tupdesc, false);
|
tupdesc, false);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* HISTORY
|
||||||
* AUTHOR DATE MAJOR EVENT
|
* 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.)
|
* probably not important, but let's be careful.)
|
||||||
*/
|
*/
|
||||||
if (polymorphic)
|
if (polymorphic)
|
||||||
(void) check_sql_fn_retval(result_type, get_typtype(result_type),
|
(void) check_sql_fn_retval(funcid, result_type, querytree_list, NULL);
|
||||||
querytree_list, NULL);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Additional validity checks on the expression. It mustn't return a
|
* Additional validity checks on the expression. It mustn't return a
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* HISTORY
|
||||||
* AUTHOR DATE MAJOR EVENT
|
* AUTHOR DATE MAJOR EVENT
|
||||||
@ -2544,7 +2544,7 @@ def_elem: ColLabel '=' def_arg
|
|||||||
;
|
;
|
||||||
|
|
||||||
/* Note: any simple identifier will be returned as a type name! */
|
/* 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; }
|
| qual_all_Op { $$ = (Node *)$1; }
|
||||||
| NumericOnly { $$ = (Node *)$1; }
|
| NumericOnly { $$ = (Node *)$1; }
|
||||||
| Sconst { $$ = (Node *)makeString($1); }
|
| Sconst { $$ = (Node *)makeString($1); }
|
||||||
@ -3282,6 +3282,18 @@ CreateFunctionStmt:
|
|||||||
n->withClause = $9;
|
n->withClause = $9;
|
||||||
$$ = (Node *)n;
|
$$ = (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:
|
opt_or_replace:
|
||||||
@ -3367,7 +3379,7 @@ param_name: function_name
|
|||||||
func_return:
|
func_return:
|
||||||
func_type
|
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.
|
* but for now better to silently swallow typmod, etc.
|
||||||
* - thomas 2000-03-22
|
* - thomas 2000-03-22
|
||||||
*/
|
*/
|
||||||
@ -3424,7 +3436,6 @@ common_func_opt_item:
|
|||||||
{
|
{
|
||||||
$$ = makeDefElem("volatility", (Node *)makeString("volatile"));
|
$$ = makeDefElem("volatility", (Node *)makeString("volatile"));
|
||||||
}
|
}
|
||||||
|
|
||||||
| EXTERNAL SECURITY DEFINER
|
| EXTERNAL SECURITY DEFINER
|
||||||
{
|
{
|
||||||
$$ = makeDefElem("security", (Node *)makeInteger(TRUE));
|
$$ = makeDefElem("security", (Node *)makeInteger(TRUE));
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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/catname.h"
|
||||||
#include "catalog/pg_inherits.h"
|
#include "catalog/pg_inherits.h"
|
||||||
#include "catalog/pg_proc.h"
|
#include "catalog/pg_proc.h"
|
||||||
|
#include "funcapi.h"
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "parser/parse_agg.h"
|
#include "parser/parse_agg.h"
|
||||||
@ -1154,10 +1155,8 @@ make_fn_arguments(ParseState *pstate,
|
|||||||
static Node *
|
static Node *
|
||||||
ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg)
|
ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg)
|
||||||
{
|
{
|
||||||
Oid argtype;
|
TupleDesc tupdesc;
|
||||||
Oid argrelid;
|
int i;
|
||||||
AttrNumber attnum;
|
|
||||||
FieldSelect *fselect;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Special case for whole-row Vars so that we can resolve (foo.*).bar
|
* 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,
|
* 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
|
* and isn't resolvable as a function with OUT params, we will never
|
||||||
* must be a function.
|
* be able to recognize a column name here.
|
||||||
*/
|
*/
|
||||||
argtype = exprType(first_arg);
|
if (get_expr_result_type(first_arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
||||||
argrelid = typeidTypeRelid(argtype);
|
return NULL; /* unresolvable RECORD type */
|
||||||
if (!argrelid)
|
|
||||||
return NULL; /* can only happen if RECORD */
|
|
||||||
|
|
||||||
attnum = get_attnum(argrelid, funcname);
|
for (i = 0; i < tupdesc->natts; i++)
|
||||||
if (attnum == InvalidAttrNumber)
|
{
|
||||||
return NULL; /* funcname does not match any column */
|
Form_pg_attribute att = tupdesc->attrs[i];
|
||||||
|
|
||||||
/* Success, so generate a FieldSelect expression */
|
if (strcmp(funcname, NameStr(att->attname)) == 0 &&
|
||||||
fselect = makeNode(FieldSelect);
|
!att->attisdropped)
|
||||||
fselect->arg = (Expr *) first_arg;
|
{
|
||||||
fselect->fieldnum = attnum;
|
/* Success, so generate a FieldSelect expression */
|
||||||
get_atttypetypmod(argrelid, attnum,
|
FieldSelect *fselect = makeNode(FieldSelect);
|
||||||
&fselect->resulttype,
|
|
||||||
&fselect->resulttypmod);
|
|
||||||
|
|
||||||
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
|
* 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 <ctype.h>
|
||||||
|
|
||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
#include "access/htup.h"
|
|
||||||
#include "catalog/heap.h"
|
#include "catalog/heap.h"
|
||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
|
#include "funcapi.h"
|
||||||
#include "nodes/makefuncs.h"
|
#include "nodes/makefuncs.h"
|
||||||
#include "parser/parsetree.h"
|
#include "parser/parsetree.h"
|
||||||
#include "parser/parse_coerce.h"
|
|
||||||
#include "parser/parse_expr.h"
|
#include "parser/parse_expr.h"
|
||||||
#include "parser/parse_relation.h"
|
#include "parser/parse_relation.h"
|
||||||
#include "parser/parse_type.h"
|
#include "parser/parse_type.h"
|
||||||
#include "rewrite/rewriteManip.h"
|
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
|
|
||||||
/* GUC parameter */
|
/* GUC parameter */
|
||||||
bool add_missing_from;
|
bool add_missing_from;
|
||||||
|
|
||||||
@ -46,6 +45,10 @@ static void expandRelation(Oid relid, Alias *eref,
|
|||||||
int rtindex, int sublevels_up,
|
int rtindex, int sublevels_up,
|
||||||
bool include_dropped,
|
bool include_dropped,
|
||||||
List **colnames, List **colvars);
|
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 int specialAttNum(const char *attname);
|
||||||
static void warnAutoRange(ParseState *pstate, RangeVar *relation);
|
static void warnAutoRange(ParseState *pstate, RangeVar *relation);
|
||||||
|
|
||||||
@ -965,8 +968,9 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
bool inFromCl)
|
bool inFromCl)
|
||||||
{
|
{
|
||||||
RangeTblEntry *rte = makeNode(RangeTblEntry);
|
RangeTblEntry *rte = makeNode(RangeTblEntry);
|
||||||
Oid funcrettype = exprType(funcexpr);
|
|
||||||
TypeFuncClass functypclass;
|
TypeFuncClass functypclass;
|
||||||
|
Oid funcrettype;
|
||||||
|
TupleDesc tupdesc;
|
||||||
Alias *alias = rangefunc->alias;
|
Alias *alias = rangefunc->alias;
|
||||||
List *coldeflist = rangefunc->coldeflist;
|
List *coldeflist = rangefunc->coldeflist;
|
||||||
Alias *eref;
|
Alias *eref;
|
||||||
@ -982,58 +986,37 @@ addRangeTableEntryForFunction(ParseState *pstate,
|
|||||||
rte->eref = eref;
|
rte->eref = eref;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now determine if the function returns a simple or composite type,
|
* Now determine if the function returns a simple or composite type.
|
||||||
* and check/add column aliases.
|
*/
|
||||||
|
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)
|
if (coldeflist != NIL)
|
||||||
{
|
{
|
||||||
/*
|
if (functypclass != TYPEFUNC_RECORD)
|
||||||
* we *only* allow a coldeflist for functions returning a RECORD
|
|
||||||
* pseudo-type
|
|
||||||
*/
|
|
||||||
if (funcrettype != RECORDOID)
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("a column definition list is only allowed for functions returning \"record\"")));
|
errmsg("a column definition list is only allowed for functions returning \"record\"")));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*
|
if (functypclass == TYPEFUNC_RECORD)
|
||||||
* ... and a coldeflist is *required* for functions returning a
|
|
||||||
* RECORD pseudo-type
|
|
||||||
*/
|
|
||||||
if (funcrettype == RECORDOID)
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("a column definition list is required for functions returning \"record\"")));
|
errmsg("a column definition list is required for functions returning \"record\"")));
|
||||||
}
|
}
|
||||||
|
|
||||||
functypclass = get_type_func_class(funcrettype);
|
|
||||||
|
|
||||||
if (functypclass == TYPEFUNC_COMPOSITE)
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
||||||
{
|
{
|
||||||
/* Composite data type, e.g. a table's row type */
|
/* Composite data type, e.g. a table's row type */
|
||||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
Assert(tupdesc);
|
||||||
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);
|
|
||||||
|
|
||||||
/* Build the column alias list */
|
/* Build the column alias list */
|
||||||
buildRelationAliases(rel->rd_att, alias, eref);
|
buildRelationAliases(tupdesc, 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);
|
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
@ -1308,24 +1291,19 @@ expandRTE(List *rtable, int rtindex, int sublevels_up,
|
|||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
{
|
{
|
||||||
/* Function RTE */
|
/* Function RTE */
|
||||||
Oid funcrettype = exprType(rte->funcexpr);
|
TypeFuncClass functypclass;
|
||||||
TypeFuncClass functypclass = get_type_func_class(funcrettype);
|
Oid funcrettype;
|
||||||
|
TupleDesc tupdesc;
|
||||||
|
|
||||||
|
functypclass = get_expr_result_type(rte->funcexpr,
|
||||||
|
&funcrettype,
|
||||||
|
&tupdesc);
|
||||||
if (functypclass == TYPEFUNC_COMPOSITE)
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
||||||
{
|
{
|
||||||
/*
|
/* Composite data type, e.g. a table's row type */
|
||||||
* Composite data type, i.e. a table's row type
|
Assert(tupdesc);
|
||||||
*
|
expandTupleDesc(tupdesc, rte->eref, rtindex, sublevels_up,
|
||||||
* Same as ordinary relation RTE
|
include_dropped, colnames, colvars);
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
@ -1467,17 +1445,30 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
|
|||||||
List **colnames, List **colvars)
|
List **colnames, List **colvars)
|
||||||
{
|
{
|
||||||
Relation rel;
|
Relation rel;
|
||||||
int varattno;
|
|
||||||
int maxattrs;
|
|
||||||
int numaliases;
|
|
||||||
|
|
||||||
|
/* Get the tupledesc and turn it over to expandTupleDesc */
|
||||||
rel = relation_open(relid, AccessShareLock);
|
rel = relation_open(relid, AccessShareLock);
|
||||||
maxattrs = RelationGetNumberOfAttributes(rel);
|
expandTupleDesc(rel->rd_att, eref, rtindex, sublevels_up, include_dropped,
|
||||||
numaliases = list_length(eref->colnames);
|
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++)
|
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)
|
if (attr->attisdropped)
|
||||||
{
|
{
|
||||||
@ -1519,8 +1510,6 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
|
|||||||
*colvars = lappend(*colvars, varnode);
|
*colvars = lappend(*colvars, varnode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
relation_close(rel, AccessShareLock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1662,33 +1651,29 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||||||
case RTE_FUNCTION:
|
case RTE_FUNCTION:
|
||||||
{
|
{
|
||||||
/* Function RTE */
|
/* Function RTE */
|
||||||
Oid funcrettype = exprType(rte->funcexpr);
|
TypeFuncClass functypclass;
|
||||||
TypeFuncClass functypclass = get_type_func_class(funcrettype);
|
Oid funcrettype;
|
||||||
List *coldeflist = rte->coldeflist;
|
TupleDesc tupdesc;
|
||||||
|
|
||||||
|
functypclass = get_expr_result_type(rte->funcexpr,
|
||||||
|
&funcrettype,
|
||||||
|
&tupdesc);
|
||||||
|
|
||||||
if (functypclass == TYPEFUNC_COMPOSITE)
|
if (functypclass == TYPEFUNC_COMPOSITE)
|
||||||
{
|
{
|
||||||
/*
|
/* Composite data type, e.g. a table's row type */
|
||||||
* Composite data type, i.e. a table's row type
|
|
||||||
*
|
|
||||||
* Same as ordinary relation RTE
|
|
||||||
*/
|
|
||||||
Oid funcrelid = typeidTypeRelid(funcrettype);
|
|
||||||
HeapTuple tp;
|
|
||||||
Form_pg_attribute att_tup;
|
Form_pg_attribute att_tup;
|
||||||
|
|
||||||
if (!OidIsValid(funcrelid)) /* shouldn't happen */
|
Assert(tupdesc);
|
||||||
elog(ERROR, "invalid typrelid for complex type %u",
|
/* this is probably a can't-happen case */
|
||||||
funcrettype);
|
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,
|
att_tup = tupdesc->attrs[attnum - 1];
|
||||||
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);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If dropped column, pretend it ain't there. See
|
* 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),
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
errmsg("column \"%s\" of relation \"%s\" does not exist",
|
||||||
NameStr(att_tup->attname),
|
NameStr(att_tup->attname),
|
||||||
get_rel_name(funcrelid))));
|
rte->eref->aliasname)));
|
||||||
*vartype = att_tup->atttypid;
|
*vartype = att_tup->atttypid;
|
||||||
*vartypmod = att_tup->atttypmod;
|
*vartypmod = att_tup->atttypmod;
|
||||||
ReleaseSysCache(tp);
|
|
||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_SCALAR)
|
else if (functypclass == TYPEFUNC_SCALAR)
|
||||||
{
|
{
|
||||||
@ -1712,7 +1696,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
|
|||||||
}
|
}
|
||||||
else if (functypclass == TYPEFUNC_RECORD)
|
else if (functypclass == TYPEFUNC_RECORD)
|
||||||
{
|
{
|
||||||
ColumnDef *colDef = list_nth(coldeflist, attnum - 1);
|
ColumnDef *colDef = list_nth(rte->coldeflist, attnum - 1);
|
||||||
|
|
||||||
*vartype = typenameTypeId(colDef->typename);
|
*vartype = typenameTypeId(colDef->typename);
|
||||||
*vartypmod = -1;
|
*vartypmod = -1;
|
||||||
|
38
src/backend/utils/cache/lsyscache.c
vendored
38
src/backend/utils/cache/lsyscache.c
vendored
@ -7,7 +7,7 @@
|
|||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* Portions Copyright (c) 1994, Regents of the University of California
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* NOTES
|
||||||
* Eventually, the index information should go through here, too.
|
* Eventually, the index information should go through here, too.
|
||||||
@ -1543,42 +1543,6 @@ get_typtype(Oid typid)
|
|||||||
return '\0';
|
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
|
* get_typ_typrelid
|
||||||
*
|
*
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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
|
* We want to raise an error here only if the info function returns
|
||||||
* something bogus.
|
* 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
|
* can validate the information record for a function not yet entered into
|
||||||
* pg_proc.
|
* pg_proc.
|
||||||
*/
|
*/
|
||||||
@ -576,8 +576,8 @@ fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Specialized lookup routine for ProcedureCreate(): given the alleged name
|
* Specialized lookup routine for fmgr_internal_validator: given the alleged
|
||||||
* of an internal function, return the OID of the function.
|
* name of an internal function, return the OID of the function.
|
||||||
* If the name is not recognized, return InvalidOid.
|
* If the name is not recognized, return InvalidOid.
|
||||||
*/
|
*/
|
||||||
Oid
|
Oid
|
||||||
@ -1869,10 +1869,6 @@ get_fn_expr_rettype(FmgrInfo *flinfo)
|
|||||||
Oid
|
Oid
|
||||||
get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
|
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
|
* can't return anything useful if we have no FmgrInfo or if its
|
||||||
* fn_expr node has not been initialized
|
* fn_expr node has not been initialized
|
||||||
@ -1880,7 +1876,23 @@ get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
|
|||||||
if (!flinfo || !flinfo->fn_expr)
|
if (!flinfo || !flinfo->fn_expr)
|
||||||
return InvalidOid;
|
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))
|
if (IsA(expr, FuncExpr))
|
||||||
args = ((FuncExpr *) expr)->args;
|
args = ((FuncExpr *) expr)->args;
|
||||||
|
@ -7,17 +7,37 @@
|
|||||||
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
|
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
* IDENTIFICATION
|
* 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 "postgres.h"
|
||||||
|
|
||||||
|
#include "access/heapam.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
|
#include "catalog/namespace.h"
|
||||||
|
#include "catalog/pg_proc.h"
|
||||||
#include "catalog/pg_type.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/syscache.h"
|
||||||
|
#include "utils/typcache.h"
|
||||||
|
|
||||||
|
|
||||||
static void shutdown_MultiFuncCall(Datum arg);
|
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
|
* init_MultiFuncCall
|
||||||
@ -156,3 +176,624 @@ shutdown_MultiFuncCall(Datum arg)
|
|||||||
|
|
||||||
pfree(funcctx);
|
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) 1996-2005, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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
|
* NOTES
|
||||||
* The script catalog/genbki.sh reads this file and generates .bki
|
* 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 security_definer,
|
||||||
bool isStrict,
|
bool isStrict,
|
||||||
char volatility,
|
char volatility,
|
||||||
int parameterCount,
|
oidvector *parameterTypes,
|
||||||
const Oid *parameterTypes,
|
Datum allParameterTypes,
|
||||||
const char *parameterNames[]);
|
Datum parameterModes,
|
||||||
|
Datum parameterNames);
|
||||||
|
|
||||||
extern bool function_parse_error_transpose(const char *prosrc);
|
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) 1996-2005, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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 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,
|
List *queryTreeList,
|
||||||
JunkFilter **junkFilter);
|
JunkFilter **junkFilter);
|
||||||
|
|
||||||
|
@ -11,13 +11,16 @@
|
|||||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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
|
#ifndef FMGR_H
|
||||||
#define 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.
|
* 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 fmgr_internal_function(const char *proname);
|
||||||
extern Oid get_fn_expr_rettype(FmgrInfo *flinfo);
|
extern Oid get_fn_expr_rettype(FmgrInfo *flinfo);
|
||||||
extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
|
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
|
* Routines in dfmgr.c
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
|
* 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;
|
} 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:
|
* External declarations:
|
||||||
* TupleDesc RelationNameGetTupleDesc(const char *relname) - Use to get a
|
* TupleDesc RelationNameGetTupleDesc(const char *relname) - Use to get a
|
||||||
@ -160,7 +210,6 @@ typedef struct FuncCallContext
|
|||||||
/* obsolete version of above */
|
/* obsolete version of above */
|
||||||
#define TupleGetDatum(_slot, _tuple) PointerGetDatum((_tuple)->t_data)
|
#define TupleGetDatum(_slot, _tuple) PointerGetDatum((_tuple)->t_data)
|
||||||
|
|
||||||
/* from tupdesc.c */
|
|
||||||
extern TupleDesc RelationNameGetTupleDesc(const char *relname);
|
extern TupleDesc RelationNameGetTupleDesc(const char *relname);
|
||||||
extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);
|
extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
||||||
* Portions Copyright (c) 1994, Regents of the University of California
|
* 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
|
IOFunc_send
|
||||||
} IOFuncSelector;
|
} 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 bool op_in_opclass(Oid opno, Oid opclass);
|
||||||
extern void get_op_opclass_properties(Oid opno, Oid opclass,
|
extern void get_op_opclass_properties(Oid opno, Oid opclass,
|
||||||
int *strategy, Oid *subtype,
|
int *strategy, Oid *subtype,
|
||||||
@ -94,7 +85,6 @@ extern char get_typstorage(Oid typid);
|
|||||||
extern int32 get_typtypmod(Oid typid);
|
extern int32 get_typtypmod(Oid typid);
|
||||||
extern Node *get_typdefault(Oid typid);
|
extern Node *get_typdefault(Oid typid);
|
||||||
extern char get_typtype(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_typ_typrelid(Oid typid);
|
||||||
extern Oid get_element_type(Oid typid);
|
extern Oid get_element_type(Oid typid);
|
||||||
extern Oid get_array_type(Oid typid);
|
extern Oid get_array_type(Oid typid);
|
||||||
|
@ -396,3 +396,134 @@ DROP FUNCTION foorescan(int,int);
|
|||||||
DROP FUNCTION foorescan(int);
|
DROP FUNCTION foorescan(int);
|
||||||
DROP TABLE foorescan;
|
DROP TABLE foorescan;
|
||||||
DROP TABLE barrescan;
|
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
|
RETURNS hobbies_r.person%TYPE
|
||||||
AS 'select person from hobbies_r where name = $1'
|
AS 'select person from hobbies_r where name = $1'
|
||||||
LANGUAGE 'sql';
|
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.name%TYPE converted to text
|
||||||
|
NOTICE: type reference hobbies_r.person%TYPE converted to text
|
||||||
CREATE FUNCTION equipment(hobbies_r)
|
CREATE FUNCTION equipment(hobbies_r)
|
||||||
RETURNS setof equipment_r
|
RETURNS setof equipment_r
|
||||||
AS 'select * from equipment_r where hobby = $1.name'
|
AS 'select * from equipment_r where hobby = $1.name'
|
||||||
|
@ -199,3 +199,65 @@ DROP FUNCTION foorescan(int,int);
|
|||||||
DROP FUNCTION foorescan(int);
|
DROP FUNCTION foorescan(int);
|
||||||
DROP TABLE foorescan;
|
DROP TABLE foorescan;
|
||||||
DROP TABLE barrescan;
|
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;
|
||||||
|
Reference in New Issue
Block a user