SQL functions can have arguments and results declared ANYARRAY or
authorTom Lane
Tue, 1 Jul 2003 00:04:39 +0000 (00:04 +0000)
committerTom Lane
Tue, 1 Jul 2003 00:04:39 +0000 (00:04 +0000)
ANYELEMENT.  The effect is to postpone typechecking of the function
body until runtime.  Documentation is still lacking.

Original patch by Joe Conway, modified to postpone type checking
by Tom Lane.

src/backend/catalog/pg_proc.c
src/backend/executor/functions.c
src/backend/optimizer/util/clauses.c
src/backend/utils/adt/array_userfuncs.c
src/backend/utils/adt/arrayfuncs.c
src/backend/utils/fmgr/fmgr.c
src/include/catalog/pg_proc.h
src/include/fmgr.h

index 48b90e56ef1034aea86e92db9a522f4c46b0be80..4ec4c44bd402f0a30e6c73c3e1e58fdc46667d07 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.97 2003/06/15 17:59:10 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.98 2003/07/01 00:04:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -33,7 +33,6 @@
 #include "utils/syscache.h"
 
 
-static void checkretval(Oid rettype, char fn_typtype, List *queryTreeList);
 Datum      fmgr_internal_validator(PG_FUNCTION_ARGS);
 Datum      fmgr_c_validator(PG_FUNCTION_ARGS);
 Datum      fmgr_sql_validator(PG_FUNCTION_ARGS);
@@ -317,15 +316,20 @@ ProcedureCreate(const char *procedureName,
 }
 
 /*
- * checkretval() -- check return value of a list of sql parse trees.
+ * check_sql_fn_retval() -- check return value of a list of sql parse trees.
  *
  * The return value of a sql function is the value returned by
- * the final query in the function.  We do some ad-hoc define-time
- * type checking here to be sure that the user is returning the
- * type he claims.
+ * the final query in the function.  We do some ad-hoc type checking here
+ * to be sure that the user is returning the type he claims.
+ *
+ * This is normally applied during function definition, but in the case
+ * of a function with polymorphic arguments, we instead apply it during
+ * function execution startup.  The rettype is then the actual resolved
+ * output type of the function, rather than the declared type.  (Therefore,
+ * we should never see ANYARRAY or ANYELEMENT as rettype.)
  */
-static void
-checkretval(Oid rettype, char fn_typtype, List *queryTreeList)
+void
+check_sql_fn_retval(Oid rettype, char fn_typtype, List *queryTreeList)
 {
    Query      *parse;
    int         cmd;
@@ -472,7 +476,7 @@ checkretval(Oid rettype, char fn_typtype, List *queryTreeList)
 
        relation_close(reln, AccessShareLock);
    }
-   else if (fn_typtype == 'p' && rettype == RECORDOID)
+   else if (rettype == RECORDOID)
    {
        /* Shouldn't have a typerelid */
        Assert(typerelid == InvalidOid);
@@ -482,6 +486,14 @@ checkretval(Oid rettype, char fn_typtype, List *queryTreeList)
         * tuple.
         */
    }
+   else if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
+   {
+       /*
+        * This should already have been caught ...
+        */
+       elog(ERROR, "functions returning ANYARRAY or ANYELEMENT must " \
+            "have at least one argument of either type");
+   }
    else
        elog(ERROR, "return type %s is not supported for SQL functions",
             format_type_be(rettype));
@@ -505,7 +517,9 @@ fmgr_internal_validator(PG_FUNCTION_ARGS)
    Datum       tmp;
    char       *prosrc;
 
-   tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
+   tuple = SearchSysCache(PROCOID,
+                          ObjectIdGetDatum(funcoid),
+                          0, 0, 0);
    if (!HeapTupleIsValid(tuple))
        elog(ERROR, "cache lookup of function %u failed", funcoid);
    proc = (Form_pg_proc) GETSTRUCT(tuple);
@@ -544,7 +558,9 @@ fmgr_c_validator(PG_FUNCTION_ARGS)
    char       *prosrc;
    char       *probin;
 
-   tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
+   tuple = SearchSysCache(PROCOID,
+                          ObjectIdGetDatum(funcoid),
+                          0, 0, 0);
    if (!HeapTupleIsValid(tuple))
        elog(ERROR, "cache lookup of function %u failed", funcoid);
    proc = (Form_pg_proc) GETSTRUCT(tuple);
@@ -585,38 +601,62 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
    Datum       tmp;
    char       *prosrc;
    char        functyptype;
+   bool        haspolyarg;
    int         i;
 
-   tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
+   tuple = SearchSysCache(PROCOID,
+                          ObjectIdGetDatum(funcoid),
+                          0, 0, 0);
    if (!HeapTupleIsValid(tuple))
        elog(ERROR, "cache lookup of function %u failed", funcoid);
    proc = (Form_pg_proc) GETSTRUCT(tuple);
 
    functyptype = get_typtype(proc->prorettype);
 
-   /* Disallow pseudotypes in arguments and result */
-   /* except that return type can be RECORD or VOID */
+   /* Disallow pseudotype result */
+   /* except for RECORD, VOID, ANYARRAY, or ANYELEMENT */
    if (functyptype == 'p' &&
        proc->prorettype != RECORDOID &&
-       proc->prorettype != VOIDOID)
+       proc->prorettype != VOIDOID &&
+       proc->prorettype != ANYARRAYOID &&
+       proc->prorettype != ANYELEMENTOID)
        elog(ERROR, "SQL functions cannot return type %s",
             format_type_be(proc->prorettype));
 
+   /* Disallow pseudotypes in arguments */
+   /* except for ANYARRAY or ANYELEMENT */
+   haspolyarg = false;
    for (i = 0; i < proc->pronargs; i++)
    {
        if (get_typtype(proc->proargtypes[i]) == 'p')
-           elog(ERROR, "SQL functions cannot have arguments of type %s",
-                format_type_be(proc->proargtypes[i]));
+       {
+           if (proc->proargtypes[i] == ANYARRAYOID ||
+               proc->proargtypes[i] == ANYELEMENTOID)
+               haspolyarg = true;
+           else
+               elog(ERROR, "SQL functions cannot have arguments of type %s",
+                    format_type_be(proc->proargtypes[i]));
+       }
    }
 
-   tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
-   if (isnull)
-       elog(ERROR, "null prosrc");
+   /*
+    * We can't precheck the function definition if there are any polymorphic
+    * input types, because actual datatypes of expression results will be
+    * unresolvable.  The check will be done at runtime instead.
+    */
+   if (!haspolyarg)
+   {
+       tmp = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_prosrc, &isnull);
+       if (isnull)
+           elog(ERROR, "null prosrc");
 
-   prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
+       prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
 
-   querytree_list = pg_parse_and_rewrite(prosrc, proc->proargtypes, proc->pronargs);
-   checkretval(proc->prorettype, functyptype, querytree_list);
+       querytree_list = pg_parse_and_rewrite(prosrc,
+                                             proc->proargtypes,
+                                             proc->pronargs);
+       check_sql_fn_retval(proc->prorettype, functyptype, querytree_list);
+   }
 
    ReleaseSysCache(tuple);
 
index a0e0919fd0b61f01f9545e1c02f0e2d875f6553b..e0ab9e92d5010ec2474122a82a18a7c192936756 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.66 2003/06/12 17:29:26 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.67 2003/07/01 00:04:37 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,6 +24,7 @@
 #include "tcop/tcopprot.h"
 #include "tcop/utility.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
 
@@ -76,7 +77,8 @@ typedef SQLFunctionCache *SQLFunctionCachePtr;
 
 /* non-export function prototypes */
 static execution_state *init_execution_state(char *src,
-                    Oid *argOidVect, int nargs);
+                    Oid *argOidVect, int nargs,
+                    Oid rettype, bool haspolyarg);
 static void init_sql_fcache(FmgrInfo *finfo);
 static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
 static TupleTableSlot *postquel_getnext(execution_state *es);
@@ -90,7 +92,8 @@ static void ShutdownSQLFunction(Datum arg);
 
 
 static execution_state *
-init_execution_state(char *src, Oid *argOidVect, int nargs)
+init_execution_state(char *src, Oid *argOidVect, int nargs,
+                    Oid rettype, bool haspolyarg)
 {
    execution_state *firstes;
    execution_state *preves;
@@ -99,6 +102,13 @@ init_execution_state(char *src, Oid *argOidVect, int nargs)
 
    queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs);
 
+   /*
+    * If the function has any arguments declared as polymorphic types,
+    * then it wasn't type-checked at definition time; must do so now.
+    */
+   if (haspolyarg)
+       check_sql_fn_retval(rettype, get_typtype(rettype), queryTree_list);
+
    firstes = NULL;
    preves = NULL;
 
@@ -133,17 +143,21 @@ static void
 init_sql_fcache(FmgrInfo *finfo)
 {
    Oid         foid = finfo->fn_oid;
+   Oid         rettype;
    HeapTuple   procedureTuple;
    HeapTuple   typeTuple;
    Form_pg_proc procedureStruct;
    Form_pg_type typeStruct;
    SQLFunctionCachePtr fcache;
    Oid        *argOidVect;
+   bool        haspolyarg;
    char       *src;
    int         nargs;
    Datum       tmp;
    bool        isNull;
 
+   fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache));
+
    /*
     * get the procedure tuple corresponding to the given function Oid
     */
@@ -153,30 +167,37 @@ init_sql_fcache(FmgrInfo *finfo)
    if (!HeapTupleIsValid(procedureTuple))
        elog(ERROR, "init_sql_fcache: Cache lookup failed for procedure %u",
             foid);
-
    procedureStruct = (Form_pg_proc) GETSTRUCT(procedureTuple);
 
    /*
-    * get the return type from the procedure tuple
+    * get the result type from the procedure tuple, and check for
+    * polymorphic result type; if so, find out the actual result type.
     */
+   rettype = procedureStruct->prorettype;
+
+   if (rettype == ANYARRAYOID || rettype == ANYELEMENTOID)
+   {
+       rettype = get_fn_expr_rettype(finfo);
+       if (rettype == InvalidOid)
+           elog(ERROR, "could not determine actual result type for function declared %s",
+                format_type_be(procedureStruct->prorettype));
+   }
+
+   /* Now look up the actual result type */
    typeTuple = SearchSysCache(TYPEOID,
-                          ObjectIdGetDatum(procedureStruct->prorettype),
+                              ObjectIdGetDatum(rettype),
                               0, 0, 0);
    if (!HeapTupleIsValid(typeTuple))
        elog(ERROR, "init_sql_fcache: Cache lookup failed for type %u",
-            procedureStruct->prorettype);
-
+            rettype);
    typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
 
-   fcache = (SQLFunctionCachePtr) palloc0(sizeof(SQLFunctionCache));
-
    /*
     * get the type length and by-value flag from the type tuple
     */
    fcache->typlen = typeStruct->typlen;
 
-   if (typeStruct->typtype != 'c' &&
-       procedureStruct->prorettype != RECORDOID)
+   if (typeStruct->typtype != 'c' && rettype != RECORDOID)
    {
        /* The return type is not a composite type, so just use byval */
        fcache->typbyval = typeStruct->typbyval;
@@ -205,17 +226,35 @@ init_sql_fcache(FmgrInfo *finfo)
        fcache->funcSlot = NULL;
 
    /*
-    * Parse and plan the queries.  We need the argument info to pass
+    * Parse and plan the queries.  We need the argument type info to pass
     * to the parser.
     */
    nargs = procedureStruct->pronargs;
+   haspolyarg = false;
 
    if (nargs > 0)
    {
+       int     argnum;
+
        argOidVect = (Oid *) palloc(nargs * sizeof(Oid));
        memcpy(argOidVect,
               procedureStruct->proargtypes,
               nargs * sizeof(Oid));
+       /* Resolve any polymorphic argument types */
+       for (argnum = 0; argnum < nargs; argnum++)
+       {
+           Oid     argtype = argOidVect[argnum];
+
+           if (argtype == ANYARRAYOID || argtype == ANYELEMENTOID)
+           {
+               argtype = get_fn_expr_argtype(finfo, argnum);
+               if (argtype == InvalidOid)
+                   elog(ERROR, "could not determine actual type of argument declared %s",
+                        format_type_be(argOidVect[argnum]));
+               argOidVect[argnum] = argtype;
+               haspolyarg = true;
+           }
+       }
    }
    else
        argOidVect = (Oid *) NULL;
@@ -229,7 +268,8 @@ init_sql_fcache(FmgrInfo *finfo)
             foid);
    src = DatumGetCString(DirectFunctionCall1(textout, tmp));
 
-   fcache->func_state = init_execution_state(src, argOidVect, nargs);
+   fcache->func_state = init_execution_state(src, argOidVect, nargs,
+                                             rettype, haspolyarg);
 
    pfree(src);
 
index 54f2d7bd69b2a4c0e330ac539e8e6ce80cd250f7..3da79cc4957cdaafdf05ec361183dc57dc58422e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.142 2003/06/29 00:33:43 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/optimizer/util/clauses.c,v 1.143 2003/07/01 00:04:37 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -1731,6 +1731,7 @@ inline_function(Oid funcid, Oid result_type, List *args,
    int        *usecounts;
    List       *arg;
    int         i;
+   int         j;
 
    /*
     * Forget it if the function is not SQL-language or has other
@@ -1742,12 +1743,20 @@ inline_function(Oid funcid, Oid result_type, List *args,
        funcform->pronargs != length(args))
        return NULL;
 
-   /* Forget it if declared return type is tuple or void */
+   /* Forget it if declared return type is not base or domain */
    result_typtype = get_typtype(funcform->prorettype);
    if (result_typtype != 'b' &&
        result_typtype != 'd')
        return NULL;
 
+   /* Forget it if any declared argument type is polymorphic */
+   for (j = 0; j < funcform->pronargs; j++)
+   {
+       if (funcform->proargtypes[j] == ANYARRAYOID ||
+           funcform->proargtypes[j] == ANYELEMENTOID)
+           return NULL;
+   }
+
    /* Check for recursive function, and give up trying to expand if so */
    if (oidMember(funcid, active_fns))
        return NULL;
index 3aa70b0d3324c87975ec9a8bc311498d148d282e..6c28b211cebb4c801169843f6c86c776fc39731a 100644 (file)
@@ -6,7 +6,7 @@
  * Copyright (c) 2003, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.4 2003/06/27 00:33:25 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/array_userfuncs.c,v 1.5 2003/07/01 00:04:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,8 +37,8 @@ array_push(PG_FUNCTION_ARGS)
    int16       typlen;
    bool        typbyval;
    char        typalign;
-   Oid         arg0_typeid = get_fn_expr_argtype(fcinfo, 0);
-   Oid         arg1_typeid = get_fn_expr_argtype(fcinfo, 1);
+   Oid         arg0_typeid = get_fn_expr_argtype(fcinfo->flinfo, 0);
+   Oid         arg1_typeid = get_fn_expr_argtype(fcinfo->flinfo, 1);
    Oid         arg0_elemid;
    Oid         arg1_elemid;
    ArrayMetaState *my_extra;
index 808353a63d4299344438d083c024f28ef72db0c0..d0a876a877b1fc54bb9fa2bdb5ad8c3559012695 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.92 2003/06/27 00:33:25 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.93 2003/07/01 00:04:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2792,7 +2792,7 @@ array_type_coerce(PG_FUNCTION_ARGS)
 
    if (my_extra->srctype != src_elem_type)
    {
-       Oid         tgt_type = get_fn_expr_rettype(fcinfo);
+       Oid         tgt_type = get_fn_expr_rettype(fmgr_info);
        Oid         tgt_elem_type;
        Oid         funcId;
 
index 0d69ac1083e600dcf5c35b24e376ec567ac86678..04e72e53413a46f3e53d3f9223fefce542d4f194 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.71 2003/06/29 00:33:44 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/utils/fmgr/fmgr.c,v 1.72 2003/07/01 00:04:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1616,16 +1616,19 @@ pg_detoast_datum_slice(struct varlena * datum, int32 first, int32 count)
 
 /*-------------------------------------------------------------------------
  *     Support routines for extracting info from fn_expr parse tree
+ *
+ * These are needed by polymorphic functions, which accept multiple possible
+ * input types and need help from the parser to know what they've got.
  *-------------------------------------------------------------------------
  */
 
 /*
- * Get the OID of the function return type
+ * Get the actual type OID of the function return type
  *
  * Returns InvalidOid if information is not available
  */
 Oid
-get_fn_expr_rettype(FunctionCallInfo fcinfo)
+get_fn_expr_rettype(FmgrInfo *flinfo)
 {
    Node   *expr;
 
@@ -1633,21 +1636,21 @@ get_fn_expr_rettype(FunctionCallInfo fcinfo)
     * can't return anything useful if we have no FmgrInfo or if
     * its fn_expr node has not been initialized
     */
-   if (!fcinfo || !fcinfo->flinfo || !fcinfo->flinfo->fn_expr)
+   if (!flinfo || !flinfo->fn_expr)
        return InvalidOid;
 
-   expr = fcinfo->flinfo->fn_expr;
+   expr = flinfo->fn_expr;
 
    return exprType(expr);
 }
 
 /*
- * Get the type OID of a specific function argument (counting from 0)
+ * Get the actual type OID of a specific function argument (counting from 0)
  *
  * Returns InvalidOid if information is not available
  */
 Oid
-get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum)
+get_fn_expr_argtype(FmgrInfo *flinfo, int argnum)
 {
    Node   *expr;
    List   *args;
@@ -1657,10 +1660,10 @@ get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum)
     * can't return anything useful if we have no FmgrInfo or if
     * its fn_expr node has not been initialized
     */
-   if (!fcinfo || !fcinfo->flinfo || !fcinfo->flinfo->fn_expr)
+   if (!flinfo || !flinfo->fn_expr)
        return InvalidOid;
 
-   expr = fcinfo->flinfo->fn_expr;
+   expr = flinfo->fn_expr;
 
    if (IsA(expr, FuncExpr))
        args = ((FuncExpr *) expr)->args;
index 578a6b7d42a639f02263cf3dc45096b99de9d055..b1f16c2d4214b2630297c9d8b3ae0f25802305ea 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_proc.h,v 1.308 2003/06/27 00:33:25 tgl Exp $
+ * $Id: pg_proc.h,v 1.309 2003/07/01 00:04:38 tgl Exp $
  *
  * NOTES
  *   The script catalog/genbki.sh reads this file and generates .bki
@@ -3438,4 +3438,7 @@ extern Oid ProcedureCreate(const char *procedureName,
                int parameterCount,
                const Oid *parameterTypes);
 
+extern void check_sql_fn_retval(Oid rettype, char fn_typtype,
+                               List *queryTreeList);
+
 #endif   /* PG_PROC_H */
index 51844eac38bb794a2f6badd70c00b1b7bf707ad0..1cc6ed023e45a8a0fb233f711883c7e267a211bd 100644 (file)
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: fmgr.h,v 1.29 2003/06/25 21:30:32 momjian Exp $
+ * $Id: fmgr.h,v 1.30 2003/07/01 00:04:39 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -378,8 +378,8 @@ extern Datum OidFunctionCall9(Oid functionId, Datum arg1, Datum arg2,
  */
 extern Pg_finfo_record *fetch_finfo_record(void *filehandle, char *funcname);
 extern Oid fmgr_internal_function(const char *proname);
-extern Oid get_fn_expr_rettype(FunctionCallInfo fcinfo);
-extern Oid get_fn_expr_argtype(FunctionCallInfo fcinfo, int argnum);
+extern Oid get_fn_expr_rettype(FmgrInfo *flinfo);
+extern Oid get_fn_expr_argtype(FmgrInfo *flinfo, int argnum);
 
 /*
  * Routines in dfmgr.c