From 6a1e2b3c2884eb842a7af5f0f92a01c2a7ee7ac9 Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Sun, 7 Mar 2004 02:27:00 +0000 Subject: [PATCH] Added new versions of dblink, dblink_exec, dblink_open, dblink_close, and, dblink_fetch -- allows ERROR on remote side of connection to throw NOTICE locally instead of ERROR. Also removed documentation for previously deprecated, now removed, functions. --- contrib/dblink/README.dblink | 33 ++--- contrib/dblink/dblink.c | 220 ++++++++++++++++++++++++++--- contrib/dblink/dblink.sql.in | 86 ++++++++--- contrib/dblink/doc/cursor | 32 ++++- contrib/dblink/doc/deprecated | 105 -------------- contrib/dblink/doc/execute | 25 +++- contrib/dblink/doc/query | 12 +- contrib/dblink/expected/dblink.out | 163 +++++++++++++++++++-- contrib/dblink/sql/dblink.sql | 65 ++++++++- 9 files changed, 556 insertions(+), 185 deletions(-) delete mode 100644 contrib/dblink/doc/deprecated diff --git a/contrib/dblink/README.dblink b/contrib/dblink/README.dblink index c315cdaa36..f5f07af641 100644 --- a/contrib/dblink/README.dblink +++ b/contrib/dblink/README.dblink @@ -30,13 +30,11 @@ * */ -Version 0.6 (14 June, 2003): - Completely removed previously deprecated functions. Added ability - to create "named" persistent connections in addition to the single global - "unnamed" persistent connection. - Tested under Linux (Red Hat 9) and PostgreSQL 7.4devel. - Release Notes: + Version 0.7 (as of 25 Feb, 2004) + - Added new version of dblink, dblink_exec, dblink_open, dblink_close, + and, dblink_fetch -- allows ERROR on remote side of connection to + throw NOTICE locally instead of ERROR Version 0.6 - functions deprecated in 0.5 have been removed - added ability to create "named" persistent connections @@ -85,7 +83,7 @@ Installation: You can use dblink.sql to create the functions in your database of choice, e.g. - psql -U postgres template1 < dblink.sql + psql template1 < dblink.sql installs following functions into database template1: @@ -104,40 +102,40 @@ Installation: cursor ------------ - dblink_open(text,text) RETURNS text + dblink_open(text,text [, bool fail_on_error]) RETURNS text - opens a cursor using unnamed connection already opened with dblink_connect() that will persist for duration of current backend or until it is closed - dblink_open(text,text,text) RETURNS text + dblink_open(text,text,text [, bool fail_on_error]) RETURNS text - opens a cursor using a named connection already opened with dblink_connect() that will persist for duration of current backend or until it is closed - dblink_fetch(text, int) RETURNS setof record + dblink_fetch(text, int [, bool fail_on_error]) RETURNS setof record - fetches data from an already opened cursor on the unnamed connection - dblink_fetch(text, text, int) RETURNS setof record + dblink_fetch(text, text, int [, bool fail_on_error]) RETURNS setof record - fetches data from an already opened cursor on a named connection - dblink_close(text) RETURNS text + dblink_close(text [, bool fail_on_error]) RETURNS text - closes a cursor on the unnamed connection - dblink_close(text,text) RETURNS text + dblink_close(text,text [, bool fail_on_error]) RETURNS text - closes a cursor on a named connection query ------------ - dblink(text,text) RETURNS setof record + dblink(text,text [, bool fail_on_error]) RETURNS setof record - returns a set of results from remote SELECT query; the first argument is either a connection string, or the name of an already opened persistant connection - dblink(text) RETURNS setof record + dblink(text [, bool fail_on_error]) RETURNS setof record - returns a set of results from remote SELECT query, using the unnamed connection already opened with dblink_connect() execute ------------ - dblink_exec(text, text) RETURNS text + dblink_exec(text, text [, bool fail_on_error]) RETURNS text - executes an INSERT/UPDATE/DELETE query remotely; the first argument is either a connection string, or the name of an already opened persistant connection - dblink_exec(text) RETURNS text + dblink_exec(text [, bool fail_on_error]) RETURNS text - executes an INSERT/UPDATE/DELETE query remotely, using connection already opened with dblink_connect() @@ -169,7 +167,6 @@ Documentation: doc/query doc/execute doc/misc - doc/deprecated ================================================================== -- Joe Conway diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c index 4ecca8eff8..ac941598be 100644 --- a/contrib/dblink/dblink.c +++ b/contrib/dblink/dblink.c @@ -134,6 +134,16 @@ typedef struct remoteConnHashEnt errmsg("%s", p2), \ errdetail("%s", msg))); \ } while (0) +#define DBLINK_RES_ERROR_AS_NOTICE(p2) \ + do { \ + msg = pstrdup(PQerrorMessage(conn)); \ + if (res) \ + PQclear(res); \ + ereport(NOTICE, \ + (errcode(ERRCODE_SYNTAX_ERROR), \ + errmsg("%s", p2), \ + errdetail("%s", msg))); \ + } while (0) #define DBLINK_CONN_NOT_AVAIL \ do { \ if(conname) \ @@ -152,7 +162,6 @@ typedef struct remoteConnHashEnt if(rcon) \ { \ conn = rcon->con; \ - freeconn = false; \ } \ else \ { \ @@ -167,6 +176,7 @@ typedef struct remoteConnHashEnt errmsg("could not establish connection"), \ errdetail("%s", msg))); \ } \ + freeconn = true; \ } \ } while (0) @@ -276,18 +286,42 @@ dblink_open(PG_FUNCTION_ARGS) char *conname = NULL; StringInfo str = makeStringInfo(); remoteConn *rcon = NULL; + bool fail = true; /* default to backward compatible behavior */ if (PG_NARGS() == 2) { + /* text,text */ curname = GET_STR(PG_GETARG_TEXT_P(0)); sql = GET_STR(PG_GETARG_TEXT_P(1)); conn = persistent_conn; } else if (PG_NARGS() == 3) { + /* might be text,text,text or text,text,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 2) == BOOLOID) + { + curname = GET_STR(PG_GETARG_TEXT_P(0)); + sql = GET_STR(PG_GETARG_TEXT_P(1)); + fail = PG_GETARG_BOOL(2); + conn = persistent_conn; + } + else + { + conname = GET_STR(PG_GETARG_TEXT_P(0)); + curname = GET_STR(PG_GETARG_TEXT_P(1)); + sql = GET_STR(PG_GETARG_TEXT_P(2)); + } + rcon = getConnectionByName(conname); + if (rcon) + conn = rcon->con; + } + else if (PG_NARGS() == 4) + { + /* text,text,text,bool */ conname = GET_STR(PG_GETARG_TEXT_P(0)); curname = GET_STR(PG_GETARG_TEXT_P(1)); sql = GET_STR(PG_GETARG_TEXT_P(2)); + fail = PG_GETARG_BOOL(3); rcon = getConnectionByName(conname); if (rcon) conn = rcon->con; @@ -304,13 +338,19 @@ dblink_open(PG_FUNCTION_ARGS) appendStringInfo(str, "DECLARE %s CURSOR FOR %s", curname, sql); res = PQexec(conn, str->data); - if (!res || - (PQresultStatus(res) != PGRES_COMMAND_OK && - PQresultStatus(res) != PGRES_TUPLES_OK)) - DBLINK_RES_ERROR("sql error"); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + { + if (fail) + DBLINK_RES_ERROR("sql error"); + else + { + DBLINK_RES_ERROR_AS_NOTICE("sql error"); + PQclear(res); + PG_RETURN_TEXT_P(GET_TEXT("ERROR")); + } + } PQclear(res); - PG_RETURN_TEXT_P(GET_TEXT("OK")); } @@ -328,16 +368,38 @@ dblink_close(PG_FUNCTION_ARGS) StringInfo str = makeStringInfo(); char *msg; remoteConn *rcon = NULL; + bool fail = true; /* default to backward compatible behavior */ if (PG_NARGS() == 1) { + /* text */ curname = GET_STR(PG_GETARG_TEXT_P(0)); conn = persistent_conn; } else if (PG_NARGS() == 2) { + /* might be text,text or text,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) + { + curname = GET_STR(PG_GETARG_TEXT_P(0)); + fail = PG_GETARG_BOOL(1); + conn = persistent_conn; + } + else + { + conname = GET_STR(PG_GETARG_TEXT_P(0)); + curname = GET_STR(PG_GETARG_TEXT_P(1)); + rcon = getConnectionByName(conname); + if (rcon) + conn = rcon->con; + } + } + if (PG_NARGS() == 3) + { + /* text,text,bool */ conname = GET_STR(PG_GETARG_TEXT_P(0)); curname = GET_STR(PG_GETARG_TEXT_P(1)); + fail = PG_GETARG_BOOL(2); rcon = getConnectionByName(conname); if (rcon) conn = rcon->con; @@ -351,7 +413,16 @@ dblink_close(PG_FUNCTION_ARGS) /* close the cursor */ res = PQexec(conn, str->data); if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) - DBLINK_RES_ERROR("sql error"); + { + if (fail) + DBLINK_RES_ERROR("sql error"); + else + { + DBLINK_RES_ERROR_AS_NOTICE("sql error"); + PQclear(res); + PG_RETURN_TEXT_P(GET_TEXT("ERROR")); + } + } PQclear(res); @@ -395,19 +466,44 @@ dblink_fetch(PG_FUNCTION_ARGS) char *curname = NULL; int howmany = 0; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + bool fail = true; /* default to backward compatible */ - if (PG_NARGS() == 3) + if (PG_NARGS() == 4) { + /* text,text,int,bool */ conname = GET_STR(PG_GETARG_TEXT_P(0)); curname = GET_STR(PG_GETARG_TEXT_P(1)); howmany = PG_GETARG_INT32(2); + fail = PG_GETARG_BOOL(3); rcon = getConnectionByName(conname); if (rcon) conn = rcon->con; } + else if (PG_NARGS() == 3) + { + /* text,text,int or text,int,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 2) == BOOLOID) + { + curname = GET_STR(PG_GETARG_TEXT_P(0)); + howmany = PG_GETARG_INT32(1); + fail = PG_GETARG_BOOL(2); + conn = persistent_conn; + } + else + { + conname = GET_STR(PG_GETARG_TEXT_P(0)); + curname = GET_STR(PG_GETARG_TEXT_P(1)); + howmany = PG_GETARG_INT32(2); + + rcon = getConnectionByName(conname); + if (rcon) + conn = rcon->con; + } + } else if (PG_NARGS() == 2) { + /* text,int */ curname = GET_STR(PG_GETARG_TEXT_P(0)); howmany = PG_GETARG_INT32(1); conn = persistent_conn; @@ -431,7 +527,17 @@ dblink_fetch(PG_FUNCTION_ARGS) if (!res || (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)) - DBLINK_RES_ERROR("sql error"); + { + if (fail) + DBLINK_RES_ERROR("sql error"); + else + { + if (res) + PQclear(res); + DBLINK_RES_ERROR_AS_NOTICE("sql error"); + SRF_RETURN_DONE(funcctx); + } + } else if (PQresultStatus(res) == PGRES_COMMAND_OK) { /* cursor does not exist - closed already or bad name */ @@ -448,7 +554,11 @@ dblink_fetch(PG_FUNCTION_ARGS) /* fast track when no results */ if (funcctx->max_calls < 1) + { + if (res) + PQclear(res); SRF_RETURN_DONE(funcctx); + } /* check typtype to see if we have a predetermined return type */ functypeid = get_func_rettype(funcid); @@ -546,7 +656,7 @@ dblink_record(PG_FUNCTION_ARGS) bool is_sql_cmd = false; char *sql_cmd_status = NULL; MemoryContext oldcontext; - bool freeconn = true; + bool freeconn = false; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) @@ -560,6 +670,7 @@ dblink_record(PG_FUNCTION_ARGS) 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 */ funcctx = SRF_FIRSTCALL_INIT(); @@ -570,13 +681,31 @@ dblink_record(PG_FUNCTION_ARGS) */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - if (PG_NARGS() == 2) + if (PG_NARGS() == 3) { + /* text,text,bool */ DBLINK_GET_CONN; sql = GET_STR(PG_GETARG_TEXT_P(1)); + fail = PG_GETARG_BOOL(2); + } + else if (PG_NARGS() == 2) + { + /* text,text or text,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) + { + conn = persistent_conn; + sql = GET_STR(PG_GETARG_TEXT_P(0)); + fail = PG_GETARG_BOOL(1); + } + else + { + DBLINK_GET_CONN; + sql = GET_STR(PG_GETARG_TEXT_P(1)); + } } else if (PG_NARGS() == 1) { + /* text */ conn = persistent_conn; sql = GET_STR(PG_GETARG_TEXT_P(0)); } @@ -588,8 +717,22 @@ dblink_record(PG_FUNCTION_ARGS) DBLINK_CONN_NOT_AVAIL; res = PQexec(conn, sql); - if (!res || (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)) - DBLINK_RES_ERROR("sql error"); + if (!res || + (PQresultStatus(res) != PGRES_COMMAND_OK && + PQresultStatus(res) != PGRES_TUPLES_OK)) + { + if (fail) + DBLINK_RES_ERROR("sql error"); + else + { + if (res) + PQclear(res); + if (freeconn) + PQfinish(conn); + DBLINK_RES_ERROR_AS_NOTICE("sql error"); + SRF_RETURN_DONE(funcctx); + } + } if (PQresultStatus(res) == PGRES_COMMAND_OK) { @@ -614,12 +757,16 @@ dblink_record(PG_FUNCTION_ARGS) funcctx->user_fctx = res; /* if needed, close the connection to the database and cleanup */ - if (freeconn && PG_NARGS() == 2) + if (freeconn) PQfinish(conn); /* fast track when no results */ if (funcctx->max_calls < 1) + { + if (res) + PQclear(res); SRF_RETURN_DONE(funcctx); + } /* check typtype to see if we have a predetermined return type */ functypeid = get_func_rettype(funcid); @@ -727,15 +874,34 @@ dblink_exec(PG_FUNCTION_ARGS) char *sql = NULL; char *conname = NULL; remoteConn *rcon = NULL; - bool freeconn = true; + bool freeconn = false; + bool fail = true; /* default to backward compatible behavior */ - if (PG_NARGS() == 2) + if (PG_NARGS() == 3) { + /* must be text,text,bool */ DBLINK_GET_CONN; sql = GET_STR(PG_GETARG_TEXT_P(1)); + fail = PG_GETARG_BOOL(2); + } + else if (PG_NARGS() == 2) + { + /* might be text,text or text,bool */ + if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID) + { + conn = persistent_conn; + sql = GET_STR(PG_GETARG_TEXT_P(0)); + fail = PG_GETARG_BOOL(1); + } + else + { + DBLINK_GET_CONN; + sql = GET_STR(PG_GETARG_TEXT_P(1)); + } } else if (PG_NARGS() == 1) { + /* must be single text argument */ conn = persistent_conn; sql = GET_STR(PG_GETARG_TEXT_P(0)); } @@ -750,9 +916,25 @@ dblink_exec(PG_FUNCTION_ARGS) if (!res || (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)) - DBLINK_RES_ERROR("sql error"); + { + if (fail) + DBLINK_RES_ERROR("sql error"); + else + DBLINK_RES_ERROR_AS_NOTICE("sql error"); - if (PQresultStatus(res) == PGRES_COMMAND_OK) + /* need a tuple descriptor representing one TEXT column */ + tupdesc = CreateTemplateTupleDesc(1, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status", + TEXTOID, -1, 0, false); + + /* + * and save a copy of the command status string to return as our + * result tuple + */ + sql_cmd_status = GET_TEXT("ERROR"); + + } + else if (PQresultStatus(res) == PGRES_COMMAND_OK) { /* need a tuple descriptor representing one TEXT column */ tupdesc = CreateTemplateTupleDesc(1, false); @@ -773,7 +955,7 @@ dblink_exec(PG_FUNCTION_ARGS) PQclear(res); /* if needed, close the connection to the database and cleanup */ - if (freeconn && fcinfo->nargs == 2) + if (freeconn) PQfinish(conn); PG_RETURN_TEXT_P(sql_cmd_status); diff --git a/contrib/dblink/dblink.sql.in b/contrib/dblink/dblink.sql.in index cd670390d7..c38f8be888 100644 --- a/contrib/dblink/dblink.sql.in +++ b/contrib/dblink/dblink.sql.in @@ -1,94 +1,144 @@ CREATE OR REPLACE FUNCTION dblink_connect (text) RETURNS text AS 'MODULE_PATHNAME','dblink_connect' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_connect (text, text) RETURNS text AS 'MODULE_PATHNAME','dblink_connect' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_disconnect () RETURNS text AS 'MODULE_PATHNAME','dblink_disconnect' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_disconnect (text) RETURNS text AS 'MODULE_PATHNAME','dblink_disconnect' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_open (text,text) RETURNS text AS 'MODULE_PATHNAME','dblink_open' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink_open (text,text,bool) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_open (text,text,text) RETURNS text AS 'MODULE_PATHNAME','dblink_open' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink_open (text,text,text,bool) +RETURNS text +AS 'MODULE_PATHNAME','dblink_open' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_fetch (text,int) RETURNS setof record AS 'MODULE_PATHNAME','dblink_fetch' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink_fetch (text,int,bool) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_fetch (text,text,int) RETURNS setof record AS 'MODULE_PATHNAME','dblink_fetch' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink_fetch (text,text,int,bool) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_fetch' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_close (text) RETURNS text AS 'MODULE_PATHNAME','dblink_close' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink_close (text,bool) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_close (text,text) RETURNS text AS 'MODULE_PATHNAME','dblink_close' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink_close (text,text,bool) +RETURNS text +AS 'MODULE_PATHNAME','dblink_close' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink (text,text) RETURNS setof record AS 'MODULE_PATHNAME','dblink_record' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink (text,text,bool) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink (text) RETURNS setof record AS 'MODULE_PATHNAME','dblink_record' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink (text,bool) +RETURNS setof record +AS 'MODULE_PATHNAME','dblink_record' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_exec (text,text) RETURNS text AS 'MODULE_PATHNAME','dblink_exec' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; + +CREATE OR REPLACE FUNCTION dblink_exec (text,text,bool) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_exec (text) RETURNS text AS 'MODULE_PATHNAME','dblink_exec' -LANGUAGE 'c' WITH (isstrict); +LANGUAGE 'c' STRICT; + +CREATE OR REPLACE FUNCTION dblink_exec (text,bool) +RETURNS text +AS 'MODULE_PATHNAME','dblink_exec' +LANGUAGE 'c' STRICT; CREATE TYPE dblink_pkey_results AS (position int4, colname text); CREATE OR REPLACE FUNCTION dblink_get_pkey (text) RETURNS setof dblink_pkey_results AS 'MODULE_PATHNAME','dblink_get_pkey' -LANGUAGE 'c' WITH (isstrict); +LANGUAGE 'c' STRICT; CREATE OR REPLACE FUNCTION dblink_build_sql_insert (text, int2vector, int4, _text, _text) RETURNS text AS 'MODULE_PATHNAME','dblink_build_sql_insert' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_build_sql_delete (text, int2vector, int4, _text) RETURNS text AS 'MODULE_PATHNAME','dblink_build_sql_delete' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_build_sql_update (text, int2vector, int4, _text, _text) RETURNS text AS 'MODULE_PATHNAME','dblink_build_sql_update' -LANGUAGE 'C' WITH (isstrict); +LANGUAGE 'C' STRICT; CREATE OR REPLACE FUNCTION dblink_current_query () RETURNS text diff --git a/contrib/dblink/doc/cursor b/contrib/dblink/doc/cursor index 7c9cc3cde2..23a3ce89ba 100644 --- a/contrib/dblink/doc/cursor +++ b/contrib/dblink/doc/cursor @@ -5,8 +5,8 @@ dblink_open -- Opens a cursor on a remote database Synopsis -dblink_open(text cursorname, text sql) -dblink_open(text connname, text cursorname, text sql) +dblink_open(text cursorname, text sql [, bool fail_on_error]) +dblink_open(text connname, text cursorname, text sql [, bool fail_on_error]) Inputs @@ -23,6 +23,13 @@ Inputs sql statement that you wish to execute on the remote host e.g. "select * from pg_class" + fail_on_error + + If true (default when not present) then an ERROR thrown on the remote side + of the connection causes an ERROR to also be thrown locally. If false, the + remote ERROR is locally treated as a NOTICE, and the return value is set + to 'ERROR'. + Outputs Returns status = "OK" @@ -56,8 +63,8 @@ dblink_fetch -- Returns a set from an open cursor on a remote database Synopsis -dblink_fetch(text cursorname, int32 howmany) -dblink_fetch(text connname, text cursorname, int32 howmany) +dblink_fetch(text cursorname, int32 howmany [, bool fail_on_error]) +dblink_fetch(text connname, text cursorname, int32 howmany [, bool fail_on_error]) Inputs @@ -75,6 +82,12 @@ Inputs starting at the current cursor position, moving forward. Once the cursor has positioned to the end, no more rows are produced. + fail_on_error + + If true (default when not present) then an ERROR thrown on the remote side + of the connection causes an ERROR to also be thrown locally. If false, the + remote ERROR is locally treated as a NOTICE, and no rows are returned. + Outputs Returns setof record @@ -132,8 +145,8 @@ dblink_close -- Closes a cursor on a remote database Synopsis -dblink_close(text cursorname) -dblink_close(text connname, text cursorname) +dblink_close(text cursorname [, bool fail_on_error]) +dblink_close(text connname, text cursorname [, bool fail_on_error]) Inputs @@ -145,6 +158,13 @@ Inputs a reference name for the cursor + fail_on_error + + If true (default when not present) then an ERROR thrown on the remote side + of the connection causes an ERROR to also be thrown locally. If false, the + remote ERROR is locally treated as a NOTICE, and the return value is set + to 'ERROR'. + Outputs Returns status = "OK" diff --git a/contrib/dblink/doc/deprecated b/contrib/dblink/doc/deprecated deleted file mode 100644 index 09a9998757..0000000000 --- a/contrib/dblink/doc/deprecated +++ /dev/null @@ -1,105 +0,0 @@ -================================================================== -Name - -*DEPRECATED* use new dblink syntax -dblink -- Returns a resource id for a data set from a remote database - -Synopsis - -dblink(text connstr, text sql) - -Inputs - - connstr - - standard libpq format connection srting, - e.g. "hostaddr=127.0.0.1 port=5432 dbname=mydb user=postgres password=mypasswd" - - sql - - sql statement that you wish to execute on the remote host - e.g. "select * from pg_class" - -Outputs - - Returns setof int (res_id) - -Example usage - - select dblink('hostaddr=127.0.0.1 port=5432 dbname=mydb user=postgres password=mypasswd' - ,'select f1, f2 from mytable'); - -================================================================== - -Name - -*DEPRECATED* use new dblink syntax -dblink_tok -- Returns individual select field results from a dblink remote query - -Synopsis - -dblink_tok(int res_id, int fnumber) - -Inputs - - res_id - - a resource id returned by a call to dblink() - - fnumber - - the ordinal position (zero based) of the field to be returned from the dblink result set - -Outputs - - Returns text - -Example usage - - select dblink_tok(t1.dblink_p,0) as f1, dblink_tok(t1.dblink_p,1) as f2 - from (select dblink('hostaddr=127.0.0.1 port=5432 dbname=mydb user=postgres password=mypasswd' - ,'select f1, f2 from mytable') as dblink_p) as t1; - - -================================================================== -*DEPRECATED* use new dblink syntax -A more convenient way to use dblink may be to create a view: - - create view myremotetable as - select dblink_tok(t1.dblink_p,0) as f1, dblink_tok(t1.dblink_p,1) as f2 - from (select dblink('hostaddr=127.0.0.1 port=5432 dbname=template1 user=postgres password=postgres' - ,'select proname, prosrc from pg_proc') as dblink_p) as t1; - -Then you can simply write: - - select f1, f2 from myremotetable where f1 like 'bytea%'; - -================================================================== -Name -*DEPRECATED* use new dblink_exec syntax -dblink_last_oid -- Returns last inserted oid - -Synopsis - -dblink_last_oid(int res_id) RETURNS oid - -Inputs - - res_id - - any resource id returned by dblink function; - -Outputs - - Returns oid of last inserted tuple - -Example usage - -test=# select dblink_last_oid(dblink('hostaddr=127.0.0.1 port=5432 dbname=mydb user=postgres password=mypasswd' - ,'insert into mytable (f1, f2) values (1,2)')); - - dblink_last_oid ----------------- - 16553 -(1 row) - diff --git a/contrib/dblink/doc/execute b/contrib/dblink/doc/execute index 72a21276d9..d4c09b22de 100644 --- a/contrib/dblink/doc/execute +++ b/contrib/dblink/doc/execute @@ -5,14 +5,15 @@ dblink_exec -- Executes an UPDATE/INSERT/DELETE on a remote database Synopsis -dblink_exec(text connstr, text sql) -dblink_exec(text connname, text sql) -dblink_exec(text sql) +dblink_exec(text connstr, text sql [, bool fail_on_error]) +dblink_exec(text connname, text sql [, bool fail_on_error]) +dblink_exec(text sql [, bool fail_on_error]) Inputs connname connstr + If two arguments are present, the first is first assumed to be a specific connection name to use. If the name is not found, the argument is then assumed to be a valid connection string, of standard libpq format, @@ -25,9 +26,16 @@ Inputs sql statement that you wish to execute on the remote host, e.g.: insert into foo values(0,'a','{"a0","b0","c0"}'); + fail_on_error + + If true (default when not present) then an ERROR thrown on the remote side + of the connection causes an ERROR to also be thrown locally. If false, the + remote ERROR is locally treated as a NOTICE, and the return value is set + to 'ERROR'. + Outputs - Returns status of the command + Returns status of the command, or 'ERROR' if the command failed. Notes 1) dblink_open starts an explicit transaction. If, after using dblink_open, @@ -60,3 +68,12 @@ select dblink_exec('myconn','insert into foo values(21,''z'',''{"a0","b0","c0"}' ------------------ INSERT 6432584 1 (1 row) + +select dblink_exec('myconn','insert into pg_class values (''foo'')',false); +NOTICE: sql error +DETAIL: ERROR: null value in column "relnamespace" violates not-null constraint + + dblink_exec +------------- + ERROR +(1 row) diff --git a/contrib/dblink/doc/query b/contrib/dblink/doc/query index 9c81417741..d232f80dbd 100644 --- a/contrib/dblink/doc/query +++ b/contrib/dblink/doc/query @@ -5,9 +5,9 @@ dblink -- Returns a set from a remote database Synopsis -dblink(text connstr, text sql) -dblink(text connname, text sql) -dblink(text sql) +dblink(text connstr, text sql [, bool fail_on_error]) +dblink(text connname, text sql [, bool fail_on_error]) +dblink(text sql [, bool fail_on_error]) Inputs @@ -25,6 +25,12 @@ Inputs sql statement that you wish to execute on the remote host e.g. "select * from pg_class" + fail_on_error + + If true (default when not present) then an ERROR thrown on the remote side + of the connection causes an ERROR to also be thrown locally. If false, the + remote ERROR is locally treated as a NOTICE, and no rows are returned. + Outputs Returns setof record diff --git a/contrib/dblink/expected/dblink.out b/contrib/dblink/expected/dblink.out index 7ef40b06d7..4aebfd974d 100644 --- a/contrib/dblink/expected/dblink.out +++ b/contrib/dblink/expected/dblink.out @@ -128,6 +128,23 @@ WHERE t.a > 7; 9 | j | {a9,b9,c9} (2 rows) +-- open a cursor with bad SQL and fail_on_error set to false +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foobar',false); +NOTICE: sql error +DETAIL: ERROR: relation "foobar" does not exist + + dblink_open +------------- + ERROR +(1 row) + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + -- open a cursor SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); dblink_open @@ -135,6 +152,20 @@ SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); OK (1 row) +-- close the cursor +SELECT dblink_close('rmt_foo_cursor',false); + dblink_close +-------------- + OK +(1 row) + +-- open the cursor again +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); + dblink_open +------------- + OK +(1 row) + -- fetch some data SELECT * FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); @@ -165,11 +196,38 @@ FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); 9 | j | {a9,b9,c9} (2 rows) --- close the cursor -SELECT dblink_close('rmt_foo_cursor'); +-- intentionally botch a fetch +SELECT * +FROM dblink_fetch('rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]); +NOTICE: sql error +DETAIL: ERROR: cursor "rmt_foobar_cursor" does not exist + + a | b | c +---+---+--- +(0 rows) + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + +-- close the wrong cursor +SELECT dblink_close('rmt_foobar_cursor',false); +NOTICE: sql error +DETAIL: ERROR: cursor "rmt_foobar_cursor" does not exist + dblink_close -------------- - OK + ERROR +(1 row) + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + dblink_exec +------------- + ROLLBACK (1 row) -- should generate 'cursor "rmt_foo_cursor" not found' error @@ -178,6 +236,16 @@ FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); ERROR: sql error DETAIL: ERROR: cursor "rmt_foo_cursor" does not exist +-- this time, 'cursor "rmt_foo_cursor" not found' as a notice +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4,false) AS t(a int, b text, c text[]); +NOTICE: sql error +DETAIL: ERROR: cursor "rmt_foo_cursor" does not exist + + a | b | c +---+---+--- +(0 rows) + -- close the persistent connection SELECT dblink_disconnect(); dblink_disconnect @@ -232,6 +300,23 @@ FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]); 11 | l | {a11,b11,c11} (12 rows) +-- bad remote select +SELECT * +FROM dblink('SELECT * FROM foobar',false) AS t(a int, b text, c text[]); +NOTICE: sql error +DETAIL: ERROR: relation "foobar" does not exist + + a | b | c +---+---+--- +(0 rows) + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + -- change some data SELECT dblink_exec('UPDATE foo SET f3[2] = ''b99'' WHERE f1 = 11'); dblink_exec @@ -248,6 +333,23 @@ WHERE a = 11; 11 | l | {a11,b99,c11} (1 row) +-- botch a change to some other data +SELECT dblink_exec('UPDATE foobar SET f3[2] = ''b99'' WHERE f1 = 11',false); +NOTICE: sql error +DETAIL: ERROR: relation "foobar" does not exist + + dblink_exec +------------- + ERROR +(1 row) + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + -- delete some data SELECT dblink_exec('DELETE FROM foo WHERE f1 = 11'); dblink_exec @@ -298,6 +400,24 @@ WHERE t.a > 7; 10 | k | {a10,b10,c10} (3 rows) +-- use the named persistent connection, but get it wrong +SELECT * +FROM dblink('myconn','SELECT * FROM foobar',false) AS t(a int, b text, c text[]) +WHERE t.a > 7; +NOTICE: sql error +DETAIL: ERROR: relation "foobar" does not exist + + a | b | c +---+---+--- +(0 rows) + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + -- create a second named persistent connection -- should error with "duplicate connection name" SELECT dblink_connect('myconn','dbname=regression'); @@ -327,6 +447,23 @@ SELECT dblink_disconnect('myconn2'); OK (1 row) +-- open a cursor incorrectly +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foobar',false); +NOTICE: sql error +DETAIL: ERROR: relation "foobar" does not exist + + dblink_open +------------- + ERROR +(1 row) + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + dblink_exec +------------- + ROLLBACK +(1 row) + -- open a cursor SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo'); dblink_open @@ -365,11 +502,21 @@ FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); 10 | k | {a10,b10,c10} (3 rows) --- close the cursor -SELECT dblink_close('myconn','rmt_foo_cursor'); - dblink_close --------------- - OK +-- fetch some data incorrectly +SELECT * +FROM dblink_fetch('myconn','rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]); +NOTICE: sql error +DETAIL: ERROR: cursor "rmt_foobar_cursor" does not exist + + a | b | c +---+---+--- +(0 rows) + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + dblink_exec +------------- + ROLLBACK (1 row) -- should generate 'cursor "rmt_foo_cursor" not found' error diff --git a/contrib/dblink/sql/dblink.sql b/contrib/dblink/sql/dblink.sql index d0e448fc0b..61d0adb020 100644 --- a/contrib/dblink/sql/dblink.sql +++ b/contrib/dblink/sql/dblink.sql @@ -81,9 +81,21 @@ SELECT * FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) WHERE t.a > 7; +-- open a cursor with bad SQL and fail_on_error set to false +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foobar',false); + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + -- open a cursor SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); +-- close the cursor +SELECT dblink_close('rmt_foo_cursor',false); + +-- open the cursor again +SELECT dblink_open('rmt_foo_cursor','SELECT * FROM foo'); + -- fetch some data SELECT * FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); @@ -95,13 +107,27 @@ FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); SELECT * FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); --- close the cursor -SELECT dblink_close('rmt_foo_cursor'); +-- intentionally botch a fetch +SELECT * +FROM dblink_fetch('rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]); + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + +-- close the wrong cursor +SELECT dblink_close('rmt_foobar_cursor',false); + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); -- should generate 'cursor "rmt_foo_cursor" not found' error SELECT * FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]); +-- this time, 'cursor "rmt_foo_cursor" not found' as a notice +SELECT * +FROM dblink_fetch('rmt_foo_cursor',4,false) AS t(a int, b text, c text[]); + -- close the persistent connection SELECT dblink_disconnect(); @@ -125,6 +151,13 @@ SELECT substr(dblink_exec('INSERT INTO foo VALUES(11,''l'',''{"a11","b11","c11"} SELECT * FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]); +-- bad remote select +SELECT * +FROM dblink('SELECT * FROM foobar',false) AS t(a int, b text, c text[]); + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + -- change some data SELECT dblink_exec('UPDATE foo SET f3[2] = ''b99'' WHERE f1 = 11'); @@ -133,6 +166,12 @@ SELECT * FROM dblink('SELECT * FROM foo') AS t(a int, b text, c text[]) WHERE a = 11; +-- botch a change to some other data +SELECT dblink_exec('UPDATE foobar SET f3[2] = ''b99'' WHERE f1 = 11',false); + +-- reset remote transaction state +SELECT dblink_exec('ABORT'); + -- delete some data SELECT dblink_exec('DELETE FROM foo WHERE f1 = 11'); @@ -161,6 +200,14 @@ SELECT * FROM dblink('myconn','SELECT * FROM foo') AS t(a int, b text, c text[]) WHERE t.a > 7; +-- use the named persistent connection, but get it wrong +SELECT * +FROM dblink('myconn','SELECT * FROM foobar',false) AS t(a int, b text, c text[]) +WHERE t.a > 7; + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + -- create a second named persistent connection -- should error with "duplicate connection name" SELECT dblink_connect('myconn','dbname=regression'); @@ -176,6 +223,12 @@ WHERE t.a > 7; -- close the second named persistent connection SELECT dblink_disconnect('myconn2'); +-- open a cursor incorrectly +SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foobar',false); + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); + -- open a cursor SELECT dblink_open('myconn','rmt_foo_cursor','SELECT * FROM foo'); @@ -190,8 +243,12 @@ FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); SELECT * FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]); --- close the cursor -SELECT dblink_close('myconn','rmt_foo_cursor'); +-- fetch some data incorrectly +SELECT * +FROM dblink_fetch('myconn','rmt_foobar_cursor',4,false) AS t(a int, b text, c text[]); + +-- reset remote transaction state +SELECT dblink_exec('myconn','ABORT'); -- should generate 'cursor "rmt_foo_cursor" not found' error SELECT * -- GitLab