> Am Son, 2003-06-22 um 02.09 schrieb Joe Conway:
>>Sounds like all that's needed for your case. But to be complete, in >>addition to changing tablefunc.c we'd have to: >>1) come up with a new function call signature that makes sense and does >>not cause backward compatibility problems for other people >>2) make needed changes to tablefunc.sql.in >>3) adjust the README.tablefunc appropriately >>4) adjust the regression test for new functionality >>5) be sure we don't break any of the old cases >> >>If you want to submit a complete patch, it would be gratefully accepted >>-- for review at least ;-) > > Here's the patch, at least for steps 1-3 Nabil Sayegh Joe Conway
This commit is contained in:
parent
9ae6905520
commit
a265b7f70a
@ -4,6 +4,8 @@
|
|||||||
* Sample to demonstrate C functions which return setof scalar
|
* Sample to demonstrate C functions which return setof scalar
|
||||||
* and setof composite.
|
* and setof composite.
|
||||||
* Joe Conway <mail@joeconway.com>
|
* Joe Conway <mail@joeconway.com>
|
||||||
|
* And contributors:
|
||||||
|
* Nabil Sayegh <postgresql@e-trolley.de>
|
||||||
*
|
*
|
||||||
* Copyright 2002 by PostgreSQL Global Development Group
|
* Copyright 2002 by PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
@ -60,9 +62,11 @@ Installation:
|
|||||||
- requires anonymous composite type syntax in the FROM clause. See
|
- requires anonymous composite type syntax in the FROM clause. See
|
||||||
the instructions in the documentation below.
|
the instructions in the documentation below.
|
||||||
|
|
||||||
connectby(text relname, text keyid_fld, text parent_keyid_fld,
|
connectby(text relname, text keyid_fld, text parent_keyid_fld
|
||||||
text start_with, int max_depth [, text branch_delim])
|
[, text orderby_fld], text start_with, int max_depth
|
||||||
|
[, text branch_delim])
|
||||||
- returns keyid, parent_keyid, level, and an optional branch string
|
- returns keyid, parent_keyid, level, and an optional branch string
|
||||||
|
and an optional serial column for ordering siblings
|
||||||
- requires anonymous composite type syntax in the FROM clause. See
|
- requires anonymous composite type syntax in the FROM clause. See
|
||||||
the instructions in the documentation below.
|
the instructions in the documentation below.
|
||||||
|
|
||||||
@ -452,13 +456,14 @@ AS
|
|||||||
==================================================================
|
==================================================================
|
||||||
Name
|
Name
|
||||||
|
|
||||||
connectby(text, text, text, text, int[, text]) - returns a set
|
connectby(text, text, text[, text], text, text, int[, text]) - returns a set
|
||||||
representing a hierarchy (tree structure)
|
representing a hierarchy (tree structure)
|
||||||
|
|
||||||
Synopsis
|
Synopsis
|
||||||
|
|
||||||
connectby(text relname, text keyid_fld, text parent_keyid_fld,
|
connectby(text relname, text keyid_fld, text parent_keyid_fld
|
||||||
text start_with, int max_depth [, text branch_delim])
|
[, text orderby_fld], text start_with, int max_depth
|
||||||
|
[, text branch_delim])
|
||||||
|
|
||||||
Inputs
|
Inputs
|
||||||
|
|
||||||
@ -474,6 +479,11 @@ Inputs
|
|||||||
|
|
||||||
Name of the key_parent field
|
Name of the key_parent field
|
||||||
|
|
||||||
|
orderby_fld
|
||||||
|
|
||||||
|
If optional ordering of siblings is desired:
|
||||||
|
Name of the field to order siblings
|
||||||
|
|
||||||
start_with
|
start_with
|
||||||
|
|
||||||
root value of the tree input as a text value regardless of keyid_fld type
|
root value of the tree input as a text value regardless of keyid_fld type
|
||||||
@ -501,6 +511,16 @@ Outputs
|
|||||||
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0)
|
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0)
|
||||||
AS t(keyid text, parent_keyid text, level int);
|
AS t(keyid text, parent_keyid text, level int);
|
||||||
|
|
||||||
|
- or -
|
||||||
|
|
||||||
|
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~')
|
||||||
|
AS t(keyid text, parent_keyid text, level int, branch text, pos int);
|
||||||
|
|
||||||
|
- or -
|
||||||
|
|
||||||
|
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0)
|
||||||
|
AS t(keyid text, parent_keyid text, level int, pos int);
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
|
|
||||||
1. keyid and parent_keyid must be the same data type
|
1. keyid and parent_keyid must be the same data type
|
||||||
@ -520,22 +540,25 @@ Notes
|
|||||||
5. The parameters representing table and field names must include double
|
5. The parameters representing table and field names must include double
|
||||||
quotes if the names are mixed-case or contain special characters.
|
quotes if the names are mixed-case or contain special characters.
|
||||||
|
|
||||||
|
6. If sorting of siblings is desired, the orderby_fld input parameter *and*
|
||||||
|
a name for the resulting serial field (type INT32) in the query column
|
||||||
|
definition must be given.
|
||||||
|
|
||||||
Example usage
|
Example usage
|
||||||
|
|
||||||
CREATE TABLE connectby_tree(keyid text, parent_keyid text);
|
CREATE TABLE connectby_tree(keyid text, parent_keyid text, pos int);
|
||||||
|
|
||||||
INSERT INTO connectby_tree VALUES('row1',NULL);
|
INSERT INTO connectby_tree VALUES('row1',NULL, 0);
|
||||||
INSERT INTO connectby_tree VALUES('row2','row1');
|
INSERT INTO connectby_tree VALUES('row2','row1', 0);
|
||||||
INSERT INTO connectby_tree VALUES('row3','row1');
|
INSERT INTO connectby_tree VALUES('row3','row1', 0);
|
||||||
INSERT INTO connectby_tree VALUES('row4','row2');
|
INSERT INTO connectby_tree VALUES('row4','row2', 1);
|
||||||
INSERT INTO connectby_tree VALUES('row5','row2');
|
INSERT INTO connectby_tree VALUES('row5','row2', 0);
|
||||||
INSERT INTO connectby_tree VALUES('row6','row4');
|
INSERT INTO connectby_tree VALUES('row6','row4', 0);
|
||||||
INSERT INTO connectby_tree VALUES('row7','row3');
|
INSERT INTO connectby_tree VALUES('row7','row3', 0);
|
||||||
INSERT INTO connectby_tree VALUES('row8','row6');
|
INSERT INTO connectby_tree VALUES('row8','row6', 0);
|
||||||
INSERT INTO connectby_tree VALUES('row9','row5');
|
INSERT INTO connectby_tree VALUES('row9','row5', 0);
|
||||||
|
|
||||||
-- with branch
|
-- with branch, without orderby_fld
|
||||||
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0, '~')
|
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0, '~')
|
||||||
AS t(keyid text, parent_keyid text, level int, branch text);
|
AS t(keyid text, parent_keyid text, level int, branch text);
|
||||||
keyid | parent_keyid | level | branch
|
keyid | parent_keyid | level | branch
|
||||||
@ -548,7 +571,7 @@ SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0, '~
|
|||||||
row9 | row5 | 2 | row2~row5~row9
|
row9 | row5 | 2 | row2~row5~row9
|
||||||
(6 rows)
|
(6 rows)
|
||||||
|
|
||||||
-- without branch
|
-- without branch, without orderby_fld
|
||||||
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0)
|
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0)
|
||||||
AS t(keyid text, parent_keyid text, level int);
|
AS t(keyid text, parent_keyid text, level int);
|
||||||
keyid | parent_keyid | level
|
keyid | parent_keyid | level
|
||||||
@ -561,6 +584,32 @@ SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'row2', 0)
|
|||||||
row9 | row5 | 2
|
row9 | row5 | 2
|
||||||
(6 rows)
|
(6 rows)
|
||||||
|
|
||||||
|
-- with branch, with orderby_fld (notice that row5 comes before row4)
|
||||||
|
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~')
|
||||||
|
AS t(keyid text, parent_keyid text, level int, branch text, pos int) ORDER BY t.pos;
|
||||||
|
keyid | parent_keyid | level | branch | pos
|
||||||
|
-------+--------------+-------+---------------------+-----
|
||||||
|
row2 | | 0 | row2 | 1
|
||||||
|
row5 | row2 | 1 | row2~row5 | 2
|
||||||
|
row9 | row5 | 2 | row2~row5~row9 | 3
|
||||||
|
row4 | row2 | 1 | row2~row4 | 4
|
||||||
|
row6 | row4 | 2 | row2~row4~row6 | 5
|
||||||
|
row8 | row6 | 3 | row2~row4~row6~row8 | 6
|
||||||
|
(6 rows)
|
||||||
|
|
||||||
|
-- without branch, with orderby_fld (notice that row5 comes before row4)
|
||||||
|
SELECT * FROM connectby('connectby_tree', 'keyid', 'parent_keyid', 'pos', 'row2', 0)
|
||||||
|
AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY t.pos;
|
||||||
|
keyid | parent_keyid | level | pos
|
||||||
|
-------+--------------+-------+-----
|
||||||
|
row2 | | 0 | 1
|
||||||
|
row5 | row2 | 1 | 2
|
||||||
|
row9 | row5 | 2 | 3
|
||||||
|
row4 | row2 | 1 | 4
|
||||||
|
row6 | row4 | 2 | 5
|
||||||
|
row8 | row6 | 3 | 6
|
||||||
|
(6 rows)
|
||||||
|
|
||||||
==================================================================
|
==================================================================
|
||||||
-- Joe Conway
|
-- Joe Conway
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
row1 \N
|
row1 \N 0
|
||||||
row2 row1
|
row2 row1 0
|
||||||
row3 row1
|
row3 row1 0
|
||||||
row4 row2
|
row4 row2 1
|
||||||
row5 row2
|
row5 row2 0
|
||||||
row6 row4
|
row6 row4 0
|
||||||
row7 row3
|
row7 row3 0
|
||||||
row8 row6
|
row8 row6 0
|
||||||
row9 row5
|
row9 row5 0
|
||||||
|
@ -197,9 +197,9 @@ ERROR: provided "categories" SQL must return 1 column of at least one row
|
|||||||
-- connectby
|
-- connectby
|
||||||
--
|
--
|
||||||
-- test connectby with text based hierarchy
|
-- test connectby with text based hierarchy
|
||||||
CREATE TABLE connectby_text(keyid text, parent_keyid text);
|
CREATE TABLE connectby_text(keyid text, parent_keyid text, pos int);
|
||||||
\copy connectby_text from 'data/connectby_text.data'
|
\copy connectby_text from 'data/connectby_text.data'
|
||||||
-- with branch
|
-- with branch, without orderby
|
||||||
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text);
|
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text);
|
||||||
keyid | parent_keyid | level | branch
|
keyid | parent_keyid | level | branch
|
||||||
-------+--------------+-------+---------------------
|
-------+--------------+-------+---------------------
|
||||||
@ -211,7 +211,7 @@ SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~
|
|||||||
row9 | row5 | 2 | row2~row5~row9
|
row9 | row5 | 2 | row2~row5~row9
|
||||||
(6 rows)
|
(6 rows)
|
||||||
|
|
||||||
-- without branch
|
-- without branch, without orderby
|
||||||
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int);
|
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int);
|
||||||
keyid | parent_keyid | level
|
keyid | parent_keyid | level
|
||||||
-------+--------------+-------
|
-------+--------------+-------
|
||||||
@ -223,6 +223,30 @@ SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS
|
|||||||
row9 | row5 | 2
|
row9 | row5 | 2
|
||||||
(6 rows)
|
(6 rows)
|
||||||
|
|
||||||
|
-- with branch, with orderby
|
||||||
|
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int) ORDER BY t.pos;
|
||||||
|
keyid | parent_keyid | level | branch | pos
|
||||||
|
-------+--------------+-------+---------------------+-----
|
||||||
|
row2 | | 0 | row2 | 1
|
||||||
|
row5 | row2 | 1 | row2~row5 | 2
|
||||||
|
row9 | row5 | 2 | row2~row5~row9 | 3
|
||||||
|
row4 | row2 | 1 | row2~row4 | 4
|
||||||
|
row6 | row4 | 2 | row2~row4~row6 | 5
|
||||||
|
row8 | row6 | 3 | row2~row4~row6~row8 | 6
|
||||||
|
(6 rows)
|
||||||
|
|
||||||
|
-- without branch, with orderby
|
||||||
|
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY t.pos;
|
||||||
|
keyid | parent_keyid | level | pos
|
||||||
|
-------+--------------+-------+-----
|
||||||
|
row2 | | 0 | 1
|
||||||
|
row5 | row2 | 1 | 2
|
||||||
|
row9 | row5 | 2 | 3
|
||||||
|
row4 | row2 | 1 | 4
|
||||||
|
row6 | row4 | 2 | 5
|
||||||
|
row8 | row6 | 3 | 6
|
||||||
|
(6 rows)
|
||||||
|
|
||||||
-- test connectby with int based hierarchy
|
-- test connectby with int based hierarchy
|
||||||
CREATE TABLE connectby_int(keyid int, parent_keyid int);
|
CREATE TABLE connectby_int(keyid int, parent_keyid int);
|
||||||
\copy connectby_int from 'data/connectby_int.data'
|
\copy connectby_int from 'data/connectby_int.data'
|
||||||
|
@ -94,15 +94,21 @@ AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_start
|
|||||||
--
|
--
|
||||||
|
|
||||||
-- test connectby with text based hierarchy
|
-- test connectby with text based hierarchy
|
||||||
CREATE TABLE connectby_text(keyid text, parent_keyid text);
|
CREATE TABLE connectby_text(keyid text, parent_keyid text, pos int);
|
||||||
\copy connectby_text from 'data/connectby_text.data'
|
\copy connectby_text from 'data/connectby_text.data'
|
||||||
|
|
||||||
-- with branch
|
-- with branch, without orderby
|
||||||
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text);
|
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text);
|
||||||
|
|
||||||
-- without branch
|
-- without branch, without orderby
|
||||||
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int);
|
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'row2', 0) AS t(keyid text, parent_keyid text, level int);
|
||||||
|
|
||||||
|
-- with branch, with orderby
|
||||||
|
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos int) ORDER BY t.pos;
|
||||||
|
|
||||||
|
-- without branch, with orderby
|
||||||
|
SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos int) ORDER BY t.pos;
|
||||||
|
|
||||||
-- test connectby with int based hierarchy
|
-- test connectby with int based hierarchy
|
||||||
CREATE TABLE connectby_int(keyid int, parent_keyid int);
|
CREATE TABLE connectby_int(keyid int, parent_keyid int);
|
||||||
\copy connectby_int from 'data/connectby_int.data'
|
\copy connectby_int from 'data/connectby_int.data'
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
* Sample to demonstrate C functions which return setof scalar
|
* Sample to demonstrate C functions which return setof scalar
|
||||||
* and setof composite.
|
* and setof composite.
|
||||||
* Joe Conway <mail@joeconway.com>
|
* Joe Conway <mail@joeconway.com>
|
||||||
|
* And contributors:
|
||||||
|
* Nabil Sayegh <postgresql@e-trolley.de>
|
||||||
*
|
*
|
||||||
* Copyright 2002 by PostgreSQL Global Development Group
|
* Copyright 2002 by PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
@ -45,7 +47,7 @@ static Tuplestorestate *get_crosstab_tuplestore(char *sql,
|
|||||||
int num_categories,
|
int num_categories,
|
||||||
TupleDesc tupdesc,
|
TupleDesc tupdesc,
|
||||||
MemoryContext per_query_ctx);
|
MemoryContext per_query_ctx);
|
||||||
static void validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch);
|
static void validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial);
|
||||||
static bool compatCrosstabTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
|
static bool compatCrosstabTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
|
||||||
static bool compatConnectbyTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
|
static bool compatConnectbyTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2);
|
||||||
static void get_normal_pair(float8 *x1, float8 *x2);
|
static void get_normal_pair(float8 *x1, float8 *x2);
|
||||||
@ -54,21 +56,26 @@ static TupleDesc make_crosstab_tupledesc(TupleDesc spi_tupdesc,
|
|||||||
static Tuplestorestate *connectby(char *relname,
|
static Tuplestorestate *connectby(char *relname,
|
||||||
char *key_fld,
|
char *key_fld,
|
||||||
char *parent_key_fld,
|
char *parent_key_fld,
|
||||||
|
char *orderby_fld,
|
||||||
char *branch_delim,
|
char *branch_delim,
|
||||||
char *start_with,
|
char *start_with,
|
||||||
int max_depth,
|
int max_depth,
|
||||||
bool show_branch,
|
bool show_branch,
|
||||||
|
bool show_serial,
|
||||||
MemoryContext per_query_ctx,
|
MemoryContext per_query_ctx,
|
||||||
AttInMetadata *attinmeta);
|
AttInMetadata *attinmeta);
|
||||||
static Tuplestorestate *build_tuplestore_recursively(char *key_fld,
|
static Tuplestorestate *build_tuplestore_recursively(char *key_fld,
|
||||||
char *parent_key_fld,
|
char *parent_key_fld,
|
||||||
char *relname,
|
char *relname,
|
||||||
|
char *orderby_fld,
|
||||||
char *branch_delim,
|
char *branch_delim,
|
||||||
char *start_with,
|
char *start_with,
|
||||||
char *branch,
|
char *branch,
|
||||||
int level,
|
int level,
|
||||||
|
int *serial,
|
||||||
int max_depth,
|
int max_depth,
|
||||||
bool show_branch,
|
bool show_branch,
|
||||||
|
bool show_serial,
|
||||||
MemoryContext per_query_ctx,
|
MemoryContext per_query_ctx,
|
||||||
AttInMetadata *attinmeta,
|
AttInMetadata *attinmeta,
|
||||||
Tuplestorestate *tupstore);
|
Tuplestorestate *tupstore);
|
||||||
@ -1017,31 +1024,32 @@ get_crosstab_tuplestore(char *sql,
|
|||||||
*
|
*
|
||||||
* e.g. given table foo:
|
* e.g. given table foo:
|
||||||
*
|
*
|
||||||
* keyid parent_keyid
|
* keyid parent_keyid pos
|
||||||
* ------+--------------
|
* ------+------------+--
|
||||||
* row1 NULL
|
* row1 NULL 0
|
||||||
* row2 row1
|
* row2 row1 0
|
||||||
* row3 row1
|
* row3 row1 0
|
||||||
* row4 row2
|
* row4 row2 1
|
||||||
* row5 row2
|
* row5 row2 0
|
||||||
* row6 row4
|
* row6 row4 0
|
||||||
* row7 row3
|
* row7 row3 0
|
||||||
* row8 row6
|
* row8 row6 0
|
||||||
* row9 row5
|
* row9 row5 0
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* connectby(text relname, text keyid_fld, text parent_keyid_fld,
|
* connectby(text relname, text keyid_fld, text parent_keyid_fld
|
||||||
* text start_with, int max_depth [, text branch_delim])
|
* [, text orderby_fld], text start_with, int max_depth
|
||||||
* connectby('foo', 'keyid', 'parent_keyid', 'row2', 0, '~') returns:
|
* [, text branch_delim])
|
||||||
|
* connectby('foo', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') returns:
|
||||||
*
|
*
|
||||||
* keyid parent_id level branch
|
* keyid parent_id level branch serial
|
||||||
* ------+-----------+--------+-----------------------
|
* ------+-----------+--------+-----------------------
|
||||||
* row2 NULL 0 row2
|
* row2 NULL 0 row2 1
|
||||||
* row4 row2 1 row2~row4
|
* row5 row2 1 row2~row5 2
|
||||||
* row6 row4 2 row2~row4~row6
|
* row9 row5 2 row2~row5~row9 3
|
||||||
* row8 row6 3 row2~row4~row6~row8
|
* row4 row2 1 row2~row4 4
|
||||||
* row5 row2 1 row2~row5
|
* row6 row4 2 row2~row4~row6 5
|
||||||
* row9 row5 2 row2~row5~row9
|
* row8 row6 3 row2~row4~row6~row8 6
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
PG_FUNCTION_INFO_V1(connectby_text);
|
PG_FUNCTION_INFO_V1(connectby_text);
|
||||||
@ -1059,6 +1067,7 @@ connectby_text(PG_FUNCTION_ARGS)
|
|||||||
int max_depth = PG_GETARG_INT32(4);
|
int max_depth = PG_GETARG_INT32(4);
|
||||||
char *branch_delim = NULL;
|
char *branch_delim = NULL;
|
||||||
bool show_branch = false;
|
bool show_branch = false;
|
||||||
|
bool show_serial = false;
|
||||||
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||||
TupleDesc tupdesc;
|
TupleDesc tupdesc;
|
||||||
AttInMetadata *attinmeta;
|
AttInMetadata *attinmeta;
|
||||||
@ -1088,7 +1097,7 @@ connectby_text(PG_FUNCTION_ARGS)
|
|||||||
tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
|
tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
|
||||||
|
|
||||||
/* does it meet our needs */
|
/* does it meet our needs */
|
||||||
validateConnectbyTupleDesc(tupdesc, show_branch);
|
validateConnectbyTupleDesc(tupdesc, show_branch, show_serial);
|
||||||
|
|
||||||
/* OK, use it then */
|
/* OK, use it then */
|
||||||
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||||
@ -1105,10 +1114,12 @@ connectby_text(PG_FUNCTION_ARGS)
|
|||||||
rsinfo->setResult = connectby(relname,
|
rsinfo->setResult = connectby(relname,
|
||||||
key_fld,
|
key_fld,
|
||||||
parent_key_fld,
|
parent_key_fld,
|
||||||
|
NULL,
|
||||||
branch_delim,
|
branch_delim,
|
||||||
start_with,
|
start_with,
|
||||||
max_depth,
|
max_depth,
|
||||||
show_branch,
|
show_branch,
|
||||||
|
show_serial,
|
||||||
per_query_ctx,
|
per_query_ctx,
|
||||||
attinmeta);
|
attinmeta);
|
||||||
rsinfo->setDesc = tupdesc;
|
rsinfo->setDesc = tupdesc;
|
||||||
@ -1125,6 +1136,85 @@ connectby_text(PG_FUNCTION_ARGS)
|
|||||||
return (Datum) 0;
|
return (Datum) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(connectby_text_serial);
|
||||||
|
Datum
|
||||||
|
connectby_text_serial(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
char *relname = GET_STR(PG_GETARG_TEXT_P(0));
|
||||||
|
char *key_fld = GET_STR(PG_GETARG_TEXT_P(1));
|
||||||
|
char *parent_key_fld = GET_STR(PG_GETARG_TEXT_P(2));
|
||||||
|
char *orderby_fld = GET_STR(PG_GETARG_TEXT_P(3));
|
||||||
|
char *start_with = GET_STR(PG_GETARG_TEXT_P(4));
|
||||||
|
int max_depth = PG_GETARG_INT32(5);
|
||||||
|
char *branch_delim = NULL;
|
||||||
|
bool show_branch = false;
|
||||||
|
bool show_serial = true;
|
||||||
|
|
||||||
|
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
|
||||||
|
TupleDesc tupdesc;
|
||||||
|
AttInMetadata *attinmeta;
|
||||||
|
MemoryContext per_query_ctx;
|
||||||
|
MemoryContext oldcontext;
|
||||||
|
|
||||||
|
/* check to see if caller supports us returning a tuplestore */
|
||||||
|
if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize))
|
||||||
|
elog(ERROR, "connectby: materialize mode required, but it is not "
|
||||||
|
"allowed in this context");
|
||||||
|
|
||||||
|
if (fcinfo->nargs == 7)
|
||||||
|
{
|
||||||
|
branch_delim = GET_STR(PG_GETARG_TEXT_P(6));
|
||||||
|
show_branch = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
/* default is no show, tilde for the delimiter */
|
||||||
|
branch_delim = pstrdup("~");
|
||||||
|
|
||||||
|
per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
|
||||||
|
oldcontext = MemoryContextSwitchTo(per_query_ctx);
|
||||||
|
|
||||||
|
/* get the requested return tuple description */
|
||||||
|
tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
|
||||||
|
|
||||||
|
/* does it meet our needs */
|
||||||
|
validateConnectbyTupleDesc(tupdesc, show_branch, show_serial);
|
||||||
|
|
||||||
|
/* OK, use it then */
|
||||||
|
attinmeta = TupleDescGetAttInMetadata(tupdesc);
|
||||||
|
|
||||||
|
/* check to see if caller supports us returning a tuplestore */
|
||||||
|
if (!rsinfo->allowedModes & SFRM_Materialize)
|
||||||
|
elog(ERROR, "connectby requires Materialize mode, but it is not "
|
||||||
|
"allowed in this context");
|
||||||
|
|
||||||
|
/* OK, go to work */
|
||||||
|
rsinfo->returnMode = SFRM_Materialize;
|
||||||
|
rsinfo->setResult = connectby(relname,
|
||||||
|
key_fld,
|
||||||
|
parent_key_fld,
|
||||||
|
orderby_fld,
|
||||||
|
branch_delim,
|
||||||
|
start_with,
|
||||||
|
max_depth,
|
||||||
|
show_branch,
|
||||||
|
show_serial,
|
||||||
|
per_query_ctx,
|
||||||
|
attinmeta);
|
||||||
|
rsinfo->setDesc = tupdesc;
|
||||||
|
|
||||||
|
MemoryContextSwitchTo(oldcontext);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SFRM_Materialize mode expects us to return a NULL Datum. The actual
|
||||||
|
* tuples are in our tuplestore and passed back through
|
||||||
|
* rsinfo->setResult. rsinfo->setDesc is set to the tuple description
|
||||||
|
* that we actually used to build our tuples with, so the caller can
|
||||||
|
* verify we did what it was expecting.
|
||||||
|
*/
|
||||||
|
return (Datum) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* connectby - does the real work for connectby_text()
|
* connectby - does the real work for connectby_text()
|
||||||
*/
|
*/
|
||||||
@ -1132,10 +1222,12 @@ static Tuplestorestate *
|
|||||||
connectby(char *relname,
|
connectby(char *relname,
|
||||||
char *key_fld,
|
char *key_fld,
|
||||||
char *parent_key_fld,
|
char *parent_key_fld,
|
||||||
|
char *orderby_fld,
|
||||||
char *branch_delim,
|
char *branch_delim,
|
||||||
char *start_with,
|
char *start_with,
|
||||||
int max_depth,
|
int max_depth,
|
||||||
bool show_branch,
|
bool show_branch,
|
||||||
|
bool show_serial,
|
||||||
MemoryContext per_query_ctx,
|
MemoryContext per_query_ctx,
|
||||||
AttInMetadata *attinmeta)
|
AttInMetadata *attinmeta)
|
||||||
{
|
{
|
||||||
@ -1143,6 +1235,8 @@ connectby(char *relname,
|
|||||||
int ret;
|
int ret;
|
||||||
MemoryContext oldcontext;
|
MemoryContext oldcontext;
|
||||||
|
|
||||||
|
int serial = 1;
|
||||||
|
|
||||||
/* Connect to SPI manager */
|
/* Connect to SPI manager */
|
||||||
if ((ret = SPI_connect()) < 0)
|
if ((ret = SPI_connect()) < 0)
|
||||||
/* internal error */
|
/* internal error */
|
||||||
@ -1160,12 +1254,15 @@ connectby(char *relname,
|
|||||||
tupstore = build_tuplestore_recursively(key_fld,
|
tupstore = build_tuplestore_recursively(key_fld,
|
||||||
parent_key_fld,
|
parent_key_fld,
|
||||||
relname,
|
relname,
|
||||||
|
orderby_fld,
|
||||||
branch_delim,
|
branch_delim,
|
||||||
start_with,
|
start_with,
|
||||||
start_with, /* current_branch */
|
start_with, /* current_branch */
|
||||||
0, /* initial level is 0 */
|
0, /* initial level is 0 */
|
||||||
|
&serial, /* initial serial is 1 */
|
||||||
max_depth,
|
max_depth,
|
||||||
show_branch,
|
show_branch,
|
||||||
|
show_serial,
|
||||||
per_query_ctx,
|
per_query_ctx,
|
||||||
attinmeta,
|
attinmeta,
|
||||||
tupstore);
|
tupstore);
|
||||||
@ -1179,12 +1276,15 @@ static Tuplestorestate *
|
|||||||
build_tuplestore_recursively(char *key_fld,
|
build_tuplestore_recursively(char *key_fld,
|
||||||
char *parent_key_fld,
|
char *parent_key_fld,
|
||||||
char *relname,
|
char *relname,
|
||||||
|
char *orderby_fld,
|
||||||
char *branch_delim,
|
char *branch_delim,
|
||||||
char *start_with,
|
char *start_with,
|
||||||
char *branch,
|
char *branch,
|
||||||
int level,
|
int level,
|
||||||
|
int *serial,
|
||||||
int max_depth,
|
int max_depth,
|
||||||
bool show_branch,
|
bool show_branch,
|
||||||
|
bool show_serial,
|
||||||
MemoryContext per_query_ctx,
|
MemoryContext per_query_ctx,
|
||||||
AttInMetadata *attinmeta,
|
AttInMetadata *attinmeta,
|
||||||
Tuplestorestate *tupstore)
|
Tuplestorestate *tupstore)
|
||||||
@ -1194,11 +1294,14 @@ build_tuplestore_recursively(char *key_fld,
|
|||||||
StringInfo sql = makeStringInfo();
|
StringInfo sql = makeStringInfo();
|
||||||
int ret;
|
int ret;
|
||||||
int proc;
|
int proc;
|
||||||
|
int serial_column;
|
||||||
|
|
||||||
if (max_depth > 0 && level > max_depth)
|
if (max_depth > 0 && level > max_depth)
|
||||||
return tupstore;
|
return tupstore;
|
||||||
|
|
||||||
/* Build initial sql statement */
|
/* Build initial sql statement */
|
||||||
|
if (!show_serial)
|
||||||
|
{
|
||||||
appendStringInfo(sql, "SELECT %s, %s FROM %s WHERE %s = '%s' AND %s IS NOT NULL",
|
appendStringInfo(sql, "SELECT %s, %s FROM %s WHERE %s = '%s' AND %s IS NOT NULL",
|
||||||
key_fld,
|
key_fld,
|
||||||
parent_key_fld,
|
parent_key_fld,
|
||||||
@ -1206,6 +1309,20 @@ build_tuplestore_recursively(char *key_fld,
|
|||||||
parent_key_fld,
|
parent_key_fld,
|
||||||
start_with,
|
start_with,
|
||||||
key_fld);
|
key_fld);
|
||||||
|
serial_column=0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendStringInfo(sql, "SELECT %s, %s FROM %s WHERE %s = '%s' AND %s IS NOT NULL ORDER BY %s",
|
||||||
|
key_fld,
|
||||||
|
parent_key_fld,
|
||||||
|
relname,
|
||||||
|
parent_key_fld,
|
||||||
|
start_with,
|
||||||
|
key_fld,
|
||||||
|
orderby_fld);
|
||||||
|
serial_column=1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Retrieve the desired rows */
|
/* Retrieve the desired rows */
|
||||||
ret = SPI_exec(sql->data, 0);
|
ret = SPI_exec(sql->data, 0);
|
||||||
@ -1222,6 +1339,7 @@ build_tuplestore_recursively(char *key_fld,
|
|||||||
char *current_key;
|
char *current_key;
|
||||||
char *current_key_parent;
|
char *current_key_parent;
|
||||||
char current_level[INT32_STRLEN];
|
char current_level[INT32_STRLEN];
|
||||||
|
char serial_str[INT32_STRLEN];
|
||||||
char *current_branch;
|
char *current_branch;
|
||||||
char **values;
|
char **values;
|
||||||
StringInfo branchstr = NULL;
|
StringInfo branchstr = NULL;
|
||||||
@ -1236,9 +1354,9 @@ build_tuplestore_recursively(char *key_fld,
|
|||||||
chk_current_key = makeStringInfo();
|
chk_current_key = makeStringInfo();
|
||||||
|
|
||||||
if (show_branch)
|
if (show_branch)
|
||||||
values = (char **) palloc(CONNECTBY_NCOLS * sizeof(char *));
|
values = (char **) palloc((CONNECTBY_NCOLS + serial_column) * sizeof(char *));
|
||||||
else
|
else
|
||||||
values = (char **) palloc(CONNECTBY_NCOLS_NOBRANCH * sizeof(char *));
|
values = (char **) palloc((CONNECTBY_NCOLS_NOBRANCH + serial_column) * sizeof(char *));
|
||||||
|
|
||||||
/* First time through, do a little setup */
|
/* First time through, do a little setup */
|
||||||
if (level == 0)
|
if (level == 0)
|
||||||
@ -1270,6 +1388,16 @@ build_tuplestore_recursively(char *key_fld,
|
|||||||
if (show_branch)
|
if (show_branch)
|
||||||
values[3] = start_with;
|
values[3] = start_with;
|
||||||
|
|
||||||
|
/* root starts the serial with 1 */
|
||||||
|
if (show_serial)
|
||||||
|
{
|
||||||
|
sprintf(serial_str, "%d", (*serial)++);
|
||||||
|
if (show_branch)
|
||||||
|
values[4] = serial_str;
|
||||||
|
else
|
||||||
|
values[3] = serial_str;
|
||||||
|
}
|
||||||
|
|
||||||
/* construct the tuple */
|
/* construct the tuple */
|
||||||
tuple = BuildTupleFromCStrings(attinmeta, values);
|
tuple = BuildTupleFromCStrings(attinmeta, values);
|
||||||
|
|
||||||
@ -1317,6 +1445,14 @@ build_tuplestore_recursively(char *key_fld,
|
|||||||
values[2] = current_level;
|
values[2] = current_level;
|
||||||
if (show_branch)
|
if (show_branch)
|
||||||
values[3] = current_branch;
|
values[3] = current_branch;
|
||||||
|
if (show_serial)
|
||||||
|
{
|
||||||
|
sprintf(serial_str, "%d", (*serial)++);
|
||||||
|
if (show_branch)
|
||||||
|
values[4] = serial_str;
|
||||||
|
else
|
||||||
|
values[3] = serial_str;
|
||||||
|
}
|
||||||
|
|
||||||
tuple = BuildTupleFromCStrings(attinmeta, values);
|
tuple = BuildTupleFromCStrings(attinmeta, values);
|
||||||
|
|
||||||
@ -1338,12 +1474,15 @@ build_tuplestore_recursively(char *key_fld,
|
|||||||
tupstore = build_tuplestore_recursively(key_fld,
|
tupstore = build_tuplestore_recursively(key_fld,
|
||||||
parent_key_fld,
|
parent_key_fld,
|
||||||
relname,
|
relname,
|
||||||
|
orderby_fld,
|
||||||
branch_delim,
|
branch_delim,
|
||||||
values[0],
|
values[0],
|
||||||
current_branch,
|
current_branch,
|
||||||
level + 1,
|
level + 1,
|
||||||
|
serial,
|
||||||
max_depth,
|
max_depth,
|
||||||
show_branch,
|
show_branch,
|
||||||
|
show_serial,
|
||||||
per_query_ctx,
|
per_query_ctx,
|
||||||
attinmeta,
|
attinmeta,
|
||||||
tupstore);
|
tupstore);
|
||||||
@ -1367,12 +1506,17 @@ build_tuplestore_recursively(char *key_fld,
|
|||||||
* Check expected (query runtime) tupdesc suitable for Connectby
|
* Check expected (query runtime) tupdesc suitable for Connectby
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch)
|
validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch, bool show_serial)
|
||||||
{
|
{
|
||||||
|
int serial_column=0;
|
||||||
|
|
||||||
|
if (show_serial)
|
||||||
|
serial_column=1;
|
||||||
|
|
||||||
/* are there the correct number of columns */
|
/* are there the correct number of columns */
|
||||||
if (show_branch)
|
if (show_branch)
|
||||||
{
|
{
|
||||||
if (tupdesc->natts != CONNECTBY_NCOLS)
|
if (tupdesc->natts != (CONNECTBY_NCOLS + serial_column))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("invalid return type"),
|
errmsg("invalid return type"),
|
||||||
@ -1381,7 +1525,7 @@ validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (tupdesc->natts != CONNECTBY_NCOLS_NOBRANCH)
|
if (tupdesc->natts != CONNECTBY_NCOLS_NOBRANCH + serial_column)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("invalid return type"),
|
errmsg("invalid return type"),
|
||||||
@ -1412,6 +1556,16 @@ validateConnectbyTupleDesc(TupleDesc tupdesc, bool show_branch)
|
|||||||
errdetail("Fourth column must be type %s.",
|
errdetail("Fourth column must be type %s.",
|
||||||
format_type_be(TEXTOID))));
|
format_type_be(TEXTOID))));
|
||||||
|
|
||||||
|
/* check that the type of the fifth column is INT4 */
|
||||||
|
if (show_branch && show_serial && tupdesc->attrs[4]->atttypid != INT4OID)
|
||||||
|
elog(ERROR, "Query-specified return tuple not valid for Connectby: "
|
||||||
|
"fifth column must be type %s", format_type_be(INT4OID));
|
||||||
|
|
||||||
|
/* check that the type of the fifth column is INT4 */
|
||||||
|
if (!show_branch && show_serial && tupdesc->attrs[3]->atttypid != INT4OID)
|
||||||
|
elog(ERROR, "Query-specified return tuple not valid for Connectby: "
|
||||||
|
"fourth column must be type %s", format_type_be(INT4OID));
|
||||||
|
|
||||||
/* OK, the tupdesc is valid for our purposes */
|
/* OK, the tupdesc is valid for our purposes */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
* Sample to demonstrate C functions which return setof scalar
|
* Sample to demonstrate C functions which return setof scalar
|
||||||
* and setof composite.
|
* and setof composite.
|
||||||
* Joe Conway <mail@joeconway.com>
|
* Joe Conway <mail@joeconway.com>
|
||||||
|
* And contributors:
|
||||||
|
* Nabil Sayegh <postgresql@e-trolley.de>
|
||||||
*
|
*
|
||||||
* Copyright 2002 by PostgreSQL Global Development Group
|
* Copyright 2002 by PostgreSQL Global Development Group
|
||||||
*
|
*
|
||||||
@ -36,5 +38,6 @@ extern Datum normal_rand(PG_FUNCTION_ARGS);
|
|||||||
extern Datum crosstab(PG_FUNCTION_ARGS);
|
extern Datum crosstab(PG_FUNCTION_ARGS);
|
||||||
extern Datum crosstab_hash(PG_FUNCTION_ARGS);
|
extern Datum crosstab_hash(PG_FUNCTION_ARGS);
|
||||||
extern Datum connectby_text(PG_FUNCTION_ARGS);
|
extern Datum connectby_text(PG_FUNCTION_ARGS);
|
||||||
|
extern Datum connectby_text_serial(PG_FUNCTION_ARGS);
|
||||||
|
|
||||||
#endif /* TABLEFUNC_H */
|
#endif /* TABLEFUNC_H */
|
||||||
|
@ -64,3 +64,15 @@ CREATE OR REPLACE FUNCTION connectby(text,text,text,text,int)
|
|||||||
RETURNS setof record
|
RETURNS setof record
|
||||||
AS 'MODULE_PATHNAME','connectby_text'
|
AS 'MODULE_PATHNAME','connectby_text'
|
||||||
LANGUAGE 'C' STABLE STRICT;
|
LANGUAGE 'C' STABLE STRICT;
|
||||||
|
|
||||||
|
-- These 2 take the name of a field to ORDER BY as 4th arg (for sorting siblings)
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION connectby(text,text,text,text,text,int,text)
|
||||||
|
RETURNS setof record
|
||||||
|
AS 'MODULE_PATHNAME','connectby_text_serial'
|
||||||
|
LANGUAGE 'C' STABLE STRICT;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION connectby(text,text,text,text,text,int)
|
||||||
|
RETURNS setof record
|
||||||
|
AS 'MODULE_PATHNAME','connectby_text_serial'
|
||||||
|
LANGUAGE 'C' STABLE STRICT;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user