From 9a8920e1d7291e41b3d0d9f15f735f68c2ad987c Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 18 Aug 2006 19:52:39 +0000 Subject: [PATCH] Add PQdescribePrepared, PQdescribePortal, and related functions to libpq to allow obtaining information about previously prepared statements and open cursors. Volkan Yazici --- doc/src/sgml/libpq.sgml | 176 ++++++++++++++++++++++++--- src/interfaces/libpq/exports.txt | 8 +- src/interfaces/libpq/fe-exec.c | 182 +++++++++++++++++++++++++++- src/interfaces/libpq/fe-protocol3.c | 107 +++++++++++++++- src/interfaces/libpq/libpq-fe.h | 10 +- src/interfaces/libpq/libpq-int.h | 13 +- 6 files changed, 472 insertions(+), 24 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 7ffd15a038..db6525e0c8 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1,4 +1,4 @@ - + <application>libpq</application> - C Library @@ -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 nParams; data types will be inferred for these symbols as -well. +well. (See PQdescribePrepared for a means to find out +what data types were inferred.) @@ -1255,13 +1256,6 @@ send the command at all. Use PQerrorMessage to get more information about such errors. - - -At present, there is no way to determine the actual data type inferred for -any parameters whose types are not specified in paramTypes[]. -This is a libpq omission that will probably be rectified -in a future release. - @@ -1315,6 +1309,72 @@ the prepared statement's parameter types were determined when it was created). + + +PQdescribePreparedPQdescribePrepared + + + Submits a request to obtain information about the specified + prepared statement, and waits for completion. + +PGresult *PQdescribePrepared(PGconn *conn, const char *stmtName); + + + + +PQdescribePrepared allows an application to obtain information +about a previously prepared statement. +PQdescribePrepared is supported only in protocol 3.0 and later +connections; it will fail when using protocol 2.0. + + + +stmtName may be "" or NULL to reference the unnamed +statement, otherwise it must be the name of an existing prepared statement. +On success, a PGresult with status +PGRES_COMMAND_OK is returned. The functions +PQnparams and PQparamtype +may be applied to this PGresult to obtain information +about the parameters of the prepared statement, and the functions +PQnfields, PQfname, +PQftype, etc provide information about the result +columns (if any) of the statement. + + + + + +PQdescribePortalPQdescribePortal + + + Submits a request to obtain information about the specified + portal, and waits for completion. + +PGresult *PQdescribePortal(PGconn *conn, const char *portalName); + + + + +PQdescribePortal allows an application to obtain information +about a previously created portal. (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 DECLARE CURSOR SQL command.) +PQdescribePortal is supported only in protocol 3.0 and later +connections; it will fail when using protocol 2.0. + + + +portalName may be "" or NULL to reference the unnamed +portal, otherwise it must be the name of an existing portal. +On success, a PGresult with status +PGRES_COMMAND_OK is returned. The functions +PQnfields, PQfname, +PQftype, etc may be applied to the +PGresult to obtain information about the result +columns (if any) of the portal. + + + @@ -1707,8 +1767,11 @@ object, just as with a PGresult returned by These functions are used to extract information from a PGresult object that represents a successful query result (that is, one that has status -PGRES_TUPLES_OK). For objects with other status -values they will act as though the result has zero rows and zero columns. +PGRES_TUPLES_OK). 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. @@ -2040,6 +2103,43 @@ on PQfsize to obtain the actual data length. + +PQnparamsPQnparams + + + Returns the number of parameters of a prepared statement. + +int PQnparams(const PGresult *res); + + + + +This function is only useful when inspecting the result of +PQdescribePrepared. For other types of queries it will +return zero. + + + + + +PQparamtypePQparamtype + + + Returns the data type of the indicated statement parameter. + Parameter numbers start at 0. + +Oid PQparamtype(const PGresult *res, int param_number); + + + + +This function is only useful when inspecting the result of +PQdescribePrepared. For other types of queries it will +return zero. + + + + PQprintPQprint @@ -2486,13 +2586,17 @@ underlying functions that PQexec is built from: PQsendQuery and PQgetResult. There are also PQsendQueryParams, -PQsendPrepare, and +PQsendPrepare, PQsendQueryPrepared, +PQsendDescribePrepared, and +PQsendDescribePortal, which can be used with PQgetResult to duplicate the functionality of PQexecParams, -PQprepare, and -PQexecPrepared +PQprepare, +PQexecPrepared, +PQdescribePrepared, and +PQdescribePortal respectively. @@ -2598,6 +2702,50 @@ int PQsendQueryPrepared(PGconn *conn, + +PQsendDescribePreparedPQsendDescribePrepared + + + Submits a request to obtain information about the specified + prepared statement, without waiting for completion. + +int PQsendDescribePrepared(PGconn *conn, const char *stmtName); + + + This is an asynchronous version of PQdescribePrepared: it + returns 1 if it was able to dispatch the request, and 0 if not. + After a successful call, call PQgetResult + to obtain the results. + The function's parameters are handled identically to + PQdescribePrepared. Like + PQdescribePrepared, it will not work on 2.0-protocol + connections. + + + + + +PQsendDescribePortalPQsendDescribePortal + + + Submits a request to obtain information about the specified + portal, without waiting for completion. + +int PQsendDescribePortal(PGconn *conn, const char *portalName); + + + This is an asynchronous version of PQdescribePortal: it + returns 1 if it was able to dispatch the request, and 0 if not. + After a successful call, call PQgetResult + to obtain the results. + The function's parameters are handled identically to + PQdescribePortal. Like + PQdescribePortal, it will not work on 2.0-protocol + connections. + + + + PQgetResultPQgetResult diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 078e4f9771..606133bd00 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -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 diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 3906a52c8c..71d9c02eb1 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.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 diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 35f015ecce..a6abb06285 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.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; diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index e2542c3a05..26c0a104fb 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -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); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 2c5f3f288b..e3532fbf6f 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -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 */