mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +03:00
Document get_call_result_type() and friends; mark TypeGetTupleDesc()
and RelationNameGetTupleDesc() as deprecated; remove uses of the latter in the contrib library. Along the way, clean up crosstab() code and documentation a little.
This commit is contained in:
parent
b215fae891
commit
978129f28e
@ -454,14 +454,10 @@ dblink_fetch(PG_FUNCTION_ARGS)
|
||||
/* 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)
|
||||
@ -554,27 +550,27 @@ dblink_fetch(PG_FUNCTION_ARGS)
|
||||
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);
|
||||
@ -651,15 +647,11 @@ dblink_record(PG_FUNCTION_ARGS)
|
||||
/* 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 */
|
||||
@ -756,29 +748,29 @@ dblink_record(PG_FUNCTION_ARGS)
|
||||
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 */
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* $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
|
||||
*
|
||||
@ -38,7 +38,8 @@ PG_FUNCTION_INFO_V1(pgstattuplebyid);
|
||||
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:
|
||||
@ -50,7 +51,6 @@ static Datum pgstattuple_real(Relation rel);
|
||||
* ----------
|
||||
*/
|
||||
|
||||
#define DUMMY_TUPLE "public.pgstattuple_type"
|
||||
#define NCOLUMNS 9
|
||||
#define NCHARS 32
|
||||
|
||||
@ -66,7 +66,7 @@ pgstattuple(PG_FUNCTION_ARGS)
|
||||
relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
|
||||
rel = heap_openrv(relrv, AccessShareLock);
|
||||
|
||||
result = pgstattuple_real(rel);
|
||||
result = pgstattuple_real(rel, fcinfo);
|
||||
|
||||
PG_RETURN_DATUM(result);
|
||||
}
|
||||
@ -81,7 +81,7 @@ pgstattuplebyid(PG_FUNCTION_ARGS)
|
||||
/* open relation */
|
||||
rel = heap_open(relid, AccessShareLock);
|
||||
|
||||
result = pgstattuple_real(rel);
|
||||
result = pgstattuple_real(rel, fcinfo);
|
||||
|
||||
PG_RETURN_DATUM(result);
|
||||
}
|
||||
@ -92,7 +92,7 @@ pgstattuplebyid(PG_FUNCTION_ARGS)
|
||||
* The real work occurs here
|
||||
*/
|
||||
static Datum
|
||||
pgstattuple_real(Relation rel)
|
||||
pgstattuple_real(Relation rel, FunctionCallInfo fcinfo)
|
||||
{
|
||||
HeapScanDesc scan;
|
||||
HeapTuple tuple;
|
||||
@ -115,10 +115,12 @@ pgstattuple_real(Relation rel)
|
||||
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
|
||||
|
@ -57,11 +57,16 @@ Installation:
|
||||
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])
|
||||
@ -133,7 +138,7 @@ Inputs
|
||||
|
||||
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:
|
||||
|
||||
@ -152,15 +157,15 @@ Outputs
|
||||
|
||||
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.
|
||||
|
||||
@ -188,31 +193,9 @@ Notes
|
||||
|
||||
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.
|
||||
crosstab() function of the installed library. See below for
|
||||
details.
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
|
||||
@ -241,11 +224,12 @@ select * from crosstab3(
|
||||
==================================================================
|
||||
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
|
||||
@ -271,15 +255,16 @@ 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 ==>
|
||||
@ -292,9 +277,12 @@ Notes
|
||||
|
||||
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.
|
||||
@ -304,6 +292,44 @@ Notes
|
||||
|
||||
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
|
||||
|
||||
@ -418,6 +444,10 @@ Notes
|
||||
|
||||
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
|
||||
|
||||
|
@ -102,27 +102,40 @@ SELECT * FROM crosstab4('SELECT rowid, attribute, val FROM ct where rowclass = '
|
||||
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
|
||||
--
|
||||
@ -211,6 +224,40 @@ AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_start
|
||||
-------+-------+-------------+-------------+----------------+-------
|
||||
(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
|
||||
--
|
||||
|
@ -34,9 +34,19 @@ SELECT * FROM crosstab2('SELECT rowid, attribute, val FROM ct where rowclass = '
|
||||
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
|
||||
@ -100,6 +110,35 @@ SELECT * FROM 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
|
||||
--
|
||||
|
@ -51,8 +51,6 @@ static void validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool
|
||||
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,
|
||||
@ -332,12 +330,14 @@ get_normal_pair(float8 *x1, float8 *x2)
|
||||
* 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
|
||||
@ -360,10 +360,7 @@ crosstab(PG_FUNCTION_ARGS)
|
||||
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;
|
||||
|
||||
@ -391,20 +388,23 @@ crosstab(PG_FUNCTION_ARGS)
|
||||
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
|
||||
{
|
||||
@ -416,39 +416,31 @@ crosstab(PG_FUNCTION_ARGS)
|
||||
/* 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,
|
||||
@ -679,8 +671,8 @@ crosstab(PG_FUNCTION_ARGS)
|
||||
* 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
|
||||
@ -1628,52 +1620,6 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
|
||||
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
|
||||
|
@ -6,6 +6,13 @@ RETURNS setof float8
|
||||
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,
|
||||
@ -45,6 +52,7 @@ RETURNS setof tablefunc_crosstab_4
|
||||
AS 'MODULE_PATHNAME','crosstab'
|
||||
LANGUAGE 'C' STABLE STRICT;
|
||||
|
||||
-- obsolete:
|
||||
CREATE OR REPLACE FUNCTION crosstab(text,int)
|
||||
RETURNS setof record
|
||||
AS 'MODULE_PATHNAME','crosstab'
|
||||
|
@ -330,7 +330,8 @@ typedef struct
|
||||
} StatStorage;
|
||||
|
||||
static void
|
||||
ts_setup_firstcall(FuncCallContext *funcctx, tsstat * stat)
|
||||
ts_setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx,
|
||||
tsstat * stat)
|
||||
{
|
||||
TupleDesc tupdesc;
|
||||
MemoryContext oldcontext;
|
||||
@ -342,7 +343,9 @@ ts_setup_firstcall(FuncCallContext *funcctx, tsstat * stat)
|
||||
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);
|
||||
}
|
||||
@ -399,7 +402,7 @@ ts_accum_finish(PG_FUNCTION_ARGS)
|
||||
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();
|
||||
@ -544,7 +547,7 @@ ts_stat(PG_FUNCTION_ARGS)
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ typedef struct
|
||||
} TypeStorage;
|
||||
|
||||
static void
|
||||
setup_firstcall(FuncCallContext *funcctx, Oid prsid)
|
||||
setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx, Oid prsid)
|
||||
{
|
||||
TupleDesc tupdesc;
|
||||
MemoryContext oldcontext;
|
||||
@ -197,7 +197,9 @@ setup_firstcall(FuncCallContext *funcctx, Oid prsid)
|
||||
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);
|
||||
}
|
||||
@ -250,7 +252,7 @@ token_type(PG_FUNCTION_ARGS)
|
||||
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();
|
||||
@ -274,7 +276,7 @@ token_type_byname(PG_FUNCTION_ARGS)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -299,7 +301,7 @@ token_type_current(PG_FUNCTION_ARGS)
|
||||
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();
|
||||
@ -352,7 +354,8 @@ typedef struct
|
||||
|
||||
|
||||
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;
|
||||
@ -405,7 +408,9 @@ prs_setup_firstcall(FuncCallContext *funcctx, int prsid, text *txt)
|
||||
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);
|
||||
}
|
||||
@ -458,7 +463,7 @@ parse(PG_FUNCTION_ARGS)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -484,7 +489,7 @@ parse_byname(PG_FUNCTION_ARGS)
|
||||
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);
|
||||
}
|
||||
@ -513,7 +518,7 @@ parse_current(PG_FUNCTION_ARGS)
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$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">
|
||||
@ -2324,21 +2324,68 @@ CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
|
||||
</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>
|
||||
@ -2587,12 +2634,13 @@ my_set_returning_function(PG_FUNCTION_ARGS)
|
||||
</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;
|
||||
@ -2614,8 +2662,12 @@ testpassbyval(PG_FUNCTION_ARGS)
|
||||
/* 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
|
||||
@ -2675,14 +2727,25 @@ testpassbyval(PG_FUNCTION_ARGS)
|
||||
}
|
||||
</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>
|
||||
@ -2711,7 +2774,8 @@ CREATE OR REPLACE FUNCTION testpassbyval(integer, integer) RETURNS SETOF __testp
|
||||
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>
|
||||
|
@ -7,7 +7,7 @@
|
||||
* 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 $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -822,6 +822,10 @@ build_function_result_tupdesc_d(Datum proallargtypes,
|
||||
* 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)
|
||||
@ -844,7 +848,10 @@ 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
|
||||
|
@ -9,7 +9,7 @@
|
||||
*
|
||||
* 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 $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -181,11 +181,6 @@ extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
|
||||
* 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.
|
||||
@ -203,6 +198,10 @@ extern TupleDesc build_function_result_tupdesc_t(HeapTuple procTuple);
|
||||
* 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
|
||||
|
Loading…
x
Reference in New Issue
Block a user