diff --git a/contrib/dblink/Makefile b/contrib/dblink/Makefile index e833b92..a00466e 100644 --- a/contrib/dblink/Makefile +++ b/contrib/dblink/Makefile @@ -6,7 +6,8 @@ PG_CPPFLAGS = -I$(libpq_srcdir) SHLIB_LINK = $(libpq) EXTENSION = dblink -DATA = dblink--1.1.sql dblink--1.0--1.1.sql dblink--unpackaged--1.0.sql +DATA = dblink--1.1.sql dblink--1.0--1.1.sql dblink--unpackaged--1.0.sql \ + dblink--1.2.sql dblink--1.1--1.2.sql REGRESS = paths dblink REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress \ diff --git a/contrib/dblink/dblink--1.1--1.2.sql b/contrib/dblink/dblink--1.1--1.2.sql index e5a0900..128611d 100644 --- a/contrib/dblink/dblink--1.1--1.2.sql +++ b/contrib/dblink/dblink--1.1--1.2.sql @@ -32,3 +32,15 @@ CREATE FUNCTION dblink_get_result(text, bool, anyelement) RETURNS SETOF anyelement AS 'MODULE_PATHNAME', 'dblink_get_result' LANGUAGE C; + +CREATE FUNCTION dblink_fetch (text, int, anyelement) +RETURNS setof anyelement +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C; + +CREATE FUNCTION dblink_fetch (text, int, boolean, anyelement) +RETURNS setof anyelement +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C; + + diff --git a/contrib/dblink/dblink--1.2.sql b/contrib/dblink/dblink--1.2.sql index bf5ddaa..9d31e2e 100644 --- a/contrib/dblink/dblink--1.2.sql +++ b/contrib/dblink/dblink--1.2.sql @@ -71,6 +71,19 @@ RETURNS setof record AS 'MODULE_PATHNAME','dblink_fetch' LANGUAGE C STRICT; +CREATE FUNCTION dblink_fetch (text, int, anyelement) +RETURNS setof anyelement +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C; + +CREATE FUNCTION dblink_fetch (text, int, boolean, anyelement) +RETURNS setof anyelement +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE C; + + + + CREATE FUNCTION dblink_fetch (text, text, int) RETURNS setof record AS 'MODULE_PATHNAME','dblink_fetch' diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 009b877..fde750c 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -537,52 +537,39 @@ dblink_fetch(PG_FUNCTION_ARGS) char *curname = NULL; int howmany = 0; bool fail = true; /* default to backward compatible */ + int first_optarg; prepTuplestoreResult(fcinfo); DBLINK_INIT; - if (PG_NARGS() == 4) + if (get_fn_expr_argtype(fcinfo->flinfo,1) == TEXTOID) { - /* text,text,int,bool */ + /* text,text,int,[bool],[anytype] */ conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); curname = text_to_cstring(PG_GETARG_TEXT_PP(1)); howmany = PG_GETARG_INT32(2); - fail = PG_GETARG_BOOL(3); - + first_optarg = 3; rconn = getConnectionByName(conname); if (rconn) conn = rconn->conn; } - else if (PG_NARGS() == 3) - { - /* text,text,int or text,int,bool */ - if (get_fn_expr_argtype(fcinfo->flinfo, 2) == BOOLOID) - { - curname = text_to_cstring(PG_GETARG_TEXT_PP(0)); - howmany = PG_GETARG_INT32(1); - fail = PG_GETARG_BOOL(2); - conn = pconn->conn; - } - else - { - conname = text_to_cstring(PG_GETARG_TEXT_PP(0)); - curname = text_to_cstring(PG_GETARG_TEXT_PP(1)); - howmany = PG_GETARG_INT32(2); - - rconn = getConnectionByName(conname); - if (rconn) - conn = rconn->conn; - } - } - else if (PG_NARGS() == 2) + else { - /* text,int */ + /* text,int,[bool],[anytype] */ curname = text_to_cstring(PG_GETARG_TEXT_PP(0)); howmany = PG_GETARG_INT32(1); + first_optarg = 2; conn = pconn->conn; } + if ((first_optarg < PG_NARGS()) && + (get_fn_expr_argtype(fcinfo->flinfo, first_optarg) == BOOLOID)) + { + fail = PG_GETARG_BOOL(first_optarg); + } + /* ignore the last arg (if any) as it just provides a return rowtype */ + if (!conn) DBLINK_CONN_NOT_AVAIL; @@ -680,27 +667,68 @@ dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) if (!is_async) { - if (PG_NARGS() == 3) + if (PG_NARGS() == 4) { - /* text,text,bool */ + /* text,text,bool,anyelement */ + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2)) + PG_RETURN_NULL(); + DBLINK_GET_CONN; sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); fail = PG_GETARG_BOOL(2); + /* ignore fourth arg as it just provides a return rowtype */ + } + else if (PG_NARGS() == 3) + { + /* text,text,bool or text,text,anyelement or text,bool,anyelement */ + if (get_fn_expr_argtype(fcinfo->flinfo, 2) == BOOLOID) + { + DBLINK_GET_CONN; + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + fail = PG_GETARG_BOOL(2); + } + else + { + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + PG_RETURN_NULL(); + + if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) + { + conn = pconn->conn; + sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); + fail = PG_GETARG_BOOL(1); + } + else + { + DBLINK_GET_CONN; + sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); + } + /* ignore third arg as it just provides a return rowtype */ + } } else if (PG_NARGS() == 2) { - /* text,text or text,bool */ + /* text,text or text,bool or text,anyelement */ if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) { conn = pconn->conn; sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); fail = PG_GETARG_BOOL(1); } - else + else if (get_fn_expr_argtype(fcinfo->flinfo, 1) == TEXTOID) { DBLINK_GET_CONN; sql = text_to_cstring(PG_GETARG_TEXT_PP(1)); } + else + { + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + conn = pconn->conn; + sql = text_to_cstring(PG_GETARG_TEXT_PP(0)); + /* ignore second arg as it just provides a return rowtype */ + } } else if (PG_NARGS() == 1) { @@ -715,11 +743,32 @@ dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) else /* is_async */ { /* get async result */ - if (PG_NARGS() == 2) + if (PG_NARGS() == 3) { - /* text,bool */ + /* text,bool,anyelement */ + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + PG_RETURN_NULL(); + DBLINK_GET_NAMED_CONN; fail = PG_GETARG_BOOL(1); + /* ignore third arg as it just provides a return rowtype */ + } + else if (PG_NARGS() == 2) + { + /* text,bool or text,anyelement */ + if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) + { + DBLINK_GET_NAMED_CONN; + fail = PG_GETARG_BOOL(1); + } + else + { + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + DBLINK_GET_NAMED_CONN; + /* ignore second arg as it just provides a return rowtype */ + } } else if (PG_NARGS() == 1) { diff --git a/contrib/dblink/dblink.control b/contrib/dblink/dblink.control index 39f439a..bdd17d2 100644 --- a/contrib/dblink/dblink.control +++ b/contrib/dblink/dblink.control @@ -1,5 +1,5 @@ # dblink extension comment = 'connect to other PostgreSQL databases from within a database' -default_version = '1.1' +default_version = '1.2' module_pathname = '$libdir/dblink' relocatable = true diff --git a/contrib/dblink/expected/dblink.out b/contrib/dblink/expected/dblink.out index 87eb142..799ba95 100644 --- a/contrib/dblink/expected/dblink.out +++ b/contrib/dblink/expected/dblink.out @@ -98,6 +98,16 @@ WHERE t.a > 7; 9 | j | {a9,b9,c9} (2 rows) +-- dblink with anyelement +SELECT * +FROM dblink('dbname=contrib_regression','SELECT * FROM foo',null::foo) as t +WHERE t.f1 > 7; + f1 | f2 | f3 +----+----+------------ + 8 | i | {a8,b8,c8} + 9 | j | {a9,b9,c9} +(2 rows) + -- should generate "connection not available" error SELECT * FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) @@ -195,14 +205,15 @@ FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); 3 | d | {a3,b3,c3} (4 rows) +-- fetch using anyelement, which will change the column names SELECT * -FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); - a | b | c ----+---+------------ - 4 | e | {a4,b4,c4} - 5 | f | {a5,b5,c5} - 6 | g | {a6,b6,c6} - 7 | h | {a7,b7,c7} +FROM dblink_fetch('rmt_foo_cursor',4,null::foo) AS t; + f1 | f2 | f3 +----+----+------------ + 4 | e | {a4,b4,c4} + 5 | f | {a5,b5,c5} + 6 | g | {a6,b6,c6} + 7 | h | {a7,b7,c7} (4 rows) -- this one only finds two rows left @@ -723,6 +734,55 @@ UNION UNION (SELECT * from dblink_get_result('dtest3') as t3(f1 int, f2 text, f3 text[])) ORDER by f1; +-- clear off the connections for the next query; +SELECT * from dblink_get_result('dtest1') as t1(f1 int, f2 text, f3 text[]) +UNION +SELECT * from dblink_get_result('dtest2') as t2(f1 int, f2 text, f3 text[]) +UNION +SELECT * from dblink_get_result('dtest3') as t3(f1 int, f2 text, f3 text[]); + f1 | f2 | f3 +----+----+---- +(0 rows) + +SELECT * from + dblink_send_query('dtest1', 'select * from foo where f1 < 3') as t1; + t1 +---- + 1 +(1 row) + +SELECT * from + dblink_send_query('dtest2', 'select * from foo where f1 > 2 and f1 < 7') as t1; + t1 +---- + 1 +(1 row) + +SELECT * from + dblink_send_query('dtest3', 'select * from foo where f1 > 6') as t1; + t1 +---- + 1 +(1 row) + +CREATE TEMPORARY TABLE result_anyelement AS +(SELECT * from dblink_get_result('dtest1',null::foo)) +UNION +(SELECT * from dblink_get_result('dtest2',null::foo)) +UNION +(SELECT * from dblink_get_result('dtest3',null::foo)) +ORDER by f1; +-- result and result_anyelement should be identical +SELECT * FROM result EXCEPT SELECT * FROM result_anyelement; + f1 | f2 | f3 +----+----+---- +(0 rows) + +SELECT * FROM result_anyelement EXCEPT SELECT * FROM result; + f1 | f2 | f3 +----+----+---- +(0 rows) + -- dblink_get_connections returns an array with elements in a machine-dependent -- ordering, so we must resort to unnesting and sorting for a stable result create function unnest(anyarray) returns setof anyelement diff --git a/contrib/dblink/sql/dblink.sql b/contrib/dblink/sql/dblink.sql index 5305d5a..a29ac44 100644 --- a/contrib/dblink/sql/dblink.sql +++ b/contrib/dblink/sql/dblink.sql @@ -60,6 +60,11 @@ SELECT * FROM dblink('dbname=contrib_regression','SELECT * FROM foo') AS t(a int, b text, c text[]) WHERE t.a > 7; +-- dblink with anyelement +SELECT * +FROM dblink('dbname=contrib_regression','SELECT * FROM foo',null::foo) as t +WHERE t.f1 > 7; + -- should generate "connection not available" error SELECT * FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) @@ -120,8 +125,9 @@ SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); SELECT * FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); +-- fetch using anyelement, which will change the column names SELECT * -FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); +FROM dblink_fetch('rmt_foo_cursor',4,null::foo) AS t; -- this one only finds two rows left SELECT * @@ -361,6 +367,32 @@ UNION (SELECT * from dblink_get_result('dtest3') as t3(f1 int, f2 text, f3 text[])) ORDER by f1; +-- clear off the connections for the next query; +SELECT * from dblink_get_result('dtest1') as t1(f1 int, f2 text, f3 text[]) +UNION +SELECT * from dblink_get_result('dtest2') as t2(f1 int, f2 text, f3 text[]) +UNION +SELECT * from dblink_get_result('dtest3') as t3(f1 int, f2 text, f3 text[]); + +SELECT * from + dblink_send_query('dtest1', 'select * from foo where f1 < 3') as t1; +SELECT * from + dblink_send_query('dtest2', 'select * from foo where f1 > 2 and f1 < 7') as t1; +SELECT * from + dblink_send_query('dtest3', 'select * from foo where f1 > 6') as t1; + +CREATE TEMPORARY TABLE result_anyelement AS +(SELECT * from dblink_get_result('dtest1',null::foo)) +UNION +(SELECT * from dblink_get_result('dtest2',null::foo)) +UNION +(SELECT * from dblink_get_result('dtest3',null::foo)) +ORDER by f1; + +-- result and result_anyelement should be identical +SELECT * FROM result EXCEPT SELECT * FROM result_anyelement; +SELECT * FROM result_anyelement EXCEPT SELECT * FROM result; + -- dblink_get_connections returns an array with elements in a machine-dependent -- ordering, so we must resort to unnesting and sorting for a stable result create function unnest(anyarray) returns setof anyelement diff --git a/doc/src/sgml/dblink.sgml b/doc/src/sgml/dblink.sgml index b07ac48..4c472ab 100644 --- a/doc/src/sgml/dblink.sgml +++ b/doc/src/sgml/dblink.sgml @@ -334,6 +334,9 @@ SELECT dblink_disconnect('myconn'); dblink(text connname, text sql [, bool fail_on_error]) returns setof record dblink(text connstr, text sql [, bool fail_on_error]) returns setof record dblink(text sql [, bool fail_on_error]) returns setof record +dblink(text connname, text sql [, bool fail_on_error], anyelement rowtype) returns setof rowtype +dblink(text connstr, text sql [, bool fail_on_error], anyelement rowtype) returns setof rowtype +dblink(text sql [, bool fail_on_error], anyelement rowtype) returns setof rowtype @@ -399,6 +402,19 @@ dblink(text sql [, bool fail_on_error]) returns setof record + + + rowtype + + + The structure of the result set to be returned from the SQL query. + This is typically a null value casted as an existing composite type + or table type. The type must exist on the local server, but need + not exist on the remote server. + + + + @@ -406,11 +422,12 @@ dblink(text sql [, bool fail_on_error]) returns setof record Return Value + The function returns the row(s) produced by the query. Since - dblink can be used with any query, it is declared - to return record, rather than specifying any particular - set of columns. This means that you must specify the expected - set of columns in the calling query — otherwise + dblink can be used with any query, the structure + of the set of columns must be declared either with the + rowtype parameter, or the set of columns must + be specified in the calling query — otherwise PostgreSQL would not know what to expect. Here is an example: @@ -435,6 +452,17 @@ SELECT * does not insist on exact type matches either. It will succeed so long as the returned data strings are valid input for the column type declared in the FROM clause. + + +CREATE TYPE subset_of_pg_proc AS (proname name, prosrc text); +SELECT * + FROM dblink('dbname=mydb', 'select proname, prosrc from pg_proc', + null::subset_of_pg_proc) + WHERE proname LIKE 'bytea%'; + + + Alternately, one can specify a rowtype which has the + same effect as if a typed alias had been used. @@ -528,6 +556,15 @@ SELECT * FROM dblink('myconn', 'select proname, prosrc from pg_proc') byteain | byteain byteaout | byteaout (14 rows) + +SELECT schemaname, tablename, hasindexes + FROM dblink('dbname=dblink_test','select * from pg_tables', + null::pg_tables) + WHERE schemaname = 'pg_catalog' and tablename = 'pg_type'; + schemaname | tablename | hasindexes +------------+-----------+------------ + pg_catalog | pg_type | t +(1 row) @@ -812,6 +849,10 @@ SELECT dblink_open('foo', 'select proname, prosrc from pg_proc'); dblink_fetch(text cursorname, int howmany [, bool fail_on_error]) returns setof record dblink_fetch(text connname, text cursorname, int howmany [, bool fail_on_error]) returns setof record +dblink_fetch(text cursorname, int howmany, anyelement rowtype) returns setof anyelement +dblink_fetch(text cursorname, int howmany, bool fail_on_error, anyelement rowtype) returns setof anyelement +dblink_fetch(text connname, text cursorname, int howmany, anyelement rowtype) returns setof anyelement +dblink_fetch(text connname, text cursorname, int howmany, bool fail_on_error, anyelement rowtype) returns setof anyelement @@ -869,6 +910,17 @@ dblink_fetch(text connname, text cursorname, int howmany [, bool fail_on_error]) + + + rowtype + + + Specifies the structure of the result set to be returned. + + + + + @@ -877,8 +929,8 @@ dblink_fetch(text connname, text cursorname, int howmany [, bool fail_on_error]) The function returns the row(s) fetched from the cursor. To use this - function, you will need to specify the expected set of columns, - as previously discussed for dblink. + function, you will need to specify the expected set of columns, or specify + a rowtype as previously discussed for dblink. @@ -1410,6 +1462,8 @@ SELECT * FROM dblink_get_notify(); dblink_get_result(text connname [, bool fail_on_error]) returns setof record +dblink_get_result(text connname, anyelement rowtype) returns setof anyelement +dblink_get_result(text connname, bool fail_on_error, anyelement rowtype) returns setof anyelement @@ -1448,6 +1502,18 @@ dblink_get_result(text connname [, bool fail_on_error]) returns setof record + + + rowtype + + + Specifies the expected column types and names expected from the + query. + + + + + @@ -1457,8 +1523,9 @@ dblink_get_result(text connname [, bool fail_on_error]) returns setof record For an async query (that is, a SQL statement returning rows), the function returns the row(s) produced by the query. To use this - function, you will need to specify the expected set of columns, - as previously discussed for dblink. + function, you will need to specify the expected set of columns + or a rowtype, as previously discussed for + dblink. @@ -1552,6 +1619,27 @@ contrib_regression=# SELECT * FROM dblink_get_result('dtest1') AS t1(f1 int, f2 f1 | f2 | f3 ----+----+---- (0 rows) + +dblink_test=# SELECT dblink_connect('dtest1rowtype','dbname=dblink_test'); + dblink_connect +---------------- + OK +(1 row) + +dblink_test=# CREATE TYPE a_composite_type AS ( x int, y text, z float ); +CREATE TYPE +dblink_test=# SELECT * FROM dblink_send_query('dtest1rowtype', +dblink_test-# format('SELECT %s, %L, %s',1,'two',3.0)); + dblink_send_query +------------------- + 1 +(1 row) + +dblink_test=# SELECT * FROM dblink_get_result('dtest1rowtype',null::a_composite_type); + x | y | z +---+-----+--- + 1 | two | 3 +(1 row)