Thread: Re: [HACKERS] Final libpq patch?

Re: [HACKERS] Final libpq patch?

From
Tom Lane
Date:
Here's the patch actually applied.  I cleaned up the state problem,
fixed a couple minor omissions in the code, and did some work on the
documentation; also updated exports.txt which is now the source for
the *.def files.

            regards, tom lane

*** doc/src/sgml/libpq.sgml.orig    Fri Oct  1 13:34:17 2004
--- doc/src/sgml/libpq.sgml    Mon Oct 18 17:50:04 2004
***************
*** 1055,1062 ****
            out-of-memory conditions or serious errors such as inability
            to send the command to the server.
            If a null pointer is returned, it
!           should be treated like a <symbol>PGRES_FATAL_ERROR</symbol> result.  Use
!           <function>PQerrorMessage</function> to get more information about the error.
  </para>
  </listitem>
  </varlistentry>
--- 1055,1063 ----
            out-of-memory conditions or serious errors such as inability
            to send the command to the server.
            If a null pointer is returned, it
!           should be treated like a <symbol>PGRES_FATAL_ERROR</symbol> result.
!           Use <function>PQerrorMessage</function> to get more information
!           about such errors.
  </para>
  </listitem>
  </varlistentry>
***************
*** 1147,1152 ****
--- 1148,1228 ----
  <para>
  <variablelist>
  <varlistentry>
+ <term><function>PQprepare</function><indexterm><primary>PQprepare</></></term>
+ <listitem>
+ <para>
+           Submits a request to create a prepared statement with the
+           given parameters, and waits for completion.
+ <synopsis>
+ PGresult *PQprepare(PGconn *conn,
+                     const char *stmtName,
+                     const char *query,
+                     int nParams,
+                     const Oid *paramTypes);
+ </synopsis>
+ </para>
+
+ <para>
+ <function>PQprepare</> creates a prepared statement for later execution with
+ <function>PQexecPrepared</>.
+ This feature allows commands
+ that will be used repeatedly to be parsed and planned just once, rather
+ than each time they are executed.
+ <function>PQprepare</> is supported only in protocol 3.0 and later
+ connections; it will fail when using protocol 2.0.
+ </para>
+
+ <para>
+ The function creates a prepared statement named <parameter>stmtName</>
+ from the <parameter>query</> string, which must contain a single SQL command.
+ <parameter>stmtName</> may be <literal>""</> to create an unnamed statement,
+ in which case any pre-existing unnamed statement is automatically replaced;
+ otherwise it is an error if the statement name is already defined in the
+ current session.
+ If any parameters are used, they are referred
+ to in the query as <literal>$1</>, <literal>$2</>, etc.
+ <parameter>nParams</> is the number of parameters for which types are
+ pre-specified in the array <parameter>paramTypes[]</>.  (The array pointer
+ may be <symbol>NULL</symbol> when <parameter>nParams</> is zero.)
+ <parameter>paramTypes[]</> specifies, by OID, the data types to be assigned to
+ the parameter symbols.  If <parameter>paramTypes</> is <symbol>NULL</symbol>,
+ or any particular element in the array is zero, the server assigns a data type
+ to the parameter symbol in the same way it would do for an untyped literal
+ string.  Also, the query may use parameter symbols with numbers higher than
+ <parameter>nParams</>; data types will be inferred for these symbols as
+ well.
+ </para>
+
+ <para>
+ As with <function>PQexec</>, the result is normally a
+ <structname>PGresult</structname> object whose contents indicate server-side
+ success or failure.  A null result indicates out-of-memory or inability to
+ send the command at all.
+ Use <function>PQerrorMessage</function> to get more information
+ about such errors.
+ </para>
+
+ <para>
+ At present, there is no way to determine the actual datatype inferred for
+ any parameters whose types are not specified in <parameter>paramTypes[]</>.
+ This is a <application>libpq</> omission that will probably be rectified
+ in a future release.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ Prepared statements for use with <function>PQexecPrepared</> can also be
+ created by executing SQL <command>PREPARE</> statements.  (But
+ <function>PQprepare</> is more flexible since it does not require
+ parameter types to be pre-specified.)  Also, although there is no
+ <application>libpq</> function for deleting a prepared statement,
+ the SQL <command>DEALLOCATE</> statement can be used for that purpose.
+ </para>
+
+ <para>
+ <variablelist>
+ <varlistentry>
  <term><function>PQexecPrepared</function><indexterm><primary>PQexecPrepared</></></term>
  <listitem>
  <para>
***************
*** 1166,1172 ****
  <para>
  <function>PQexecPrepared</> is like <function>PQexecParams</>, but the
  command to be executed is specified by naming a previously-prepared
! statement, instead of giving a query string.  This feature allows commands
  that will be used repeatedly to be parsed and planned just once, rather
  than each time they are executed.
  <function>PQexecPrepared</> is supported only in protocol 3.0 and later
--- 1242,1249 ----
  <para>
  <function>PQexecPrepared</> is like <function>PQexecParams</>, but the
  command to be executed is specified by naming a previously-prepared
! statement, instead of giving a query string.
! This feature allows commands
  that will be used repeatedly to be parsed and planned just once, rather
  than each time they are executed.
  <function>PQexecPrepared</> is supported only in protocol 3.0 and later
***************
*** 1182,1194 ****
  </listitem>
  </varlistentry>
  </variablelist>
-
- Presently, prepared statements for use with <function>PQexecPrepared</>
- must be set up by executing an SQL <command>PREPARE</> command,
- which is typically sent with <function>PQexec</> (though any of
- <application>libpq</>'s query-submission functions may be used).
- A lower-level interface for preparing statements may be offered in a
- future release.
  </para>

  <para>
--- 1259,1264 ----
***************
*** 2270,2279 ****
  Applications that do not like these limitations can instead use the
  underlying functions that <function>PQexec</function> is built from:
  <function>PQsendQuery</function> and <function>PQgetResult</function>.
! There are also <function>PQsendQueryParams</function> and
! <function>PQsendQueryPrepared</function>, which can be used with
! <function>PQgetResult</function> to duplicate the functionality of
! <function>PQexecParams</function> and <function>PQexecPrepared</function>
  respectively.

  <variablelist>
--- 2340,2354 ----
  Applications that do not like these limitations can instead use the
  underlying functions that <function>PQexec</function> is built from:
  <function>PQsendQuery</function> and <function>PQgetResult</function>.
! There are also
! <function>PQsendQueryParams</function>,
! <function>PQsendPrepare</function>, and
! <function>PQsendQueryPrepared</function>,
! which can be used with <function>PQgetResult</function> to duplicate the
! functionality of
! <function>PQexecParams</function>,
! <function>PQprepare</function>, and
! <function>PQexecPrepared</function>
  respectively.

  <variablelist>
***************
*** 2326,2331 ****
--- 2401,2433 ----
  </varlistentry>

  <varlistentry>
+ <term><function>PQsendPrepare</><indexterm><primary>PQsendPrepare</></></term>
+ <listitem>
+ <para>
+         Sends a request to create a prepared statement with the given
+         parameters, without waiting for completion.
+ <synopsis>
+ int PQsendPrepare(PGconn *conn,
+                   const char *stmtName,
+                   const char *query,
+                   int nParams,
+                   const Oid *paramTypes);
+ </synopsis>
+
+         This is an asynchronous version of <function>PQprepare</>: it
+         returns 1 if it was able to dispatch the request, and 0 if not.
+         After a successful call, call <function>PQgetResult</function>
+         to determine whether the server successfully created the prepared
+         statement.
+         The function's parameters are handled identically to
+         <function>PQprepare</function>.  Like
+         <function>PQprepare</function>, it will not work on 2.0-protocol
+         connections.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
  <term><function>PQsendQueryPrepared</function><indexterm><primary>PQsendQueryPrepared</></></term>
  <listitem>
  <para>
***************
*** 2358,2364 ****
  <para>
            Waits for the next result from a prior
            <function>PQsendQuery</function>,
!           <function>PQsendQueryParams</function>, or
            <function>PQsendQueryPrepared</function> call,
            and returns it.  A null pointer is returned when the command is complete
            and there will be no more results.
--- 2460,2467 ----
  <para>
            Waits for the next result from a prior
            <function>PQsendQuery</function>,
!           <function>PQsendQueryParams</function>,
!           <function>PQsendPrepare</function>, or
            <function>PQsendQueryPrepared</function> call,
            and returns it.  A null pointer is returned when the command is complete
            and there will be no more results.
*** src/interfaces/libpq/exports.txt.orig    Sat Oct 16 16:03:45 2004
--- src/interfaces/libpq/exports.txt    Mon Oct 18 17:36:43 2004
***************
*** 1,3 ****
--- 1,4 ----
+ # $PostgreSQL$
  # Functions to be exported by libpq DLLs
  PQconnectdb               1
  PQsetdbLogin              2
***************
*** 116,118 ****
--- 117,121 ----
  pg_char_to_encoding       115
  pg_valid_server_encoding  116
  pqsignal                  117
+ PQprepare                 118
+ PQsendPrepare             119
*** src/interfaces/libpq/fe-exec.c.orig    Sat Oct 16 18:52:53 2004
--- src/interfaces/libpq/fe-exec.c    Mon Oct 18 17:35:24 2004
***************
*** 664,670 ****
      }

      /* remember we are using simple query protocol */
!     conn->ext_query = false;

      /*
       * Give the data a push.  In nonblock mode, don't complain if we're
--- 664,670 ----
      }

      /* remember we are using simple query protocol */
!     conn->queryclass = PGQUERY_SIMPLE;

      /*
       * Give the data a push.  In nonblock mode, don't complain if we're
***************
*** 718,723 ****
--- 718,811 ----
  }

  /*
+  * PQsendPrepare
+  *   Submit a Parse message, but don't wait for it to finish
+  *
+  * Returns: 1 if successfully submitted
+  *          0 if error (conn->errorMessage is set)
+  */
+ int
+ PQsendPrepare(PGconn *conn,
+               const char *stmtName, const char *query,
+               int nParams, const Oid *paramTypes)
+ {
+     if (!PQsendQueryStart(conn))
+         return 0;
+
+     if (!stmtName)
+     {
+         printfPQExpBuffer(&conn->errorMessage,
+                     libpq_gettext("statement name is a null pointer\n"));
+         return 0;
+     }
+
+     if (!query)
+     {
+         printfPQExpBuffer(&conn->errorMessage,
+                     libpq_gettext("command string is a null pointer\n"));
+         return 0;
+     }
+
+     /* This isn't gonna work on a 2.0 server */
+     if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
+     {
+         printfPQExpBuffer(&conn->errorMessage,
+                           libpq_gettext("function requires at least protocol version 3.0\n"));
+         return 0;
+     }
+
+     /* construct the Parse message */
+     if (pqPutMsgStart('P', false, conn) < 0 ||
+         pqPuts(stmtName, conn) < 0 ||
+         pqPuts(query, conn) < 0)
+         goto sendFailed;
+
+     if (nParams > 0 && paramTypes)
+     {
+         int i;
+
+         if (pqPutInt(nParams, 2, conn) < 0)
+             goto sendFailed;
+         for (i = 0; i < nParams; i++)
+         {
+             if (pqPutInt(paramTypes[i], 4, conn) < 0)
+                 goto sendFailed;
+         }
+     }
+     else
+     {
+         if (pqPutInt(0, 2, conn) < 0)
+             goto sendFailed;
+     }
+     if (pqPutMsgEnd(conn) < 0)
+         goto sendFailed;
+
+     /* construct the Sync message */
+     if (pqPutMsgStart('S', false, conn) < 0 ||
+         pqPutMsgEnd(conn) < 0)
+         goto sendFailed;
+
+     /* remember we are doing just a Parse */
+     conn->queryclass = PGQUERY_PREPARE;
+
+     /*
+      * Give the data a push.  In nonblock mode, don't complain if we're
+      * unable to send it all; PQgetResult() will do any additional
+      * flushing needed.
+      */
+     if (pqFlush(conn) < 0)
+         goto sendFailed;
+
+     /* OK, it's launched! */
+     conn->asyncStatus = PGASYNC_BUSY;
+     return 1;
+
+ sendFailed:
+     pqHandleSendFailure(conn);
+     return 0;
+ }
+
+ /*
   * PQsendQueryPrepared
   *        Like PQsendQuery, but execute a previously prepared statement,
   *        using protocol 3.0 so we can pass parameters
***************
*** 921,927 ****
          goto sendFailed;

      /* remember we are using extended query protocol */
!     conn->ext_query = true;

      /*
       * Give the data a push.  In nonblock mode, don't complain if we're
--- 1009,1015 ----
          goto sendFailed;

      /* remember we are using extended query protocol */
!     conn->queryclass = PGQUERY_EXTENDED;

      /*
       * Give the data a push.  In nonblock mode, don't complain if we're
***************
*** 1134,1140 ****
   * The user is responsible for freeing the PGresult via PQclear()
   * when done with it.
   */
-
  PGresult *
  PQexec(PGconn *conn, const char *query)
  {
--- 1222,1227 ----
***************
*** 1169,1174 ****
--- 1256,1284 ----
  }

  /*
+  * PQprepare
+  *    Creates a prepared statement by issuing a v3.0 parse message.
+  *
+  * If the query was not even sent, return NULL; conn->errorMessage is set to
+  * a relevant message.
+  * If the query was sent, a new PGresult is returned (which could indicate
+  * either success or failure).
+  * The user is responsible for freeing the PGresult via PQclear()
+  * when done with it.
+  */
+ PGresult *
+ PQprepare(PGconn *conn,
+           const char *stmtName, const char *query,
+           int nParams, const Oid *paramTypes)
+ {
+     if (!PQexecStart(conn))
+         return NULL;
+     if (!PQsendPrepare(conn, stmtName, query, nParams, paramTypes))
+         return NULL;
+     return PQexecFinish(conn);
+ }
+
+ /*
   * PQexecPrepared
   *        Like PQexec, but execute a previously prepared statement,
   *        using protocol 3.0 so we can pass parameters
***************
*** 1451,1457 ****
           * If we sent the COPY command in extended-query mode, we must
           * issue a Sync as well.
           */
!         if (conn->ext_query)
          {
              if (pqPutMsgStart('S', false, conn) < 0 ||
                  pqPutMsgEnd(conn) < 0)
--- 1561,1567 ----
           * If we sent the COPY command in extended-query mode, we must
           * issue a Sync as well.
           */
!         if (conn->queryclass != PGQUERY_SIMPLE)
          {
              if (pqPutMsgStart('S', false, conn) < 0 ||
                  pqPutMsgEnd(conn) < 0)
*** src/interfaces/libpq/fe-protocol3.c.orig    Sat Oct 16 18:52:54 2004
--- src/interfaces/libpq/fe-protocol3.c    Mon Oct 18 17:35:24 2004
***************
*** 220,225 ****
--- 220,234 ----
                      conn->asyncStatus = PGASYNC_READY;
                      break;
                  case '1':        /* Parse Complete */
+                     /* If we're doing PQprepare, we're done; else ignore */
+                     if (conn->queryclass == PGQUERY_PREPARE)
+                     {
+                         if (conn->result == NULL)
+                             conn->result = PQmakeEmptyPGresult(conn,
+                                                                PGRES_COMMAND_OK);
+                         conn->asyncStatus = PGASYNC_READY;
+                     }
+                     break;
                  case '2':        /* Bind Complete */
                  case '3':        /* Close Complete */
                      /* Nothing to do for these message types */
***************
*** 1118,1124 ****
           * If we sent the COPY command in extended-query mode, we must
           * issue a Sync as well.
           */
!         if (conn->ext_query)
          {
              if (pqPutMsgStart('S', false, conn) < 0 ||
                  pqPutMsgEnd(conn) < 0)
--- 1127,1133 ----
           * If we sent the COPY command in extended-query mode, we must
           * issue a Sync as well.
           */
!         if (conn->queryclass != PGQUERY_SIMPLE)
          {
              if (pqPutMsgStart('S', false, conn) < 0 ||
                  pqPutMsgEnd(conn) < 0)
*** src/interfaces/libpq/libpq-fe.h.orig    Sat Oct 16 18:52:55 2004
--- src/interfaces/libpq/libpq-fe.h    Mon Oct 18 17:35:24 2004
***************
*** 304,309 ****
--- 304,312 ----
               const int *paramLengths,
               const int *paramFormats,
               int resultFormat);
+ extern PGresult *PQprepare(PGconn *conn, const char *stmtName,
+                            const char *query, int nParams,
+                            const Oid *paramTypes);
  extern PGresult *PQexecPrepared(PGconn *conn,
                 const char *stmtName,
                 int nParams,
***************
*** 322,327 ****
--- 325,333 ----
                    const int *paramLengths,
                    const int *paramFormats,
                    int resultFormat);
+ extern int PQsendPrepare(PGconn *conn, const char *stmtName,
+                          const char *query, int nParams,
+                          const Oid *paramTypes);
  extern int PQsendQueryPrepared(PGconn *conn,
                      const char *stmtName,
                      int nParams,
*** src/interfaces/libpq/libpq-int.h.orig    Sat Oct 16 18:52:55 2004
--- src/interfaces/libpq/libpq-int.h    Mon Oct 18 17:35:25 2004
***************
*** 185,190 ****
--- 185,198 ----
      PGASYNC_COPY_OUT            /* Copy Out data transfer in progress */
  } PGAsyncStatusType;

+ /* PGQueryClass tracks which query protocol we are now executing */
+ typedef enum
+ {
+     PGQUERY_SIMPLE,                /* simple Query protocol (PQexec) */
+     PGQUERY_EXTENDED,            /* full Extended protocol (PQexecParams) */
+     PGQUERY_PREPARE                /* Parse only (PQprepare) */
+ } PGQueryClass;
+
  /* PGSetenvStatusType defines the state of the PQSetenv state machine */
  /* (this is used only for 2.0-protocol connections) */
  typedef enum
***************
*** 264,273 ****
      PGAsyncStatusType asyncStatus;
      PGTransactionStatusType xactStatus;
      /* note: xactStatus never changes to ACTIVE */
      bool        nonblocking;    /* whether this connection is using
                                   * nonblock sending semantics */
-     bool        ext_query;        /* was our last query sent with extended
-                                  * query protocol? */
      char        copy_is_binary; /* 1 = copy binary, 0 = copy text */
      int            copy_already_done;        /* # bytes already returned in
                                           * COPY OUT */
--- 272,280 ----
      PGAsyncStatusType asyncStatus;
      PGTransactionStatusType xactStatus;
      /* note: xactStatus never changes to ACTIVE */
+     PGQueryClass queryclass;
      bool        nonblocking;    /* whether this connection is using
                                   * nonblock sending semantics */
      char        copy_is_binary; /* 1 = copy binary, 0 = copy text */
      int            copy_already_done;        /* # bytes already returned in
                                           * COPY OUT */