/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
- Oid functypeid;
- char functyptype;
- Oid funcid = fcinfo->flinfo->fn_oid;
PGconn *conn = NULL;
StringInfo str = makeStringInfo();
char *curname = NULL;
int howmany = 0;
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool fail = true; /* default to backward compatible */
if (PG_NARGS() == 4)
SRF_RETURN_DONE(funcctx);
}
- /* check typtype to see if we have a predetermined return type */
- functypeid = get_func_rettype(funcid);
- functyptype = get_typtype(functypeid);
-
- if (functyptype == 'c')
- tupdesc = TypeGetTupleDesc(functypeid, NIL);
- else if (functypeid == RECORDOID)
+ /* get a tuple descriptor for our result type */
+ switch (get_call_result_type(fcinfo, NULL, &tupdesc))
{
- if (!rsinfo || !IsA(rsinfo, ReturnSetInfo) ||
- rsinfo->expectedDesc == NULL)
+ case TYPEFUNC_COMPOSITE:
+ /* success */
+ break;
+ case TYPEFUNC_RECORD:
+ /* failed to determine actual type of RECORD */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("function returning record called in context "
- "that cannot accept type record")));
-
- /* get the requested return tuple description */
- tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+ break;
+ default:
+ /* result type isn't composite */
+ elog(ERROR, "return type must be a row type");
+ break;
}
- else
- /* shouldn't happen */
- elog(ERROR, "return type must be a row type");
+
+ /* make sure we have a persistent copy of the tupdesc */
+ tupdesc = CreateTupleDescCopy(tupdesc);
/* store needed metadata for subsequent calls */
attinmeta = TupleDescGetAttInMetadata(tupdesc);
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
- Oid functypeid;
- char functyptype;
- Oid funcid = fcinfo->flinfo->fn_oid;
PGconn *conn = NULL;
char *connstr = NULL;
char *sql = NULL;
char *conname = NULL;
remoteConn *rcon = NULL;
- ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
bool fail = true; /* default to backward compatible */
/* create a function context for cross-call persistence */
SRF_RETURN_DONE(funcctx);
}
- /* check typtype to see if we have a predetermined return type */
- functypeid = get_func_rettype(funcid);
- functyptype = get_typtype(functypeid);
-
if (!is_sql_cmd)
{
- if (functyptype == 'c')
- tupdesc = TypeGetTupleDesc(functypeid, NIL);
- else if (functypeid == RECORDOID)
+ /* get a tuple descriptor for our result type */
+ switch (get_call_result_type(fcinfo, NULL, &tupdesc))
{
- if (!rsinfo || !IsA(rsinfo, ReturnSetInfo) ||
- rsinfo->expectedDesc == NULL)
+ case TYPEFUNC_COMPOSITE:
+ /* success */
+ break;
+ case TYPEFUNC_RECORD:
+ /* failed to determine actual type of RECORD */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("function returning record called in context "
- "that cannot accept type record")));
-
- /* get the requested return tuple description */
- tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+ break;
+ default:
+ /* result type isn't composite */
+ elog(ERROR, "return type must be a row type");
+ break;
}
- else
- /* shouldn't happen */
- elog(ERROR, "return type must be a row type");
+
+ /* make sure we have a persistent copy of the tupdesc */
+ tupdesc = CreateTupleDescCopy(tupdesc);
}
/* store needed metadata for subsequent calls */
/*
- * $PostgreSQL: pgsql/contrib/pgstattuple/pgstattuple.c,v 1.18 2005/05/27 00:57:49 neilc Exp $
+ * $PostgreSQL: pgsql/contrib/pgstattuple/pgstattuple.c,v 1.19 2005/05/30 23:09:06 tgl Exp $
*
* Copyright (c) 2001,2002 Tatsuo Ishii
*
extern Datum pgstattuple(PG_FUNCTION_ARGS);
extern Datum pgstattuplebyid(PG_FUNCTION_ARGS);
-static Datum pgstattuple_real(Relation rel);
+static Datum pgstattuple_real(Relation rel, FunctionCallInfo fcinfo);
+
/* ----------
* pgstattuple:
* ----------
*/
-#define DUMMY_TUPLE "public.pgstattuple_type"
#define NCOLUMNS 9
#define NCHARS 32
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
rel = heap_openrv(relrv, AccessShareLock);
- result = pgstattuple_real(rel);
+ result = pgstattuple_real(rel, fcinfo);
PG_RETURN_DATUM(result);
}
/* open relation */
rel = heap_open(relid, AccessShareLock);
- result = pgstattuple_real(rel);
+ result = pgstattuple_real(rel, fcinfo);
PG_RETURN_DATUM(result);
}
* The real work occurs here
*/
static Datum
-pgstattuple_real(Relation rel)
+pgstattuple_real(Relation rel, FunctionCallInfo fcinfo)
{
HeapScanDesc scan;
HeapTuple tuple;
int i;
Datum result;
- /*
- * Build a tuple description for a pgstattupe_type tuple
- */
- tupdesc = RelationNameGetTupleDesc(DUMMY_TUPLE);
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* make sure we have a persistent copy of the tupdesc */
+ tupdesc = CreateTupleDescCopy(tupdesc);
/*
* Generate attribute metadata needed later to produce tuples from raw
but you can create additional crosstab functions per the instructions
in the documentation below.
- crosstab(text sql, N int)
+ crosstab(text sql)
- returns a set of row_name plus N category value columns
- requires anonymous composite type syntax in the FROM clause. See
the instructions in the documentation below.
+ crosstab(text sql, N int)
+ - obsolete version of crosstab()
+ - the argument N is now ignored, since the number of value columns
+ is always determined by the calling query
+
connectby(text relname, text keyid_fld, text parent_keyid_fld
[, text orderby_fld], text start_with, int max_depth
[, text branch_delim])
A SQL statement which produces the source set of data. The SQL statement
must return one row_name column, one category column, and one value
- column.
+ column. row_name and value must be of type text.
e.g. provided sql must produce a set something like:
Returns setof tablefunc_crosstab_N, which is defined by:
- CREATE VIEW tablefunc_crosstab_N AS
- SELECT
- ''::TEXT AS row_name,
- ''::TEXT AS category_1,
- ''::TEXT AS category_2,
+ CREATE TYPE tablefunc_crosstab_N AS (
+ row_name TEXT,
+ category_1 TEXT,
+ category_2 TEXT,
.
.
.
- ''::TEXT AS category_N;
+ category_N TEXT
+ );
for the default installed functions, where N is 2, 3, or 4.
6. The installed defaults are for illustration purposes. You
can create your own return types and functions based on the
- crosstab() function of the installed library.
-
- The return type must have a first column that matches the data
- type of the sql set used as its source. The subsequent category
- columns must have the same data type as the value column of the
- sql result set.
-
- Create a VIEW to define your return type, similar to the VIEWS
- in the provided installation script. Then define a unique function
- name accepting one text parameter and returning setof your_view_name.
- For example, if your source data produces row_names that are TEXT,
- and values that are FLOAT8, and you want 5 category columns:
+ crosstab() function of the installed library. See below for
+ details.
- CREATE VIEW my_crosstab_float8_5_cols AS
- SELECT
- ''::TEXT AS row_name,
- 0::FLOAT8 AS category_1,
- 0::FLOAT8 AS category_2,
- 0::FLOAT8 AS category_3,
- 0::FLOAT8 AS category_4,
- 0::FLOAT8 AS category_5;
-
- CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(text)
- RETURNS setof my_crosstab_float8_5_cols
- AS '$libdir/tablefunc','crosstab' LANGUAGE 'c' STABLE STRICT;
Example usage
==================================================================
Name
-crosstab(text, int) - returns a set of row_name
- plus N category value columns
+crosstab(text) - returns a set of row_names plus category value columns
Synopsis
+crosstab(text sql)
+
crosstab(text sql, int N)
Inputs
N
- number of category value columns
+ Obsolete argument; ignored if supplied (formerly this had to match
+ the number of category columns determined by the calling query)
Outputs
- Returns setof record, which must defined with a column definition
+ Returns setof record, which must be defined with a column definition
in the FROM clause of the SELECT statement, e.g.:
SELECT *
- FROM crosstab(sql, 2) AS ct(row_name text, category_1 text, category_2 text);
+ FROM crosstab(sql) AS ct(row_name text, category_1 text, category_2 text);
the example crosstab function produces a set something like:
<== values columns ==>
1. The sql result must be ordered by 1,2.
- 2. The number of values columns is determined at run-time. The
- column definition provided in the FROM clause must provide for
- N + 1 columns of the proper data types.
+ 2. The number of values columns is determined by the column definition
+ provided in the FROM clause. The FROM clause must define one
+ row_name column (of the same datatype as the first result column
+ of the sql query) followed by N category columns (of the same
+ datatype as the third result column of the sql query). You can
+ set up as many category columns as you wish.
3. Missing values (i.e. not enough adjacent rows of same row_name to
fill the number of result values columns) are filled in with nulls.
5. Rows with all nulls in the values columns are skipped.
+ 6. You can avoid always having to write out a FROM clause that defines the
+ output columns by setting up a custom crosstab function that has
+ the desired output row type wired into its definition.
+
+ There are two ways you can set up a custom crosstab function:
+
+ A. Create a composite type to define your return type, similar to the
+ examples in the installation script. Then define a unique function
+ name accepting one text parameter and returning setof your_type_name.
+ For example, if your source data produces row_names that are TEXT,
+ and values that are FLOAT8, and you want 5 category columns:
+
+ CREATE TYPE my_crosstab_float8_5_cols AS (
+ row_name TEXT,
+ category_1 FLOAT8,
+ category_2 FLOAT8,
+ category_3 FLOAT8,
+ category_4 FLOAT8,
+ category_5 FLOAT8
+ );
+
+ CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(text)
+ RETURNS setof my_crosstab_float8_5_cols
+ AS '$libdir/tablefunc','crosstab' LANGUAGE 'c' STABLE STRICT;
+
+ B. Use OUT parameters to define the return type implicitly.
+ The same example could also be done this way:
+
+ CREATE OR REPLACE FUNCTION crosstab_float8_5_cols(IN text,
+ OUT row_name TEXT,
+ OUT category_1 FLOAT8,
+ OUT category_2 FLOAT8,
+ OUT category_3 FLOAT8,
+ OUT category_4 FLOAT8,
+ OUT category_5 FLOAT8)
+ RETURNS setof record
+ AS '$libdir/tablefunc','crosstab' LANGUAGE 'c' STABLE STRICT;
+
Example usage
5. Rows with a null row_name column are skipped.
+ 6. You can create predefined functions to avoid having to write out
+ the result column names/types in each query. See the examples
+ for crosstab(text).
+
Example usage
test4 | val4 | val5 | val6 |
(2 rows)
-SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;', 2) AS c(rowid text, att1 text, att2 text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text);
rowid | att1 | att2
-------+------+------
test1 | val1 | val2
test2 | val5 | val6
(2 rows)
-SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;', 3) AS c(rowid text, att1 text, att2 text, att3 text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text);
rowid | att1 | att2 | att3
-------+------+------+------
test1 | val1 | val2 | val3
test2 | val5 | val6 | val7
(2 rows)
-SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;', 4) AS c(rowid text, att1 text, att2 text, att3 text, att4 text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text, att4 text);
rowid | att1 | att2 | att3 | att4
-------+------+------+------+------
test1 | val1 | val2 | val3 | val4
test2 | val5 | val6 | val7 | val8
(2 rows)
+-- check it works with OUT parameters, too
+CREATE FUNCTION crosstab_out(text,
+ OUT rowid text, OUT att1 text, OUT att2 text, OUT att3 text)
+RETURNS setof record
+AS '$libdir/tablefunc','crosstab'
+LANGUAGE 'C' STABLE STRICT;
+SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;');
+ rowid | att1 | att2 | att3
+-------+------+------+------
+ test1 | val1 | val2 | val3
+ test2 | val5 | val6 | val7
+(2 rows)
+
--
-- hash based crosstab
--
-------+-------+-------------+-------------+----------------+-------
(0 rows)
+-- check it works with a named result rowtype
+create type my_crosstab_result as (
+ rowid text, rowdt timestamp,
+ temperature int4, test_result text, test_startdate timestamp, volts float8);
+CREATE FUNCTION crosstab_named(text, text)
+RETURNS setof my_crosstab_result
+AS '$libdir/tablefunc','crosstab_hash'
+LANGUAGE 'C' STABLE STRICT;
+SELECT * FROM crosstab_named(
+ 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
+ 'SELECT DISTINCT attribute FROM cth ORDER BY 1');
+ rowid | rowdt | temperature | test_result | test_startdate | volts
+-------+--------------------------+-------------+-------------+--------------------------+--------
+ test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
+ test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234
+(2 rows)
+
+-- check it works with OUT parameters
+CREATE FUNCTION crosstab_out(text, text,
+ OUT rowid text, OUT rowdt timestamp,
+ OUT temperature int4, OUT test_result text,
+ OUT test_startdate timestamp, OUT volts float8)
+RETURNS setof record
+AS '$libdir/tablefunc','crosstab_hash'
+LANGUAGE 'C' STABLE STRICT;
+SELECT * FROM crosstab_out(
+ 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
+ 'SELECT DISTINCT attribute FROM cth ORDER BY 1');
+ rowid | rowdt | temperature | test_result | test_startdate | volts
+-------+--------------------------+-------------+-------------+--------------------------+--------
+ test1 | Sat Mar 01 00:00:00 2003 | 42 | PASS | | 2.6987
+ test2 | Sun Mar 02 00:00:00 2003 | 53 | FAIL | Sat Mar 01 00:00:00 2003 | 3.1234
+(2 rows)
+
--
-- connectby
--
SELECT * FROM crosstab3('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;');
SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = ''group2'' ORDER BY 1,2;');
-SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;', 2) AS c(rowid text, att1 text, att2 text);
-SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;', 3) AS c(rowid text, att1 text, att2 text, att3 text);
-SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;', 4) AS c(rowid text, att1 text, att2 text, att3 text, att4 text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;') AS c(rowid text, att1 text, att2 text, att3 text, att4 text);
+
+-- check it works with OUT parameters, too
+
+CREATE FUNCTION crosstab_out(text,
+ OUT rowid text, OUT att1 text, OUT att2 text, OUT att3 text)
+RETURNS setof record
+AS '$libdir/tablefunc','crosstab'
+LANGUAGE 'C' STABLE STRICT;
+
+SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;');
--
-- hash based crosstab
'SELECT DISTINCT attribute FROM cth WHERE false ORDER BY 1')
AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text);
+-- check it works with a named result rowtype
+
+create type my_crosstab_result as (
+ rowid text, rowdt timestamp,
+ temperature int4, test_result text, test_startdate timestamp, volts float8);
+
+CREATE FUNCTION crosstab_named(text, text)
+RETURNS setof my_crosstab_result
+AS '$libdir/tablefunc','crosstab_hash'
+LANGUAGE 'C' STABLE STRICT;
+
+SELECT * FROM crosstab_named(
+ 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
+ 'SELECT DISTINCT attribute FROM cth ORDER BY 1');
+
+-- check it works with OUT parameters
+
+CREATE FUNCTION crosstab_out(text, text,
+ OUT rowid text, OUT rowdt timestamp,
+ OUT temperature int4, OUT test_result text,
+ OUT test_startdate timestamp, OUT volts float8)
+RETURNS setof record
+AS '$libdir/tablefunc','crosstab_hash'
+LANGUAGE 'C' STABLE STRICT;
+
+SELECT * FROM crosstab_out(
+ 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
+ 'SELECT DISTINCT attribute FROM cth ORDER BY 1');
+
--
-- connectby
--
static bool compatCrosstabTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
static bool compatConnectbyTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
static void get_normal_pair(float8 *x1, float8 *x2);
-static TupleDesc make_crosstab_tupledesc(TupleDesc spi_tupdesc,
- int num_categories);
static Tuplestorestate *connectby(char *relname,
char *key_fld,
char *parent_key_fld,
* NOTES:
* 1. SQL result must be ordered by 1,2.
* 2. The number of values columns depends on the tuple description
- * of the function's declared return type.
- * 2. Missing values (i.e. not enough adjacent rows of same rowid to
+ * of the function's declared return type. The return type's columns
+ * must match the datatypes of the SQL query's result. The datatype
+ * of the category column can be anything, however.
+ * 3. Missing values (i.e. not enough adjacent rows of same rowid to
* fill the number of result values columns) are filled in with nulls.
- * 3. Extra values (i.e. too many adjacent rows of same rowid to fill
+ * 4. Extra values (i.e. too many adjacent rows of same rowid to fill
* the number of result values columns) are skipped.
- * 4. Rows with all nulls in the values columns are skipped.
+ * 5. Rows with all nulls in the values columns are skipped.
*/
PG_FUNCTION_INFO_V1(crosstab);
Datum
if (SRF_IS_FIRSTCALL())
{
char *sql = GET_STR(PG_GETARG_TEXT_P(0));
- Oid funcid = fcinfo->flinfo->fn_oid;
- Oid functypeid;
- char functyptype;
- TupleDesc tupdesc = NULL;
+ TupleDesc tupdesc;
int ret;
int proc;
spi_tuptable = SPI_tuptable;
spi_tupdesc = spi_tuptable->tupdesc;
- /*
+ /*----------
* The provided SQL query must always return three columns.
*
- * 1. rowname the label or identifier for each row in the final
- * result 2. category the label or identifier for each column
- * in the final result 3. values the value for each column
- * in the final result
+ * 1. rowname
+ * the label or identifier for each row in the final result
+ * 2. category
+ * the label or identifier for each column in the final result
+ * 3. values
+ * the value for each column in the final result
+ *----------
*/
if (spi_tupdesc->natts != 3)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid source data SQL statement"),
- errdetail("The provided SQL must return 3 " \
- " columns; rowid, category, and values.")));
+ errdetail("The provided SQL must return 3 "
+ "columns: rowid, category, and values.")));
}
else
{
/* SPI switches context on us, so reset it */
MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
- /* get the typeid that represents our return type */
- functypeid = get_func_rettype(funcid);
-
- /* check typtype to see if we have a predetermined return type */
- functyptype = get_typtype(functypeid);
-
- if (functyptype == 'c')
+ /* get a tuple descriptor for our result type */
+ switch (get_call_result_type(fcinfo, NULL, &tupdesc))
{
- /* Build a tuple description for a named composite type */
- tupdesc = TypeGetTupleDesc(functypeid, NIL);
- }
- else if (functypeid == RECORDOID)
- {
- if (fcinfo->nargs != 2)
+ case TYPEFUNC_COMPOSITE:
+ /* success */
+ break;
+ case TYPEFUNC_RECORD:
+ /* failed to determine actual type of RECORD */
ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("wrong number of arguments")));
- else
- {
- int num_categories = PG_GETARG_INT32(1);
-
- tupdesc = make_crosstab_tupledesc(spi_tupdesc, num_categories);
- }
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+ break;
+ default:
+ /* result type isn't composite */
+ elog(ERROR, "return type must be a row type");
+ break;
}
- else
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("return type must be a row type")));
+
+ /* make sure we have a persistent copy of the tupdesc */
+ tupdesc = CreateTupleDescCopy(tupdesc);
/*
- * Check that return tupdesc is compatible with the one we got
- * from ret_relname, at least based on number and type of
- * attributes
+ * Check that return tupdesc is compatible with the data we got
+ * from SPI, at least based on number and type of attributes
*/
if (!compatCrosstabTupleDescs(tupdesc, spi_tupdesc))
ereport(ERROR,
* 1. SQL result must be ordered by 1.
* 2. The number of values columns depends on the tuple description
* of the function's declared return type.
- * 2. Missing values (i.e. missing category) are filled in with nulls.
- * 3. Extra values (i.e. not in category results) are skipped.
+ * 3. Missing values (i.e. missing category) are filled in with nulls.
+ * 4. Extra values (i.e. not in category results) are skipped.
*/
PG_FUNCTION_INFO_V1(crosstab_hash);
Datum
return true;
}
-static TupleDesc
-make_crosstab_tupledesc(TupleDesc spi_tupdesc, int num_categories)
-{
- Form_pg_attribute sql_attr;
- Oid sql_atttypid;
- TupleDesc tupdesc;
- int natts;
- AttrNumber attnum;
- char attname[NAMEDATALEN];
- int i;
-
- /*
- * We need to build a tuple description with one column for the
- * rowname, and num_categories columns for the values. Each must be of
- * the same type as the corresponding spi result input column.
- */
- natts = num_categories + 1;
- tupdesc = CreateTemplateTupleDesc(natts, false);
-
- /* first the rowname column */
- attnum = 1;
-
- sql_attr = spi_tupdesc->attrs[0];
- sql_atttypid = sql_attr->atttypid;
-
- strcpy(attname, "rowname");
-
- TupleDescInitEntry(tupdesc, attnum, attname, sql_atttypid,
- -1, 0);
-
- /* now the category values columns */
- sql_attr = spi_tupdesc->attrs[2];
- sql_atttypid = sql_attr->atttypid;
-
- for (i = 0; i < num_categories; i++)
- {
- attnum++;
-
- sprintf(attname, "category_%d", i + 1);
- TupleDescInitEntry(tupdesc, attnum, attname, sql_atttypid,
- -1, 0);
- }
-
- return tupdesc;
-}
-
/*
* Return a properly quoted literal value.
* Uses quote_literal in quote.c
AS 'MODULE_PATHNAME','normal_rand'
LANGUAGE 'C' VOLATILE STRICT;
+-- the generic crosstab function:
+CREATE OR REPLACE FUNCTION crosstab(text)
+RETURNS setof record
+AS 'MODULE_PATHNAME','crosstab'
+LANGUAGE 'C' STABLE STRICT;
+
+-- examples of building custom type-specific crosstab functions:
CREATE TYPE tablefunc_crosstab_2 AS
(
row_name TEXT,
AS 'MODULE_PATHNAME','crosstab'
LANGUAGE 'C' STABLE STRICT;
+-- obsolete:
CREATE OR REPLACE FUNCTION crosstab(text,int)
RETURNS setof record
AS 'MODULE_PATHNAME','crosstab'
} StatStorage;
static void
-ts_setup_firstcall(FuncCallContext *funcctx, tsstat * stat)
+ts_setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx,
+ tsstat * stat)
{
TupleDesc tupdesc;
MemoryContext oldcontext;
st->stat = palloc(stat->len);
memcpy(st->stat, stat, stat->len);
funcctx->user_fctx = (void *) st;
- tupdesc = RelationNameGetTupleDesc("statinfo");
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ tupdesc = CreateTupleDescCopy(tupdesc);
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
MemoryContextSwitchTo(oldcontext);
}
if (SRF_IS_FIRSTCALL())
{
funcctx = SRF_FIRSTCALL_INIT();
- ts_setup_firstcall(funcctx, (tsstat *) PG_GETARG_POINTER(0));
+ ts_setup_firstcall(fcinfo, funcctx, (tsstat *) PG_GETARG_POINTER(0));
}
funcctx = SRF_PERCALL_SETUP();
PG_FREE_IF_COPY(txt, 0);
if (PG_NARGS() > 1)
PG_FREE_IF_COPY(ws, 1);
- ts_setup_firstcall(funcctx, stat);
+ ts_setup_firstcall(fcinfo, funcctx, stat);
SPI_finish();
}
} TypeStorage;
static void
-setup_firstcall(FuncCallContext *funcctx, Oid prsid)
+setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx, Oid prsid)
{
TupleDesc tupdesc;
MemoryContext oldcontext;
OidFunctionCall1(prs->lextype, PointerGetDatum(prs->prs))
);
funcctx->user_fctx = (void *) st;
- tupdesc = RelationNameGetTupleDesc("tokentype");
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ tupdesc = CreateTupleDescCopy(tupdesc);
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
MemoryContextSwitchTo(oldcontext);
}
if (SRF_IS_FIRSTCALL())
{
funcctx = SRF_FIRSTCALL_INIT();
- setup_firstcall(funcctx, PG_GETARG_OID(0));
+ setup_firstcall(fcinfo, funcctx, PG_GETARG_OID(0));
}
funcctx = SRF_PERCALL_SETUP();
text *name = PG_GETARG_TEXT_P(0);
funcctx = SRF_FIRSTCALL_INIT();
- setup_firstcall(funcctx, name2id_prs(name));
+ setup_firstcall(fcinfo, funcctx, name2id_prs(name));
PG_FREE_IF_COPY(name, 0);
}
funcctx = SRF_FIRSTCALL_INIT();
if (current_parser_id == InvalidOid)
current_parser_id = name2id_prs(char2text("default"));
- setup_firstcall(funcctx, current_parser_id);
+ setup_firstcall(fcinfo, funcctx, current_parser_id);
}
funcctx = SRF_PERCALL_SETUP();
static void
-prs_setup_firstcall(FuncCallContext *funcctx, int prsid, text *txt)
+prs_setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx,
+ int prsid, text *txt)
{
TupleDesc tupdesc;
MemoryContext oldcontext;
st->cur = 0;
funcctx->user_fctx = (void *) st;
- tupdesc = RelationNameGetTupleDesc("tokenout");
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+ tupdesc = CreateTupleDescCopy(tupdesc);
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
MemoryContextSwitchTo(oldcontext);
}
text *txt = PG_GETARG_TEXT_P(1);
funcctx = SRF_FIRSTCALL_INIT();
- prs_setup_firstcall(funcctx, PG_GETARG_OID(0), txt);
+ prs_setup_firstcall(fcinfo, funcctx, PG_GETARG_OID(0), txt);
PG_FREE_IF_COPY(txt, 1);
}
text *txt = PG_GETARG_TEXT_P(1);
funcctx = SRF_FIRSTCALL_INIT();
- prs_setup_firstcall(funcctx, name2id_prs(name), txt);
+ prs_setup_firstcall(fcinfo, funcctx, name2id_prs(name), txt);
PG_FREE_IF_COPY(name, 0);
PG_FREE_IF_COPY(txt, 1);
}
funcctx = SRF_FIRSTCALL_INIT();
if (current_parser_id == InvalidOid)
current_parser_id = name2id_prs(char2text("default"));
- prs_setup_firstcall(funcctx, current_parser_id, txt);
+ prs_setup_firstcall(fcinfo, funcctx, current_parser_id, txt);
PG_FREE_IF_COPY(txt, 0);
}
<!--
-$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.102 2005/03/31 22:46:02 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.103 2005/05/30 23:09:07 tgl Exp $
-->
<sect1 id="xfunc">
</para>
<para>
- Several helper functions are available for setting up the initial
- <structname>TupleDesc</>. If you want to use a named composite type,
- you can fetch the information from the system catalogs. Use
+ Several helper functions are available for setting up the needed
+ <structname>TupleDesc</>. The recommended way to do this in most
+ functions returning composite values is to call
+<programlisting>
+TypeFuncClass get_call_result_type(FunctionCallInfo fcinfo,
+ Oid *resultTypeId,
+ TupleDesc *resultTupleDesc)
+</programlisting>
+ passing the same <literal>fcinfo</> struct passed to the calling function
+ itself. (This of course requires that you use the version-1
+ calling conventions.) <varname>resultTypeId</> can be specified
+ as <literal>NULL</> or as the address of a local variable to receive the
+ function's result type OID. <varname>resultTupleDesc</> should be the
+ address of a local <structname>TupleDesc</> variable. Check that the
+ result is <literal>TYPEFUNC_COMPOSITE</>; if so,
+ <varname>resultTupleDesc</> has been filled with the needed
+ <structname>TupleDesc</>. (If it is not, you can report an error along
+ the lines of <quote>function returning record called in context that
+ cannot accept type record</quote>.)
+ </para>
+
+ <tip>
+ <para>
+ <function>get_call_result_type</> can resolve the actual type of a
+ polymorphic function result; so it is useful in functions that return
+ scalar polymorphic results, not only functions that return composites.
+ The <varname>resultTypeId</> output is primarily useful for functions
+ returning polymorphic scalars.
+ </para>
+ </tip>
+
+ <note>
+ <para>
+ <function>get_call_result_type</> has a sibling
+ <function>get_expr_result_type</>, which can be used to resolve the
+ expected output type for a function call represented by an expression
+ tree. This can be used when trying to determine the result type from
+ outside the function itself. There is also
+ <function>get_func_result_type</>, which can be used when only the
+ function's OID is available. However these functions are not able
+ to deal with functions declared to return <structname>record</>, and
+ <function>get_func_result_type</> cannot resolve polymorphic types,
+ so you should preferentially use <function>get_call_result_type</>.
+ </para>
+ </note>
+
+ <para>
+ Older, now-deprecated functions for obtaining
+ <structname>TupleDesc</>s are
<programlisting>
TupleDesc RelationNameGetTupleDesc(const char *relname)
</programlisting>
- to get a <structname>TupleDesc</> for a named relation, or
+ to get a <structname>TupleDesc</> for the row type of a named relation,
+ and
<programlisting>
TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases)
</programlisting>
to get a <structname>TupleDesc</> based on a type OID. This can
be used to get a <structname>TupleDesc</> for a base or
- composite type. When writing a function that returns
- <structname>record</>, the expected <structname>TupleDesc</>
- must be passed in by the caller.
+ composite type. It will not work for a function that returns
+ <structname>record</>, however, and it cannot resolve polymorphic
+ types.
</para>
<para>
</para>
<para>
- A complete example of a simple <acronym>SRF</> returning a composite type looks like:
+ A complete example of a simple <acronym>SRF</> returning a composite type
+ looks like:
<programlisting>
-PG_FUNCTION_INFO_V1(testpassbyval);
+PG_FUNCTION_INFO_V1(retcomposite);
Datum
-testpassbyval(PG_FUNCTION_ARGS)
+retcomposite(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
int call_cntr;
/* total number of tuples to be returned */
funcctx->max_calls = PG_GETARG_UINT32(0);
- /* Build a tuple description for a __testpassbyval tuple */
- tupdesc = RelationNameGetTupleDesc("__testpassbyval");
+ /* Build a tuple descriptor for our result type */
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
/*
* generate attribute metadata needed later to produce tuples from raw
}
</programlisting>
- The SQL code to declare this function is:
+ One way to declare this function in SQL is:
<programlisting>
-CREATE TYPE __testpassbyval AS (f1 integer, f2 integer, f3 integer);
+CREATE TYPE __retcomposite AS (f1 integer, f2 integer, f3 integer);
-CREATE OR REPLACE FUNCTION testpassbyval(integer, integer) RETURNS SETOF __testpassbyval
- AS '<replaceable>filename</>', 'testpassbyval'
+CREATE OR REPLACE FUNCTION retcomposite(integer, integer)
+ RETURNS SETOF __retcomposite
+ AS '<replaceable>filename</>', 'retcomposite'
+ LANGUAGE C IMMUTABLE STRICT;
+</programlisting>
+ A different way is to use OUT parameters:
+<programlisting>
+CREATE OR REPLACE FUNCTION retcomposite(IN integer, IN integer,
+ OUT f1 integer, OUT f2 integer, OUT f3 integer)
+ RETURNS SETOF record
+ AS '<replaceable>filename</>', 'retcomposite'
LANGUAGE C IMMUTABLE STRICT;
</programlisting>
+ Notice that in this method the output type of the function is formally
+ an anonymous <structname>record</> type.
</para>
<para>
information is not available.
The structure <literal>flinfo</> is normally accessed as
<literal>fcinfo->flinfo</>. The parameter <literal>argnum</>
- is zero based.
+ is zero based. <function>get_call_result_type</> can also be used
+ as an alternative to <function>get_fn_expr_rettype</>.
</para>
<para>
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.22 2005/05/28 05:10:47 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.23 2005/05/30 23:09:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* RelationNameGetTupleDesc
*
* Given a (possibly qualified) relation name, build a TupleDesc.
+ *
+ * Note: while this works as advertised, it's seldom the best way to
+ * build a tupdesc for a function's result type. It's kept around
+ * only for backwards compatibility with existing user-written code.
*/
TupleDesc
RelationNameGetTupleDesc(const char *relname)
/*
* TypeGetTupleDesc
*
- * Given a type Oid, build a TupleDesc.
+ * Given a type Oid, build a TupleDesc. (In most cases you should be
+ * using get_call_result_type or one of its siblings instead of this
+ * routine, so that you can handle OUT parameters, RECORD result type,
+ * and polymorphic results.)
*
* If the type is composite, *and* a colaliases List is provided, *and*
* the List is of natts length, use the aliases instead of the relation
*
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.17 2005/04/05 06:22:15 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.18 2005/05/30 23:09:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Support to ease writing functions returning composite types
*
* External declarations:
- * TupleDesc RelationNameGetTupleDesc(const char *relname) - Use to get a
- * TupleDesc based on a specified relation.
- * TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases) - Use to get a
- * TupleDesc based on a type OID. This can be used to get
- * a TupleDesc for a base (scalar) or composite (relation) type.
* TupleDesc BlessTupleDesc(TupleDesc tupdesc) - "Bless" a completed tuple
* descriptor so that it can be used to return properly labeled tuples.
* You need to call this if you are going to use heap_formtuple directly.
* HeapTupleGetDatum(HeapTuple tuple) - convert a HeapTuple to a Datum.
*
* Obsolete routines and macros:
+ * TupleDesc RelationNameGetTupleDesc(const char *relname) - Use to get a
+ * TupleDesc based on a named relation.
+ * TupleDesc TypeGetTupleDesc(Oid typeoid, List *colaliases) - Use to get a
+ * TupleDesc based on a type OID.
* TupleTableSlot *TupleDescGetSlot(TupleDesc tupdesc) - Builds a
* TupleTableSlot, which is not needed anymore.
* TupleGetDatum(TupleTableSlot *slot, HeapTuple tuple) - get a Datum