diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml index 9c203f5e57f7e50f0ef058dcbb4c6f0222ac243a..a1970c3e88747f359d5cef188990653cd6357374 100644 --- a/doc/src/sgml/ref/select.sgml +++ b/doc/src/sgml/ref/select.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/ref/select.sgml,v 1.54 2002/04/23 02:07:16 tgl Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/ref/select.sgml,v 1.55 2002/08/04 19:48:09 momjian Exp $ PostgreSQL documentation --> @@ -40,6 +40,12 @@ where <replaceable class="PARAMETER">from_item</replaceable> can be: ( <replaceable class="PARAMETER">select</replaceable> ) [ AS ] <replaceable class="PARAMETER">alias</replaceable> [ ( <replaceable class="PARAMETER">column_alias_list</replaceable> ) ] | +<replaceable class="PARAMETER">table_function_name</replaceable> ( [ <replaceable class="parameter">argtype</replaceable> [, ...] ] ) + [ AS ] <replaceable class="PARAMETER">alias</replaceable> [ ( <replaceable class="PARAMETER">column_alias_list</replaceable> | <replaceable class="PARAMETER">column_definition_list</replaceable> ) ] +| +<replaceable class="PARAMETER">table_function_name</replaceable> ( [ <replaceable class="parameter">argtype</replaceable> [, ...] ] ) + AS ( <replaceable class="PARAMETER">column_definition_list</replaceable> ) +| <replaceable class="PARAMETER">from_item</replaceable> [ NATURAL ] <replaceable class="PARAMETER">join_type</replaceable> <replaceable class="PARAMETER">from_item</replaceable> [ ON <replaceable class="PARAMETER">join_condition</replaceable> | USING ( <replaceable class="PARAMETER">join_column_list</replaceable> ) ] </synopsis> @@ -82,7 +88,7 @@ where <replaceable class="PARAMETER">from_item</replaceable> can be: <term><replaceable class="PARAMETER">from_item</replaceable></term> <listitem> <para> - 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. </para> </listitem> </varlistentry> @@ -156,6 +162,23 @@ where <replaceable class="PARAMETER">from_item</replaceable> can be: </para> </listitem> </varlistentry> + + <varlistentry> + <term><replaceable class="PARAMETER">table function</replaceable></term> + <listitem> + <para> + 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 + ( <replaceable class="PARAMETER">column_name</replaceable> <replaceable class="PARAMETER">data_type</replaceable> [, ... ] ). + The column definition list must match the actual number and types returned by the function. + </para> + </listitem> + </varlistentry> <varlistentry> <term><replaceable class="PARAMETER">join_type</replaceable></term> @@ -380,6 +403,19 @@ where <replaceable class="PARAMETER">from_item</replaceable> can be: grouping, aggregation, or sorting in a single query. </para> + <para> + 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. + </para> + <para> 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 +</programlisting> + </para> + + <para> + This example shows how to use a table function, both with and without + a column definition list. + +<programlisting> +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) </programlisting> </para> </refsect1> diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 249be8700021127f0e927b9f2d84070de87d52e4..e97f77ce700885dfeec8d6b572134f72e6d076cf 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -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; } diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c index 2624fdaf25dd5baf43068cb5532b59b1b673897c..f2d8e70589ce909782893a51773bcaf18303d52f 100644 --- a/src/backend/catalog/pg_proc.c +++ b/src/backend/catalog/pg_proc.c @@ -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); diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index c7b7c398eb27344df9ff7264a841b925c7c4b96d..ab414e1958130e38765e9608cd1ef563fec68038 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -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; diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c index d87f8e6fe887abb7c696ab9454a19e19c90d5980..66c418c8ba62194da915127a988ce39745f64daa 100644 --- a/src/backend/executor/nodeFunctionscan.c +++ b/src/backend/executor/nodeFunctionscan.c @@ -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" @@ -39,14 +40,11 @@ #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; +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index d7aefc9acfefda4528e56ac43f818eef9aa271e7..954a372181a927691d0eb18676138915f9f0d8f9 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -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; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 9b655640b0dcbf99178fadb2f9ebd1f220b1e697..da7567e7c335e84961420cc41858247c00a4ef6b 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -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)) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index c4dfbec67d0b6335d651eb95b4e63536e09b6144..b992e45a62fa97db1069a0e55b554c1ee518cf32 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -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 ", diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index ba9eb0449ee3d9776e7b60141bc6f4f6b9be29eb..46b2ca2dc85699e9fb8c53224fc073df4cb868b1 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -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: diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c794dd7d80e1a34afeeb52a2fc9f186d8074ac74..53759b8029658a1de0baf48ac323a499d4a12c00 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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 <range> into_clause, OptTempTableName @@ -256,8 +257,8 @@ static void doNegateFloat(Value *v); %type <vsetstmt> set_rest -%type <node> OptTableElement, ConstraintElem -%type <node> columnDef +%type <node> OptTableElement, ConstraintElem, tableFuncElement +%type <node> columnDef, tableFuncColumnDef %type <defelt> def_elem %type <node> 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 diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 81328798eb57529f3efaa1db735273dbe04fb0bb..ae87b056f3e8bd0fc531a81751a1dfba97e2a141 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -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 diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 99b639d73e37be865e2590d6644daec224ed60bd..6a4bda5d62bc5b95f135d66ae7b3456a1d563d7b 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -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; +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index f3e36226b015a5dcb1981e2abd63435a8568321a..12669ed78ddc9b200bd909169d533e21ffea8758 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -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 diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 1f801611a0559c84447daba09e0c9c98d17a0749..6bc4a2a92aec82dd28183e4a1675b777c7165a47 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -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 diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 2dea171e255e0672e44737c6b631809697d63fcb..ee25cc62c95a44e282e7019ec272fdf0689c37b0 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -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; /* ---------------------------------------------------------------- diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 07e985b377bb607b971b44714c9088afdc7137ae..765d1ca0514795c7498737b8647b87b9a1447240 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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): diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index a34e2a5619f086cd642fd93a402093cf944319df..7abfd047865200366b801fc4ef116b15e686321e 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -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 */ diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 711628c2e40380ace2d0807706e15c6c18dc4af1..949393386a4d400fda7195054f09c4c559e7b7f3 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -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); diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index 87bcad1979308fb78d164d4f275a73b182eea1f7..58019a15d25aa4d382b9707a28128fbfd1855e46 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -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);