1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-27 12:41:57 +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:
Tom Lane
2005-05-30 23:09:07 +00:00
parent b215fae891
commit 978129f28e
12 changed files with 373 additions and 231 deletions

View File

@ -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

View File

@ -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
--

View File

@ -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
--

View File

@ -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

View File

@ -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'