Commit 9d9cfb1a authored by Tom Lane's avatar Tom Lane

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.
parent b3fe6bca
<!-- <!--
$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"> <chapter id="libpq">
...@@ -1055,8 +1055,9 @@ PGresult *PQexec(PGconn *conn, const char *command); ...@@ -1055,8 +1055,9 @@ PGresult *PQexec(PGconn *conn, const char *command);
out-of-memory conditions or serious errors such as inability out-of-memory conditions or serious errors such as inability
to send the command to the server. to send the command to the server.
If a null pointer is returned, it If a null pointer is returned, it
should be treated like a <symbol>PGRES_FATAL_ERROR</symbol> result. Use should be treated like a <symbol>PGRES_FATAL_ERROR</symbol> result.
<function>PQerrorMessage</function> to get more information about the error. Use <function>PQerrorMessage</function> to get more information
about such errors.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1144,6 +1145,81 @@ than one nonempty command.) This is a limitation of the underlying protocol, ...@@ -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. but has some usefulness as an extra defense against SQL-injection attacks.
</para> </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> <para>
<variablelist> <variablelist>
<varlistentry> <varlistentry>
...@@ -1166,7 +1242,8 @@ PGresult *PQexecPrepared(PGconn *conn, ...@@ -1166,7 +1242,8 @@ PGresult *PQexecPrepared(PGconn *conn,
<para> <para>
<function>PQexecPrepared</> is like <function>PQexecParams</>, but the <function>PQexecPrepared</> is like <function>PQexecParams</>, but the
command to be executed is specified by naming a previously-prepared 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 that will be used repeatedly to be parsed and planned just once, rather
than each time they are executed. than each time they are executed.
<function>PQexecPrepared</> is supported only in protocol 3.0 and later <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). ...@@ -1182,13 +1259,6 @@ the prepared statement's parameter types were determined when it was created).
</listitem> </listitem>
</varlistentry> </varlistentry>
</variablelist> </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>
<para> <para>
...@@ -2270,10 +2340,15 @@ discarded by <function>PQexec</function>. ...@@ -2270,10 +2340,15 @@ discarded by <function>PQexec</function>.
Applications that do not like these limitations can instead use the Applications that do not like these limitations can instead use the
underlying functions that <function>PQexec</function> is built from: underlying functions that <function>PQexec</function> is built from:
<function>PQsendQuery</function> and <function>PQgetResult</function>. <function>PQsendQuery</function> and <function>PQgetResult</function>.
There are also <function>PQsendQueryParams</function> and There are also
<function>PQsendQueryPrepared</function>, which can be used with <function>PQsendQueryParams</function>,
<function>PQgetResult</function> to duplicate the functionality of <function>PQsendPrepare</function>, and
<function>PQexecParams</function> and <function>PQexecPrepared</function> <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. respectively.
<variablelist> <variablelist>
...@@ -2325,6 +2400,33 @@ int PQsendQueryParams(PGconn *conn, ...@@ -2325,6 +2400,33 @@ int PQsendQueryParams(PGconn *conn,
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><function>PQsendQueryPrepared</function><indexterm><primary>PQsendQueryPrepared</></></term> <term><function>PQsendQueryPrepared</function><indexterm><primary>PQsendQueryPrepared</></></term>
<listitem> <listitem>
...@@ -2358,7 +2460,8 @@ int PQsendQueryPrepared(PGconn *conn, ...@@ -2358,7 +2460,8 @@ int PQsendQueryPrepared(PGconn *conn,
<para> <para>
Waits for the next result from a prior Waits for the next result from a prior
<function>PQsendQuery</function>, <function>PQsendQuery</function>,
<function>PQsendQueryParams</function>, or <function>PQsendQueryParams</function>,
<function>PQsendPrepare</function>, or
<function>PQsendQueryPrepared</function> call, <function>PQsendQueryPrepared</function> call,
and returns it. A null pointer is returned when the command is complete and returns it. A null pointer is returned when the command is complete
and there will be no more results. and there will be no more results.
......
# $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 # Functions to be exported by libpq DLLs
PQconnectdb 1 PQconnectdb 1
PQsetdbLogin 2 PQsetdbLogin 2
...@@ -116,3 +117,5 @@ PQgetssl 114 ...@@ -116,3 +117,5 @@ PQgetssl 114
pg_char_to_encoding 115 pg_char_to_encoding 115
pg_valid_server_encoding 116 pg_valid_server_encoding 116
pqsignal 117 pqsignal 117
PQprepare 118
PQsendPrepare 119
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -664,7 +664,7 @@ PQsendQuery(PGconn *conn, const char *query)
} }
/* remember we are using simple query protocol */ /* 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 * Give the data a push. In nonblock mode, don't complain if we're
...@@ -717,6 +717,94 @@ PQsendQueryParams(PGconn *conn, ...@@ -717,6 +717,94 @@ PQsendQueryParams(PGconn *conn,
resultFormat); 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 * PQsendQueryPrepared
* Like PQsendQuery, but execute a previously prepared statement, * Like PQsendQuery, but execute a previously prepared statement,
...@@ -921,7 +1009,7 @@ PQsendQueryGuts(PGconn *conn, ...@@ -921,7 +1009,7 @@ PQsendQueryGuts(PGconn *conn,
goto sendFailed; goto sendFailed;
/* remember we are using extended query protocol */ /* 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 * Give the data a push. In nonblock mode, don't complain if we're
...@@ -1134,7 +1222,6 @@ PQgetResult(PGconn *conn) ...@@ -1134,7 +1222,6 @@ PQgetResult(PGconn *conn)
* The user is responsible for freeing the PGresult via PQclear() * The user is responsible for freeing the PGresult via PQclear()
* when done with it. * when done with it.
*/ */
PGresult * PGresult *
PQexec(PGconn *conn, const char *query) PQexec(PGconn *conn, const char *query)
{ {
...@@ -1168,6 +1255,29 @@ PQexecParams(PGconn *conn, ...@@ -1168,6 +1255,29 @@ PQexecParams(PGconn *conn,
return PQexecFinish(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 * PQexecPrepared
* Like PQexec, but execute a previously prepared statement, * Like PQexec, but execute a previously prepared statement,
...@@ -1451,7 +1561,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg) ...@@ -1451,7 +1561,7 @@ PQputCopyEnd(PGconn *conn, const char *errormsg)
* If we sent the COPY command in extended-query mode, we must * If we sent the COPY command in extended-query mode, we must
* issue a Sync as well. * issue a Sync as well.
*/ */
if (conn->ext_query) if (conn->queryclass != PGQUERY_SIMPLE)
{ {
if (pqPutMsgStart('S', false, conn) < 0 || if (pqPutMsgStart('S', false, conn) < 0 ||
pqPutMsgEnd(conn) < 0) pqPutMsgEnd(conn) < 0)
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -220,6 +220,15 @@ pqParseInput3(PGconn *conn)
conn->asyncStatus = PGASYNC_READY; conn->asyncStatus = PGASYNC_READY;
break; break;
case '1': /* Parse Complete */ 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 '2': /* Bind Complete */
case '3': /* Close Complete */ case '3': /* Close Complete */
/* Nothing to do for these message types */ /* Nothing to do for these message types */
...@@ -1118,7 +1127,7 @@ pqEndcopy3(PGconn *conn) ...@@ -1118,7 +1127,7 @@ pqEndcopy3(PGconn *conn)
* If we sent the COPY command in extended-query mode, we must * If we sent the COPY command in extended-query mode, we must
* issue a Sync as well. * issue a Sync as well.
*/ */
if (conn->ext_query) if (conn->queryclass != PGQUERY_SIMPLE)
{ {
if (pqPutMsgStart('S', false, conn) < 0 || if (pqPutMsgStart('S', false, conn) < 0 ||
pqPutMsgEnd(conn) < 0) pqPutMsgEnd(conn) < 0)
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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, ...@@ -304,6 +304,9 @@ extern PGresult *PQexecParams(PGconn *conn,
const int *paramLengths, const int *paramLengths,
const int *paramFormats, const int *paramFormats,
int resultFormat); int resultFormat);
extern PGresult *PQprepare(PGconn *conn, const char *stmtName,
const char *query, int nParams,
const Oid *paramTypes);
extern PGresult *PQexecPrepared(PGconn *conn, extern PGresult *PQexecPrepared(PGconn *conn,
const char *stmtName, const char *stmtName,
int nParams, int nParams,
...@@ -322,6 +325,9 @@ extern int PQsendQueryParams(PGconn *conn, ...@@ -322,6 +325,9 @@ extern int PQsendQueryParams(PGconn *conn,
const int *paramLengths, const int *paramLengths,
const int *paramFormats, const int *paramFormats,
int resultFormat); int resultFormat);
extern int PQsendPrepare(PGconn *conn, const char *stmtName,
const char *query, int nParams,
const Oid *paramTypes);
extern int PQsendQueryPrepared(PGconn *conn, extern int PQsendQueryPrepared(PGconn *conn,
const char *stmtName, const char *stmtName,
int nParams, int nParams,
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -185,6 +185,14 @@ typedef enum
PGASYNC_COPY_OUT /* Copy Out data transfer in progress */ PGASYNC_COPY_OUT /* Copy Out data transfer in progress */
} PGAsyncStatusType; } 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 */ /* PGSetenvStatusType defines the state of the PQSetenv state machine */
/* (this is used only for 2.0-protocol connections) */ /* (this is used only for 2.0-protocol connections) */
typedef enum typedef enum
...@@ -264,10 +272,9 @@ struct pg_conn ...@@ -264,10 +272,9 @@ struct pg_conn
PGAsyncStatusType asyncStatus; PGAsyncStatusType asyncStatus;
PGTransactionStatusType xactStatus; PGTransactionStatusType xactStatus;
/* note: xactStatus never changes to ACTIVE */ /* note: xactStatus never changes to ACTIVE */
PGQueryClass queryclass;
bool nonblocking; /* whether this connection is using bool nonblocking; /* whether this connection is using
* nonblock sending semantics */ * 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 */ char copy_is_binary; /* 1 = copy binary, 0 = copy text */
int copy_already_done; /* # bytes already returned in int copy_already_done; /* # bytes already returned in
* COPY OUT */ * COPY OUT */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment