Thread: Re: [GENERAL] dblink: rollback transaction

Re: [GENERAL] dblink: rollback transaction

From
Joe Conway
Date:
Oleg Lebedev wrote:
> Your fix is awesome! That's exactly what I need.
> What version of postgres do I need to have installed to try this patch?
> I am on 7.3 now.

I plan to apply the attached to cvs tip in 24 hours or so. I don't think
it qualifies as a bug fix, and it does represent a change in user facing
behavior, so I do not intend to apply for 7.3.6 or 7.4.2.

The patch changes dblink_exec() such that an ERROR on the remote side of
the connection will generate a NOTICE on the local side, with the
dblink_exec() return value set to 'ERROR'. This allows the remote side
ERROR to be trapped and handled locally.

One question that I'd like some feedback on is the following: should the
same change be applied to other functions that might throw an ERROR
based on the remote side of the connection? For example, currently if
dblink() is used in an attempt to access a non-existent remote table, an
ERROR is thrown locally in response, killing any currently open
transaction. Thoughts?

Thanks,

Joe

Index: contrib/dblink/dblink.c
===================================================================
RCS file: /opt/src/cvs/pgsql-server/contrib/dblink/dblink.c,v
retrieving revision 1.29
diff -c -r1.29 dblink.c
*** contrib/dblink/dblink.c    28 Nov 2003 05:03:01 -0000    1.29
--- contrib/dblink/dblink.c    5 Feb 2004 19:49:00 -0000
***************
*** 135,140 ****
--- 135,150 ----
                       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) \
***************
*** 731,739 ****
      if (!res ||
          (PQresultStatus(res) != PGRES_COMMAND_OK &&
           PQresultStatus(res) != PGRES_TUPLES_OK))
!         DBLINK_RES_ERROR("sql error");

!     if (PQresultStatus(res) == PGRES_COMMAND_OK)
      {
          /* need a tuple descriptor representing one TEXT column */
          tupdesc = CreateTemplateTupleDesc(1, false);
--- 741,762 ----
      if (!res ||
          (PQresultStatus(res) != PGRES_COMMAND_OK &&
           PQresultStatus(res) != PGRES_TUPLES_OK))
!     {
!         DBLINK_RES_ERROR_AS_NOTICE("sql error");
!
!         /* 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);

Re: [GENERAL] dblink: rollback transaction

From
Tom Lane
Date:
Joe Conway <mail@joeconway.com> writes:
> One question that I'd like some feedback on is the following: should the
> same change be applied to other functions that might throw an ERROR
> based on the remote side of the connection? For example, currently if
> dblink() is used in an attempt to access a non-existent remote table, an
> ERROR is thrown locally in response, killing any currently open
> transaction. Thoughts?

It seems like a good idea to offer a consistent policy about this.
But I'm not sure that you should make a 180-degree change in error
handling without any backward-compatibility option.  (In view of
recent discussions, the phrase "GUC variable" will not cross my
lips here.)

What seems like a good idea after a few moments' thought is to leave the
behavior of the various dblink_foo() functions the same as now (ie,
throw error on remote error) and add new API functions named something
like dblink_foo_noerror() that don't throw error but return a
recognizable failure code instead.  My argument for this approach is
that there is no situation in which the programmer shouldn't have to
think when he writes a given call whether it will elog or return an
error indicator, because if he wants an error indicator then he is going
to have to check for it.

I'm not wedded to that in particular, but I do think you need to offer
a consistent approach with reasonable regard to backwards compatibility.
If you apply the patch as given you will surely be breaking existing
callers ... what's worse, the breakage is silent, and will only show up
under stress in the field.

            regards, tom lane

Re: [GENERAL] dblink: rollback transaction

From
Joe Conway
Date:
Tom Lane wrote:

> Joe Conway <mail@joeconway.com> writes:
>>One question that I'd like some feedback on is the following: should the
>>same change be applied to other functions that might throw an ERROR
>>based on the remote side of the connection? For example, currently if
>>dblink() is used in an attempt to access a non-existent remote table, an
>>ERROR is thrown locally in response, killing any currently open
>>transaction. Thoughts?

> What seems like a good idea after a few moments' thought is to leave the
> behavior of the various dblink_foo() functions the same as now (ie,
> throw error on remote error) and add new API functions named something
> like dblink_foo_noerror() that don't throw error but return a
> recognizable failure code instead.  My argument for this approach is
> that there is no situation in which the programmer shouldn't have to
> think when he writes a given call whether it will elog or return an
> error indicator, because if he wants an error indicator then he is going
> to have to check for it.

I like the idea in general, but maybe instead there should be a new
overloaded version of the existing function names that accepts an
additional bool argument. Without the argument, behavior would be as it
is now; with it, you could specify the old or new behavior.

Joe


Re: [GENERAL] dblink: rollback transaction

From
Tom Lane
Date:
Joe Conway <mail@joeconway.com> writes:
> I like the idea in general, but maybe instead there should be a new
> overloaded version of the existing function names that accepts an
> additional bool argument. Without the argument, behavior would be as it
> is now; with it, you could specify the old or new behavior.

Um, maybe I'm confused about the context, but aren't we talking about C
function names here?  No overloading is possible in C ...

            regards, tom lane

Re: [GENERAL] dblink: rollback transaction

From
Joe Conway
Date:
Tom Lane wrote:

> Joe Conway <mail@joeconway.com> writes:
>
>>I like the idea in general, but maybe instead there should be a new
>>overloaded version of the existing function names that accepts an
>>additional bool argument. Without the argument, behavior would be as it
>>is now; with it, you could specify the old or new behavior.
>
> Um, maybe I'm confused about the context, but aren't we talking about C
> function names here?  No overloading is possible in C ...

I was thinking in terms of overloaded SQL function names. For example,
in addition to dblink_exec(text) and dblink_exec(text,text) we create
dblink_exec(text,bool) and dblink_exec(text,text,bool).

Currently both SQL versions of dblink_exec are implemented by a single C
level function. But yes, we'd need another C level function to support
the new SQL functions because there would be no way to distinguish the 2
two-argument versions otherwise. (Actually, now I'm wondering if we
could use a single C function for all four SQL versions -- between
PG_NARGS() and get_fn_expr_argtype() we should be able to figure out how
we were called, shouldn't we?)

Joe


Re: [GENERAL] dblink: rollback transaction

From
Joe Conway
Date:
Joe Conway wrote:
> Tom Lane wrote:
>> Joe Conway <mail@joeconway.com> writes:
>>> I like the idea in general, but maybe instead there should be a new
>>> overloaded version of the existing function names that accepts an
>>> additional bool argument. Without the argument, behavior would be as
>>> it is now; with it, you could specify the old or new behavior.
>>
>> Um, maybe I'm confused about the context, but aren't we talking about C
>> function names here?  No overloading is possible in C ...
>
> I was thinking in terms of overloaded SQL function names. For example,
> in addition to dblink_exec(text) and dblink_exec(text,text) we create
> dblink_exec(text,bool) and dblink_exec(text,text,bool).
>
> Currently both SQL versions of dblink_exec are implemented by a single C
> level function. But yes, we'd need another C level function to support
> the new SQL functions because there would be no way to distinguish the 2
> two-argument versions otherwise. (Actually, now I'm wondering if we
> could use a single C function for all four SQL versions -- between
> PG_NARGS() and get_fn_expr_argtype() we should be able to figure out how
> we were called, shouldn't we?)

The attached implements the new overloaded SQL functions as discussed
above (i.e. start with existing argument combinations, add a new bool
argument to each). I ended up with a single C function (by making use of
number and type of the arguments) for each overloaded SQL function name.

I'll commit in a day or two if there are no objections.

Thanks,

Joe

Index: contrib/dblink/README.dblink
===================================================================
RCS file: /cvsroot/pgsql-server/contrib/dblink/README.dblink,v
retrieving revision 1.9
diff -c -r1.9 README.dblink
*** contrib/dblink/README.dblink    4 Aug 2003 23:59:37 -0000    1.9
--- contrib/dblink/README.dblink    5 Mar 2004 05:55:45 -0000
***************
*** 30,42 ****
   *
   */

- 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.6
      - functions deprecated in 0.5 have been removed
      - added ability to create "named" persistent connections
--- 30,40 ----
   *
   */

  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,91 ****

    You can use dblink.sql to create the functions in your database of choice, e.g.

!     psql -U postgres template1 < dblink.sql

    installs following functions into database template1:

--- 83,89 ----

    You can use dblink.sql to create the functions in your database of choice, e.g.

!     psql template1 < dblink.sql

    installs following functions into database template1:

***************
*** 104,143 ****

       cursor
       ------------
!      dblink_open(text,text) 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
         - 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
         - fetches data from an already opened cursor on the unnamed connection
!      dblink_fetch(text, text, int) RETURNS setof record
         - fetches data from an already opened cursor on a named connection
!      dblink_close(text) RETURNS text
         - closes a cursor on the unnamed connection
!      dblink_close(text,text) RETURNS text
         - closes a cursor on a named connection

       query
       ------------
!      dblink(text,text) 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
         - 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
         - 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
         - executes an INSERT/UPDATE/DELETE query remotely, using connection
           already opened with dblink_connect()

--- 102,141 ----

       cursor
       ------------
!      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 [, 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 [, bool fail_on_error]) RETURNS setof record
         - fetches data from an already opened cursor on the unnamed connection
!      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 [, bool fail_on_error]) RETURNS text
         - closes a cursor on the unnamed connection
!      dblink_close(text,text [, bool fail_on_error]) RETURNS text
         - closes a cursor on a named connection

       query
       ------------
!      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 [, 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 [, 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 [, bool fail_on_error]) RETURNS text
         - executes an INSERT/UPDATE/DELETE query remotely, using connection
           already opened with dblink_connect()

***************
*** 169,175 ****
       doc/query
       doc/execute
       doc/misc
-      doc/deprecated

  ==================================================================
  -- Joe Conway
--- 167,172 ----
Index: contrib/dblink/dblink.c
===================================================================
RCS file: /cvsroot/pgsql-server/contrib/dblink/dblink.c,v
retrieving revision 1.30
diff -c -r1.30 dblink.c
*** contrib/dblink/dblink.c    24 Feb 2004 06:07:18 -0000    1.30
--- contrib/dblink/dblink.c    5 Mar 2004 05:55:46 -0000
***************
*** 134,139 ****
--- 134,149 ----
                       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,158 ****
              if(rcon) \
              { \
                  conn = rcon->con; \
-                 freeconn = false; \
              } \
              else \
              { \
--- 162,167 ----
***************
*** 167,172 ****
--- 176,182 ----
                               errmsg("could not establish connection"), \
                               errdetail("%s", msg))); \
                  } \
+                 freeconn = true; \
              } \
      } while (0)

***************
*** 276,293 ****
--- 286,327 ----
      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,316 ****

      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");

      PQclear(res);
-
      PG_RETURN_TEXT_P(GET_TEXT("OK"));
  }

--- 338,356 ----

      appendStringInfo(str, "DECLARE %s CURSOR FOR %s", curname, sql);
      res = PQexec(conn, str->data);
!     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,343 ****
--- 368,405 ----
      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,357 ****
      /* close the cursor */
      res = PQexec(conn, str->data);
      if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
!         DBLINK_RES_ERROR("sql error");

      PQclear(res);

--- 413,428 ----
      /* close the cursor */
      res = PQexec(conn, str->data);
      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);

***************
*** 395,413 ****
          char       *curname = NULL;
          int            howmany = 0;
          ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;

!         if (PG_NARGS() == 3)
          {
              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)
          {
              curname = GET_STR(PG_GETARG_TEXT_P(0));
              howmany = PG_GETARG_INT32(1);
              conn = persistent_conn;
--- 466,509 ----
          char       *curname = NULL;
          int            howmany = 0;
          ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+         bool        fail = true;    /* default to backward compatible */

!         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,437 ****
          if (!res ||
              (PQresultStatus(res) != PGRES_COMMAND_OK &&
               PQresultStatus(res) != PGRES_TUPLES_OK))
!             DBLINK_RES_ERROR("sql error");
          else if (PQresultStatus(res) == PGRES_COMMAND_OK)
          {
              /* cursor does not exist - closed already or bad name */
--- 527,543 ----
          if (!res ||
              (PQresultStatus(res) != PGRES_COMMAND_OK &&
               PQresultStatus(res) != PGRES_TUPLES_OK))
!         {
!             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,454 ****
--- 554,564 ----

          /* 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,552 ****
      bool        is_sql_cmd = false;
      char       *sql_cmd_status = NULL;
      MemoryContext oldcontext;
!     bool        freeconn = true;

      /* stuff done only on the first call of the function */
      if (SRF_IS_FIRSTCALL())
--- 656,662 ----
      bool        is_sql_cmd = false;
      char       *sql_cmd_status = NULL;
      MemoryContext oldcontext;
!     bool        freeconn = false;

      /* stuff done only on the first call of the function */
      if (SRF_IS_FIRSTCALL())
***************
*** 560,565 ****
--- 670,676 ----
          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,582 ****
           */
          oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

!         if (PG_NARGS() == 2)
          {
              DBLINK_GET_CONN;
              sql = GET_STR(PG_GETARG_TEXT_P(1));
          }
          else if (PG_NARGS() == 1)
          {
              conn = persistent_conn;
              sql = GET_STR(PG_GETARG_TEXT_P(0));
          }
--- 681,711 ----
           */
          oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

!         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,595 ****
              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 (PQresultStatus(res) == PGRES_COMMAND_OK)
          {
--- 717,738 ----
              DBLINK_CONN_NOT_AVAIL;

          res = PQexec(conn, sql);
!         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,625 ****
          funcctx->user_fctx = res;

          /* if needed, close the connection to the database and cleanup */
!         if (freeconn && PG_NARGS() == 2)
              PQfinish(conn);

          /* fast track when no results */
          if (funcctx->max_calls < 1)
              SRF_RETURN_DONE(funcctx);

          /* check typtype to see if we have a predetermined return type */
          functypeid = get_func_rettype(funcid);
--- 757,772 ----
          funcctx->user_fctx = res;

          /* if needed, close the connection to the database and cleanup */
!         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,741 ****
      char       *sql = NULL;
      char       *conname = NULL;
      remoteConn *rcon = NULL;
!     bool        freeconn = true;

!     if (PG_NARGS() == 2)
      {
          DBLINK_GET_CONN;
          sql = GET_STR(PG_GETARG_TEXT_P(1));
      }
      else if (PG_NARGS() == 1)
      {
          conn = persistent_conn;
          sql = GET_STR(PG_GETARG_TEXT_P(0));
      }
--- 874,907 ----
      char       *sql = NULL;
      char       *conname = NULL;
      remoteConn *rcon = NULL;
!     bool        freeconn = false;
!     bool        fail = true;    /* default to backward compatible behavior */

!     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,758 ****
      if (!res ||
          (PQresultStatus(res) != PGRES_COMMAND_OK &&
           PQresultStatus(res) != PGRES_TUPLES_OK))
!         DBLINK_RES_ERROR("sql error");

!     if (PQresultStatus(res) == PGRES_COMMAND_OK)
      {
          /* need a tuple descriptor representing one TEXT column */
          tupdesc = CreateTemplateTupleDesc(1, false);
--- 916,940 ----
      if (!res ||
          (PQresultStatus(res) != PGRES_COMMAND_OK &&
           PQresultStatus(res) != PGRES_TUPLES_OK))
!     {
!         if (fail)
!             DBLINK_RES_ERROR("sql error");
!         else
!             DBLINK_RES_ERROR_AS_NOTICE("sql error");
!
!         /* 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,779 ****
      PQclear(res);

      /* if needed, close the connection to the database and cleanup */
!     if (freeconn && fcinfo->nargs == 2)
          PQfinish(conn);

      PG_RETURN_TEXT_P(sql_cmd_status);
--- 955,961 ----
      PQclear(res);

      /* if needed, close the connection to the database and cleanup */
!     if (freeconn)
          PQfinish(conn);

      PG_RETURN_TEXT_P(sql_cmd_status);
Index: contrib/dblink/dblink.sql.in
===================================================================
RCS file: /cvsroot/pgsql-server/contrib/dblink/dblink.sql.in,v
retrieving revision 1.8
diff -c -r1.8 dblink.sql.in
*** contrib/dblink/dblink.sql.in    25 Jun 2003 01:10:15 -0000    1.8
--- contrib/dblink/dblink.sql.in    5 Mar 2004 05:55:46 -0000
***************
*** 1,94 ****
  CREATE OR REPLACE FUNCTION dblink_connect (text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_connect'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_connect (text, text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_connect'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_disconnect ()
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_disconnect'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_disconnect (text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_disconnect'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_open (text,text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_open'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_open (text,text,text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_open'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_fetch (text,int)
  RETURNS setof record
  AS 'MODULE_PATHNAME','dblink_fetch'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_fetch (text,text,int)
  RETURNS setof record
  AS 'MODULE_PATHNAME','dblink_fetch'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_close (text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_close'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_close (text,text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_close'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink (text,text)
  RETURNS setof record
  AS 'MODULE_PATHNAME','dblink_record'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink (text)
  RETURNS setof record
  AS 'MODULE_PATHNAME','dblink_record'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_exec (text,text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_exec'
! LANGUAGE 'C' WITH (isstrict);

  CREATE OR REPLACE FUNCTION dblink_exec (text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_exec'
! LANGUAGE 'c' WITH (isstrict);

  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);

  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);

  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);

  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);

  CREATE OR REPLACE FUNCTION dblink_current_query ()
  RETURNS text
--- 1,144 ----
  CREATE OR REPLACE FUNCTION dblink_connect (text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_connect'
! LANGUAGE 'C' STRICT;

  CREATE OR REPLACE FUNCTION dblink_connect (text, text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_connect'
! LANGUAGE 'C' STRICT;

  CREATE OR REPLACE FUNCTION dblink_disconnect ()
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_disconnect'
! LANGUAGE 'C' STRICT;

  CREATE OR REPLACE FUNCTION dblink_disconnect (text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_disconnect'
! LANGUAGE 'C' STRICT;

  CREATE OR REPLACE FUNCTION dblink_open (text,text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_open'
! 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' 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' 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' 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' 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' 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' 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' 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' 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' 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' 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' STRICT;

  CREATE OR REPLACE FUNCTION dblink_build_sql_delete (text, int2vector, int4, _text)
  RETURNS text
  AS 'MODULE_PATHNAME','dblink_build_sql_delete'
! 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' STRICT;

  CREATE OR REPLACE FUNCTION dblink_current_query ()
  RETURNS text
Index: contrib/dblink/doc/cursor
===================================================================
RCS file: /cvsroot/pgsql-server/contrib/dblink/doc/cursor,v
retrieving revision 1.2
diff -c -r1.2 cursor
*** contrib/dblink/doc/cursor    25 Jun 2003 01:10:15 -0000    1.2
--- contrib/dblink/doc/cursor    5 Mar 2004 05:55:46 -0000
***************
*** 5,12 ****

  Synopsis

! dblink_open(text cursorname, text sql)
! dblink_open(text connname, text cursorname, text sql)

  Inputs

--- 5,12 ----

  Synopsis

! dblink_open(text cursorname, text sql [, bool fail_on_error])
! dblink_open(text connname, text cursorname, text sql [, bool fail_on_error])

  Inputs

***************
*** 23,28 ****
--- 23,35 ----
      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,63 ****

  Synopsis

! dblink_fetch(text cursorname, int32 howmany)
! dblink_fetch(text connname, text cursorname, int32 howmany)

  Inputs

--- 63,70 ----

  Synopsis

! dblink_fetch(text cursorname, int32 howmany [, bool fail_on_error])
! dblink_fetch(text connname, text cursorname, int32 howmany [, bool fail_on_error])

  Inputs

***************
*** 75,80 ****
--- 82,93 ----
      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,139 ****

  Synopsis

! dblink_close(text cursorname)
! dblink_close(text connname, text cursorname)

  Inputs

--- 145,152 ----

  Synopsis

! dblink_close(text cursorname [, bool fail_on_error])
! dblink_close(text connname, text cursorname [, bool fail_on_error])

  Inputs

***************
*** 144,149 ****
--- 157,169 ----
    cursorname

      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

Index: contrib/dblink/doc/execute
===================================================================
RCS file: /cvsroot/pgsql-server/contrib/dblink/doc/execute,v
retrieving revision 1.2
diff -c -r1.2 execute
*** contrib/dblink/doc/execute    25 Jun 2003 01:10:15 -0000    1.2
--- contrib/dblink/doc/execute    5 Mar 2004 05:55:46 -0000
***************
*** 5,18 ****

  Synopsis

! dblink_exec(text connstr, text sql)
! dblink_exec(text connname, text sql)
! dblink_exec(text sql)

  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,
--- 5,19 ----

  Synopsis

! 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,33 ****
      sql statement that you wish to execute on the remote host, e.g.:
         insert into foo values(0,'a','{"a0","b0","c0"}');

  Outputs

!   Returns status of the command

  Notes
    1) dblink_open starts an explicit transaction. If, after using dblink_open,
--- 26,41 ----
      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, or 'ERROR' if the command failed.

  Notes
    1) dblink_open starts an explicit transaction. If, after using dblink_open,
***************
*** 59,62 ****
--- 67,79 ----
     dblink_exec
  ------------------
   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)
Index: contrib/dblink/doc/query
===================================================================
RCS file: /cvsroot/pgsql-server/contrib/dblink/doc/query,v
retrieving revision 1.2
diff -c -r1.2 query
*** contrib/dblink/doc/query    25 Jun 2003 01:10:15 -0000    1.2
--- contrib/dblink/doc/query    5 Mar 2004 05:55:46 -0000
***************
*** 5,13 ****

  Synopsis

! dblink(text connstr, text sql)
! dblink(text connname, text sql)
! dblink(text sql)

  Inputs

--- 5,13 ----

  Synopsis

! 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

***************
*** 24,29 ****
--- 24,35 ----

      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

Index: contrib/dblink/expected/dblink.out
===================================================================
RCS file: /cvsroot/pgsql-server/contrib/dblink/expected/dblink.out,v
retrieving revision 1.12
diff -c -r1.12 dblink.out
*** contrib/dblink/expected/dblink.out    28 Nov 2003 05:03:01 -0000    1.12
--- contrib/dblink/expected/dblink.out    5 Mar 2004 05:55:46 -0000
***************
*** 128,133 ****
--- 128,150 ----
   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,140 ****
--- 152,171 ----
   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,175 ****
   9 | j | {a9,b9,c9}
  (2 rows)

! -- close the cursor
! SELECT dblink_close('rmt_foo_cursor');
   dblink_close
  --------------
!  OK
  (1 row)

  -- should generate 'cursor "rmt_foo_cursor" not found' error
--- 196,233 ----
   9 | j | {a9,b9,c9}
  (2 rows)

! -- 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
  --------------
!  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,183 ****
--- 236,251 ----
  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,237 ****
--- 300,322 ----
   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,253 ****
--- 333,355 ----
   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,303 ****
--- 400,423 ----
   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,332 ****
--- 447,469 ----
   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,375 ****
   10 | k | {a10,b10,c10}
  (3 rows)

! -- close the cursor
! SELECT dblink_close('myconn','rmt_foo_cursor');
!  dblink_close
! --------------
!  OK
  (1 row)

  -- should generate 'cursor "rmt_foo_cursor" not found' error
--- 502,522 ----
   10 | k | {a10,b10,c10}
  (3 rows)

! -- 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
Index: contrib/dblink/sql/dblink.sql
===================================================================
RCS file: /cvsroot/pgsql-server/contrib/dblink/sql/dblink.sql,v
retrieving revision 1.11
diff -c -r1.11 dblink.sql
*** contrib/dblink/sql/dblink.sql    28 Nov 2003 05:03:02 -0000    1.11
--- contrib/dblink/sql/dblink.sql    5 Mar 2004 05:55:46 -0000
***************
*** 81,89 ****
--- 81,101 ----
  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,107 ****
  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');

  -- 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[]);

  -- close the persistent connection
  SELECT dblink_disconnect();

--- 107,133 ----
  SELECT *
  FROM dblink_fetch('rmt_foo_cursor',4) AS t(a int, b text, c text[]);

! -- 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,130 ****
--- 151,163 ----
  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,138 ****
--- 166,177 ----
  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,166 ****
--- 200,213 ----
  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,181 ****
--- 223,234 ----
  -- 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,197 ****
  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');

  -- should generate 'cursor "rmt_foo_cursor" not found' error
  SELECT *
--- 243,254 ----
  SELECT *
  FROM dblink_fetch('myconn','rmt_foo_cursor',4) AS t(a int, b text, c text[]);

! -- 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 *