From 9d9cfb1ad75b60f4e9258078d7f483ea123dc7b2 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 18 Oct 2004 22:00:42 +0000
Subject: [PATCH] Add PQprepare/PQsendPrepared functions to libpq to support
 preparing statements without necessarily specifying the datatypes of their
 parameters. Abhijit Menon-Sen with some help from Tom Lane.

---
 doc/src/sgml/libpq.sgml             | 135 ++++++++++++++++++++++++----
 src/interfaces/libpq/exports.txt    |   3 +
 src/interfaces/libpq/fe-exec.c      | 120 +++++++++++++++++++++++--
 src/interfaces/libpq/fe-protocol3.c |  13 ++-
 src/interfaces/libpq/libpq-fe.h     |   8 +-
 src/interfaces/libpq/libpq-int.h    |  13 ++-
 6 files changed, 265 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 97a6ab5e712..d0c59424fc9 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.165 2004/10/01 17:34:17 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.166 2004/10/18 22:00:41 tgl Exp $
 -->
 
  <chapter id="libpq">
@@ -1055,8 +1055,9 @@ PGresult *PQexec(PGconn *conn, const char *command);
           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.
+          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>
@@ -1144,6 +1145,81 @@ than one nonempty command.)  This is a limitation of the underlying protocol,
 but has some usefulness as an extra defense against SQL-injection attacks.
 </para>
 
+<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>
@@ -1166,7 +1242,8 @@ PGresult *PQexecPrepared(PGconn *conn,
 <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
+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,13 +1259,6 @@ the prepared statement's parameter types were determined when it was created).
 </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>
@@ -2270,10 +2340,15 @@ discarded by <function>PQexec</function>.
 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>
+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>
@@ -2325,6 +2400,33 @@ int PQsendQueryParams(PGconn *conn,
 </listitem>
 </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>
@@ -2358,7 +2460,8 @@ int PQsendQueryPrepared(PGconn *conn,
 <para>
           Waits for the next result from a prior
           <function>PQsendQuery</function>,
-          <function>PQsendQueryParams</function>, or
+          <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.
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index d6532155a38..e14a8bb58a9 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -1,3 +1,4 @@
+# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.2 2004/10/18 22:00:42 tgl Exp $
 # Functions to be exported by libpq DLLs
 PQconnectdb               1
 PQsetdbLogin              2
@@ -116,3 +117,5 @@ PQgetssl                  114
 pg_char_to_encoding       115
 pg_valid_server_encoding  116
 pqsignal                  117
+PQprepare                 118
+PQsendPrepare             119
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 5e19d78b37a..fff9746e33b 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.163 2004/10/16 22:52:53 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.164 2004/10/18 22:00:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -664,7 +664,7 @@ PQsendQuery(PGconn *conn, const char *query)
 	}
 
 	/* remember we are using simple query protocol */
-	conn->ext_query = false;
+	conn->queryclass = PGQUERY_SIMPLE;
 
 	/*
 	 * Give the data a push.  In nonblock mode, don't complain if we're
@@ -717,6 +717,94 @@ PQsendQueryParams(PGconn *conn,
 						   resultFormat);
 }
 
+/*
+ * 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,
@@ -921,7 +1009,7 @@ PQsendQueryGuts(PGconn *conn,
 		goto sendFailed;
 
 	/* remember we are using extended query protocol */
-	conn->ext_query = true;
+	conn->queryclass = PGQUERY_EXTENDED;
 
 	/*
 	 * Give the data a push.  In nonblock mode, don't complain if we're
@@ -1134,7 +1222,6 @@ PQgetResult(PGconn *conn)
  * The user is responsible for freeing the PGresult via PQclear()
  * when done with it.
  */
-
 PGresult *
 PQexec(PGconn *conn, const char *query)
 {
@@ -1168,6 +1255,29 @@ PQexecParams(PGconn *conn,
 	return PQexecFinish(conn);
 }
 
+/*
+ * 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,
@@ -1451,7 +1561,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
 		 * If we sent the COPY command in extended-query mode, we must
 		 * issue a Sync as well.
 		 */
-		if (conn->ext_query)
+		if (conn->queryclass != PGQUERY_SIMPLE)
 		{
 			if (pqPutMsgStart('S', false, conn) < 0 ||
 				pqPutMsgEnd(conn) < 0)
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index cec7a672058..af88ddc8f43 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.18 2004/10/16 22:52:54 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.19 2004/10/18 22:00:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -220,6 +220,15 @@ pqParseInput3(PGconn *conn)
 					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,7 +1127,7 @@ pqEndcopy3(PGconn *conn)
 		 * If we sent the COPY command in extended-query mode, we must
 		 * issue a Sync as well.
 		 */
-		if (conn->ext_query)
+		if (conn->queryclass != PGQUERY_SIMPLE)
 		{
 			if (pqPutMsgStart('S', false, conn) < 0 ||
 				pqPutMsgEnd(conn) < 0)
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 55e288b417a..1fba91bde4e 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.111 2004/10/16 22:52:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.112 2004/10/18 22:00:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -304,6 +304,9 @@ extern PGresult *PQexecParams(PGconn *conn,
 			 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,6 +325,9 @@ extern int PQsendQueryParams(PGconn *conn,
 				  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,
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e0992c4103a..d1fab1395b2 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.94 2004/10/16 22:52:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.95 2004/10/18 22:00:42 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -185,6 +185,14 @@ typedef enum
 	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,10 +272,9 @@ struct pg_conn
 	PGAsyncStatusType asyncStatus;
 	PGTransactionStatusType xactStatus;
 	/* note: xactStatus never changes to ACTIVE */
+	PGQueryClass queryclass;
 	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 */
-- 
GitLab