Add PQdescribePrepared, PQdescribePortal, and related functions to libpq

to allow obtaining information about previously prepared statements and
open cursors.  Volkan Yazici
This commit is contained in:
Tom Lane 2006-08-18 19:52:39 +00:00
parent 46d61eb218
commit 9a8920e1d7
6 changed files with 472 additions and 24 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.214 2006/07/27 13:20:24 momjian Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.215 2006/08/18 19:52:39 tgl Exp $ -->
<chapter id="libpq">
<title><application>libpq</application> - C Library</title>
@ -1244,7 +1244,8 @@ 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.
well. (See <function>PQdescribePrepared</function> for a means to find out
what data types were inferred.)
</para>
<para>
@ -1255,13 +1256,6 @@ 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 data type 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>
@ -1315,6 +1309,72 @@ the prepared statement's parameter types were determined when it was created).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><function>PQdescribePrepared</function><indexterm><primary>PQdescribePrepared</></></term>
<listitem>
<para>
Submits a request to obtain information about the specified
prepared statement, and waits for completion.
<synopsis>
PGresult *PQdescribePrepared(PGconn *conn, const char *stmtName);
</synopsis>
</para>
<para>
<function>PQdescribePrepared</> allows an application to obtain information
about a previously prepared statement.
<function>PQdescribePrepared</> is supported only in protocol 3.0 and later
connections; it will fail when using protocol 2.0.
</para>
<para>
<parameter>stmtName</> may be <literal>""</> or NULL to reference the unnamed
statement, otherwise it must be the name of an existing prepared statement.
On success, a <structname>PGresult</> with status
<literal>PGRES_COMMAND_OK</literal> is returned. The functions
<function>PQnparams</function> and <function>PQparamtype</function>
may be applied to this <structname>PGresult</> to obtain information
about the parameters of the prepared statement, and the functions
<function>PQnfields</function>, <function>PQfname</function>,
<function>PQftype</function>, etc provide information about the result
columns (if any) of the statement.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><function>PQdescribePortal</function><indexterm><primary>PQdescribePortal</></></term>
<listitem>
<para>
Submits a request to obtain information about the specified
portal, and waits for completion.
<synopsis>
PGresult *PQdescribePortal(PGconn *conn, const char *portalName);
</synopsis>
</para>
<para>
<function>PQdescribePortal</> allows an application to obtain information
about a previously created portal. (<application>libpq</> does not provide
any direct access to portals, but you can use this function to inspect the
properties of a cursor created with a <command>DECLARE CURSOR</> SQL command.)
<function>PQdescribePortal</> is supported only in protocol 3.0 and later
connections; it will fail when using protocol 2.0.
</para>
<para>
<parameter>portalName</> may be <literal>""</> or NULL to reference the unnamed
portal, otherwise it must be the name of an existing portal.
On success, a <structname>PGresult</> with status
<literal>PGRES_COMMAND_OK</literal> is returned. The functions
<function>PQnfields</function>, <function>PQfname</function>,
<function>PQftype</function>, etc may be applied to the
<structname>PGresult</> to obtain information about the result
columns (if any) of the portal.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
@ -1707,8 +1767,11 @@ object, just as with a <structname>PGresult</structname> returned by
These functions are used to extract information from a
<structname>PGresult</structname> object that represents a successful
query result (that is, one that has status
<literal>PGRES_TUPLES_OK</literal>). For objects with other status
values they will act as though the result has zero rows and zero columns.
<literal>PGRES_TUPLES_OK</literal>). They can also be used to extract
information from a successful Describe operation: a Describe's result
has all the same column information that actual execution of the query
would provide, but it has zero rows. For objects with other status values,
these functions will act as though the result has zero rows and zero columns.
</para>
<variablelist>
@ -2040,6 +2103,43 @@ on <function>PQfsize</function> to obtain the actual data length.
</listitem>
</varlistentry>
<varlistentry>
<term><function>PQnparams</function><indexterm><primary>PQnparams</></></term>
<listitem>
<para>
Returns the number of parameters of a prepared statement.
<synopsis>
int PQnparams(const PGresult *res);
</synopsis>
</para>
<para>
This function is only useful when inspecting the result of
<function>PQdescribePrepared</>. For other types of queries it will
return zero.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><function>PQparamtype</function><indexterm><primary>PQparamtype</></></term>
<listitem>
<para>
Returns the data type of the indicated statement parameter.
Parameter numbers start at 0.
<synopsis>
Oid PQparamtype(const PGresult *res, int param_number);
</synopsis>
</para>
<para>
This function is only useful when inspecting the result of
<function>PQdescribePrepared</>. For other types of queries it will
return zero.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><function>PQprint</function><indexterm><primary>PQprint</></></term>
<listitem>
@ -2486,13 +2586,17 @@ 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>PQsendPrepare</function>,
<function>PQsendQueryPrepared</function>,
<function>PQsendDescribePrepared</function>, and
<function>PQsendDescribePortal</function>,
which can be used with <function>PQgetResult</function> to duplicate the
functionality of
<function>PQexecParams</function>,
<function>PQprepare</function>, and
<function>PQexecPrepared</function>
<function>PQprepare</function>,
<function>PQexecPrepared</function>,
<function>PQdescribePrepared</function>, and
<function>PQdescribePortal</function>
respectively.
<variablelist>
@ -2598,6 +2702,50 @@ int PQsendQueryPrepared(PGconn *conn,
</listitem>
</varlistentry>
<varlistentry>
<term><function>PQsendDescribePrepared</><indexterm><primary>PQsendDescribePrepared</></></term>
<listitem>
<para>
Submits a request to obtain information about the specified
prepared statement, without waiting for completion.
<synopsis>
int PQsendDescribePrepared(PGconn *conn, const char *stmtName);
</synopsis>
This is an asynchronous version of <function>PQdescribePrepared</>: it
returns 1 if it was able to dispatch the request, and 0 if not.
After a successful call, call <function>PQgetResult</function>
to obtain the results.
The function's parameters are handled identically to
<function>PQdescribePrepared</function>. Like
<function>PQdescribePrepared</function>, it will not work on 2.0-protocol
connections.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><function>PQsendDescribePortal</><indexterm><primary>PQsendDescribePortal</></></term>
<listitem>
<para>
Submits a request to obtain information about the specified
portal, without waiting for completion.
<synopsis>
int PQsendDescribePortal(PGconn *conn, const char *portalName);
</synopsis>
This is an asynchronous version of <function>PQdescribePortal</>: it
returns 1 if it was able to dispatch the request, and 0 if not.
After a successful call, call <function>PQgetResult</function>
to obtain the results.
The function's parameters are handled identically to
<function>PQdescribePortal</function>. Like
<function>PQdescribePortal</function>, it will not work on 2.0-protocol
connections.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><function>PQgetResult</function><indexterm><primary>PQgetResult</></></term>
<listitem>

View File

@ -1,4 +1,4 @@
# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.13 2006/07/04 13:22:15 momjian Exp $
# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.14 2006/08/18 19:52:39 tgl Exp $
# Functions to be exported by libpq DLLs
PQconnectdb 1
PQsetdbLogin 2
@ -130,3 +130,9 @@ PQescapeByteaConn 127
PQencryptPassword 128
PQisthreadsafe 129
enlargePQExpBuffer 130
PQnparams 131
PQparamtype 132
PQdescribePrepared 133
PQdescribePortal 134
PQsendDescribePrepared 135
PQsendDescribePortal 136

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.189 2006/08/04 22:20:06 momjian Exp $
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-exec.c,v 1.190 2006/08/18 19:52:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -61,6 +61,8 @@ static int PQsendQueryGuts(PGconn *conn,
static void parseInput(PGconn *conn);
static bool PQexecStart(PGconn *conn);
static PGresult *PQexecFinish(PGconn *conn);
static int PQsendDescribe(PGconn *conn, char desc_type,
const char *desc_target);
/* ----------------
@ -147,6 +149,8 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
result->attDescs = NULL;
result->tuples = NULL;
result->tupArrSize = 0;
result->numParameters = 0;
result->paramDescs = NULL;
result->resultStatus = status;
result->cmdStatus[0] = '\0';
result->binary = 0;
@ -367,6 +371,7 @@ PQclear(PGresult *res)
/* zero out the pointer fields to catch programming errors */
res->attDescs = NULL;
res->tuples = NULL;
res->paramDescs = NULL;
res->errFields = NULL;
/* res->curBlock was zeroed out earlier */
@ -1471,6 +1476,139 @@ PQexecFinish(PGconn *conn)
return lastResult;
}
/*
* PQdescribePrepared
* Obtain information about a previously prepared statement
*
* 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). On success, the PGresult contains status
* PGRES_COMMAND_OK, and its parameter and column-heading fields describe
* the statement's inputs and outputs respectively.
* The user is responsible for freeing the PGresult via PQclear()
* when done with it.
*/
PGresult *
PQdescribePrepared(PGconn *conn, const char *stmt)
{
if (!PQexecStart(conn))
return NULL;
if (!PQsendDescribe(conn, 'S', stmt))
return NULL;
return PQexecFinish(conn);
}
/*
* PQdescribePortal
* Obtain information about a previously created portal
*
* This is much like PQdescribePrepared, except that no parameter info is
* returned. Note that at the moment, libpq doesn't really expose portals
* to the client; but this can be used with a portal created by a SQL
* DECLARE CURSOR command.
*/
PGresult *
PQdescribePortal(PGconn *conn, const char *portal)
{
if (!PQexecStart(conn))
return NULL;
if (!PQsendDescribe(conn, 'P', portal))
return NULL;
return PQexecFinish(conn);
}
/*
* PQsendDescribePrepared
* Submit a Describe Statement command, but don't wait for it to finish
*
* Returns: 1 if successfully submitted
* 0 if error (conn->errorMessage is set)
*/
int
PQsendDescribePrepared(PGconn *conn, const char *stmt)
{
return PQsendDescribe(conn, 'S', stmt);
}
/*
* PQsendDescribePortal
* Submit a Describe Portal command, but don't wait for it to finish
*
* Returns: 1 if successfully submitted
* 0 if error (conn->errorMessage is set)
*/
int
PQsendDescribePortal(PGconn *conn, const char *portal)
{
return PQsendDescribe(conn, 'P', portal);
}
/*
* PQsendDescribe
* Common code to send a Describe command
*
* Available options for desc_type are
* 'S' to describe a prepared statement; or
* 'P' to describe a portal.
* Returns 1 on success and 0 on failure.
*/
static int
PQsendDescribe(PGconn *conn, char desc_type, const char *desc_target)
{
/* Treat null desc_target as empty string */
if (!desc_target)
desc_target = "";
if (!PQsendQueryStart(conn))
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 Describe message */
if (pqPutMsgStart('D', false, conn) < 0 ||
pqPutc(desc_type, conn) < 0 ||
pqPuts(desc_target, conn) < 0 ||
pqPutMsgEnd(conn) < 0)
goto sendFailed;
/* construct the Sync message */
if (pqPutMsgStart('S', false, conn) < 0 ||
pqPutMsgEnd(conn) < 0)
goto sendFailed;
/* remember we are doing a Describe */
conn->queryclass = PGQUERY_DESCRIBE;
/* reset last-query string (not relevant now) */
if (conn->last_query)
{
free(conn->last_query);
conn->last_query = NULL;
}
/*
* 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;
}
/*
* PQnotifies
* returns a PGnotify* structure of the latest async notification
@ -1984,6 +2122,22 @@ check_tuple_field_number(const PGresult *res,
return TRUE;
}
static int
check_param_number(const PGresult *res, int param_num)
{
if (!res)
return FALSE; /* no way to display error message... */
if (param_num < 0 || param_num >= res->numParameters)
{
pqInternalNotice(&res->noticeHooks,
"parameter number %d is out of range 0..%d",
param_num, res->numParameters - 1);
return FALSE;
}
return TRUE;
}
/*
* returns NULL if the field_num is invalid
*/
@ -2307,6 +2461,32 @@ PQgetisnull(const PGresult *res, int tup_num, int field_num)
return 0;
}
/* PQnparams:
* returns the number of input parameters of a prepared statement.
*/
int
PQnparams(const PGresult *res)
{
if (!res)
return 0;
return res->numParameters;
}
/* PQparamtype:
* returns type Oid of the specified statement parameter.
*/
Oid
PQparamtype(const PGresult *res, int param_num)
{
if (!check_param_number(res, param_num))
return InvalidOid;
if (res->paramDescs)
return res->paramDescs[param_num].typid;
else
return InvalidOid;
}
/* PQsetnonblocking:
* sets the PGconn's database connection non-blocking if the arg is TRUE
* or makes it non-blocking if the arg is FALSE, this will not protect

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.26 2006/03/14 22:48:23 tgl Exp $
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-protocol3.c,v 1.27 2006/08/18 19:52:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -45,6 +45,7 @@
static void handleSyncLoss(PGconn *conn, char id, int msgLength);
static int getRowDescriptions(PGconn *conn);
static int getParamDescriptions(PGconn *conn);
static int getAnotherTuple(PGconn *conn, int msgLength);
static int getParameterStatus(PGconn *conn);
static int getNotify(PGconn *conn);
@ -263,11 +264,18 @@ pqParseInput3(PGconn *conn)
return;
break;
case 'T': /* Row Description */
if (conn->result == NULL)
if (conn->result == NULL ||
conn->queryclass == PGQUERY_DESCRIBE)
{
/* First 'T' in a query sequence */
if (getRowDescriptions(conn))
return;
/*
* If we're doing a Describe, we're ready to pass
* the result back to the client.
*/
if (conn->queryclass == PGQUERY_DESCRIBE)
conn->asyncStatus = PGASYNC_READY;
}
else
{
@ -293,6 +301,16 @@ pqParseInput3(PGconn *conn)
if (conn->result == NULL)
conn->result = PQmakeEmptyPGresult(conn,
PGRES_COMMAND_OK);
/*
* If we're doing a Describe, we're ready to pass
* the result back to the client.
*/
if (conn->queryclass == PGQUERY_DESCRIBE)
conn->asyncStatus = PGASYNC_READY;
break;
case 't': /* Parameter Description */
if (getParamDescriptions(conn))
return;
break;
case 'D': /* Data Row */
if (conn->result != NULL &&
@ -409,7 +427,8 @@ handleSyncLoss(PGconn *conn, char id, int msgLength)
/*
* parseInput subroutine to read a 'T' (row descriptions) message.
* We build a PGresult structure containing the attribute data.
* We'll build a new PGresult structure (unless called for a Describe
* command for a prepared statement) containing the attribute data.
* Returns: 0 if completed message, EOF if not enough data yet.
*
* Note that if we run out of data, we have to release the partially
@ -423,12 +442,25 @@ getRowDescriptions(PGconn *conn)
int nfields;
int i;
result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
/*
* When doing Describe for a prepared statement, there'll already be
* a PGresult created by getParamDescriptions, and we should fill
* data into that. Otherwise, create a new, empty PGresult.
*/
if (conn->queryclass == PGQUERY_DESCRIBE)
{
if (conn->result)
result = conn->result;
else
result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK);
}
else
result = PQmakeEmptyPGresult(conn, PGRES_TUPLES_OK);
if (!result)
goto failure;
/* parseInput already read the 'T' label and message length. */
/* the next two bytes are the number of fields */
/* the next two bytes are the number of fields */
if (pqGetInt(&(result->numAttributes), 2, conn))
goto failure;
nfields = result->numAttributes;
@ -494,6 +526,71 @@ getRowDescriptions(PGconn *conn)
conn->result = result;
return 0;
failure:
/*
* Discard incomplete result, unless it's from getParamDescriptions.
*
* Note that if we hit a bufferload boundary while handling the
* describe-statement case, we'll forget any PGresult space we just
* allocated, and then reallocate it on next try. This will bloat
* the PGresult a little bit but the space will be freed at PQclear,
* so it doesn't seem worth trying to be smarter.
*/
if (result != conn->result)
PQclear(result);
return EOF;
}
/*
* parseInput subroutine to read a 't' (ParameterDescription) message.
* We'll build a new PGresult structure containing the parameter data.
* Returns: 0 if completed message, EOF if not enough data yet.
*
* Note that if we run out of data, we have to release the partially
* constructed PGresult, and rebuild it again next time. Fortunately,
* that shouldn't happen often, since 't' messages usually fit in a packet.
*/
static int
getParamDescriptions(PGconn *conn)
{
PGresult *result;
int nparams;
int i;
result = PQmakeEmptyPGresult(conn, PGRES_COMMAND_OK);
if (!result)
goto failure;
/* parseInput already read the 't' label and message length. */
/* the next two bytes are the number of parameters */
if (pqGetInt(&(result->numParameters), 2, conn))
goto failure;
nparams = result->numParameters;
/* allocate space for the parameter descriptors */
if (nparams > 0)
{
result->paramDescs = (PGresParamDesc *)
pqResultAlloc(result, nparams * sizeof(PGresParamDesc), TRUE);
if (!result->paramDescs)
goto failure;
MemSet(result->paramDescs, 0, nparams * sizeof(PGresParamDesc));
}
/* get parameter info */
for (i = 0; i < nparams; i++)
{
int typid;
if (pqGetInt(&typid, 4, conn))
goto failure;
result->paramDescs[i].typid = typid;
}
/* Success! */
conn->result = result;
return 0;
failure:
PQclear(result);
return EOF;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.131 2006/07/04 13:22:15 momjian Exp $
* $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.132 2006/08/18 19:52:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -406,6 +406,14 @@ extern char *PQcmdTuples(PGresult *res);
extern char *PQgetvalue(const PGresult *res, int tup_num, int field_num);
extern int PQgetlength(const PGresult *res, int tup_num, int field_num);
extern int PQgetisnull(const PGresult *res, int tup_num, int field_num);
extern int PQnparams(const PGresult *res);
extern Oid PQparamtype(const PGresult *res, int param_num);
/* Describe prepared statements and portals */
extern PGresult *PQdescribePrepared(PGconn *conn, const char *stmt);
extern PGresult *PQdescribePortal(PGconn *conn, const char *portal);
extern int PQsendDescribePrepared(PGconn *conn, const char *stmt);
extern int PQsendDescribePortal(PGconn *conn, const char *portal);
/* Delete a PGresult */
extern void PQclear(PGresult *res);

View File

@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.114 2006/08/04 18:58:33 momjian Exp $
* $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.115 2006/08/18 19:52:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -94,6 +94,12 @@ typedef struct pgresAttDesc
int atttypmod; /* type-specific modifier info */
} PGresAttDesc;
/* Data about a single parameter of a prepared statement */
typedef struct pgresParamDesc
{
Oid typid; /* type id */
} PGresParamDesc;
/*
* Data for a single attribute of a single tuple
*
@ -145,6 +151,8 @@ struct pg_result
PGresAttValue **tuples; /* each PGresTuple is an array of
* PGresAttValue's */
int tupArrSize; /* allocated size of tuples array */
int numParameters;
PGresParamDesc *paramDescs;
ExecStatusType resultStatus;
char cmdStatus[CMDSTATUS_LEN]; /* cmd status from the query */
int binary; /* binary tuple values if binary == 1,
@ -193,7 +201,8 @@ typedef enum
{
PGQUERY_SIMPLE, /* simple Query protocol (PQexec) */
PGQUERY_EXTENDED, /* full Extended protocol (PQexecParams) */
PGQUERY_PREPARE /* Parse only (PQprepare) */
PGQUERY_PREPARE, /* Parse only (PQprepare) */
PGQUERY_DESCRIBE /* Describe Statement or Portal */
} PGQueryClass;
/* PGSetenvStatusType defines the state of the PQSetenv state machine */