<!--
-$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
-->
( <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>
<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>
</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>
</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
of nesting.)
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>
<refsect1 id="R1-SQL-SELECT-3">
*
*
* 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
#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"
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;
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;
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
#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);
*/
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");
}
bool isnull;
Datum tmp;
char *prosrc;
+ char functyptype;
tuple = SearchSysCache(PROCOID, funcoid, 0, 0, 0);
if (!HeapTupleIsValid(tuple))
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);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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
FunctionNext(FunctionScan *node)
{
TupleTableSlot *slot;
- Node *expr;
- ExprContext *econtext;
- TupleDesc tupdesc;
EState *estate;
ScanDirection direction;
Tuplestorestate *tuplestorestate;
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
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;
RangeTblEntry *rte;
Oid funcrettype;
Oid funcrelid;
- TupleDesc tupdesc;
+ char functyptype;
+ TupleDesc tupdesc = NULL;
/*
* FunctionScan should not have any children.
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
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);
* 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
* 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
{
*/
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;
+}
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
Node_Copy(from, newnode, funccallnode);
Node_Copy(from, newnode, alias);
+ Node_Copy(from, newnode, coldeflist);
return newnode;
}
* 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 $
*
*-------------------------------------------------------------------------
*/
return false;
if (!equal(a->alias, b->alias))
return false;
+ if (!equal(a->coldeflist, b->coldeflist))
+ return false;
return true;
}
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))
* 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
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 ",
*
*
* 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
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:
*
*
* 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
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
%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,
{
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
RangeFunction *n = makeNode(RangeFunction);
n->funccallnode = $1;
n->alias = $2;
+ n->coldeflist = NIL;
$$ = (Node *) n;
}
| select_with_parens
;
+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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
rte->relid = InvalidOid;
rte->subquery = NULL;
rte->funcexpr = funcexpr;
+ rte->coldeflist = coldeflist;
rte->alias = alias;
eref = alias ? (Alias *) copyObject(alias) : makeAlias(funcname, NIL);
* 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.
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:
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
*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:
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
*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:
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;
+}
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200208011
+#define CATALOG_VERSION_NO 200208041
#endif
* 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
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;
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
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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
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;
/* ----------------------------------------------------------------
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
/*
* 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):
* 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 $
*
*-------------------------------------------------------------------------
*/
extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
char *funcname,
Node *funcexpr,
- Alias *alias,
+ RangeFunction *rangefunc,
bool inFromCl);
extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
List *colnames,
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 */
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
-- 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);
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
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);