Attached are two patches to implement and document anonymous composite
authorBruce Momjian
Sun, 4 Aug 2002 19:48:11 +0000 (19:48 +0000)
committerBruce Momjian
Sun, 4 Aug 2002 19:48:11 +0000 (19:48 +0000)
types for Table Functions, as previously proposed on HACKERS. Here is a
brief explanation:

1. Creates a new pg_type typtype: 'p' for pseudo type (currently either
     'b' for base or 'c' for catalog, i.e. a class).

2. Creates new builtin type of typtype='p' named RECORD. This is the
     first of potentially several pseudo types.

3. Modify FROM clause grammer to accept:
     SELECT * FROM my_func() AS m(colname1 type1, colname2 type1, ...)
     where m is the table alias, colname1, etc are the column names, and
     type1, etc are the column types.

4. When typtype == 'p' and the function return type is RECORD, a list
     of column defs is required, and when typtype != 'p', it is
disallowed.

5. A check was added to ensure that the tupdesc provide via the parser
     and the actual return tupdesc match in number and type of
attributes.

When creating a function you can do:
     CREATE FUNCTION foo(text) RETURNS setof RECORD ...

When using it you can do:
     SELECT * from foo(sqlstmt) AS (f1 int, f2 text, f3 timestamp)
       or
     SELECT * from foo(sqlstmt) AS f(f1 int, f2 text, f3 timestamp)
       or
     SELECT * from foo(sqlstmt) f(f1 int, f2 text, f3 timestamp)

Included in the patches are adjustments to the regression test sql and
expected files, and documentation.

p.s.
     This potentially solves (or at least improves) the issue of builtin
     Table Functions. They can be bootstrapped as returning RECORD, and
     we can wrap system views around them with properly specified column
     defs. For example:

     CREATE VIEW pg_settings AS
       SELECT s.name, s.setting
       FROM show_all_settings()AS s(name text, setting text);

     Then we can also add the UPDATE RULE that I previously posted to
     pg_settings, and have pg_settings act like a virtual table, allowing
     settings to be queried and set.

Joe Conway

19 files changed:
doc/src/sgml/ref/select.sgml
src/backend/access/common/tupdesc.c
src/backend/catalog/pg_proc.c
src/backend/executor/functions.c
src/backend/executor/nodeFunctionscan.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/readfuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_clause.c
src/backend/parser/parse_relation.c
src/include/catalog/catversion.h
src/include/catalog/pg_type.h
src/include/nodes/execnodes.h
src/include/nodes/parsenodes.h
src/include/parser/parse_relation.h
src/test/regress/expected/type_sanity.out
src/test/regress/sql/type_sanity.sql

index 9c203f5e57f7e50f0ef058dcbb4c6f0222ac243a..a1970c3e88747f359d5cef188990653cd6357374 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -40,6 +40,12 @@ where from_item can be:
 ( select )
     [ AS ] alias [ ( column_alias_list ) ]
 |
+table_function_name ( [ argtype [, ...] ] )
+    [ AS ] alias [ ( column_alias_list | column_definition_list ) ]
+|
+table_function_name ( [ argtype [, ...] ] )
+    AS ( column_definition_list )
+|
 from_item [ NATURAL ] join_type from_item
     [ ON join_condition | USING ( join_column_list ) ]
   
@@ -82,7 +88,7 @@ where from_item can be:
       from_item
       
        
-        A table reference, sub-SELECT, or JOIN clause.  See below for details.
+        A table reference, sub-SELECT, table function, or JOIN clause.  See below for details.
        
       
      
@@ -156,6 +162,23 @@ where from_item can be:
        
       
      
+
+     
+      table function
+      
+       
+   A table function can appear in the FROM clause.  This acts as though
+   its output were created as a temporary table for the duration of
+   this single SELECT command. An alias may also be used. If an alias is
+   written, a column alias list can also be written to provide substitute names
+   for one or more columns of the table function. If the table function has been
+   defined as returning the RECORD data type, an alias, or the keyword AS, must
+    also be present, followed by a column definition list in the form
+   ( column_name data_type [, ... ] ).
+   The column definition list must match the actual number and types returned by the function.
+       
+      
+     
      
      
       join_type
@@ -380,6 +403,19 @@ where from_item can be:
     grouping, aggregation, or sorting in a single query.
    
 
+   
+    A FROM item can be a table function (i.e. a function that returns
+    multiple rows and columns).  When a table function is created, it may
+   be defined to return a named scalar or composite data type (an existing
+   scalar data type, or a table or view name), or it may be defined to return
+   a RECORD data type. When a table function is defined to return RECORD, it
+   must be followed in the FROM clause by an alias, or the keyword AS alone,
+   and then by a parenthesized list of column names and types. This provides
+   a query-time composite type definition. The FROM clause composite type
+   must match the actual composite type returned from the function or an
+   ERROR will be generated.
+   
+
    
     Finally, a FROM item can be a JOIN clause, which combines two simpler
     FROM items.  (Use parentheses if necessary to determine the order
@@ -925,6 +961,43 @@ SELECT actors.name
  Warren Beatty
  Westward
  Woody Allen
+
+  
+
+  
+   This example shows how to use a table function, both with and without
+   a column definition list.
+
+
+distributors:
+ did |     name
+-----+--------------
+ 108 | Westward
+ 111 | Walt Disney
+ 112 | Warner Bros.
+ ...
+
+CREATE FUNCTION distributors(int)
+  RETURNS SETOF distributors AS '
+  SELECT * FROM distributors WHERE did = $1;
+  ' LANGUAGE SQL;
+
+SELECT * FROM distributors(111);
+ did |    name
+-----+-------------
+ 111 | Walt Disney
+(1 row)
+
+CREATE FUNCTION distributors_2(int)
+  RETURNS SETOF RECORD AS '
+  SELECT * FROM distributors WHERE did = $1;
+  ' LANGUAGE SQL;
+
+SELECT * FROM distributors_2(111) AS (f1 int, f2 text);
+ f1  |     f2
+-----+-------------
+ 111 | Walt Disney
+(1 row)
 
   
  
index 249be8700021127f0e927b9f2d84070de87d52e4..e97f77ce700885dfeec8d6b572134f72e6d076cf 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/access/common/tupdesc.c,v 1.83 2002/08/02 18:15:04 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/access/common/tupdesc.c,v 1.84 2002/08/04 19:48:09 momjian Exp $
  *
  * NOTES
  *   some of the executor utility code such as "ExecTypeFromTL" should be
@@ -24,6 +24,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "nodes/parsenodes.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
 #include "utils/syscache.h"
@@ -600,46 +601,53 @@ RelationNameGetTupleDesc(char *relname)
 TupleDesc
 TypeGetTupleDesc(Oid typeoid, List *colaliases)
 {
-   Oid         relid = typeidTypeRelid(typeoid);
-   TupleDesc   tupdesc;
+   char        functyptype = typeid_get_typtype(typeoid);
+   TupleDesc   tupdesc = NULL;
 
    /*
     * Build a suitable tupledesc representing the output rows
     */
-   if (OidIsValid(relid))
+   if (functyptype == 'c')
    {
        /* Composite data type, i.e. a table's row type */
-       Relation    rel;
-       int         natts;
-
-       rel = relation_open(relid, AccessShareLock);
-       tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
-       natts = tupdesc->natts;
-       relation_close(rel, AccessShareLock);
+       Oid         relid = typeidTypeRelid(typeoid);
 
-       /* check to see if we've given column aliases */
-       if(colaliases != NIL)
+       if (OidIsValid(relid))
        {
-           char       *label;
-           int         varattno;
+           Relation    rel;
+           int         natts;
 
-           /* does the List length match the number of attributes */
-           if (length(colaliases) != natts)
-               elog(ERROR, "TypeGetTupleDesc: number of aliases does not match number of attributes");
+           rel = relation_open(relid, AccessShareLock);
+           tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+           natts = tupdesc->natts;
+           relation_close(rel, AccessShareLock);
 
-           /* OK, use the aliases instead */
-           for (varattno = 0; varattno < natts; varattno++)
+           /* check to see if we've given column aliases */
+           if(colaliases != NIL)
            {
-               label = strVal(nth(varattno, colaliases));
-
-               if (label != NULL)
-                   namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
-               else
-                   MemSet(NameStr(tupdesc->attrs[varattno]->attname), 0, NAMEDATALEN);
+               char       *label;
+               int         varattno;
+
+               /* does the List length match the number of attributes */
+               if (length(colaliases) != natts)
+                   elog(ERROR, "TypeGetTupleDesc: number of aliases does not match number of attributes");
+
+               /* OK, use the aliases instead */
+               for (varattno = 0; varattno < natts; varattno++)
+               {
+                   label = strVal(nth(varattno, colaliases));
+
+                   if (label != NULL)
+                       namestrcpy(&(tupdesc->attrs[varattno]->attname), label);
+                   else
+                       MemSet(NameStr(tupdesc->attrs[varattno]->attname), 0, NAMEDATALEN);
+               }
            }
        }
+       else
+           elog(ERROR, "Invalid return relation specified for function");
    }
-   else
+   else if (functyptype == 'b')
    {
        /* Must be a base data type, i.e. scalar */
        char       *attname;
@@ -664,6 +672,11 @@ TypeGetTupleDesc(Oid typeoid, List *colaliases)
                           0,
                           false);
    }
+   else if (functyptype == 'p' && typeoid == RECORDOID)
+       elog(ERROR, "Unable to determine tuple description for function"
+                       " returning \"record\"");
+   else
+       elog(ERROR, "Unknown kind of return type specified for function");
 
    return tupdesc;
 }
index 2624fdaf25dd5baf43068cb5532b59b1b673897c..f2d8e70589ce909782893a51773bcaf18303d52f 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.82 2002/08/02 18:15:05 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/catalog/pg_proc.c,v 1.83 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,6 +25,7 @@
 #include "miscadmin.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
@@ -33,7 +34,7 @@
 #include "utils/syscache.h"
 
 
-static void checkretval(Oid rettype, List *queryTreeList);
+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);
@@ -367,94 +368,113 @@ checkretval(Oid rettype, List *queryTreeList)
     */
    tlistlen = ExecCleanTargetListLength(tlist);
 
-   /*
-    * For base-type returns, the target list should have exactly one
-    * entry, and its type should agree with what the user declared. (As
-    * of Postgres 7.2, we accept binary-compatible types too.)
-    */
    typerelid = typeidTypeRelid(rettype);
-   if (typerelid == InvalidOid)
+
+   if (fn_typtype == 'b')
    {
-       if (tlistlen != 1)
-           elog(ERROR, "function declared to return %s returns multiple columns in final SELECT",
-                format_type_be(rettype));
+       /*
+        * For base-type returns, the target list should have exactly one
+        * entry, and its type should agree with what the user declared. (As
+        * of Postgres 7.2, we accept binary-compatible types too.)
+        */
 
-       restype = ((TargetEntry *) lfirst(tlist))->resdom->restype;
-       if (!IsBinaryCompatible(restype, rettype))
-           elog(ERROR, "return type mismatch in function: declared to return %s, returns %s",
-                format_type_be(rettype), format_type_be(restype));
+       if (typerelid == InvalidOid)
+       {
+           if (tlistlen != 1)
+               elog(ERROR, "function declared to return %s returns multiple columns in final SELECT",
+                    format_type_be(rettype));
 
-       return;
-   }
+           restype = ((TargetEntry *) lfirst(tlist))->resdom->restype;
+           if (!IsBinaryCompatible(restype, rettype))
+               elog(ERROR, "return type mismatch in function: declared to return %s, returns %s",
+                    format_type_be(rettype), format_type_be(restype));
 
-   /*
-    * 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 *) lfirst(tlist))->resdom->restype;
-       if (IsBinaryCompatible(restype, rettype))
            return;
-   }
-
-   /*
-    * By here, the procedure returns a tuple or set of tuples.  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.
-    */
-   reln = heap_open(typerelid, AccessShareLock);
-   relnatts = reln->rd_rel->relnatts;
-   rellogcols = 0;             /* we'll count nondeleted cols as we go */
-   colindex = 0;
+       }
 
-   foreach(tlistitem, tlist)
+       /*
+        * 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 *) lfirst(tlist))->resdom->restype;
+           if (IsBinaryCompatible(restype, rettype))
+               return;
+       }
+   }
+   else if (fn_typtype == 'c')
    {
-       TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
-       Form_pg_attribute attr;
-       Oid         tletype;
-       Oid         atttype;
+       /*
+        * By here, the procedure returns a tuple or set of tuples.  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.
+        */
+       reln = heap_open(typerelid, AccessShareLock);
+       relnatts = reln->rd_rel->relnatts;
+       rellogcols = 0;             /* we'll count nondeleted cols as we go */
+       colindex = 0;
+
+       foreach(tlistitem, tlist)
+       {
+           TargetEntry *tle = (TargetEntry *) lfirst(tlistitem);
+           Form_pg_attribute attr;
+           Oid         tletype;
+           Oid         atttype;
+
+           if (tle->resdom->resjunk)
+               continue;
+
+           do {
+               colindex++;
+               if (colindex > relnatts)
+                   elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
+                        format_type_be(rettype), rellogcols);
+               attr = reln->rd_att->attrs[colindex - 1];
+           } while (attr->attisdropped);
+           rellogcols++;
 
-       if (tle->resdom->resjunk)
-           continue;
+           tletype = exprType(tle->expr);
+           atttype = attr->atttypid;
+           if (!IsBinaryCompatible(tletype, atttype))
+               elog(ERROR, "function declared to return %s returns %s instead of %s at column %d",
+                    format_type_be(rettype),
+                    format_type_be(tletype),
+                    format_type_be(atttype),
+                    rellogcols);
+       }
 
-       do {
+       for (;;)
+       {
            colindex++;
            if (colindex > relnatts)
-               elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
-                    format_type_be(rettype), rellogcols);
-           attr = reln->rd_att->attrs[colindex - 1];
-       } while (attr->attisdropped);
-       rellogcols++;
-
-       tletype = exprType(tle->expr);
-       atttype = attr->atttypid;
-       if (!IsBinaryCompatible(tletype, atttype))
-           elog(ERROR, "function declared to return %s returns %s instead of %s at column %d",
-                format_type_be(rettype),
-                format_type_be(tletype),
-                format_type_be(atttype),
-                rellogcols);
-   }
+               break;
+           if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
+               rellogcols++;
+       }
 
-   for (;;)
-   {
-       colindex++;
-       if (colindex > relnatts)
-           break;
-       if (!reln->rd_att->attrs[colindex - 1]->attisdropped)
-           rellogcols++;
-   }
+       if (tlistlen != rellogcols)
+           elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
+                format_type_be(rettype), rellogcols);
 
-   if (tlistlen != rellogcols)
-       elog(ERROR, "function declared to return %s does not SELECT the right number of columns (%d)",
-            format_type_be(rettype), rellogcols);
+       heap_close(reln, AccessShareLock);
 
-   heap_close(reln, AccessShareLock);
+       return;
+   }
+   else if (fn_typtype == 'p' && rettype == RECORDOID)
+   {
+       /*
+        * For RECORD return type, defer this check until we get the
+        * first tuple.
+        */
+       return;
+   }
+   else
+       elog(ERROR, "Unknown kind of return type specified for function");
 }
 
 
@@ -553,6 +573,7 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
    bool        isnull;
    Datum       tmp;
    char       *prosrc;
+   char        functyptype;
 
    tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
    if (!HeapTupleIsValid(tuple))
@@ -569,8 +590,11 @@ fmgr_sql_validator(PG_FUNCTION_ARGS)
 
    prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
 
+   /* check typtype to see if we have a predetermined return type */
+   functyptype = typeid_get_typtype(proc->prorettype);
+
    querytree_list = pg_parse_and_rewrite(prosrc, proc->proargtypes, proc->pronargs);
-   checkretval(proc->prorettype, querytree_list);
+   checkretval(proc->prorettype, functyptype, querytree_list);
 
    ReleaseSysCache(tuple);
    PG_RETURN_BOOL(true);
index c7b7c398eb27344df9ff7264a841b925c7c4b96d..ab414e1958130e38765e9608cd1ef563fec68038 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.52 2002/06/20 20:29:28 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.53 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -194,7 +194,8 @@ init_sql_fcache(FmgrInfo *finfo)
     * get the type length and by-value flag from the type tuple
     */
    fcache->typlen = typeStruct->typlen;
-   if (typeStruct->typrelid == InvalidOid)
+
+   if (typeStruct->typtype == 'b')
    {
        /* The return type is not a relation, so just use byval */
        fcache->typbyval = typeStruct->typbyval;
index d87f8e6fe887abb7c696ab9454a19e19c90d5980..66c418c8ba62194da915127a988ce39745f64daa 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.3 2002/07/20 05:16:58 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.4 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,6 +31,7 @@
 #include "executor/nodeFunctionscan.h"
 #include "parser/parsetree.h"
 #include "parser/parse_expr.h"
+#include "parser/parse_relation.h"
 #include "parser/parse_type.h"
 #include "storage/lmgr.h"
 #include "tcop/pquery.h"
 #include "utils/tuplestore.h"
 
 static TupleTableSlot *FunctionNext(FunctionScan *node);
-static TupleTableSlot *function_getonetuple(TupleTableSlot *slot,
-                                           Node *expr,
-                                           ExprContext *econtext,
-                                           TupleDesc tupdesc,
-                                           bool returnsTuple,
+static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate,
                                            bool *isNull,
                                            ExprDoneCond *isDone);
 static FunctionMode get_functionmode(Node *expr);
+static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2);
 
 /* ----------------------------------------------------------------
  *                     Scan Support
@@ -62,9 +60,6 @@ static TupleTableSlot *
 FunctionNext(FunctionScan *node)
 {
    TupleTableSlot     *slot;
-   Node               *expr;
-   ExprContext        *econtext;
-   TupleDesc           tupdesc;
    EState             *estate;
    ScanDirection       direction;
    Tuplestorestate    *tuplestorestate;
@@ -78,11 +73,8 @@ FunctionNext(FunctionScan *node)
    scanstate = (FunctionScanState *) node->scan.scanstate;
    estate = node->scan.plan.state;
    direction = estate->es_direction;
-   econtext = scanstate->csstate.cstate.cs_ExprContext;
 
    tuplestorestate = scanstate->tuplestorestate;
-   tupdesc = scanstate->tupdesc;
-   expr = scanstate->funcexpr;
 
    /*
     * If first time through, read all tuples from function and pass them to
@@ -108,10 +100,7 @@ FunctionNext(FunctionScan *node)
 
            isNull = false;
            isDone = ExprSingleResult;
-           slot = function_getonetuple(scanstate->csstate.css_ScanTupleSlot,
-                                       expr, econtext, tupdesc,
-                                       scanstate->returnsTuple,
-                                       &isNull, &isDone);
+           slot = function_getonetuple(scanstate, &isNull, &isDone);
            if (TupIsNull(slot))
                break;
 
@@ -169,7 +158,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
    RangeTblEntry      *rte;
    Oid                 funcrettype;
    Oid                 funcrelid;
-   TupleDesc           tupdesc;
+   char                functyptype;
+   TupleDesc           tupdesc = NULL;
 
    /*
     * FunctionScan should not have any children.
@@ -209,25 +199,36 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
    rte = rt_fetch(node->scan.scanrelid, estate->es_range_table);
    Assert(rte->rtekind == RTE_FUNCTION);
    funcrettype = exprType(rte->funcexpr);
-   funcrelid = typeidTypeRelid(funcrettype);
+
+   /*
+    * Now determine if the function returns a simple or composite type,
+    * and check/add column aliases.
+    */
+   functyptype = typeid_get_typtype(funcrettype);
 
    /*
     * Build a suitable tupledesc representing the output rows
     */
-   if (OidIsValid(funcrelid))
+   if (functyptype == 'c')
    {
-       /*
-        * Composite data type, i.e. a table's row type
-        * Same as ordinary relation RTE
-        */
-       Relation    rel;
+       funcrelid = typeidTypeRelid(funcrettype);
+       if (OidIsValid(funcrelid))
+       {
+           /*
+            * Composite data type, i.e. a table's row type
+            * Same as ordinary relation RTE
+            */
+           Relation    rel;
 
-       rel = relation_open(funcrelid, AccessShareLock);
-       tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
-       relation_close(rel, AccessShareLock);
-       scanstate->returnsTuple = true;
+           rel = relation_open(funcrelid, AccessShareLock);
+           tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+           relation_close(rel, AccessShareLock);
+           scanstate->returnsTuple = true;
+       }
+       else
+           elog(ERROR, "Invalid return relation specified for function");
    }
-   else
+   else if (functyptype == 'b')
    {
        /*
         * Must be a base data type, i.e. scalar
@@ -244,6 +245,21 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, Plan *parent)
                           false);
        scanstate->returnsTuple = false;
    }
+   else if (functyptype == 'p' && funcrettype == RECORDOID)
+   {
+       /*
+        * Must be a pseudo type, i.e. record
+        */
+       List *coldeflist = rte->coldeflist;
+
+       tupdesc = BuildDescForRelation(coldeflist);
+       scanstate->returnsTuple = true;
+   }
+   else
+       elog(ERROR, "Unknown kind of return type specified for function");
+
+   scanstate->fn_typeid = funcrettype;
+   scanstate->fn_typtype = functyptype;
    scanstate->tupdesc = tupdesc;
    ExecSetSlotDescriptor(scanstate->csstate.css_ScanTupleSlot,
                          tupdesc, false);
@@ -404,17 +420,20 @@ ExecFunctionReScan(FunctionScan *node, ExprContext *exprCtxt, Plan *parent)
  * Run the underlying function to get the next tuple
  */
 static TupleTableSlot *
-function_getonetuple(TupleTableSlot *slot,
-                    Node *expr,
-                    ExprContext *econtext,
-                    TupleDesc tupdesc,
-                    bool returnsTuple,
+function_getonetuple(FunctionScanState *scanstate,
                     bool *isNull,
                     ExprDoneCond *isDone)
 {
-   HeapTuple           tuple;
-   Datum               retDatum;
-   char                nullflag;
+   HeapTuple       tuple;
+   Datum           retDatum;
+   char            nullflag;
+   TupleDesc       tupdesc = scanstate->tupdesc;
+   bool            returnsTuple = scanstate->returnsTuple;
+   Node           *expr = scanstate->funcexpr;
+   Oid             fn_typeid = scanstate->fn_typeid;
+   char            fn_typtype = scanstate->fn_typtype;
+   ExprContext    *econtext = scanstate->csstate.cstate.cs_ExprContext;
+   TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
 
    /*
     * get the next Datum from the function
@@ -435,6 +454,16 @@ function_getonetuple(TupleTableSlot *slot,
             * function returns pointer to tts??
             */
            slot = (TupleTableSlot *) retDatum;
+
+           /*
+            * if function return type was RECORD, we need to check to be
+            * sure the structure from the query matches the actual return
+            * structure
+            */
+           if (fn_typtype == 'p' && fn_typeid == RECORDOID)
+               if (tupledesc_mismatch(tupdesc, slot->ttc_tupleDescriptor))
+                   elog(ERROR, "Query specified return tuple and actual"
+                                   " function return tuple do not match");
        }
        else
        {
@@ -467,3 +496,26 @@ get_functionmode(Node *expr)
     */
    return PM_REPEATEDCALL;
 }
+
+static bool
+tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2)
+{
+   int         i;
+
+   if (tupdesc1->natts != tupdesc2->natts)
+       return true;
+
+   for (i = 0; i < tupdesc1->natts; i++)
+   {
+       Form_pg_attribute attr1 = tupdesc1->attrs[i];
+       Form_pg_attribute attr2 = tupdesc2->attrs[i];
+
+       /*
+        * We really only care about number of attributes and data type
+        */
+       if (attr1->atttypid != attr2->atttypid)
+           return true;
+   }
+
+   return false;
+}
index d7aefc9acfefda4528e56ac43f818eef9aa271e7..954a372181a927691d0eb18676138915f9f0d8f9 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.199 2002/08/04 04:31:44 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.200 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1482,6 +1482,7 @@ _copyRangeTblEntry(RangeTblEntry *from)
    newnode->relid = from->relid;
    Node_Copy(from, newnode, subquery);
    Node_Copy(from, newnode, funcexpr);
+   Node_Copy(from, newnode, coldeflist);
    newnode->jointype = from->jointype;
    Node_Copy(from, newnode, joinaliasvars);
    Node_Copy(from, newnode, alias);
@@ -1707,6 +1708,7 @@ _copyRangeFunction(RangeFunction *from)
 
    Node_Copy(from, newnode, funccallnode);
    Node_Copy(from, newnode, alias);
+   Node_Copy(from, newnode, coldeflist);
 
    return newnode;
 }
index 9b655640b0dcbf99178fadb2f9ebd1f220b1e697..da7567e7c335e84961420cc41858247c00a4ef6b 100644 (file)
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.146 2002/08/04 04:31:44 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.147 2002/08/04 19:48:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1607,6 +1607,8 @@ _equalRangeVar(RangeVar *a, RangeVar *b)
        return false;
    if (!equal(a->alias, b->alias))
        return false;
+   if (!equal(a->coldeflist, b->coldeflist))
+       return false;
 
    return true;
 }
@@ -1742,6 +1744,8 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
        return false;
    if (!equal(a->funcexpr, b->funcexpr))
        return false;
+   if (!equal(a->coldeflist, b->coldeflist))
+       return false;
    if (a->jointype != b->jointype)
        return false;
    if (!equal(a->joinaliasvars, b->joinaliasvars))
index c4dfbec67d0b6335d651eb95b4e63536e09b6144..b992e45a62fa97db1069a0e55b554c1ee518cf32 100644 (file)
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.165 2002/07/18 17:14:19 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.166 2002/08/04 19:48:09 momjian Exp $
  *
  * NOTES
  *   Every (plan) node in POSTGRES has an associated "out" routine which
@@ -1004,6 +1004,8 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
        case RTE_FUNCTION:
            appendStringInfo(str, ":funcexpr ");
            _outNode(str, node->funcexpr);
+           appendStringInfo(str, ":coldeflist ");
+           _outNode(str, node->coldeflist);
            break;
        case RTE_JOIN:
            appendStringInfo(str, ":jointype %d :joinaliasvars ",
index ba9eb0449ee3d9776e7b60141bc6f4f6b9be29eb..46b2ca2dc85699e9fb8c53224fc073df4cb868b1 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.126 2002/07/18 17:14:19 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.127 2002/08/04 19:48:09 momjian Exp $
  *
  * NOTES
  *   Most of the read functions for plan nodes are tested. (In fact, they
@@ -1545,6 +1545,10 @@ _readRangeTblEntry(void)
        case RTE_FUNCTION:
            token = pg_strtok(&length); /* eat :funcexpr */
            local_node->funcexpr = nodeRead(true);      /* now read it */
+
+           token = pg_strtok(&length); /* eat :coldeflist */
+           local_node->coldeflist = nodeRead(true);    /* now read it */
+
            break;
 
        case RTE_JOIN:
index c794dd7d80e1a34afeeb52a2fc9f186d8074ac74..53759b8029658a1de0baf48ac323a499d4a12c00 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.354 2002/08/04 06:51:23 thomas Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.355 2002/08/04 19:48:09 momjian Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -215,7 +215,8 @@ static void doNegateFloat(Value *v);
                target_list, update_target_list, insert_column_list,
                insert_target_list, def_list, opt_indirection,
                group_clause, TriggerFuncArgs, select_limit,
-               opt_select_limit, opclass_item_list, trans_options
+               opt_select_limit, opclass_item_list, trans_options,
+               tableFuncElementList
 
 %type   into_clause, OptTempTableName
 
@@ -256,8 +257,8 @@ static void doNegateFloat(Value *v);
 
 %type  set_rest
 
-%type    OptTableElement, ConstraintElem
-%type    columnDef
+%type    OptTableElement, ConstraintElem, tableFuncElement
+%type    columnDef, tableFuncColumnDef
 %type  def_elem
 %type    def_arg, columnElem, where_clause, insert_column_item,
                a_expr, b_expr, c_expr, r_expr, AexprConst,
@@ -4448,6 +4449,34 @@ table_ref:   relation_expr
                {
                    RangeFunction *n = makeNode(RangeFunction);
                    n->funccallnode = $1;
+                   n->coldeflist = NIL;
+                   $$ = (Node *) n;
+               }
+           | func_table AS '(' tableFuncElementList ')'
+               {
+                   RangeFunction *n = makeNode(RangeFunction);
+                   n->funccallnode = $1;
+                   n->coldeflist = $4;
+                   $$ = (Node *) n;
+               }
+           | func_table AS ColId '(' tableFuncElementList ')'
+               {
+                   RangeFunction *n = makeNode(RangeFunction);
+                   Alias *a = makeNode(Alias);
+                   n->funccallnode = $1;
+                   a->aliasname = $3;
+                   n->alias = a;
+                   n->coldeflist = $5;
+                   $$ = (Node *) n;
+               }
+           | func_table ColId '(' tableFuncElementList ')'
+               {
+                   RangeFunction *n = makeNode(RangeFunction);
+                   Alias *a = makeNode(Alias);
+                   n->funccallnode = $1;
+                   a->aliasname = $2;
+                   n->alias = a;
+                   n->coldeflist = $4;
                    $$ = (Node *) n;
                }
            | func_table alias_clause
@@ -4455,6 +4484,7 @@ table_ref:    relation_expr
                    RangeFunction *n = makeNode(RangeFunction);
                    n->funccallnode = $1;
                    n->alias = $2;
+                   n->coldeflist = NIL;
                    $$ = (Node *) n;
                }
            | select_with_parens
@@ -4703,6 +4733,39 @@ where_clause:
        ;
 
 
+tableFuncElementList:
+           tableFuncElementList ',' tableFuncElement
+               {
+                   if ($3 != NULL)
+                       $$ = lappend($1, $3);
+                   else
+                       $$ = $1;
+               }
+           | tableFuncElement
+               {
+                   if ($1 != NULL)
+                       $$ = makeList1($1);
+                   else
+                       $$ = NIL;
+               }
+           | /*EMPTY*/                         { $$ = NIL; }
+       ;
+
+tableFuncElement:
+           tableFuncColumnDef                  { $$ = $1; }
+       ;
+
+tableFuncColumnDef:    ColId Typename
+               {
+                   ColumnDef *n = makeNode(ColumnDef);
+                   n->colname = $1;
+                   n->typename = $2;
+                   n->constraints = NIL;
+
+                   $$ = (Node *)n;
+               }
+       ;
+
 /*****************************************************************************
  *
  * Type syntax
index 81328798eb57529f3efaa1db735273dbe04fb0bb..ae87b056f3e8bd0fc531a81751a1dfba97e2a141 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.94 2002/06/20 20:29:32 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.95 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -515,7 +515,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
     * OK, build an RTE for the function.
     */
    rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
-                                       r->alias, true);
+                                       r, true);
 
    /*
     * We create a RangeTblRef, but we do not add it to the joinlist or
index 99b639d73e37be865e2590d6644daec224ed60bd..6a4bda5d62bc5b95f135d66ae7b3456a1d563d7b 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.71 2002/08/02 18:15:07 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.72 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -699,12 +699,14 @@ RangeTblEntry *
 addRangeTableEntryForFunction(ParseState *pstate,
                              char *funcname,
                              Node *funcexpr,
-                             Alias *alias,
+                             RangeFunction *rangefunc,
                              bool inFromCl)
 {
    RangeTblEntry *rte = makeNode(RangeTblEntry);
    Oid         funcrettype = exprType(funcexpr);
-   Oid         funcrelid;
+   char        functyptype;
+   Alias      *alias = rangefunc->alias;
+   List       *coldeflist = rangefunc->coldeflist;
    Alias      *eref;
    int         numaliases;
    int         varattno;
@@ -713,6 +715,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
    rte->relid = InvalidOid;
    rte->subquery = NULL;
    rte->funcexpr = funcexpr;
+   rte->coldeflist = coldeflist;
    rte->alias = alias;
 
    eref = alias ? (Alias *) copyObject(alias) : makeAlias(funcname, NIL);
@@ -724,47 +727,56 @@ addRangeTableEntryForFunction(ParseState *pstate,
     * Now determine if the function returns a simple or composite type,
     * and check/add column aliases.
     */
-   funcrelid = typeidTypeRelid(funcrettype);
+   functyptype = typeid_get_typtype(funcrettype);
 
-   if (OidIsValid(funcrelid))
+   if (functyptype == 'c')
    {
        /*
-        * Composite data type, i.e. a table's row type
-        *
-        * Get the rel's relcache entry.  This access ensures that we have an
-        * up-to-date relcache entry for the rel.
+        * Named composite data type, i.e. a table's row type
         */
-       Relation    rel;
-       int         maxattrs;
+       Oid         funcrelid = typeidTypeRelid(funcrettype);
 
-       rel = heap_open(funcrelid, AccessShareLock);
+       if (OidIsValid(funcrelid))
+       {
+           /*
+            * Get the rel's relcache entry.  This access ensures that we have an
+            * up-to-date relcache entry for the rel.
+            */
+           Relation    rel;
+           int         maxattrs;
 
-       /*
-        * Since the rel is open anyway, let's check that the number of column
-        * aliases is reasonable.
-        */
-       maxattrs = RelationGetNumberOfAttributes(rel);
-       if (maxattrs < numaliases)
-           elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
-                RelationGetRelationName(rel), maxattrs, numaliases);
+           rel = heap_open(funcrelid, AccessShareLock);
 
-       /* fill in alias columns using actual column names */
-       for (varattno = numaliases; varattno < maxattrs; varattno++)
-       {
-           char       *attrname;
+           /*
+            * Since the rel is open anyway, let's check that the number of column
+            * aliases is reasonable.
+            */
+           maxattrs = RelationGetNumberOfAttributes(rel);
+           if (maxattrs < numaliases)
+               elog(ERROR, "Table \"%s\" has %d columns available but %d columns specified",
+                    RelationGetRelationName(rel), maxattrs, numaliases);
 
-           attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
-           eref->colnames = lappend(eref->colnames, makeString(attrname));
-       }
+           /* fill in alias columns using actual column names */
+           for (varattno = numaliases; varattno < maxattrs; varattno++)
+           {
+               char       *attrname;
 
-       /*
-        * 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.
-        */
-       heap_close(rel, NoLock);
+               attrname = pstrdup(NameStr(rel->rd_att->attrs[varattno]->attname));
+               eref->colnames = lappend(eref->colnames, makeString(attrname));
+           }
+
+           /*
+            * 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.
+            */
+           heap_close(rel, NoLock);
+       }
+       else
+           elog(ERROR, "Invalid return relation specified for function %s",
+                funcname);
    }
-   else
+   else if (functyptype == 'b')
    {
        /*
         * Must be a base data type, i.e. scalar.
@@ -776,6 +788,22 @@ addRangeTableEntryForFunction(ParseState *pstate,
        if (numaliases == 0)
            eref->colnames = makeList1(makeString(funcname));
    }
+   else if (functyptype == 'p' && funcrettype == RECORDOID)
+   {
+       List       *col;
+
+       foreach(col, coldeflist)
+       {
+           char       *attrname;
+           ColumnDef  *n = lfirst(col);
+
+           attrname = pstrdup(n->colname);
+           eref->colnames = lappend(eref->colnames, makeString(attrname));
+       }
+   }
+   else
+       elog(ERROR, "Unknown kind of return type specified for function %s",
+            funcname);
 
    /*----------
     * Flags:
@@ -1051,56 +1079,70 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
        case RTE_FUNCTION:
            {
                /* Function RTE */
-               Oid         funcrettype = exprType(rte->funcexpr);
-               Oid         funcrelid = typeidTypeRelid(funcrettype);
+               Oid funcrettype = exprType(rte->funcexpr);
+               char functyptype = typeid_get_typtype(funcrettype);
+               List *coldeflist = rte->coldeflist;
 
-               if (OidIsValid(funcrelid))
+               /*
+                * Build a suitable tupledesc representing the output rows
+                */
+               if (functyptype == 'c')
                {
-                   /*
-                    * Composite data type, i.e. a table's row type
-                    * Same as ordinary relation RTE
-                    */
-                   Relation    rel;
-                   int         maxattrs;
-                   int         numaliases;
-
-                   rel = heap_open(funcrelid, AccessShareLock);
-                   maxattrs = RelationGetNumberOfAttributes(rel);
-                   numaliases = length(rte->eref->colnames);
-
-                   for (varattno = 0; varattno < maxattrs; varattno++)
+                   Oid funcrelid = typeidTypeRelid(funcrettype);
+                   if (OidIsValid(funcrelid))
                    {
-                       Form_pg_attribute attr = rel->rd_att->attrs[varattno];
 
-                       if (attr->attisdropped)
-                           continue;
+                       /*
+                        * Composite data type, i.e. a table's row type
+                        * Same as ordinary relation RTE
+                        */
+                       Relation    rel;
+                       int         maxattrs;
+                       int         numaliases;
 
-                       if (colnames)
-                       {
-                           char       *label;
+                       rel = heap_open(funcrelid, AccessShareLock);
+                       maxattrs = RelationGetNumberOfAttributes(rel);
+                       numaliases = length(rte->eref->colnames);
 
-                           if (varattno < numaliases)
-                               label = strVal(nth(varattno, rte->eref->colnames));
-                           else
-                               label = NameStr(attr->attname);
-                           *colnames = lappend(*colnames, makeString(pstrdup(label)));
-                       }
-
-                       if (colvars)
+                       for (varattno = 0; varattno < maxattrs; varattno++)
                        {
-                           Var        *varnode;
-
-                           varnode = makeVar(rtindex, attr->attnum,
-                                             attr->atttypid, attr->atttypmod,
-                                             sublevels_up);
-
-                           *colvars = lappend(*colvars, varnode);
+                           Form_pg_attribute attr = rel->rd_att->attrs[varattno];
+
+                           if (attr->attisdropped)
+                               continue;
+
+                           if (colnames)
+                           {
+                               char       *label;
+
+                               if (varattno < numaliases)
+                                   label = strVal(nth(varattno, rte->eref->colnames));
+                               else
+                                   label = NameStr(attr->attname);
+                               *colnames = lappend(*colnames, makeString(pstrdup(label)));
+                           }
+
+                           if (colvars)
+                           {
+                               Var        *varnode;
+
+                               varnode = makeVar(rtindex,
+                                               attr->attnum,
+                                               attr->atttypid,
+                                               attr->atttypmod,
+                                               sublevels_up);
+
+                               *colvars = lappend(*colvars, varnode);
+                           }
                        }
-                   }
 
-                   heap_close(rel, AccessShareLock);
+                       heap_close(rel, AccessShareLock);
+                   }
+                   else
+                       elog(ERROR, "Invalid return relation specified"
+                                   " for function");
                }
-               else
+               else if (functyptype == 'b')
                {
                    /*
                     * Must be a base data type, i.e. scalar
@@ -1120,6 +1162,47 @@ expandRTE(ParseState *pstate, RangeTblEntry *rte,
                        *colvars = lappend(*colvars, varnode);
                    }
                }
+               else if (functyptype == 'p' && funcrettype == RECORDOID)
+               {
+                   List       *col;
+                   int         attnum = 0;
+
+                   foreach(col, coldeflist)
+                   {
+                       ColumnDef  *colDef = lfirst(col);
+
+                       attnum++;
+                       if (colnames)
+                       {
+                           char       *attrname;
+
+                           attrname = pstrdup(colDef->colname);
+                           *colnames = lappend(*colnames, makeString(attrname));
+                       }
+
+                       if (colvars)
+                       {
+                           Var        *varnode;
+                           HeapTuple   typeTuple;
+                           Oid         atttypid;
+
+                           typeTuple = typenameType(colDef->typename);
+                           atttypid = HeapTupleGetOid(typeTuple);
+                           ReleaseSysCache(typeTuple);
+
+                           varnode = makeVar(rtindex,
+                                           attnum,
+                                           atttypid,
+                                           -1,
+                                           sublevels_up);
+
+                           *colvars = lappend(*colvars, varnode);
+                       }
+                   }
+               }
+               else
+                   elog(ERROR, "Unknown kind of return type specified"
+                               " for function");
            }
            break;
        case RTE_JOIN:
@@ -1308,40 +1391,52 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
        case RTE_FUNCTION:
            {
                /* Function RTE */
-               Oid         funcrettype = exprType(rte->funcexpr);
-               Oid         funcrelid = typeidTypeRelid(funcrettype);
+               Oid funcrettype = exprType(rte->funcexpr);
+               char functyptype = typeid_get_typtype(funcrettype);
+               List *coldeflist = rte->coldeflist;
 
-               if (OidIsValid(funcrelid))
+               /*
+                * Build a suitable tupledesc representing the output rows
+                */
+               if (functyptype == 'c')
                {
                    /*
                     * Composite data type, i.e. a table's row type
                     * Same as ordinary relation RTE
                     */
-                   HeapTuple           tp;
-                   Form_pg_attribute   att_tup;
+                   Oid funcrelid = typeidTypeRelid(funcrettype);
 
-                   tp = SearchSysCache(ATTNUM,
-                                       ObjectIdGetDatum(funcrelid),
-                                       Int16GetDatum(attnum),
-                                       0, 0);
-                   /* this shouldn't happen... */
-                   if (!HeapTupleIsValid(tp))
-                       elog(ERROR, "Relation %s does not have attribute %d",
-                            get_rel_name(funcrelid), attnum);
-                   att_tup = (Form_pg_attribute) GETSTRUCT(tp);
-                   /*
-                    * If dropped column, pretend it ain't there.  See notes
-                    * in scanRTEForColumn.
-                    */
-                   if (att_tup->attisdropped)
-                       elog(ERROR, "Relation \"%s\" has no column \"%s\"",
-                            get_rel_name(funcrelid),
-                            NameStr(att_tup->attname));
-                   *vartype = att_tup->atttypid;
-                   *vartypmod = att_tup->atttypmod;
-                   ReleaseSysCache(tp);
+                   if (OidIsValid(funcrelid))
+                   {
+                       HeapTuple           tp;
+                       Form_pg_attribute   att_tup;
+
+                       tp = SearchSysCache(ATTNUM,
+                                           ObjectIdGetDatum(funcrelid),
+                                           Int16GetDatum(attnum),
+                                           0, 0);
+                       /* this shouldn't happen... */
+                       if (!HeapTupleIsValid(tp))
+                           elog(ERROR, "Relation %s does not have attribute %d",
+                                get_rel_name(funcrelid), attnum);
+                       att_tup = (Form_pg_attribute) GETSTRUCT(tp);
+                       /*
+                        * If dropped column, pretend it ain't there.  See notes
+                        * in scanRTEForColumn.
+                        */
+                       if (att_tup->attisdropped)
+                           elog(ERROR, "Relation \"%s\" has no column \"%s\"",
+                                get_rel_name(funcrelid),
+                                NameStr(att_tup->attname));
+                       *vartype = att_tup->atttypid;
+                       *vartypmod = att_tup->atttypmod;
+                       ReleaseSysCache(tp);
+                   }
+                   else
+                       elog(ERROR, "Invalid return relation specified"
+                                   " for function");
                }
-               else
+               else if (functyptype == 'b')
                {
                    /*
                     * Must be a base data type, i.e. scalar
@@ -1349,6 +1444,22 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
                    *vartype = funcrettype;
                    *vartypmod = -1;
                }
+               else if (functyptype == 'p' && funcrettype == RECORDOID)
+               {
+                   ColumnDef  *colDef = nth(attnum - 1, coldeflist);
+                   HeapTuple   typeTuple;
+                   Oid         atttypid;
+
+                   typeTuple = typenameType(colDef->typename);
+                   atttypid = HeapTupleGetOid(typeTuple);
+                   ReleaseSysCache(typeTuple);
+
+                   *vartype = atttypid;
+                   *vartypmod = -1;
+               }
+               else
+                   elog(ERROR, "Unknown kind of return type specified"
+                               " for function");
            }
            break;
        case RTE_JOIN:
@@ -1576,3 +1687,28 @@ warnAutoRange(ParseState *pstate, RangeVar *relation)
             pstate->parentParseState != NULL ? " in subquery" : "",
             relation->relname);
 }
+
+char
+typeid_get_typtype(Oid typeid)
+{
+   HeapTuple       typeTuple;
+   Form_pg_type    typeStruct;
+   char            result;
+
+   /*
+    * determine if the function returns a simple, named composite,
+    * or anonymous composite type
+    */
+   typeTuple = SearchSysCache(TYPEOID,
+                              ObjectIdGetDatum(typeid),
+                              0, 0, 0);
+   if (!HeapTupleIsValid(typeTuple))
+       elog(ERROR, "cache lookup for type %u failed", typeid);
+   typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
+   result = typeStruct->typtype;
+
+   ReleaseSysCache(typeTuple);
+
+   return result;
+}
index f3e36226b015a5dcb1981e2abd63435a8568321a..12669ed78ddc9b200bd909169d533e21ffea8758 100644 (file)
@@ -37,7 +37,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: catversion.h,v 1.144 2002/08/02 18:15:09 tgl Exp $
+ * $Id: catversion.h,v 1.145 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 200208011
+#define CATALOG_VERSION_NO 200208041
 
 #endif
index 1f801611a0559c84447daba09e0c9c98d17a0749..6bc4a2a92aec82dd28183e4a1675b777c7165a47 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: pg_type.h,v 1.125 2002/07/24 19:11:13 petere Exp $
+ * $Id: pg_type.h,v 1.126 2002/08/04 19:48:10 momjian Exp $
  *
  * NOTES
  *   the genbki.sh script reads this file and generates .bki
@@ -60,10 +60,10 @@ CATALOG(pg_type) BOOTSTRAP
    bool        typbyval;
 
    /*
-    * typtype is 'b' for a basic type and 'c' for a catalog type (ie a
-    * class). If typtype is 'c', typrelid is the OID of the class' entry
-    * in pg_class. (Why do we need an entry in pg_type for classes,
-    * anyway?)
+    * typtype is 'b' for a basic type, 'c' for a catalog type (ie a
+    * class), or 'p' for a pseudo type. If typtype is 'c', typrelid is the
+    * OID of the class' entry in pg_class. (Why do we need an entry in
+    * pg_type for classes, anyway?)
     */
    char        typtype;
 
@@ -501,6 +501,16 @@ DATA(insert OID = 2209 ( _regoperator  PGNSP PGUID -1 f b t \054 0 2204 array_in
 DATA(insert OID = 2210 ( _regclass     PGNSP PGUID -1 f b t \054 0 2205 array_in array_out i x f 0 -1 0 _null_ _null_ ));
 DATA(insert OID = 2211 ( _regtype      PGNSP PGUID -1 f b t \054 0 2206 array_in array_out i x f 0 -1 0 _null_ _null_ ));
 
+/*
+ * pseudo-types 
+ *
+ * types with typtype='p' are special types that represent classes of types
+ * that are not easily defined in advance. Currently there is only one pseudo
+ * type -- record. The record type is used to specify that the value is a
+ * tuple, but of unknown structure until runtime. 
+ */
+DATA(insert OID = 2249 ( record        PGNSP PGUID  4 t p t \054 0 0 oidin oidout          i p f 0 -1 0 _null_ _null_ ));
+#define RECORDOID      2249
 
 /*
  * prototypes for functions in pg_type.c
index 2dea171e255e0672e44737c6b631809697d63fcb..ee25cc62c95a44e282e7019ec272fdf0689c37b0 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: execnodes.h,v 1.70 2002/06/20 20:29:49 momjian Exp $
+ * $Id: execnodes.h,v 1.71 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -509,11 +509,17 @@ typedef struct SubqueryScanState
  *     Function nodes are used to scan the results of a
  *     function appearing in FROM (typically a function returning set).
  *
- *     functionmode            function operating mode:
+ *     functionmode        function operating mode:
  *                         - repeated call
  *                         - materialize
  *                         - return query
+ *     tupdesc             function's return tuple description
  *     tuplestorestate     private state of tuplestore.c
+ *     funcexpr            function expression being evaluated
+ *     returnsTuple        does function return tuples?
+ *     fn_typeid           OID of function return type
+ *     fn_typtype          return Datum type, i.e. 'b'ase,
+ *                         'c'atalog, or 'p'seudo
  * ----------------
  */
 typedef enum FunctionMode
@@ -525,12 +531,14 @@ typedef enum FunctionMode
 
 typedef struct FunctionScanState
 {
-   CommonScanState csstate;    /* its first field is NodeTag */
+   CommonScanState csstate;        /* its first field is NodeTag */
    FunctionMode    functionmode;
    TupleDesc       tupdesc;
    void           *tuplestorestate;
-   Node           *funcexpr;   /* function expression being evaluated */
-   bool            returnsTuple; /* does function return tuples? */
+   Node           *funcexpr;
+   bool            returnsTuple;
+   Oid             fn_typeid;
+   char            fn_typtype;
 } FunctionScanState;
 
 /* ----------------------------------------------------------------
index 07e985b377bb607b971b44714c9088afdc7137ae..765d1ca0514795c7498737b8647b87b9a1447240 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: parsenodes.h,v 1.197 2002/08/04 04:31:44 momjian Exp $
+ * $Id: parsenodes.h,v 1.198 2002/08/04 19:48:10 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -400,6 +400,8 @@ typedef struct RangeFunction
    NodeTag     type;
    Node       *funccallnode;   /* untransformed function call tree */
    Alias      *alias;          /* table alias & optional column aliases */
+   List       *coldeflist;     /* list of ColumnDef nodes for runtime
+                                * assignment of RECORD TupleDesc */
 } RangeFunction;
 
 /*
@@ -527,6 +529,8 @@ typedef struct RangeTblEntry
     * Fields valid for a function RTE (else NULL):
     */
    Node       *funcexpr;       /* expression tree for func call */
+   List       *coldeflist;     /* list of ColumnDef nodes for runtime
+                                * assignment of RECORD TupleDesc */
 
    /*
     * Fields valid for a join RTE (else NULL/zero):
index a34e2a5619f086cd642fd93a402093cf944319df..7abfd047865200366b801fc4ef116b15e686321e 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: parse_relation.h,v 1.35 2002/08/02 18:15:09 tgl Exp $
+ * $Id: parse_relation.h,v 1.36 2002/08/04 19:48:11 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,7 +44,7 @@ extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
 extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
                                                    char *funcname,
                                                    Node *funcexpr,
-                                                   Alias *alias,
+                                                   RangeFunction *rangefunc,
                                                    bool inFromCl);
 extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
                          List *colnames,
@@ -61,5 +61,6 @@ extern List *expandRelAttrs(ParseState *pstate, RangeTblEntry *rte);
 extern int attnameAttNum(Relation rd, const char *attname, bool sysColOK);
 extern Name attnumAttName(Relation rd, int attid);
 extern Oid attnumTypeId(Relation rd, int attid);
+extern char typeid_get_typtype(Oid typeid);
 
 #endif   /* PARSE_RELATION_H */
index 711628c2e40380ace2d0807706e15c6c18dc4af1..949393386a4d400fda7195054f09c4c559e7b7f3 100644 (file)
@@ -16,7 +16,7 @@
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
 WHERE (p1.typlen <= 0 AND p1.typlen != -1) OR
-    (p1.typtype != 'b' AND p1.typtype != 'c') OR
+    (p1.typtype != 'b' AND p1.typtype != 'c' AND p1.typtype != 'p') OR
     NOT p1.typisdefined OR
     (p1.typalign != 'c' AND p1.typalign != 's' AND
      p1.typalign != 'i' AND p1.typalign != 'd') OR
@@ -60,7 +60,7 @@ WHERE (p1.typtype = 'c' AND p1.typrelid = 0) OR
 -- NOTE: as of 7.3, this check finds SET, smgr, and unknown.
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
-WHERE p1.typtype != 'c' AND p1.typname NOT LIKE '\\_%' AND NOT EXISTS
+WHERE p1.typtype = 'b' AND p1.typname NOT LIKE '\\_%' AND NOT EXISTS
     (SELECT 1 FROM pg_type as p2
      WHERE p2.typname = ('_' || p1.typname)::name AND
            p2.typelem = p1.oid);
index 87bcad1979308fb78d164d4f275a73b182eea1f7..58019a15d25aa4d382b9707a28128fbfd1855e46 100644 (file)
@@ -19,7 +19,7 @@
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
 WHERE (p1.typlen <= 0 AND p1.typlen != -1) OR
-    (p1.typtype != 'b' AND p1.typtype != 'c') OR
+    (p1.typtype != 'b' AND p1.typtype != 'c' AND p1.typtype != 'p') OR
     NOT p1.typisdefined OR
     (p1.typalign != 'c' AND p1.typalign != 's' AND
      p1.typalign != 'i' AND p1.typalign != 'd') OR
@@ -55,7 +55,7 @@ WHERE (p1.typtype = 'c' AND p1.typrelid = 0) OR
 
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
-WHERE p1.typtype != 'c' AND p1.typname NOT LIKE '\\_%' AND NOT EXISTS
+WHERE p1.typtype = 'b' AND p1.typname NOT LIKE '\\_%' AND NOT EXISTS
     (SELECT 1 FROM pg_type as p2
      WHERE p2.typname = ('_' || p1.typname)::name AND
            p2.typelem = p1.oid);