First phase of OUT-parameters project. We can now define and use SQL
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 31 Mar 2005 22:46:33 +0000 (22:46 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 31 Mar 2005 22:46:33 +0000 (22:46 +0000)
functions with OUT parameters.  The various PLs still need work, as does
pg_dump.  Rudimentary docs and regression tests included.

23 files changed:
doc/src/sgml/ref/create_function.sgml
doc/src/sgml/xfunc.sgml
src/backend/access/common/tupdesc.c
src/backend/catalog/pg_aggregate.c
src/backend/catalog/pg_proc.c
src/backend/commands/functioncmds.c
src/backend/executor/functions.c
src/backend/executor/nodeFunctionscan.c
src/backend/optimizer/util/clauses.c
src/backend/parser/gram.y
src/backend/parser/parse_func.c
src/backend/parser/parse_relation.c
src/backend/utils/cache/lsyscache.c
src/backend/utils/fmgr/fmgr.c
src/backend/utils/fmgr/funcapi.c
src/include/catalog/pg_proc.h
src/include/executor/functions.h
src/include/fmgr.h
src/include/funcapi.h
src/include/utils/lsyscache.h
src/test/regress/expected/rangefuncs.out
src/test/regress/output/create_function_2.source
src/test/regress/sql/rangefuncs.sql

index 0991e96a54a0a3847efb114f06c442c121842877..768a42846b6175e3c480d23296d643e316cefe3b 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.64 2005/01/04 00:39:53 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.65 2005/03/31 22:45:59 tgl Exp $
 -->
 
 <refentry id="SQL-CREATEFUNCTION">
@@ -19,8 +19,9 @@ $PostgreSQL: pgsql/doc/src/sgml/ref/create_function.sgml,v 1.64 2005/01/04 00:39
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
-    RETURNS <replaceable class="parameter">rettype</replaceable>
+CREATE [ OR REPLACE ] FUNCTION
+    <replaceable class="parameter">name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] )
+    [ RETURNS <replaceable class="parameter">rettype</replaceable> ]
   { LANGUAGE <replaceable class="parameter">langname</replaceable>
     | IMMUTABLE | STABLE | VOLATILE
     | CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT | STRICT
@@ -57,7 +58,9 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
    tried, you would actually be creating a new, distinct function).
    Also, <command>CREATE OR REPLACE FUNCTION</command> will not let
    you change the return type of an existing function.  To do that,
-   you must drop and recreate the function.
+   you must drop and recreate the function.  (When using <literal>OUT</>
+   parameters, that means you can't change the names or types of any
+   <literal>OUT</> parameters except by dropping the function.)
   </para>
 
   <para>
@@ -88,6 +91,17 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><replaceable class="parameter">argmode</replaceable></term>
+
+     <listitem>
+      <para>
+       The mode of an argument: either <literal>IN</>, <literal>OUT</>,
+       or <literal>INOUT</>.  If omitted, the default is <literal>IN</>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><replaceable class="parameter">argname</replaceable></term>
 
@@ -95,7 +109,10 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
       <para>
        The name of an argument. Some languages (currently only PL/pgSQL) let
        you use the name in the function body.  For other languages the
-       argument name is just extra documentation.
+       name of an input argument is just extra documentation.  But the name
+       of an output argument is significant, since it defines the column
+       name in the result row type.  (If you omit the name for an output
+       argument, the system will choose a default column name.)
       </para>
      </listitem>
     </varlistentry>
@@ -137,6 +154,13 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
        Depending on the implementation language it may also be allowed
        to specify <quote>pseudotypes</> such as <type>cstring</>.
       </para>
+      <para>
+       When there are <literal>OUT</> or <literal>INOUT</> parameters,
+       the <literal>RETURNS</> clause may be omitted.  If present, it
+       must agree with the result type implied by the output parameters:
+       <literal>RECORD</> if there are multiple output parameters, or
+       the same type as the single output parameter.
+      </para>
       <para>
        The <literal>SETOF</literal>
        modifier indicates that the function will return a set of
@@ -361,6 +385,16 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
     names).
    </para>
 
+   <para>
+    Two functions are considered the same if they have the same names and
+    <emphasis>input</> argument types, ignoring any <literal>OUT</>
+    parameters.  Thus for example these declarations conflict:
+<programlisting>
+CREATE FUNCTION foo(int) ...
+CREATE FUNCTION foo(int, out text) ...
+</programlisting>
+   </para>
+
    <para>
     When repeated <command>CREATE FUNCTION</command> calls refer to
     the same object file, the file is only loaded once.  To unload and
@@ -393,7 +427,7 @@ CREATE [ OR REPLACE ] FUNCTION <replaceable class="parameter">name</replaceable>
   <title>Examples</title>
 
   <para>
-   Here is a trivial example to help you get started.  For more
+   Here are some trivial examples to help you get started.  For more
    information and examples, see <xref linkend="xfunc">.
 <programlisting>
 CREATE FUNCTION add(integer, integer) RETURNS integer
@@ -407,13 +441,34 @@ CREATE FUNCTION add(integer, integer) RETURNS integer
   <para>
    Increment an integer, making use of an argument name, in
    <application>PL/pgSQL</application>:
-
 <programlisting>
 CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$
         BEGIN
                 RETURN i + 1;
         END;
 $$ LANGUAGE plpgsql;
+</programlisting>
+  </para>
+
+  <para>
+   Return a record containing multiple output parameters:
+<programlisting>
+CREATE FUNCTION dup(in int, out f1 int, out f2 text)
+    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
+    LANGUAGE SQL;
+
+SELECT * FROM dup(42);
+</programlisting>
+   You can do the same thing more verbosely with an explicitly named
+   composite type:
+<programlisting>
+CREATE TYPE dup_result AS (f1 int, f2 text);
+
+CREATE FUNCTION dup(int) RETURNS dup_result
+    AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$
+    LANGUAGE SQL;
+
+SELECT * FROM dup(42);
 </programlisting>
   </para>
  </refsect1>
@@ -428,6 +483,13 @@ $$ LANGUAGE plpgsql;
    not fully compatible.  The attributes are not portable, neither are the
    different available languages.
   </para>
+
+  <para>
+   For compatibility with some other database systems,
+   <replaceable class="parameter">argmode</replaceable> can be written
+   either before or after <replaceable class="parameter">argname</replaceable>.
+   But only the first way is standard-compliant.
+  </para>
  </refsect1>
 
 
index 83af1f93f70e7500a75c7b7fc209e7581d54d0e0..079773d0d4667bd4323a10d72836f437d9ff9078 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.101 2005/03/16 21:38:04 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.102 2005/03/31 22:46:02 tgl Exp $
 -->
 
  <sect1 id="xfunc">
@@ -172,7 +172,7 @@ INSERT INTO $1 VALUES (42);
 </programlisting>
    </para>
 
-   <sect2>
+   <sect2 id="xfunc-sql-base-functions">
     <title><acronym>SQL</acronym> Functions on Base Types</title>
 
     <para>
@@ -484,7 +484,7 @@ SELECT emp.name, emp.double_salary FROM emp;
     </tip>
 
     <para>
-     Another way to use a function returning a row result is to pass the
+     Another way to use a function returning a composite type is to pass the
      result to another function that accepts the correct row type as input:
 
 <screen>
@@ -501,8 +501,89 @@ SELECT getname(new_emp());
     </para>     
 
     <para>
-     Another way to use a function that returns a composite type is to
-     call it as a table function, as described below.
+     Still another way to use a function that returns a composite type is to
+     call it as a table function, as described in <xref
+     linkend="xfunc-sql-table-functions">.
+    </para>
+   </sect2>
+
+   <sect2 id="xfunc-output-parameters">
+    <title>Functions with Output Parameters</title>
+
+   <indexterm>
+    <primary>function</primary>
+    <secondary>output parameter</secondary>
+   </indexterm>
+
+    <para>
+     An alternative way of describing a function's results is to define it
+     with <firstterm>output parameters</>, as in this example:
+
+<screen>
+CREATE FUNCTION add_em (IN x int, IN y int, OUT sum int)
+AS 'SELECT $1 + $2'
+LANGUAGE SQL;
+
+SELECT add_em(3,7);
+ add_em
+--------
+     10
+(1 row)
+</screen>
+
+     This is not essentially different from the version of <literal>add_em</>
+     shown in <xref linkend="xfunc-sql-base-functions">.  The real value of
+     output parameters is that they provide a convenient way of defining
+     functions that return several columns.  For example,
+
+<screen>
+CREATE FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int)
+AS 'SELECT $1 + $2, $1 * $2'
+LANGUAGE SQL;
+
+ SELECT * FROM sum_n_product(11,42);
+ sum | product
+-----+---------
+  53 |     462
+(1 row)
+</screen>
+
+     What has essentially happened here is that we have created an anonymous
+     composite type for the result of the function.  The above example has
+     the same end result as
+
+<screen>
+CREATE TYPE sum_prod AS (sum int, product int);
+
+CREATE FUNCTION sum_n_product (int, int) RETURNS sum_prod
+AS 'SELECT $1 + $2, $1 * $2'
+LANGUAGE SQL;
+</screen>
+
+     but not having to bother with the separate composite type definition
+     is often handy.
+    </para>
+
+    <para>
+     Notice that output parameters are not included in the calling argument
+     list when invoking such a function from SQL.  This is because
+     <productname>PostgreSQL</productname> considers only the input
+     parameters to define the function's calling signature.  That means
+     also that only the input parameters matter when referencing the function
+     for purposes such as dropping it.  We could drop the above function
+     with either of
+
+<screen>
+DROP FUNCTION sum_n_product (x int, y int, OUT sum int, OUT product int);
+DROP FUNCTION sum_n_product (int, int);
+</screen>
+    </para>
+
+    <para>
+     Parameters can be marked as <literal>IN</> (the default),
+     <literal>OUT</>, or <literal>INOUT</>.  An <literal>INOUT</>
+     parameter serves as both an input parameter (part of the calling
+     argument list) and an output parameter (part of the result record type).
     </para>
    </sect2>
 
@@ -692,6 +773,21 @@ CREATE FUNCTION invalid_func() RETURNS anyelement AS $$
 $$ LANGUAGE SQL;
 ERROR:  cannot determine result data type
 DETAIL:  A function returning "anyarray" or "anyelement" must have at least one argument of either type.
+</screen>
+    </para>
+
+    <para>
+     Polymorphism can be used with functions that have output arguments.
+     For example:
+<screen>
+CREATE FUNCTION dup (f1 anyelement, OUT f2 anyelement, OUT f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+
+SELECT * FROM dup(22);
+ f2 |   f3
+----+---------
+ 22 | {22,22}
+(1 row)
 </screen>
     </para>
    </sect2>
@@ -962,7 +1058,7 @@ CREATE FUNCTION square_root(double precision) RETURNS double precision
   <sect1 id="xfunc-c">
    <title>C-Language Functions</title>
 
-   <indexterm zone="xfunc-sql">
+   <indexterm zone="xfunc-c">
     <primary>function</primary>
     <secondary>user-defined</secondary>
     <tertiary>in C</tertiary>
index 0d1d4022106ab07b569fcbfca3cf1ead765934d7..fac8551dfd65a5a8d77706e6878636ec0749744e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.109 2005/03/07 04:42:16 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/access/common/tupdesc.c,v 1.110 2005/03/31 22:46:04 tgl Exp $
  *
  * NOTES
  *   some of the executor utility code such as "ExecTypeFromTL" should be
 
 #include "postgres.h"
 
-#include "funcapi.h"
 #include "access/heapam.h"
-#include "catalog/namespace.h"
 #include "catalog/pg_type.h"
-#include "nodes/parsenodes.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
-#include "utils/lsyscache.h"
 #include "utils/syscache.h"
-#include "utils/typcache.h"
 
 
 /*
@@ -548,122 +543,3 @@ BuildDescForRelation(List *schema)
 
    return desc;
 }
-
-
-/*
- * RelationNameGetTupleDesc
- *
- * Given a (possibly qualified) relation name, build a TupleDesc.
- */
-TupleDesc
-RelationNameGetTupleDesc(const char *relname)
-{
-   RangeVar   *relvar;
-   Relation    rel;
-   TupleDesc   tupdesc;
-   List       *relname_list;
-
-   /* Open relation and copy the tuple description */
-   relname_list = stringToQualifiedNameList(relname, "RelationNameGetTupleDesc");
-   relvar = makeRangeVarFromNameList(relname_list);
-   rel = relation_openrv(relvar, AccessShareLock);
-   tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
-   relation_close(rel, AccessShareLock);
-
-   return tupdesc;
-}
-
-/*
- * TypeGetTupleDesc
- *
- * Given a type Oid, build a TupleDesc.
- *
- * If the type is composite, *and* a colaliases List is provided, *and*
- * the List is of natts length, use the aliases instead of the relation
- * attnames.  (NB: this usage is deprecated since it may result in
- * creation of unnecessary transient record types.)
- *
- * If the type is a base type, a single item alias List is required.
- */
-TupleDesc
-TypeGetTupleDesc(Oid typeoid, List *colaliases)
-{
-   TypeFuncClass functypclass = get_type_func_class(typeoid);
-   TupleDesc   tupdesc = NULL;
-
-   /*
-    * Build a suitable tupledesc representing the output rows
-    */
-   if (functypclass == TYPEFUNC_COMPOSITE)
-   {
-       /* Composite data type, e.g. a table's row type */
-       tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(typeoid, -1));
-
-       if (colaliases != NIL)
-       {
-           int         natts = tupdesc->natts;
-           int         varattno;
-
-           /* does the list length match the number of attributes? */
-           if (list_length(colaliases) != natts)
-               ereport(ERROR,
-                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                        errmsg("number of aliases does not match number of columns")));
-
-           /* OK, use the aliases instead */
-           for (varattno = 0; varattno < natts; varattno++)
-           {
-               char       *label = strVal(list_nth(colaliases, varattno));
-
-               if (label != NULL)
-                   namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
-           }
-
-           /* The tuple type is now an anonymous record type */
-           tupdesc->tdtypeid = RECORDOID;
-           tupdesc->tdtypmod = -1;
-       }
-   }
-   else if (functypclass == TYPEFUNC_SCALAR)
-   {
-       /* Base data type, i.e. scalar */
-       char       *attname;
-
-       /* the alias list is required for base types */
-       if (colaliases == NIL)
-           ereport(ERROR,
-                   (errcode(ERRCODE_DATATYPE_MISMATCH),
-                    errmsg("no column alias was provided")));
-
-       /* the alias list length must be 1 */
-       if (list_length(colaliases) != 1)
-           ereport(ERROR,
-                   (errcode(ERRCODE_DATATYPE_MISMATCH),
-                    errmsg("number of aliases does not match number of columns")));
-
-       /* OK, get the column alias */
-       attname = strVal(linitial(colaliases));
-
-       tupdesc = CreateTemplateTupleDesc(1, false);
-       TupleDescInitEntry(tupdesc,
-                          (AttrNumber) 1,
-                          attname,
-                          typeoid,
-                          -1,
-                          0);
-   }
-   else if (functypclass == TYPEFUNC_RECORD)
-   {
-       /* XXX can't support this because typmod wasn't passed in ... */
-       ereport(ERROR,
-               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("could not determine row description for function returning record")));
-   }
-   else
-   {
-       /* crummy error message, but parser should have caught this */
-       elog(ERROR, "function in FROM has unsupported return type");
-   }
-
-   return tupdesc;
-}
index 246c3a0188733c7288a5f8a240955506495392bc..4428bc7ecba961a16ea01be44a8b429f0fb7b7db 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.71 2005/03/29 03:01:30 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/catalog/pg_aggregate.c,v 1.72 2005/03/31 22:46:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -180,7 +180,7 @@ AggregateCreate(const char *aggName,
                              false,    /* doesn't return a set */
                              finaltype,        /* returnType */
                              INTERNALlanguageId,       /* languageObjectId */
-                             0,
+                             InvalidOid,       /* no validator */
                              "aggregate_dummy",        /* placeholder proc */
                              "-",      /* probin */
                              true,     /* isAgg */
@@ -189,9 +189,10 @@ AggregateCreate(const char *aggName,
                              false,    /* isStrict (not needed for agg) */
                              PROVOLATILE_IMMUTABLE,    /* volatility (not
                                                         * needed for agg) */
-                             1,    /* parameterCount */
-                             fnArgs,   /* parameterTypes */
-                             NULL);    /* parameterNames */
+                             buildoidvector(fnArgs, 1),    /* paramTypes */
+                             PointerGetDatum(NULL),    /* allParamTypes */
+                             PointerGetDatum(NULL),    /* parameterModes */
+                             PointerGetDatum(NULL));   /* parameterNames */
 
    /*
     * Okay to create the pg_aggregate entry.
index a991ce901c115e3a9d305cbcd9576a56d6a4c717..b56eb54200bb66ad4d3ac8b19ff28956c829cd7d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.125 2005/03/29 19:44:23 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/catalog/pg_proc.c,v 1.126 2005/03/31 22:46:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -21,6 +21,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "executor/functions.h"
+#include "funcapi.h"
 #include "miscadmin.h"
 #include "mb/pg_wchar.h"
 #include "parser/parse_type.h"
@@ -40,8 +41,6 @@ Datum     fmgr_internal_validator(PG_FUNCTION_ARGS);
 Datum      fmgr_c_validator(PG_FUNCTION_ARGS);
 Datum      fmgr_sql_validator(PG_FUNCTION_ARGS);
 
-static Datum create_parameternames_array(int parameterCount,
-                           const char *parameterNames[]);
 static void sql_function_parse_error_callback(void *arg);
 static int match_prosrc_to_query(const char *prosrc, const char *queryText,
                      int cursorpos);
@@ -51,6 +50,10 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
 
 /* ----------------------------------------------------------------
  *     ProcedureCreate
+ *
+ * Note: allParameterTypes, parameterModes, parameterNames are either arrays
+ * of the proper types or NULL.  We declare them Datum, not "ArrayType *",
+ * to avoid importing array.h into pg_proc.h.
  * ----------------------------------------------------------------
  */
 Oid
@@ -67,26 +70,29 @@ ProcedureCreate(const char *procedureName,
                bool security_definer,
                bool isStrict,
                char volatility,
-               int parameterCount,
-               const Oid *parameterTypes,
-               const char *parameterNames[])
+               oidvector *parameterTypes,
+               Datum allParameterTypes,
+               Datum parameterModes,
+               Datum parameterNames)
 {
-   int         i;
+   Oid         retval;
+   int         parameterCount;
+   int         allParamCount;
+   Oid        *allParams;
+   bool        genericInParam = false;
    Relation    rel;
    HeapTuple   tup;
    HeapTuple   oldtup;
    char        nulls[Natts_pg_proc];
    Datum       values[Natts_pg_proc];
    char        replaces[Natts_pg_proc];
-   oidvector  *proargtypes;
-   Datum       namesarray;
    Oid         relid;
    NameData    procname;
    TupleDesc   tupDesc;
-   Oid         retval;
    bool        is_update;
    ObjectAddress myself,
                referenced;
+   int         i;
 
    /*
     * sanity checks
@@ -94,55 +100,88 @@ ProcedureCreate(const char *procedureName,
    Assert(PointerIsValid(prosrc));
    Assert(PointerIsValid(probin));
 
+   parameterCount = parameterTypes->dim1;
    if (parameterCount < 0 || parameterCount > FUNC_MAX_ARGS)
        ereport(ERROR,
                (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
                 errmsg("functions cannot have more than %d arguments",
                        FUNC_MAX_ARGS)));
+   /* note: the above is correct, we do NOT count output arguments */
+
+   if (allParameterTypes != PointerGetDatum(NULL))
+   {
+       /*
+        * We expect the array to be a 1-D OID array; verify that. We
+        * don't need to use deconstruct_array() since the array data is
+        * just going to look like a C array of OID values.
+        */
+       allParamCount = ARR_DIMS(DatumGetPointer(allParameterTypes))[0];
+       if (ARR_NDIM(DatumGetPointer(allParameterTypes)) != 1 ||
+           allParamCount <= 0 ||
+           ARR_ELEMTYPE(DatumGetPointer(allParameterTypes)) != OIDOID)
+           elog(ERROR, "allParameterTypes is not a 1-D Oid array");
+       allParams = (Oid *) ARR_DATA_PTR(DatumGetPointer(allParameterTypes));
+       Assert(allParamCount >= parameterCount);
+       /* we assume caller got the contents right */
+   }
+   else
+   {
+       allParamCount = parameterCount;
+       allParams = parameterTypes->values;
+   }
 
    /*
     * Do not allow return type ANYARRAY or ANYELEMENT unless at least one
-    * argument is also ANYARRAY or ANYELEMENT
+    * input argument is also ANYARRAY or ANYELEMENT
     */
-   if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID)
+   for (i = 0; i < parameterCount; i++)
    {
-       bool        genericParam = false;
+       if (parameterTypes->values[i] == ANYARRAYOID ||
+           parameterTypes->values[i] == ANYELEMENTOID)
+       {
+           genericInParam = true;
+           break;
+       }
+   }
 
-       for (i = 0; i < parameterCount; i++)
+   if (!genericInParam)
+   {
+       bool    genericOutParam = false;
+
+       if (allParameterTypes != PointerGetDatum(NULL))
        {
-           if (parameterTypes[i] == ANYARRAYOID ||
-               parameterTypes[i] == ANYELEMENTOID)
+           for (i = 0; i < allParamCount; i++)
            {
-               genericParam = true;
-               break;
+               if (allParams[i] == ANYARRAYOID ||
+                   allParams[i] == ANYELEMENTOID)
+               {
+                   genericOutParam = true;
+                   break;
+               }
            }
        }
 
-       if (!genericParam)
+       if (returnType == ANYARRAYOID || returnType == ANYELEMENTOID ||
+           genericOutParam)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("cannot determine result data type"),
                     errdetail("A function returning \"anyarray\" or \"anyelement\" must have at least one argument of either type.")));
    }
 
-   /* Convert param types to oidvector */
-   /* (Probably we should make caller pass it this way to start with) */
-   proargtypes = buildoidvector(parameterTypes, parameterCount);
-
-   /* Process param names, if given */
-   namesarray = create_parameternames_array(parameterCount, parameterNames);
-
    /*
     * don't allow functions of complex types that have the same name as
     * existing attributes of the type
     */
-   if (parameterCount == 1 && OidIsValid(parameterTypes[0]) &&
-       (relid = typeidTypeRelid(parameterTypes[0])) != InvalidOid &&
+   if (parameterCount == 1 &&
+       OidIsValid(parameterTypes->values[0]) &&
+       (relid = typeidTypeRelid(parameterTypes->values[0])) != InvalidOid &&
        get_attnum(relid, procedureName) != InvalidAttrNumber)
        ereport(ERROR,
                (errcode(ERRCODE_DUPLICATE_COLUMN),
                 errmsg("\"%s\" is already an attribute of type %s",
-                       procedureName, format_type_be(parameterTypes[0]))));
+                       procedureName,
+                       format_type_be(parameterTypes->values[0]))));
 
    /*
     * All seems OK; prepare the data to be inserted into pg_proc.
@@ -167,12 +206,17 @@ ProcedureCreate(const char *procedureName,
    values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility);
    values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
    values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
-   values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(proargtypes);
-   /* XXX for now, just null out the new columns */
-   nulls[Anum_pg_proc_proallargtypes - 1] = 'n';
-   nulls[Anum_pg_proc_proargmodes - 1] = 'n';
-   if (namesarray != PointerGetDatum(NULL))
-       values[Anum_pg_proc_proargnames - 1] = namesarray;
+   values[Anum_pg_proc_proargtypes - 1] = PointerGetDatum(parameterTypes);
+   if (allParameterTypes != PointerGetDatum(NULL))
+       values[Anum_pg_proc_proallargtypes - 1] = allParameterTypes;
+   else
+       nulls[Anum_pg_proc_proallargtypes - 1] = 'n';
+   if (parameterModes != PointerGetDatum(NULL))
+       values[Anum_pg_proc_proargmodes - 1] = parameterModes;
+   else
+       nulls[Anum_pg_proc_proargmodes - 1] = 'n';
+   if (parameterNames != PointerGetDatum(NULL))
+       values[Anum_pg_proc_proargnames - 1] = parameterNames;
    else
        nulls[Anum_pg_proc_proargnames - 1] = 'n';
    values[Anum_pg_proc_prosrc - 1] = DirectFunctionCall1(textin,
@@ -188,7 +232,7 @@ ProcedureCreate(const char *procedureName,
    /* Check for pre-existing definition */
    oldtup = SearchSysCache(PROCNAMEARGSNSP,
                            PointerGetDatum(procedureName),
-                           PointerGetDatum(proargtypes),
+                           PointerGetDatum(parameterTypes),
                            ObjectIdGetDatum(procNamespace),
                            0);
 
@@ -214,9 +258,33 @@ ProcedureCreate(const char *procedureName,
            returnsSet != oldproc->proretset)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-               errmsg("cannot change return type of existing function"),
+                    errmsg("cannot change return type of existing function"),
                     errhint("Use DROP FUNCTION first.")));
 
+       /*
+        * If it returns RECORD, check for possible change of record type
+        * implied by OUT parameters
+        */
+       if (returnType == RECORDOID)
+       {
+           TupleDesc   olddesc;
+           TupleDesc   newdesc;
+
+           olddesc = build_function_result_tupdesc_t(oldtup);
+           newdesc = build_function_result_tupdesc_d(allParameterTypes,
+                                                     parameterModes,
+                                                     parameterNames);
+           if (olddesc == NULL && newdesc == NULL)
+               /* ok, both are runtime-defined RECORDs */ ;
+           else if (olddesc == NULL || newdesc == NULL ||
+                    !equalTupleDescs(olddesc, newdesc))
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+                    errmsg("cannot change return type of existing function"),
+                    errdetail("Row type defined by OUT parameters is different."),
+                    errhint("Use DROP FUNCTION first.")));
+       }
+
        /* Can't change aggregate status, either */
        if (oldproc->proisagg != isAgg)
        {
@@ -285,11 +353,11 @@ ProcedureCreate(const char *procedureName,
    referenced.objectSubId = 0;
    recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
-   /* dependency on input types */
-   for (i = 0; i < parameterCount; i++)
+   /* dependency on parameter types */
+   for (i = 0; i < allParamCount; i++)
    {
        referenced.classId = RelOid_pg_type;
-       referenced.objectId = parameterTypes[i];
+       referenced.objectId = allParams[i];
        referenced.objectSubId = 0;
        recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }
@@ -310,42 +378,6 @@ ProcedureCreate(const char *procedureName,
 }
 
 
-/*
- * create_parameternames_array - build proargnames value from an array
- * of C strings.  Returns a NULL pointer if no names provided.
- */
-static Datum
-create_parameternames_array(int parameterCount, const char *parameterNames[])
-{
-   Datum       elems[FUNC_MAX_ARGS];
-   bool        found = false;
-   ArrayType  *names;
-   int         i;
-
-   if (!parameterNames)
-       return PointerGetDatum(NULL);
-
-   for (i = 0; i < parameterCount; i++)
-   {
-       const char *s = parameterNames[i];
-
-       if (s && *s)
-           found = true;
-       else
-           s = "";
-
-       elems[i] = DirectFunctionCall1(textin, CStringGetDatum(s));
-   }
-
-   if (!found)
-       return PointerGetDatum(NULL);
-
-   names = construct_array(elems, parameterCount, TEXTOID, -1, false, 'i');
-
-   return PointerGetDatum(names);
-}
-
-
 
 /*
  * Validator for internal functions
@@ -461,7 +493,6 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
    Datum       tmp;
    char       *prosrc;
    ErrorContextCallback sqlerrcontext;
-   char        functyptype;
    bool        haspolyarg;
    int         i;
 
@@ -472,11 +503,9 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
        elog(ERROR, "cache lookup failed for function %u", funcoid);
    proc = (Form_pg_proc) GETSTRUCT(tuple);
 
-   functyptype = get_typtype(proc->prorettype);
-
    /* Disallow pseudotype result */
    /* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */
-   if (functyptype == 'p' &&
+   if (get_typtype(proc->prorettype) == 'p' &&
        proc->prorettype != RECORDOID &&
        proc->prorettype != VOIDOID &&
        proc->prorettype != ANYARRAYOID &&
@@ -535,7 +564,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
            querytree_list = pg_parse_and_rewrite(prosrc,
                                                  proc->proargtypes.values,
                                                  proc->pronargs);
-           (void) check_sql_fn_retval(proc->prorettype, functyptype,
+           (void) check_sql_fn_retval(funcoid, proc->prorettype,
                                       querytree_list, NULL);
        }
        else
index c2c521bbfeab77ddc2e1ffbbbcc9bc240763c4e1..5776738045b33c7ba0106696c967f89b4cd0620d 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.58 2005/03/29 17:58:49 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/functioncmds.c,v 1.59 2005/03/31 22:46:07 tgl Exp $
  *
  * DESCRIPTION
  *   These routines take the parse tree and pick out the
@@ -55,7 +55,7 @@
 
 
 /*
- *  Examine the "returns" clause returnType of the CREATE FUNCTION statement
+ *  Examine the RETURNS clause of the CREATE FUNCTION statement
  *  and return information about it as *prorettype_p and *returnsSet.
  *
  * This is more complex than the average typename lookup because we want to
@@ -131,38 +131,44 @@ compute_return_type(TypeName *returnType, Oid languageOid,
 
 /*
  * Interpret the parameter list of the CREATE FUNCTION statement.
+ *
+ * Results are stored into output parameters.  parameterTypes must always
+ * be created, but the other arrays are set to NULL if not needed.
+ * requiredResultType is set to InvalidOid if there are no OUT parameters,
+ * else it is set to the OID of the implied result type.
  */
-static int
-examine_parameter_list(List *parameter, Oid languageOid,
-                      Oid *parameterTypes, const char *parameterNames[])
+static void
+examine_parameter_list(List *parameters, Oid languageOid,
+                      oidvector **parameterTypes,
+                      ArrayType **allParameterTypes,
+                      ArrayType **parameterModes,
+                      ArrayType **parameterNames,
+                      Oid *requiredResultType)
 {
-   int         parameterCount = 0;
+   int         parameterCount = list_length(parameters);
+   Oid        *inTypes;
+   int         inCount = 0;
+   Datum      *allTypes;
+   Datum      *paramModes;
+   Datum      *paramNames;
+   int         outCount = 0;
+   bool        have_names = false;
    ListCell   *x;
+   int         i;
 
-   MemSet(parameterTypes, 0, FUNC_MAX_ARGS * sizeof(Oid));
-   MemSet(parameterNames, 0, FUNC_MAX_ARGS * sizeof(char *));
+   inTypes = (Oid *) palloc(parameterCount * sizeof(Oid));
+   allTypes = (Datum *) palloc(parameterCount * sizeof(Datum));
+   paramModes = (Datum *) palloc(parameterCount * sizeof(Datum));
+   paramNames = (Datum *) palloc0(parameterCount * sizeof(Datum));
 
-   foreach(x, parameter)
+   /* Scan the list and extract data into work arrays */
+   i = 0;
+   foreach(x, parameters)
    {
        FunctionParameter *fp = (FunctionParameter *) lfirst(x);
        TypeName   *t = fp->argType;
        Oid         toid;
 
-       if (parameterCount >= FUNC_MAX_ARGS)
-           ereport(ERROR,
-                   (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
-                  errmsg("functions cannot have more than %d arguments",
-                         FUNC_MAX_ARGS)));
-
-       if (fp->mode == FUNC_PARAM_OUT)
-           ereport(ERROR,
-                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                    errmsg("CREATE FUNCTION / OUT parameters are not implemented")));
-       if (fp->mode == FUNC_PARAM_INOUT)
-           ereport(ERROR,
-                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                    errmsg("CREATE FUNCTION / INOUT parameters are not implemented")));
-
        toid = LookupTypeName(t);
        if (OidIsValid(toid))
        {
@@ -194,16 +200,66 @@ examine_parameter_list(List *parameter, Oid languageOid,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("functions cannot accept set arguments")));
 
-       parameterTypes[parameterCount] = toid;
+       if (fp->mode != FUNC_PARAM_OUT)
+           inTypes[inCount++] = toid;
+
+       if (fp->mode != FUNC_PARAM_IN)
+       {
+           if (outCount == 0)  /* save first OUT param's type */
+               *requiredResultType = toid;
+           outCount++;
+       }
+
+       allTypes[i] = ObjectIdGetDatum(toid);
 
-       parameterNames[parameterCount] = fp->name;
+       paramModes[i] = CharGetDatum(fp->mode);
 
-       parameterCount++;
+       if (fp->name && fp->name[0])
+       {
+           paramNames[i] = DirectFunctionCall1(textin,
+                                               CStringGetDatum(fp->name));
+           have_names = true;
+       }
+
+       i++;
    }
 
-   return parameterCount;
+   /* Now construct the proper outputs as needed */
+   *parameterTypes = buildoidvector(inTypes, inCount);
+
+   if (outCount > 0)
+   {
+       *allParameterTypes = construct_array(allTypes, parameterCount, OIDOID,
+                                            sizeof(Oid), true, 'i');
+       *parameterModes = construct_array(paramModes, parameterCount, CHAROID,
+                                         1, true, 'c');
+       if (outCount > 1)
+           *requiredResultType = RECORDOID;
+       /* otherwise we set requiredResultType correctly above */
+   }
+   else
+   {
+       *allParameterTypes = NULL;
+       *parameterModes = NULL;
+       *requiredResultType = InvalidOid;
+   }
+
+   if (have_names)
+   {
+       for (i = 0; i < parameterCount; i++)
+       {
+           if (paramNames[i] == PointerGetDatum(NULL))
+               paramNames[i] = DirectFunctionCall1(textin,
+                                                   CStringGetDatum(""));
+       }
+       *parameterNames = construct_array(paramNames, parameterCount, TEXTOID,
+                                         -1, false, 'i');
+   }
+   else
+       *parameterNames = NULL;
 }
 
+
 /*
  * Recognize one of the options that can be passed to both CREATE
  * FUNCTION and ALTER FUNCTION and return it via one of the out
@@ -321,6 +377,7 @@ compute_attributes_sql_style(List *options,
                 defel->defname);
    }
 
+   /* process required items */
    if (as_item)
        *as = (List *) as_item->arg;
    else
@@ -335,6 +392,7 @@ compute_attributes_sql_style(List *options,
                (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                 errmsg("no language specified")));
 
+   /* process optional items */
    if (volatility_item)
        *volatility_p = interpret_func_volatility(volatility_item);
    if (strict_item)
@@ -445,9 +503,11 @@ CreateFunction(CreateFunctionStmt *stmt)
    char       *funcname;
    Oid         namespaceId;
    AclResult   aclresult;
-   int         parameterCount;
-   Oid         parameterTypes[FUNC_MAX_ARGS];
-   const char *parameterNames[FUNC_MAX_ARGS];
+   oidvector  *parameterTypes;
+   ArrayType  *allParameterTypes;
+   ArrayType  *parameterModes;
+   ArrayType  *parameterNames;
+   Oid         requiredResultType;
    bool        isStrict,
                security;
    char        volatility;
@@ -465,7 +525,7 @@ CreateFunction(CreateFunctionStmt *stmt)
        aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
                       get_namespace_name(namespaceId));
 
-   /* defaults attributes */
+   /* default attributes */
    isStrict = false;
    security = false;
    volatility = PROVOLATILE_VOLATILE;
@@ -523,11 +583,39 @@ CreateFunction(CreateFunctionStmt *stmt)
     * Convert remaining parameters of CREATE to form wanted by
     * ProcedureCreate.
     */
-   compute_return_type(stmt->returnType, languageOid,
-                       &prorettype, &returnsSet);
-
-   parameterCount = examine_parameter_list(stmt->parameters, languageOid,
-                                        parameterTypes, parameterNames);
+   examine_parameter_list(stmt->parameters, languageOid,
+                          &parameterTypes,
+                          &allParameterTypes,
+                          &parameterModes,
+                          &parameterNames,
+                          &requiredResultType);
+
+   if (stmt->returnType)
+   {
+       /* explicit RETURNS clause */
+       compute_return_type(stmt->returnType, languageOid,
+                           &prorettype, &returnsSet);
+       if (OidIsValid(requiredResultType) && prorettype != requiredResultType)
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+                    errmsg("function result type must be %s because of OUT parameters",
+                           format_type_be(requiredResultType))));
+   }
+   else if (OidIsValid(requiredResultType))
+   {
+       /* default RETURNS clause from OUT parameters */
+       prorettype = requiredResultType;
+       returnsSet = false;
+   }
+   else
+   {
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+                errmsg("function result type must be specified")));
+       /* Alternative possibility: default to RETURNS VOID */
+       prorettype = VOIDOID;
+       returnsSet = false;
+   }
 
    compute_attributes_with_style(stmt->withClause, &isStrict, &volatility);
 
@@ -572,9 +660,10 @@ CreateFunction(CreateFunctionStmt *stmt)
                    security,
                    isStrict,
                    volatility,
-                   parameterCount,
                    parameterTypes,
-                   parameterNames);
+                   PointerGetDatum(allParameterTypes),
+                   PointerGetDatum(parameterModes),
+                   PointerGetDatum(parameterNames));
 }
 
 
index 2ddc614cf7e682ecb4fe4f5fa3242e6641f429df..d2e101a2d616a007a54c859b4268f7ea50f73926 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.94 2005/03/29 00:16:59 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.95 2005/03/31 22:46:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,6 +20,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/functions.h"
+#include "funcapi.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_type.h"
@@ -277,8 +278,8 @@ init_sql_fcache(FmgrInfo *finfo)
     * form.
     */
    if (haspolyarg || fcache->returnsTuple)
-       fcache->returnsTuple = check_sql_fn_retval(rettype,
-                                                  get_typtype(rettype),
+       fcache->returnsTuple = check_sql_fn_retval(foid,
+                                                  rettype,
                                                   queryTree_list,
                                                   &fcache->junkFilter);
 
@@ -858,7 +859,7 @@ ShutdownSQLFunction(Datum arg)
  * tuple result), *junkFilter is set to NULL.
  */
 bool
-check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
+check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
                    JunkFilter **junkFilter)
 {
    Query      *parse;
@@ -866,12 +867,8 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
    List       *tlist;
    ListCell   *tlistitem;
    int         tlistlen;
-   Oid         typerelid;
+   char        fn_typtype;
    Oid         restype;
-   Relation    reln;
-   int         relnatts;       /* physical number of columns in rel */
-   int         rellogcols;     /* # of nondeleted columns in rel */
-   int         colindex;       /* physical column index */
 
    if (junkFilter)
        *junkFilter = NULL;     /* default result */
@@ -922,13 +919,10 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
     */
    tlistlen = ExecCleanTargetListLength(tlist);
 
-   typerelid = typeidTypeRelid(rettype);
+   fn_typtype = get_typtype(rettype);
 
    if (fn_typtype == 'b' || fn_typtype == 'd')
    {
-       /* Shouldn't have a typerelid */
-       Assert(typerelid == InvalidOid);
-
        /*
         * For base-type returns, the target list should have exactly one
         * entry, and its type should agree with what the user declared.
@@ -950,10 +944,13 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
                     errdetail("Actual return type is %s.",
                               format_type_be(restype))));
    }
-   else if (fn_typtype == 'c')
+   else if (fn_typtype == 'c' || rettype == RECORDOID)
    {
-       /* Must have a typerelid */
-       Assert(typerelid != InvalidOid);
+       /* Returns a rowtype */
+       TupleDesc   tupdesc;
+       int         tupnatts;       /* physical number of columns in tuple */
+       int         tuplogcols;     /* # of nondeleted columns in tuple */
+       int         colindex;       /* physical column index */
 
        /*
         * If the target list is of length 1, and the type of the varnode
@@ -969,16 +966,27 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
                return false;   /* NOT returning whole tuple */
        }
 
+       /* Is the rowtype fixed, or determined only at runtime? */
+       if (get_func_result_type(func_id, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+       {
+           /*
+            * Assume we are returning the whole tuple.
+            * Crosschecking against what the caller expects will happen at
+            * runtime.
+            */
+           if (junkFilter)
+               *junkFilter = ExecInitJunkFilter(tlist, false, NULL);
+           return true;
+       }
+       Assert(tupdesc);
+
        /*
-        * Otherwise verify that the targetlist matches the return tuple
-        * type. This part of the typechecking is a hack. We look up the
-        * relation that is the declared return type, and scan the
-        * non-deleted attributes to ensure that they match the datatypes
-        * of the non-resjunk columns.
+        * Verify that the targetlist matches the return tuple type.
+        * We scan the non-deleted attributes to ensure that they match the
+        * datatypes of the non-resjunk columns.
         */
-       reln = relation_open(typerelid, AccessShareLock);
-       relnatts = reln->rd_rel->relnatts;
-       rellogcols = 0;         /* we'll count nondeleted cols as we go */
+       tupnatts = tupdesc->natts;
+       tuplogcols = 0;         /* we'll count nondeleted cols as we go */
        colindex = 0;
 
        foreach(tlistitem, tlist)
@@ -994,15 +1002,15 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
            do
            {
                colindex++;
-               if (colindex > relnatts)
+               if (colindex > tupnatts)
                    ereport(ERROR,
                            (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                             errmsg("return type mismatch in function declared to return %s",
                                    format_type_be(rettype)),
                    errdetail("Final SELECT returns too many columns.")));
-               attr = reln->rd_att->attrs[colindex - 1];
+               attr = tupdesc->attrs[colindex - 1];
            } while (attr->attisdropped);
-           rellogcols++;
+           tuplogcols++;
 
            tletype = exprType((Node *) tle->expr);
            atttype = attr->atttypid;
@@ -1014,19 +1022,19 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
                         errdetail("Final SELECT returns %s instead of %s at column %d.",
                                   format_type_be(tletype),
                                   format_type_be(atttype),
-                                  rellogcols)));
+                                  tuplogcols)));
        }
 
        for (;;)
        {
            colindex++;
-           if (colindex > relnatts)
+           if (colindex > tupnatts)
                break;
-           if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
-               rellogcols++;
+           if (!tupdesc->attrs[colindex - 1]->attisdropped)
+               tuplogcols++;
        }
 
-       if (tlistlen != rellogcols)
+       if (tlistlen != tuplogcols)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
                     errmsg("return type mismatch in function declared to return %s",
@@ -1036,40 +1044,12 @@ check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList,
        /* Set up junk filter if needed */
        if (junkFilter)
            *junkFilter = ExecInitJunkFilterConversion(tlist,
-                                           CreateTupleDescCopy(reln->rd_att),
+                                           CreateTupleDescCopy(tupdesc),
                                            NULL);
 
-       relation_close(reln, AccessShareLock);
-
        /* Report that we are returning entire tuple result */
        return true;
    }
-   else if (rettype == RECORDOID)
-   {
-       /*
-        * If the target list is of length 1, and the type of the varnode
-        * in the target list matches the declared return type, this is
-        * okay. This can happen, for example, where the body of the
-        * function is 'SELECT func2()', where func2 has the same return
-        * type as the function that's calling it.
-        */
-       if (tlistlen == 1)
-       {
-           restype = ((TargetEntry *) linitial(tlist))->resdom->restype;
-           if (IsBinaryCoercible(restype, rettype))
-               return false;   /* NOT returning whole tuple */
-       }
-
-       /*
-        * Otherwise assume we are returning the whole tuple.
-        * Crosschecking against what the caller expects will happen at
-        * runtime.
-        */
-       if (junkFilter)
-           *junkFilter = ExecInitJunkFilter(tlist, false, NULL);
-
-       return true;
-   }
    else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
    {
        /* This should already have been caught ... */
index edf5c67635263132c1cd5105f83082d4ae66db35..4cbbb5a65ff3bf6a0be54e138926999797ecadf3 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.31 2005/03/16 21:38:07 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.32 2005/03/31 22:46:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  */
 #include "postgres.h"
 
-#include "access/heapam.h"
-#include "catalog/pg_type.h"
-#include "executor/execdebug.h"
-#include "executor/execdefs.h"
-#include "executor/execdesc.h"
 #include "executor/nodeFunctionscan.h"
+#include "funcapi.h"
 #include "parser/parsetree.h"
-#include "parser/parse_expr.h"
-#include "parser/parse_type.h"
 #include "utils/builtins.h"
-#include "utils/lsyscache.h"
-#include "utils/typcache.h"
 
 
 static TupleTableSlot *FunctionNext(FunctionScanState *node);
@@ -180,18 +172,21 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate)
     */
    rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
    Assert(rte->rtekind == RTE_FUNCTION);
-   funcrettype = exprType(rte->funcexpr);
 
    /*
     * Now determine if the function returns a simple or composite type,
     * and build an appropriate tupdesc.
     */
-   functypclass = get_type_func_class(funcrettype);
+   functypclass = get_expr_result_type(rte->funcexpr,
+                                       &funcrettype,
+                                       &tupdesc);
 
    if (functypclass == TYPEFUNC_COMPOSITE)
    {
        /* Composite data type, e.g. a table's row type */
-       tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(funcrettype, -1));
+       Assert(tupdesc);
+       /* Must copy it out of typcache for safety */
+       tupdesc = CreateTupleDescCopy(tupdesc);
    }
    else if (functypclass == TYPEFUNC_SCALAR)
    {
@@ -216,14 +211,6 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate)
        elog(ERROR, "function in FROM has unsupported return type");
    }
 
-   /*
-    * For RECORD results, make sure a typmod has been assigned.  (The
-    * function should do this for itself, but let's cover things in case
-    * it doesn't.)
-    */
-   if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0)
-       assign_record_type_typmod(tupdesc);
-
    scanstate->tupdesc = tupdesc;
    ExecSetSlotDescriptor(scanstate->ss.ss_ScanTupleSlot,
                          tupdesc, false);
index 76255de53d49a9dd0531ab98e86b3f904b5743d6..2cf4fcd663d5e6641530c93278b242027a0a1408 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.191 2005/03/29 00:17:02 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.192 2005/03/31 22:46:09 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -2319,8 +2319,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
     * probably not important, but let's be careful.)
     */
    if (polymorphic)
-       (void) check_sql_fn_retval(result_type, get_typtype(result_type),
-                                  querytree_list, NULL);
+       (void) check_sql_fn_retval(funcid, result_type, querytree_list, NULL);
 
    /*
     * Additional validity checks on the expression.  It mustn't return a
index 62497957263e6c709d52bc1e83cd68894bedbffe..6bbf4a4de1820e2daa7fa46e2e481980254d6d3e 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.485 2005/03/29 17:58:50 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.486 2005/03/31 22:46:11 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -2544,7 +2544,7 @@ def_elem:  ColLabel '=' def_arg
        ;
 
 /* Note: any simple identifier will be returned as a type name! */
-def_arg:   func_return                     { $$ = (Node *)$1; }
+def_arg:   func_type                       { $$ = (Node *)$1; }
            | qual_all_Op                   { $$ = (Node *)$1; }
            | NumericOnly                   { $$ = (Node *)$1; }
            | Sconst                        { $$ = (Node *)makeString($1); }
@@ -3282,6 +3282,18 @@ CreateFunctionStmt:
                    n->withClause = $9;
                    $$ = (Node *)n;
                }
+           | CREATE opt_or_replace FUNCTION func_name func_args
+             createfunc_opt_list opt_definition
+               {
+                   CreateFunctionStmt *n = makeNode(CreateFunctionStmt);
+                   n->replace = $2;
+                   n->funcname = $4;
+                   n->parameters = $5;
+                   n->returnType = NULL;
+                   n->options = $6;
+                   n->withClause = $7;
+                   $$ = (Node *)n;
+               }
        ;
 
 opt_or_replace:
@@ -3367,7 +3379,7 @@ param_name:   function_name
 func_return:
            func_type
                {
-                   /* We can catch over-specified arguments here if we want to,
+                   /* We can catch over-specified results here if we want to,
                     * but for now better to silently swallow typmod, etc.
                     * - thomas 2000-03-22
                     */
@@ -3424,7 +3436,6 @@ common_func_opt_item:
                {
                    $$ = makeDefElem("volatility", (Node *)makeString("volatile"));
                }
-
            | EXTERNAL SECURITY DEFINER
                {
                    $$ = makeDefElem("security", (Node *)makeInteger(TRUE));
index 07d52ef5441e0b813b87b9099e86f519f5554444..64a325f10b773e395393c33eeb971a91e923c434 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.176 2005/03/29 03:01:31 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.177 2005/03/31 22:46:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,6 +18,7 @@
 #include "catalog/catname.h"
 #include "catalog/pg_inherits.h"
 #include "catalog/pg_proc.h"
+#include "funcapi.h"
 #include "lib/stringinfo.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_agg.h"
@@ -1154,10 +1155,8 @@ make_fn_arguments(ParseState *pstate,
 static Node *
 ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg)
 {
-   Oid         argtype;
-   Oid         argrelid;
-   AttrNumber  attnum;
-   FieldSelect *fselect;
+   TupleDesc   tupdesc;
+   int         i;
 
    /*
     * Special case for whole-row Vars so that we can resolve (foo.*).bar
@@ -1180,27 +1179,31 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg)
 
    /*
     * Else do it the hard way.  Note that if the arg is of RECORD type,
-    * we will never recognize a column name, and always assume the item
-    * must be a function.
+    * and isn't resolvable as a function with OUT params, we will never
+    * be able to recognize a column name here.
     */
-   argtype = exprType(first_arg);
-   argrelid = typeidTypeRelid(argtype);
-   if (!argrelid)
-       return NULL;            /* can only happen if RECORD */
-
-   attnum = get_attnum(argrelid, funcname);
-   if (attnum == InvalidAttrNumber)
-       return NULL;            /* funcname does not match any column */
-
-   /* Success, so generate a FieldSelect expression */
-   fselect = makeNode(FieldSelect);
-   fselect->arg = (Expr *) first_arg;
-   fselect->fieldnum = attnum;
-   get_atttypetypmod(argrelid, attnum,
-                     &fselect->resulttype,
-                     &fselect->resulttypmod);
-
-   return (Node *) fselect;
+   if (get_expr_result_type(first_arg, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+       return NULL;            /* unresolvable RECORD type */
+
+   for (i = 0; i < tupdesc->natts; i++)
+   {
+       Form_pg_attribute att = tupdesc->attrs[i];
+
+       if (strcmp(funcname, NameStr(att->attname)) == 0 &&
+           !att->attisdropped)
+       {
+           /* Success, so generate a FieldSelect expression */
+           FieldSelect *fselect = makeNode(FieldSelect);
+
+           fselect->arg = (Expr *) first_arg;
+           fselect->fieldnum = i + 1;
+           fselect->resulttype = att->atttypid;
+           fselect->resulttypmod = att->atttypmod;
+           return (Node *) fselect;
+       }
+   }
+
+   return NULL;                /* funcname does not match any column */
 }
 
 /*
index 6360e402f86cce5d97759a00b2fcdc746f7cf1c6..6e391f4eb8bd67a1ea3d3a6ed4f26c42dc5bbc90 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.102 2004/12/31 22:00:27 pgsql Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.103 2005/03/31 22:46:13 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include <ctype.h>
 
 #include "access/heapam.h"
-#include "access/htup.h"
 #include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
+#include "funcapi.h"
 #include "nodes/makefuncs.h"
 #include "parser/parsetree.h"
-#include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_type.h"
-#include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+
 /* GUC parameter */
 bool       add_missing_from;
 
@@ -46,6 +45,10 @@ static void expandRelation(Oid relid, Alias *eref,
               int rtindex, int sublevels_up,
               bool include_dropped,
               List **colnames, List **colvars);
+static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
+                           int rtindex, int sublevels_up,
+                           bool include_dropped,
+                           List **colnames, List **colvars);
 static int specialAttNum(const char *attname);
 static void warnAutoRange(ParseState *pstate, RangeVar *relation);
 
@@ -965,8 +968,9 @@ addRangeTableEntryForFunction(ParseState *pstate,
                              bool inFromCl)
 {
    RangeTblEntry *rte = makeNode(RangeTblEntry);
-   Oid         funcrettype = exprType(funcexpr);
    TypeFuncClass functypclass;
+   Oid         funcrettype;
+   TupleDesc   tupdesc;
    Alias      *alias = rangefunc->alias;
    List       *coldeflist = rangefunc->coldeflist;
    Alias      *eref;
@@ -982,58 +986,37 @@ addRangeTableEntryForFunction(ParseState *pstate,
    rte->eref = eref;
 
    /*
-    * Now determine if the function returns a simple or composite type,
-    * and check/add column aliases.
+    * Now determine if the function returns a simple or composite type.
+    */
+   functypclass = get_expr_result_type(funcexpr,
+                                       &funcrettype,
+                                       &tupdesc);
+
+   /*
+    * A coldeflist is required if the function returns RECORD and hasn't
+    * got a predetermined record type, and is prohibited otherwise.
     */
    if (coldeflist != NIL)
    {
-       /*
-        * we *only* allow a coldeflist for functions returning a RECORD
-        * pseudo-type
-        */
-       if (funcrettype != RECORDOID)
+       if (functypclass != TYPEFUNC_RECORD)
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
                     errmsg("a column definition list is only allowed for functions returning \"record\"")));
    }
    else
    {
-       /*
-        * ... and a coldeflist is *required* for functions returning a
-        * RECORD pseudo-type
-        */
-       if (funcrettype == RECORDOID)
+       if (functypclass == TYPEFUNC_RECORD)
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
                     errmsg("a column definition list is required for functions returning \"record\"")));
    }
 
-   functypclass = get_type_func_class(funcrettype);
-
    if (functypclass == TYPEFUNC_COMPOSITE)
    {
        /* Composite data type, e.g. a table's row type */
-       Oid         funcrelid = typeidTypeRelid(funcrettype);
-       Relation    rel;
-
-       if (!OidIsValid(funcrelid))     /* shouldn't happen */
-           elog(ERROR, "invalid typrelid for complex type %u", funcrettype);
-
-       /*
-        * Get the rel's relcache entry.  This access ensures that we have
-        * an up-to-date relcache entry for the rel.
-        */
-       rel = relation_open(funcrelid, AccessShareLock);
-
+       Assert(tupdesc);
        /* Build the column alias list */
-       buildRelationAliases(rel->rd_att, alias, eref);
-
-       /*
-        * Drop the rel refcount, but keep the access lock till end of
-        * transaction so that the table can't be deleted or have its
-        * schema modified underneath us.
-        */
-       relation_close(rel, NoLock);
+       buildRelationAliases(tupdesc, alias, eref);
    }
    else if (functypclass == TYPEFUNC_SCALAR)
    {
@@ -1308,24 +1291,19 @@ expandRTE(List *rtable, int rtindex, int sublevels_up,
        case RTE_FUNCTION:
            {
                /* Function RTE */
-               Oid         funcrettype = exprType(rte->funcexpr);
-               TypeFuncClass functypclass = get_type_func_class(funcrettype);
+               TypeFuncClass functypclass;
+               Oid         funcrettype;
+               TupleDesc   tupdesc;
 
+               functypclass = get_expr_result_type(rte->funcexpr,
+                                                   &funcrettype,
+                                                   &tupdesc);
                if (functypclass == TYPEFUNC_COMPOSITE)
                {
-                   /*
-                    * Composite data type, i.e. a table's row type
-                    *
-                    * Same as ordinary relation RTE
-                    */
-                   Oid         funcrelid = typeidTypeRelid(funcrettype);
-
-                   if (!OidIsValid(funcrelid)) /* shouldn't happen */
-                       elog(ERROR, "invalid typrelid for complex type %u",
-                            funcrettype);
-
-                   expandRelation(funcrelid, rte->eref, rtindex, sublevels_up,
-                                  include_dropped, colnames, colvars);
+                   /* Composite data type, e.g. a table's row type */
+                   Assert(tupdesc);
+                   expandTupleDesc(tupdesc, rte->eref, rtindex, sublevels_up,
+                                   include_dropped, colnames, colvars);
                }
                else if (functypclass == TYPEFUNC_SCALAR)
                {
@@ -1467,17 +1445,30 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
               List **colnames, List **colvars)
 {
    Relation    rel;
-   int         varattno;
-   int         maxattrs;
-   int         numaliases;
 
+   /* Get the tupledesc and turn it over to expandTupleDesc */
    rel = relation_open(relid, AccessShareLock);
-   maxattrs = RelationGetNumberOfAttributes(rel);
-   numaliases = list_length(eref->colnames);
+   expandTupleDesc(rel->rd_att, eref, rtindex, sublevels_up, include_dropped,
+                   colnames, colvars);
+   relation_close(rel, AccessShareLock);
+}
+
+/*
+ * expandTupleDesc -- expandRTE subroutine
+ */
+static void
+expandTupleDesc(TupleDesc tupdesc, Alias *eref,
+               int rtindex, int sublevels_up,
+               bool include_dropped,
+               List **colnames, List **colvars)
+{
+   int         maxattrs = tupdesc->natts;
+   int         numaliases = list_length(eref->colnames);
+   int         varattno;
 
    for (varattno = 0; varattno < maxattrs; varattno++)
    {
-       Form_pg_attribute attr = rel->rd_att->attrs[varattno];
+       Form_pg_attribute attr = tupdesc->attrs[varattno];
 
        if (attr->attisdropped)
        {
@@ -1519,8 +1510,6 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
            *colvars = lappend(*colvars, varnode);
        }
    }
-
-   relation_close(rel, AccessShareLock);
 }
 
 /*
@@ -1662,33 +1651,29 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
        case RTE_FUNCTION:
            {
                /* Function RTE */
-               Oid         funcrettype = exprType(rte->funcexpr);
-               TypeFuncClass functypclass = get_type_func_class(funcrettype);
-               List       *coldeflist = rte->coldeflist;
+               TypeFuncClass functypclass;
+               Oid         funcrettype;
+               TupleDesc   tupdesc;
+
+               functypclass = get_expr_result_type(rte->funcexpr,
+                                                   &funcrettype,
+                                                   &tupdesc);
 
                if (functypclass == TYPEFUNC_COMPOSITE)
                {
-                   /*
-                    * Composite data type, i.e. a table's row type
-                    *
-                    * Same as ordinary relation RTE
-                    */
-                   Oid         funcrelid = typeidTypeRelid(funcrettype);
-                   HeapTuple   tp;
+                   /* Composite data type, e.g. a table's row type */
                    Form_pg_attribute att_tup;
 
-                   if (!OidIsValid(funcrelid)) /* shouldn't happen */
-                       elog(ERROR, "invalid typrelid for complex type %u",
-                            funcrettype);
+                   Assert(tupdesc);
+                   /* this is probably a can't-happen case */
+                   if (attnum < 1 || attnum > tupdesc->natts)
+                       ereport(ERROR,
+                               (errcode(ERRCODE_UNDEFINED_COLUMN),
+                                errmsg("column %d of relation \"%s\" does not exist",
+                                       attnum,
+                                       rte->eref->aliasname)));
 
-                   tp = SearchSysCache(ATTNUM,
-                                       ObjectIdGetDatum(funcrelid),
-                                       Int16GetDatum(attnum),
-                                       0, 0);
-                   if (!HeapTupleIsValid(tp))  /* shouldn't happen */
-                       elog(ERROR, "cache lookup failed for attribute %d of relation %u",
-                            attnum, funcrelid);
-                   att_tup = (Form_pg_attribute) GETSTRUCT(tp);
+                   att_tup = tupdesc->attrs[attnum - 1];
 
                    /*
                     * If dropped column, pretend it ain't there.  See
@@ -1699,10 +1684,9 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
                                (errcode(ERRCODE_UNDEFINED_COLUMN),
                                 errmsg("column \"%s\" of relation \"%s\" does not exist",
                                        NameStr(att_tup->attname),
-                                       get_rel_name(funcrelid))));
+                                       rte->eref->aliasname)));
                    *vartype = att_tup->atttypid;
                    *vartypmod = att_tup->atttypmod;
-                   ReleaseSysCache(tp);
                }
                else if (functypclass == TYPEFUNC_SCALAR)
                {
@@ -1712,7 +1696,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
                }
                else if (functypclass == TYPEFUNC_RECORD)
                {
-                   ColumnDef  *colDef = list_nth(coldeflist, attnum - 1);
+                   ColumnDef  *colDef = list_nth(rte->coldeflist, attnum - 1);
 
                    *vartype = typenameTypeId(colDef->typename);
                    *vartypmod = -1;
index 0b40a20b25198f4260338134fbf46edd6e231cc3..3abbf65fa4833d6ef99ece190603088a49b4fac6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.121 2005/03/29 00:17:11 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/cache/lsyscache.c,v 1.122 2005/03/31 22:46:14 tgl Exp $
  *
  * NOTES
  *   Eventually, the index information should go through here, too.
@@ -1543,42 +1543,6 @@ get_typtype(Oid typid)
        return '\0';
 }
 
-/*
- * get_type_func_class
- *
- *     Given the type OID, obtain its TYPEFUNC classification.
- *
- * This is intended to centralize a bunch of formerly ad-hoc code for
- * classifying types.  The categories used here are useful for deciding
- * how to handle functions returning the datatype.
- */
-TypeFuncClass
-get_type_func_class(Oid typid)
-{
-   switch (get_typtype(typid))
-   {
-       case 'c':
-           return TYPEFUNC_COMPOSITE;
-       case 'b':
-       case 'd':
-           return TYPEFUNC_SCALAR;
-       case 'p':
-           if (typid == RECORDOID)
-               return TYPEFUNC_RECORD;
-           /*
-            * We treat VOID and CSTRING as legitimate scalar datatypes,
-            * mostly for the convenience of the JDBC driver (which wants
-            * to be able to do "SELECT * FROM foo()" for all legitimately
-            * user-callable functions).
-            */
-           if (typid == VOIDOID || typid == CSTRINGOID)
-               return TYPEFUNC_SCALAR;
-           return TYPEFUNC_OTHER;
-   }
-   /* shouldn't get here, probably */
-   return TYPEFUNC_OTHER;
-}
-
 /*
  * get_typ_typrelid
  *
index a8bb5fc0ac5a8e5135b7e08f24581e21504af36f..0e9716de0135e06d6c93450bdb0871f70ff0442d 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.92 2005/03/29 03:01:31 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.93 2005/03/31 22:46:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -403,7 +403,7 @@ fmgr_info_other_lang(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple)
  * We want to raise an error here only if the info function returns
  * something bogus.
  *
- * This function is broken out of fmgr_info_C_lang() so that ProcedureCreate()
+ * This function is broken out of fmgr_info_C_lang so that fmgr_c_validator
  * can validate the information record for a function not yet entered into
  * pg_proc.
  */
@@ -576,8 +576,8 @@ fmgr_info_copy(FmgrInfo *dstinfo, FmgrInfo *srcinfo,
 
 
 /*
- * Specialized lookup routine for ProcedureCreate(): given the alleged name
- * of an internal function, return the OID of the function.
+ * Specialized lookup routine for fmgr_internal_validator: given the alleged
+ * name of an internal function, return the OID of the function.
  * If the name is not recognized, return InvalidOid.
  */
 Oid
@@ -1869,10 +1869,6 @@ get_fn_expr_rettype(FmgrInfo *flinfo)
 Oid
 get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
 {
-   Node       *expr;
-   List       *args;
-   Oid         argtype;
-
    /*
     * can't return anything useful if we have no FmgrInfo or if its
     * fn_expr node has not been initialized
@@ -1880,7 +1876,23 @@ get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
    if (!flinfo || !flinfo->fn_expr)
        return InvalidOid;
 
-   expr = flinfo->fn_expr;
+   return get_call_expr_argtype(flinfo->fn_expr, argnum);
+}
+
+/*
+ * Get the actual type OID of a specific function argument (counting from 0),
+ * but working from the calling expression tree instead of FmgrInfo
+ *
+ * Returns InvalidOid if information is not available
+ */
+Oid
+get_call_expr_argtype(Node *expr, int argnum)
+{
+   List       *args;
+   Oid         argtype;
+
+   if (expr == NULL)
+       return InvalidOid;
 
    if (IsA(expr, FuncExpr))
        args = ((FuncExpr *) expr)->args;
index 2c3845b7bf4cb8cf2d8c9965c29fb936cb0b190d..160847de1e069a0bee26885bc342d47ba3a6a1e4 100644 (file)
@@ -7,17 +7,37 @@
  * Copyright (c) 2002-2005, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.18 2005/01/01 05:43:08 momjian Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.19 2005/03/31 22:46:16 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "funcapi.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
+
 
 static void shutdown_MultiFuncCall(Datum arg);
+static TypeFuncClass internal_get_result_type(Oid funcid,
+                                             Node *call_expr,
+                                             ReturnSetInfo *rsinfo,
+                                             Oid *resultTypeId,
+                                             TupleDesc *resultTupleDesc);
+static bool resolve_polymorphic_tupdesc(TupleDesc tupdesc,
+                                       oidvector *declared_args,
+                                       Node *call_expr);
+static TypeFuncClass get_type_func_class(Oid typid);
+
 
 /*
  * init_MultiFuncCall
@@ -156,3 +176,624 @@ shutdown_MultiFuncCall(Datum arg)
 
    pfree(funcctx);
 }
+
+
+/*
+ * get_call_result_type
+ *     Given a function's call info record, determine the kind of datatype
+ *     it is supposed to return.  If resultTypeId isn't NULL, *resultTypeId
+ *     receives the actual datatype OID (this is mainly useful for scalar
+ *     result types).  If resultTupleDesc isn't NULL, *resultTupleDesc
+ *     receives a pointer to a TupleDesc when the result is of a composite
+ *     type, or NULL when it's a scalar result.  NB: the tupledesc should
+ *     be copied if it is to be accessed over a long period.
+ *
+ * One hard case that this handles is resolution of actual rowtypes for
+ * functions returning RECORD (from either the function's OUT parameter
+ * list, or a ReturnSetInfo context node).  TYPEFUNC_RECORD is returned
+ * only when we couldn't resolve the actual rowtype for lack of information.
+ *
+ * The other hard case that this handles is resolution of polymorphism.
+ * We will never return ANYELEMENT or ANYARRAY, either as a scalar result
+ * type or as a component of a rowtype.
+ *
+ * This function is relatively expensive --- in a function returning set,
+ * try to call it only the first time through.
+ */
+TypeFuncClass
+get_call_result_type(FunctionCallInfo fcinfo,
+                    Oid *resultTypeId,
+                    TupleDesc *resultTupleDesc)
+{
+   return internal_get_result_type(fcinfo->flinfo->fn_oid,
+                                   fcinfo->flinfo->fn_expr,
+                                   (ReturnSetInfo *) fcinfo->resultinfo,
+                                   resultTypeId,
+                                   resultTupleDesc);
+}
+
+/*
+ * get_expr_result_type
+ *     As above, but work from a calling expression node tree
+ */
+TypeFuncClass
+get_expr_result_type(Node *expr,
+                    Oid *resultTypeId,
+                    TupleDesc *resultTupleDesc)
+{
+   TypeFuncClass result;
+
+   if (expr && IsA(expr, FuncExpr))
+       result = internal_get_result_type(((FuncExpr *) expr)->funcid,
+                                         expr,
+                                         NULL,
+                                         resultTypeId,
+                                         resultTupleDesc);
+   else
+   {
+       /* handle as a generic expression; no chance to resolve RECORD */
+       Oid     typid = exprType(expr);
+
+       if (resultTypeId)
+           *resultTypeId = typid;
+       if (resultTupleDesc)
+           *resultTupleDesc = NULL;
+       result = get_type_func_class(typid);
+       if (result == TYPEFUNC_COMPOSITE && resultTupleDesc)
+           *resultTupleDesc = lookup_rowtype_tupdesc(typid, -1);
+   }
+
+   return result;
+}
+
+/*
+ * get_expr_result_type
+ *     As above, but work from a function's OID only
+ *
+ * This will not be able to resolve pure-RECORD results nor polymorphism.
+ */
+TypeFuncClass
+get_func_result_type(Oid functionId,
+                    Oid *resultTypeId,
+                    TupleDesc *resultTupleDesc)
+{
+   return internal_get_result_type(functionId,
+                                   NULL,
+                                   NULL,
+                                   resultTypeId,
+                                   resultTupleDesc);
+}
+
+/*
+ * internal_get_result_type -- workhorse code implementing all the above
+ *
+ * funcid must always be supplied.  call_expr and rsinfo can be NULL if not
+ * available.  We will return TYPEFUNC_RECORD, and store NULL into
+ * *resultTupleDesc, if we cannot deduce the complete result rowtype from
+ * the available information.
+ */
+static TypeFuncClass
+internal_get_result_type(Oid funcid,
+                        Node *call_expr,
+                        ReturnSetInfo *rsinfo,
+                        Oid *resultTypeId,
+                        TupleDesc *resultTupleDesc)
+{
+   TypeFuncClass result;
+   HeapTuple   tp;
+   Form_pg_proc procform;
+   Oid         rettype;
+   TupleDesc   tupdesc;
+
+   /* First fetch the function's pg_proc row to inspect its rettype */
+   tp = SearchSysCache(PROCOID,
+                       ObjectIdGetDatum(funcid),
+                       0, 0, 0);
+   if (!HeapTupleIsValid(tp))
+       elog(ERROR, "cache lookup failed for function %u", funcid);
+   procform = (Form_pg_proc) GETSTRUCT(tp);
+
+   rettype = procform->prorettype;
+
+   /* Check for OUT parameters defining a RECORD result */
+   tupdesc = build_function_result_tupdesc_t(tp);
+   if (tupdesc)
+   {
+       /*
+        * It has OUT parameters, so it's basically like a regular
+        * composite type, except we have to be able to resolve any
+        * polymorphic OUT parameters.
+        */
+       if (resultTypeId)
+           *resultTypeId = rettype;
+
+       if (resolve_polymorphic_tupdesc(tupdesc,
+                                       &procform->proargtypes,
+                                       call_expr))
+       {
+           if (tupdesc->tdtypeid == RECORDOID &&
+               tupdesc->tdtypmod < 0)
+               assign_record_type_typmod(tupdesc);
+           if (resultTupleDesc)
+               *resultTupleDesc = tupdesc;
+           result = TYPEFUNC_COMPOSITE;
+       }
+       else
+       {
+           if (resultTupleDesc)
+               *resultTupleDesc = NULL;
+           result = TYPEFUNC_RECORD;
+       }
+
+       ReleaseSysCache(tp);
+
+       return result;
+   }
+
+   /*
+    * If scalar polymorphic result, try to resolve it.
+    */
+   if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
+   {
+       Oid     newrettype = exprType(call_expr);
+
+       if (newrettype == InvalidOid)   /* this probably should not happen */
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("could not determine actual result type for function \"%s\" declared to return type %s",
+                           NameStr(procform->proname),
+                           format_type_be(rettype))));
+       rettype = newrettype;
+   }
+
+   if (resultTypeId)
+       *resultTypeId = rettype;
+   if (resultTupleDesc)
+       *resultTupleDesc = NULL;        /* default result */
+
+   /* Classify the result type */
+   result = get_type_func_class(rettype);
+   switch (result)
+   {
+       case TYPEFUNC_COMPOSITE:
+           if (resultTupleDesc)
+               *resultTupleDesc = lookup_rowtype_tupdesc(rettype, -1);
+           /* Named composite types can't have any polymorphic columns */
+           break;
+       case TYPEFUNC_SCALAR:
+           break;
+       case TYPEFUNC_RECORD:
+           /* We must get the tupledesc from call context */
+           if (rsinfo && IsA(rsinfo, ReturnSetInfo) &&
+               rsinfo->expectedDesc != NULL)
+           {
+               result = TYPEFUNC_COMPOSITE;
+               if (resultTupleDesc)
+                   *resultTupleDesc = rsinfo->expectedDesc;
+               /* Assume no polymorphic columns here, either */
+           }
+           break;
+       default:
+           break;
+   }
+
+   ReleaseSysCache(tp);
+
+   return result;
+}
+
+/*
+ * Given the result tuple descriptor for a function with OUT parameters,
+ * replace any polymorphic columns (ANYELEMENT/ANYARRAY) with correct data
+ * types deduced from the input arguments.  Returns TRUE if able to deduce
+ * all types, FALSE if not.
+ */
+static bool
+resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args,
+                           Node *call_expr)
+{
+   int         natts = tupdesc->natts;
+   int         nargs = declared_args->dim1;
+   bool        have_anyelement_result = false;
+   bool        have_anyarray_result = false;
+   Oid         anyelement_type = InvalidOid;
+   Oid         anyarray_type = InvalidOid;
+   int         i;
+
+   /* See if there are any polymorphic outputs; quick out if not */
+   for (i = 0; i < natts; i++)
+   {
+       switch (tupdesc->attrs[i]->atttypid)
+       {
+           case ANYELEMENTOID:
+               have_anyelement_result = true;
+               break;
+           case ANYARRAYOID:
+               have_anyarray_result = true;
+               break;
+           default:
+               break;
+       }
+   }
+   if (!have_anyelement_result && !have_anyarray_result)
+       return true;
+
+   /*
+    * Otherwise, extract actual datatype(s) from input arguments.  (We assume
+    * the parser already validated consistency of the arguments.)
+    */
+   if (!call_expr)
+       return false;           /* no hope */
+
+   for (i = 0; i < nargs; i++)
+   {
+       switch (declared_args->values[i])
+       {
+           case ANYELEMENTOID:
+               if (!OidIsValid(anyelement_type))
+                   anyelement_type = get_call_expr_argtype(call_expr, i);
+               break;
+           case ANYARRAYOID:
+               if (!OidIsValid(anyarray_type))
+                   anyarray_type = get_call_expr_argtype(call_expr, i);
+               break;
+           default:
+               break;
+       }
+   }
+
+   /* If nothing found, parser messed up */
+   if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type))
+       return false;
+
+   /* If needed, deduce one polymorphic type from the other */
+   if (have_anyelement_result && !OidIsValid(anyelement_type))
+       anyelement_type = resolve_generic_type(ANYELEMENTOID,
+                                              anyarray_type,
+                                              ANYARRAYOID);
+   if (have_anyarray_result && !OidIsValid(anyarray_type))
+       anyarray_type = resolve_generic_type(ANYARRAYOID,
+                                            anyelement_type,
+                                            ANYELEMENTOID);
+
+   /* And finally replace the tuple column types as needed */
+   for (i = 0; i < natts; i++)
+   {
+       switch (tupdesc->attrs[i]->atttypid)
+       {
+           case ANYELEMENTOID:
+               TupleDescInitEntry(tupdesc, i+1,
+                                  NameStr(tupdesc->attrs[i]->attname),
+                                  anyelement_type,
+                                  -1,
+                                  0);
+               break;
+           case ANYARRAYOID:
+               TupleDescInitEntry(tupdesc, i+1,
+                                  NameStr(tupdesc->attrs[i]->attname),
+                                  anyarray_type,
+                                  -1,
+                                  0);
+               break;
+           default:
+               break;
+       }
+   }
+
+   return true;
+}
+
+/*
+ * get_type_func_class
+ *     Given the type OID, obtain its TYPEFUNC classification.
+ *
+ * This is intended to centralize a bunch of formerly ad-hoc code for
+ * classifying types.  The categories used here are useful for deciding
+ * how to handle functions returning the datatype.
+ */
+static TypeFuncClass
+get_type_func_class(Oid typid)
+{
+   switch (get_typtype(typid))
+   {
+       case 'c':
+           return TYPEFUNC_COMPOSITE;
+       case 'b':
+       case 'd':
+           return TYPEFUNC_SCALAR;
+       case 'p':
+           if (typid == RECORDOID)
+               return TYPEFUNC_RECORD;
+           /*
+            * We treat VOID and CSTRING as legitimate scalar datatypes,
+            * mostly for the convenience of the JDBC driver (which wants
+            * to be able to do "SELECT * FROM foo()" for all legitimately
+            * user-callable functions).
+            */
+           if (typid == VOIDOID || typid == CSTRINGOID)
+               return TYPEFUNC_SCALAR;
+           return TYPEFUNC_OTHER;
+   }
+   /* shouldn't get here, probably */
+   return TYPEFUNC_OTHER;
+}
+
+
+/*
+ * build_function_result_tupdesc_t
+ *
+ * Given a pg_proc row for a function, return a tuple descriptor for the
+ * result rowtype, or NULL if the function does not have OUT parameters.
+ *
+ * Note that this does not handle resolution of ANYELEMENT/ANYARRAY types;
+ * that is deliberate.
+ */
+TupleDesc
+build_function_result_tupdesc_t(HeapTuple procTuple)
+{
+   Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(procTuple);
+   Datum       proallargtypes;
+   Datum       proargmodes;
+   Datum       proargnames;
+   bool        isnull;
+
+   /* Return NULL if the function isn't declared to return RECORD */
+   if (procform->prorettype != RECORDOID)
+       return NULL;
+
+   /* If there are no OUT parameters, return NULL */
+   if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes) ||
+       heap_attisnull(procTuple, Anum_pg_proc_proargmodes))
+       return NULL;
+
+   /* Get the data out of the tuple */
+   proallargtypes = SysCacheGetAttr(PROCOID, procTuple,
+                                    Anum_pg_proc_proallargtypes,
+                                    &isnull);
+   Assert(!isnull);
+   proargmodes = SysCacheGetAttr(PROCOID, procTuple,
+                                 Anum_pg_proc_proargmodes,
+                                 &isnull);
+   Assert(!isnull);
+   proargnames = SysCacheGetAttr(PROCOID, procTuple,
+                                 Anum_pg_proc_proargnames,
+                                 &isnull);
+   if (isnull)
+       proargnames = PointerGetDatum(NULL); /* just to be sure */
+
+   return build_function_result_tupdesc_d(proallargtypes,
+                                          proargmodes,
+                                          proargnames);
+}
+
+/*
+ * build_function_result_tupdesc_d
+ *
+ * Build a RECORD function's tupledesc from the pg_proc proallargtypes,
+ * proargmodes, and proargnames arrays.  This is split out for the
+ * convenience of ProcedureCreate, which needs to be able to compute the
+ * tupledesc before actually creating the function.
+ *
+ * Returns NULL if there are not at least two OUT or INOUT arguments.
+ */
+TupleDesc
+build_function_result_tupdesc_d(Datum proallargtypes,
+                               Datum proargmodes,
+                               Datum proargnames)
+{
+   TupleDesc   desc;
+   ArrayType  *arr;
+   int         numargs;
+   Oid        *argtypes;
+   char       *argmodes;
+   Datum      *argnames = NULL;
+   Oid        *outargtypes;
+   char      **outargnames;
+   int         numoutargs;
+   int         nargnames;
+   int         i;
+
+   /* Can't have output args if columns are null */
+   if (proallargtypes == PointerGetDatum(NULL) ||
+       proargmodes == PointerGetDatum(NULL))
+       return NULL;
+
+   /*
+    * We expect the arrays to be 1-D arrays of the right types; verify that.
+    * For the OID and char arrays, we don't need to use deconstruct_array()
+    * since the array data is just going to look like a C array of values.
+    */
+   arr = DatumGetArrayTypeP(proallargtypes);   /* ensure not toasted */
+   numargs = ARR_DIMS(arr)[0];
+   if (ARR_NDIM(arr) != 1 ||
+       numargs < 0 ||
+       ARR_ELEMTYPE(arr) != OIDOID)
+       elog(ERROR, "proallargtypes is not a 1-D Oid array");
+   argtypes = (Oid *) ARR_DATA_PTR(arr);
+   arr = DatumGetArrayTypeP(proargmodes);      /* ensure not toasted */
+   if (ARR_NDIM(arr) != 1 ||
+       ARR_DIMS(arr)[0] != numargs ||
+       ARR_ELEMTYPE(arr) != CHAROID)
+       elog(ERROR, "proargmodes is not a 1-D char array");
+   argmodes = (char *) ARR_DATA_PTR(arr);
+   if (proargnames != PointerGetDatum(NULL))
+   {
+       arr = DatumGetArrayTypeP(proargnames);  /* ensure not toasted */
+       if (ARR_NDIM(arr) != 1 ||
+           ARR_DIMS(arr)[0] != numargs ||
+           ARR_ELEMTYPE(arr) != TEXTOID)
+           elog(ERROR, "proargnames is not a 1-D text array");
+       deconstruct_array(arr, TEXTOID, -1, false, 'i',
+                         &argnames, &nargnames);
+       Assert(nargnames == numargs);
+   }
+
+   /* zero elements probably shouldn't happen, but handle it gracefully */
+   if (numargs <= 0)
+       return NULL;
+
+   /* extract output-argument types and names */
+   outargtypes = (Oid *) palloc(numargs * sizeof(Oid));
+   outargnames = (char **) palloc(numargs * sizeof(char *));
+   numoutargs = 0;
+   for (i = 0; i < numargs; i++)
+   {
+       char    *pname;
+
+       if (argmodes[i] == PROARGMODE_IN)
+           continue;
+       Assert(argmodes[i] == PROARGMODE_OUT ||
+              argmodes[i] == PROARGMODE_INOUT);
+       outargtypes[numoutargs] = argtypes[i];
+       if (argnames)
+           pname = DatumGetCString(DirectFunctionCall1(textout, argnames[i]));
+       else
+           pname = NULL;
+       if (pname == NULL || pname[0] == '\0')
+       {
+           /* Parameter is not named, so gin up a column name */
+           pname = (char *) palloc(32);
+           snprintf(pname, 32, "column%d", numoutargs + 1);
+       }
+       outargnames[numoutargs] = pname;
+       numoutargs++;
+   }
+
+   /*
+    * If there is no output argument, or only one, the function does not
+    * return tuples.
+    */
+   if (numoutargs < 2)
+       return NULL;
+
+   desc = CreateTemplateTupleDesc(numoutargs, false);
+   for (i = 0; i < numoutargs; i++)
+   {
+       TupleDescInitEntry(desc, i+1,
+                          outargnames[i],
+                          outargtypes[i],
+                          -1,
+                          0);
+   }
+
+   return desc;
+}
+
+
+/*
+ * RelationNameGetTupleDesc
+ *
+ * Given a (possibly qualified) relation name, build a TupleDesc.
+ */
+TupleDesc
+RelationNameGetTupleDesc(const char *relname)
+{
+   RangeVar   *relvar;
+   Relation    rel;
+   TupleDesc   tupdesc;
+   List       *relname_list;
+
+   /* Open relation and copy the tuple description */
+   relname_list = stringToQualifiedNameList(relname, "RelationNameGetTupleDesc");
+   relvar = makeRangeVarFromNameList(relname_list);
+   rel = relation_openrv(relvar, AccessShareLock);
+   tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+   relation_close(rel, AccessShareLock);
+
+   return tupdesc;
+}
+
+/*
+ * TypeGetTupleDesc
+ *
+ * Given a type Oid, build a TupleDesc.
+ *
+ * If the type is composite, *and* a colaliases List is provided, *and*
+ * the List is of natts length, use the aliases instead of the relation
+ * attnames.  (NB: this usage is deprecated since it may result in
+ * creation of unnecessary transient record types.)
+ *
+ * If the type is a base type, a single item alias List is required.
+ */
+TupleDesc
+TypeGetTupleDesc(Oid typeoid, List *colaliases)
+{
+   TypeFuncClass functypclass = get_type_func_class(typeoid);
+   TupleDesc   tupdesc = NULL;
+
+   /*
+    * Build a suitable tupledesc representing the output rows
+    */
+   if (functypclass == TYPEFUNC_COMPOSITE)
+   {
+       /* Composite data type, e.g. a table's row type */
+       tupdesc = CreateTupleDescCopy(lookup_rowtype_tupdesc(typeoid, -1));
+
+       if (colaliases != NIL)
+       {
+           int         natts = tupdesc->natts;
+           int         varattno;
+
+           /* does the list length match the number of attributes? */
+           if (list_length(colaliases) != natts)
+               ereport(ERROR,
+                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                        errmsg("number of aliases does not match number of columns")));
+
+           /* OK, use the aliases instead */
+           for (varattno = 0; varattno < natts; varattno++)
+           {
+               char       *label = strVal(list_nth(colaliases, varattno));
+
+               if (label != NULL)
+                   namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
+           }
+
+           /* The tuple type is now an anonymous record type */
+           tupdesc->tdtypeid = RECORDOID;
+           tupdesc->tdtypmod = -1;
+       }
+   }
+   else if (functypclass == TYPEFUNC_SCALAR)
+   {
+       /* Base data type, i.e. scalar */
+       char       *attname;
+
+       /* the alias list is required for base types */
+       if (colaliases == NIL)
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("no column alias was provided")));
+
+       /* the alias list length must be 1 */
+       if (list_length(colaliases) != 1)
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("number of aliases does not match number of columns")));
+
+       /* OK, get the column alias */
+       attname = strVal(linitial(colaliases));
+
+       tupdesc = CreateTemplateTupleDesc(1, false);
+       TupleDescInitEntry(tupdesc,
+                          (AttrNumber) 1,
+                          attname,
+                          typeoid,
+                          -1,
+                          0);
+   }
+   else if (functypclass == TYPEFUNC_RECORD)
+   {
+       /* XXX can't support this because typmod wasn't passed in ... */
+       ereport(ERROR,
+               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                errmsg("could not determine row description for function returning record")));
+   }
+   else
+   {
+       /* crummy error message, but parser should have caught this */
+       elog(ERROR, "function in FROM has unsupported return type");
+   }
+
+   return tupdesc;
+}
index d86ce3ef0ebee9d04345909e0096ed87a73ba428..cb90a5e04c329b8072a6460df95f366671a6b4a0 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.356 2005/03/29 19:44:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.357 2005/03/31 22:46:18 tgl Exp $
  *
  * NOTES
  *   The script catalog/genbki.sh reads this file and generates .bki
@@ -3668,9 +3668,10 @@ extern Oid ProcedureCreate(const char *procedureName,
                bool security_definer,
                bool isStrict,
                char volatility,
-               int parameterCount,
-               const Oid *parameterTypes,
-               const char *parameterNames[]);
+               oidvector *parameterTypes,
+               Datum allParameterTypes,
+               Datum parameterModes,
+               Datum parameterNames);
 
 extern bool function_parse_error_transpose(const char *prosrc);
 
index fdc23b2812e1612948ff6a7b99e5425c74336df1..88ca87fd86d395e55d34eea7dfa796019750bd02 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.24 2004/12/31 22:03:29 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/executor/functions.h,v 1.25 2005/03/31 22:46:22 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,7 +20,7 @@
 
 extern Datum fmgr_sql(PG_FUNCTION_ARGS);
 
-extern bool check_sql_fn_retval(Oid rettype, char fn_typtype,
+extern bool check_sql_fn_retval(Oid func_id, Oid rettype,
                                List *queryTreeList,
                                JunkFilter **junkFilter);
 
index 0c11130053eaab88c76906e29319d6076b4532c1..3dc53ccb166d5eba43447c655ef3c3fb88f70014 100644 (file)
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/fmgr.h,v 1.37 2005/03/22 20:13:09 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/fmgr.h,v 1.38 2005/03/31 22:46:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef FMGR_H
 #define FMGR_H
 
+/* We don't want to include primnodes.h here, so make a stub reference */
+struct Node;
+
 
 /*
  * All functions that can be called directly by fmgr must have this signature.
@@ -402,6 +405,7 @@ extern void clear_external_function_hash(void *filehandle);
 extern Oid fmgr_internal_function(const char *proname);
 extern Oid get_fn_expr_rettype(FmgrInfo *flinfo);
 extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
+extern Oid get_call_expr_argtype(struct Node *expr, int argnum);
 
 /*
  * Routines in dfmgr.c
index 00549897d5825d4680fc2643733605c4e6e3afff..c4d1809948def6402bc145c8cce3f3dfda2fd083 100644 (file)
@@ -9,7 +9,7 @@
  *
  * Copyright (c) 2002-2005, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.15 2005/01/01 05:43:08 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.16 2005/03/31 22:46:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -124,7 +124,57 @@ typedef struct FuncCallContext
 } FuncCallContext;
 
 /*----------
- * Support to ease writing Functions returning composite types
+ * Support to ease writing functions returning composite types
+ *
+ * External declarations:
+ * get_call_result_type:
+ *      Given a function's call info record, determine the kind of datatype
+ *      it is supposed to return.  If resultTypeId isn't NULL, *resultTypeId
+ *      receives the actual datatype OID (this is mainly useful for scalar
+ *      result types).  If resultTupleDesc isn't NULL, *resultTupleDesc
+ *      receives a pointer to a TupleDesc when the result is of a composite
+ *      type, or NULL when it's a scalar result or the rowtype could not be
+ *      determined.  NB: the tupledesc should be copied if it is to be
+ *      accessed over a long period.
+ * get_expr_result_type:
+ *      Given an expression node, return the same info as for
+ *      get_call_result_type.  Note: the cases in which rowtypes cannot be
+ *      determined are different from the cases for get_call_result_type.
+ * get_func_result_type:
+ *      Given only a function's OID, return the same info as for
+ *      get_call_result_type.  Note: the cases in which rowtypes cannot be
+ *      determined are different from the cases for get_call_result_type.
+ *      Do *not* use this if you can use one of the others.
+ *----------
+ */
+
+/* Type categories for get_call_result_type and siblings */
+typedef enum TypeFuncClass
+{
+   TYPEFUNC_SCALAR,            /* scalar result type */
+   TYPEFUNC_COMPOSITE,         /* determinable rowtype result */
+   TYPEFUNC_RECORD,            /* indeterminate rowtype result */
+   TYPEFUNC_OTHER              /* bogus type, eg pseudotype */
+} TypeFuncClass;
+
+extern TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
+                                         Oid *resultTypeId,
+                                         TupleDesc *resultTupleDesc);
+extern TypeFuncClass get_expr_result_type(Node *expr,
+                                         Oid *resultTypeId,
+                                         TupleDesc *resultTupleDesc);
+extern TypeFuncClass get_func_result_type(Oid functionId,
+                                         Oid *resultTypeId,
+                                         TupleDesc *resultTupleDesc);
+
+extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes,
+                                                Datum proargmodes,
+                                                Datum proargnames);
+extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
+
+
+/*----------
+ * Support to ease writing functions returning composite types
  *
  * External declarations:
  * TupleDesc RelationNameGetTupleDesc(const char *relname) - Use to get a
@@ -160,7 +210,6 @@ typedef struct FuncCallContext
 /* obsolete version of above */
 #define TupleGetDatum(_slot, _tuple)   PointerGetDatum((_tuple)->t_data)
 
-/* from tupdesc.c */
 extern TupleDesc RelationNameGetTupleDesc(const char *relname);
 extern TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases);
 
index 1c5398a71b0b9ba25e6719ecac42dc42f16004f0..845a886ba82ff0241f7eca5b240d11e51a15ef8e 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.95 2005/03/29 00:17:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/lsyscache.h,v 1.96 2005/03/31 22:46:27 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,15 +24,6 @@ typedef enum IOFuncSelector
    IOFunc_send
 } IOFuncSelector;
 
-/* Type categories for get_type_func_class */
-typedef enum TypeFuncClass
-{
-   TYPEFUNC_SCALAR,
-   TYPEFUNC_COMPOSITE,
-   TYPEFUNC_RECORD,
-   TYPEFUNC_OTHER
-} TypeFuncClass;
-
 extern bool op_in_opclass(Oid opno, Oid opclass);
 extern void get_op_opclass_properties(Oid opno, Oid opclass,
                          int *strategy, Oid *subtype,
@@ -94,7 +85,6 @@ extern char get_typstorage(Oid typid);
 extern int32 get_typtypmod(Oid typid);
 extern Node *get_typdefault(Oid typid);
 extern char get_typtype(Oid typid);
-extern TypeFuncClass get_type_func_class(Oid typid);
 extern Oid get_typ_typrelid(Oid typid);
 extern Oid get_element_type(Oid typid);
 extern Oid get_array_type(Oid typid);
index 7acbbe9bc608d3e6093cd47c4bc7f7baf43c6d19..6caa7a1c7b5392cdb5ae7f44b2ac9cd6e1557b4f 100644 (file)
@@ -396,3 +396,134 @@ DROP FUNCTION foorescan(int,int);
 DROP FUNCTION foorescan(int);
 DROP TABLE foorescan;
 DROP TABLE barrescan;
+--
+-- Test cases involving OUT parameters
+--
+CREATE FUNCTION foo(in f1 int, out f2 int)
+AS 'select $1+1' LANGUAGE sql;
+SELECT foo(42);
+ foo 
+-----
+  43
+(1 row)
+
+SELECT * FROM foo(42);
+ foo 
+-----
+  43
+(1 row)
+
+SELECT * FROM foo(42) AS p(x);
+ x  
+----
+ 43
+(1 row)
+
+-- explicit spec of return type is OK
+CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS int
+AS 'select $1+1' LANGUAGE sql;
+-- error, wrong result type
+CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS float
+AS 'select $1+1' LANGUAGE sql;
+ERROR:  function result type must be integer because of OUT parameters
+-- with multiple OUT params you must get a RECORD result
+CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) RETURNS int
+AS 'select $1+1' LANGUAGE sql;
+ERROR:  function result type must be record because of OUT parameters
+CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text)
+RETURNS record
+AS 'select $1+1' LANGUAGE sql;
+ERROR:  cannot change return type of existing function
+HINT:  Use DROP FUNCTION first.
+CREATE OR REPLACE FUNCTION foor(in f1 int, out f2 int, out text)
+AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql;
+SELECT f1, foor(f1) FROM int4_tbl;
+     f1      |            foor            
+-------------+----------------------------
+           0 | (-1,0z)
+      123456 | (123455,123456z)
+     -123456 | (-123457,-123456z)
+  2147483647 | (2147483646,2147483647z)
+ -2147483647 | (-2147483648,-2147483647z)
+(5 rows)
+
+SELECT * FROM foor(42);
+ f2 | column2 
+----+---------
+ 41 | 42z
+(1 row)
+
+SELECT * FROM foor(42) AS p(a,b);
+ a  |  b  
+----+-----
+ 41 | 42z
+(1 row)
+
+CREATE OR REPLACE FUNCTION foob(in f1 int, inout f2 int, out text)
+AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql;
+SELECT f1, foob(f1, f1/2) FROM int4_tbl;
+     f1      |            foob            
+-------------+----------------------------
+           0 | (-1,0z)
+      123456 | (61727,123456z)
+     -123456 | (-61729,-123456z)
+  2147483647 | (1073741822,2147483647z)
+ -2147483647 | (-1073741824,-2147483647z)
+(5 rows)
+
+SELECT * FROM foob(42, 99);
+ f2 | column2 
+----+---------
+ 98 | 42z
+(1 row)
+
+SELECT * FROM foob(42, 99) AS p(a,b);
+ a  |  b  
+----+-----
+ 98 | 42z
+(1 row)
+
+-- Can reference function with or without OUT params for DROP, etc
+DROP FUNCTION foo(int);
+DROP FUNCTION foor(in f2 int, out f1 int, out text);
+DROP FUNCTION foob(in f1 int, inout f2 int);
+--
+-- For my next trick, polymorphic OUT parameters
+--
+CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+SELECT dup(22);
+      dup       
+----------------
+ (22,"{22,22}")
+(1 row)
+
+SELECT dup('xyz'); -- fails
+ERROR:  could not determine anyarray/anyelement type because input has type "unknown"
+SELECT dup('xyz'::text);
+        dup        
+-------------------
+ (xyz,"{xyz,xyz}")
+(1 row)
+
+SELECT * FROM dup('xyz'::text);
+ f2  |    f3     
+-----+-----------
+ xyz | {xyz,xyz}
+(1 row)
+
+-- equivalent specification
+CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+SELECT dup(22);
+      dup       
+----------------
+ (22,"{22,22}")
+(1 row)
+
+DROP FUNCTION dup(anyelement);
+-- fails, no way to deduce outputs
+CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+ERROR:  cannot determine result data type
+DETAIL:  A function returning "anyarray" or "anyelement" must have at least one argument of either type.
index f724963d35132d12301b83d474cd77f15aef41f7..dd308261d9f32a8470bc4618fe0bce28058dc7e0 100644 (file)
@@ -13,8 +13,8 @@ CREATE FUNCTION hobbies_by_name(hobbies_r.name%TYPE)
    RETURNS hobbies_r.person%TYPE
    AS 'select person from hobbies_r where name = $1'
    LANGUAGE 'sql';
-NOTICE:  type reference hobbies_r.person%TYPE converted to text
 NOTICE:  type reference hobbies_r.name%TYPE converted to text
+NOTICE:  type reference hobbies_r.person%TYPE converted to text
 CREATE FUNCTION equipment(hobbies_r)
    RETURNS setof equipment_r
    AS 'select * from equipment_r where hobby = $1.name'
index 2f1c8e75130b07dc7207691c28da659a63981869..50495897acd2adba59cee5f9cf86abb9a52af44e 100644 (file)
@@ -199,3 +199,65 @@ DROP FUNCTION foorescan(int,int);
 DROP FUNCTION foorescan(int);
 DROP TABLE foorescan;
 DROP TABLE barrescan;
+
+--
+-- Test cases involving OUT parameters
+--
+
+CREATE FUNCTION foo(in f1 int, out f2 int)
+AS 'select $1+1' LANGUAGE sql;
+SELECT foo(42);
+SELECT * FROM foo(42);
+SELECT * FROM foo(42) AS p(x);
+
+-- explicit spec of return type is OK
+CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS int
+AS 'select $1+1' LANGUAGE sql;
+-- error, wrong result type
+CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int) RETURNS float
+AS 'select $1+1' LANGUAGE sql;
+-- with multiple OUT params you must get a RECORD result
+CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text) RETURNS int
+AS 'select $1+1' LANGUAGE sql;
+CREATE OR REPLACE FUNCTION foo(in f1 int, out f2 int, out f3 text)
+RETURNS record
+AS 'select $1+1' LANGUAGE sql;
+
+CREATE OR REPLACE FUNCTION foor(in f1 int, out f2 int, out text)
+AS $$select $1-1, $1::text || 'z'$$ LANGUAGE sql;
+SELECT f1, foor(f1) FROM int4_tbl;
+SELECT * FROM foor(42);
+SELECT * FROM foor(42) AS p(a,b);
+
+CREATE OR REPLACE FUNCTION foob(in f1 int, inout f2 int, out text)
+AS $$select $2-1, $1::text || 'z'$$ LANGUAGE sql;
+SELECT f1, foob(f1, f1/2) FROM int4_tbl;
+SELECT * FROM foob(42, 99);
+SELECT * FROM foob(42, 99) AS p(a,b);
+
+-- Can reference function with or without OUT params for DROP, etc
+DROP FUNCTION foo(int);
+DROP FUNCTION foor(in f2 int, out f1 int, out text);
+DROP FUNCTION foob(in f1 int, inout f2 int);
+
+--
+-- For my next trick, polymorphic OUT parameters
+--
+
+CREATE FUNCTION dup (f1 anyelement, f2 out anyelement, f3 out anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+SELECT dup(22);
+SELECT dup('xyz'); -- fails
+SELECT dup('xyz'::text);
+SELECT * FROM dup('xyz'::text);
+
+-- equivalent specification
+CREATE OR REPLACE FUNCTION dup (inout f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;
+SELECT dup(22);
+
+DROP FUNCTION dup(anyelement);
+
+-- fails, no way to deduce outputs
+CREATE FUNCTION bad (f1 int, out f2 anyelement, out f3 anyarray)
+AS 'select $1, array[$1,$1]' LANGUAGE sql;