Libpq support for precision and scale - Mailing list pgsql-patches

From Fernando Nasser
Subject Libpq support for precision and scale
Date
Msg-id 3BE9B8BF.9CCAE89D@redhat.com
Whole thread Raw
Responses Re: Libpq support for precision and scale
List pgsql-patches
Some programs like utilities, IDEs, etc., frequently need to know the
precision and scale of the result fields (columns).  Unfortunately
libpq does not have such routines yet (JDBC does).

Liam and I created a few ones that do the trick, as inspired by the
JDBC code. The functions are:

char *PQftypename(const PGresult *res, int field_num);

Returns the type name (not the name of the column, as PQfname do).


int PQfprecision(const PGresult *res, int field_num);
int PQfscale(const PGresult *res, int field_num);

Return Scale and Precision of the type respectively.


Most programs won't need this information and may not be willing
to pay the overhead for metadata retrieval. Thus, we added
an alternative function to be used instead of PQexec if one
wishes extra metadata to be retrieved along with the query
results:

PGresult *PQexecIncludeMetadata(PGconn *conn, const char *query);

It provides the same functionality and it is used in exactly the
same way as PQexec but it includes extra metadata about the result
fields.  After this cal, it is possible to obtain the precision,
scale and type name for each result field.


The PQftypename function returns the internal PostgreSQL type name.
As some programs may prefer something more user friendly than the
internal type names, we've thrown in a conversion routine as well:

char *PQtypeint2ext(const char *intname);

This routine converts from the internal type name to a more user
friendly type name convention.


More details are in the patch to the SGML documentation that is
part of the patch (attached).


--
Liam Stewart <liams@redhat.com>
Fernando Nasser <fnasser@redhat.com>Index: fe-connect.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-connect.c,v
retrieving revision 1.180
diff -c -p -r1.180 fe-connect.c
*** fe-connect.c    2001/11/05 17:46:37    1.180
--- fe-connect.c    2001/11/07 19:00:35
*************** makeEmptyPGconn(void)
*** 1849,1854 ****
--- 1849,1855 ----
  #ifdef USE_SSL
      conn->allow_ssl_try = TRUE;
  #endif
+     conn->typecache = NULL;

      /*
       * The output buffer size is set to 8K, which is the usual size of
*************** freePGconn(PGconn *conn)
*** 1891,1896 ****
--- 1892,1898 ----
      if (!conn)
          return;
      pqClearAsyncResult(conn);    /* deallocate result and curTuple */
+     pqTypeCacheClear(conn);        /* free all type cache entries */
  #ifdef USE_SSL
      if (conn->ssl)
          SSL_free(conn->ssl);
Index: fe-exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-exec.c,v
retrieving revision 1.113
diff -c -p -r1.113 fe-exec.c
*** fe-exec.c    2001/10/25 05:50:13    1.113
--- fe-exec.c    2001/11/07 19:00:35
*************** char       *const pgresStatus[] = {
*** 48,53 ****
--- 48,54 ----
  static void pqCatenateResultError(PGresult *res, const char *msg);
  static void saveErrorResult(PGconn *conn);
  static PGresult *prepareAsyncResult(PGconn *conn);
+ static PGresult *pqExec(PGconn *conn, const char *query, int metadata);
  static int    addTuple(PGresult *res, PGresAttValue * tup);
  static void parseInput(PGconn *conn);
  static void handleSendFailure(PGconn *conn);
*************** static int    getRowDescriptions(PGconn *co
*** 55,60 ****
--- 56,63 ----
  static int    getAnotherTuple(PGconn *conn, int binary);
  static int    getNotify(PGconn *conn);
  static int    getNotice(PGconn *conn);
+ static char *pqTypeCacheGet(PGconn *conn, Oid typenum);
+ static void pqTypeCachePut(PGconn *conn, Oid typenum, char *typename);

  /* ---------------
   * Escaping arbitrary strings to get valid SQL strings/identifiers.
*************** addTuple(PGresult *res, PGresAttValue *
*** 609,614 ****
--- 612,678 ----
      return TRUE;
  }

+ /* Cache of the correspondence between type Oids and
+  * type names.  Without it too many queries can be made to
+  * retrieve this same information from the catalog over and over.
+  */
+
+ static char *
+ pqTypeCacheGet(PGconn *conn, Oid typenum)
+ {
+     char *typename = NULL;
+     PGtypecache *tc = conn->typecache;
+
+     /* Look for type Oid. */
+     while (tc != NULL)
+     {
+         if (tc->typenum == typenum)
+         {
+             typename = tc->typename;
+             break;
+         }
+         else
+             tc = tc->next;
+     }
+     return typename;
+ }
+
+ static void
+ pqTypeCachePut(PGconn *conn, Oid typenum, char *typename)
+ {
+     PGtypecache *typetocache;
+
+     typetocache = (PGtypecache *) malloc(sizeof(PGtypecache));
+     if (typetocache == NULL)
+     {
+         fprintf(stderr, "pqTypeCachePut: malloc failed.\n");
+         return;
+     }
+
+     typetocache->typenum = typenum;
+     typetocache->typename = strdup(typename);
+     typetocache->next = conn->typecache;
+     conn->typecache = typetocache;
+ }
+
+ void
+ pqTypeCacheClear(PGconn *conn)
+ {
+     PGtypecache *tc;
+     PGtypecache *ntc;
+
+     /* Free all tcache entries (and typenames). */
+     tc = conn->typecache;
+     conn->typecache = NULL;
+     while (tc != NULL)
+     {
+         if (tc->typename)
+             free(tc->typename);
+         ntc = tc->next;
+         free(tc);
+         tc = ntc;
+     }
+ }

  /*
   * PQsendQuery
*************** PQgetResult(PGconn *conn)
*** 1277,1301 ****
      return res;
  }


  /*
!  * PQexec
   *      send a query to the backend and package up the result in a PGresult
   *
   * 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 *
! PQexec(PGconn *conn, const char *query)
  {
      PGresult   *result;
      PGresult   *lastResult;
      bool        savedblocking;

      /*
       * we assume anyone calling PQexec wants blocking behaviour, we force
--- 1341,1381 ----
      return res;
  }

+ PGresult *
+ PQexec(PGconn *conn, const char *query)
+ {
+     /* Don't get metadata. */
+     return pqExec (conn, query, 0 /* no metadata */);
+ }

+ PGresult *
+ PQexecIncludeMetadata(PGconn *conn, const char *query)
+ {
+     /* Get metadata as well. */
+     return pqExec (conn, query, 1 /* with metadata */);
+ }
+
  /*
!  * pqExec
   *      send a query to the backend and package up the result in a PGresult
   *
   * 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).
+  * If it is called with metadata == 1, the metadata about the column
+  * results will be obtained and saved in the PGresult.
   * The user is responsible for freeing the PGresult via PQclear()
   * when done with it.
   */

! static PGresult   *
! pqExec(PGconn *conn, const char *query, int metadata)
  {
      PGresult   *result;
      PGresult   *lastResult;
      bool        savedblocking;
+     int            i;

      /*
       * we assume anyone calling PQexec wants blocking behaviour, we force
*************** PQexec(PGconn *conn, const char *query)
*** 1363,1368 ****
--- 1443,1501 ----

      if (PQsetnonblocking(conn, savedblocking) == -1)
          return NULL;
+
+     /*
+      * If metadata is requested and everything is well, loop through
+      * the result fields grabing the required information.
+      */
+
+     if (metadata && (lastResult->numAttributes > 0))
+         for (i = 0; i < lastResult->numAttributes; i++)
+         {
+             Oid typenum;
+             PGresult *result;
+             char *tempname;
+             static char query[] = "select typname from pg_type where oid = %lu";
+             char *fullquery;
+
+             if ((typenum = lastResult->attDescs[i].typid) == 0)
+                 continue;
+
+             /* Look up the cache for the type name. */
+             tempname = pqTypeCacheGet(conn, typenum);
+
+             /* If it is a type that we still don't know the name,
+                query for the type name and store it in the cache. */
+                if (tempname == NULL)
+             {
+                 fullquery = malloc (sizeof(query)
+                                     + 20 /* if Oids become 64 bits */);
+                 if (fullquery == NULL)
+                     {
+                       fprintf(stderr, "pqExec: malloc failed.\n");
+                       return NULL;
+                     }
+                 /* If the typename was not in the cache, query the catalog
+                    and add it to the cache */
+                 snprintf(fullquery, sizeof(query) + 20, query, typenum);
+                 result = PQexec(conn, fullquery);
+                 free(fullquery);
+                 if (!result || PQresultStatus(result) != PGRES_TUPLES_OK)
+                 {
+                     PQclear(result);
+                     continue;
+                 }
+                 if (PQntuples(result) != 1 || PQnfields(result) != 1) {
+                     PQclear(result);
+                     continue;
+                 }
+                 pqTypeCachePut(conn, typenum, PQgetvalue(result, 0, 0));
+                 tempname = pqTypeCacheGet(conn, typenum);
+             }
+
+             lastResult->attDescs[i].atttypname = strdup(tempname);
+         }
+
      return lastResult;

  errout:
*************** PQftype(const PGresult *res, int field_n
*** 2104,2109 ****
--- 2237,2253 ----
          return InvalidOid;
  }

+ char *
+ PQftypeName(const PGresult *res, int field_num)
+ {
+     if (!check_field_number(res, field_num))
+         return NULL;
+     if (res->attDescs)
+         return res->attDescs[field_num].atttypname;
+     else
+         return NULL;
+ }
+
  int
  PQfsize(const PGresult *res, int field_num)
  {
*************** PQfmod(const PGresult *res, int field_nu
*** 2124,2129 ****
--- 2268,2330 ----
          return res->attDescs[field_num].atttypmod;
      else
          return 0;
+ }
+
+ int
+ PQfprecision(const PGresult *res, int field_num)
+ {
+     int mod;
+     char *type;
+
+     if ((type = PQftypeName(res, field_num)) == NULL)
+         return 0;
+     mod = PQfmod(res, field_num);
+
+     if (strcmp(type, "numeric") == 0)
+         return ((0xFFFF0000) & mod) >> 16;
+     else if (strcmp(type, "int2") == 0)
+         return 5;
+     else if (strcmp(type, "int4") == 0)
+         return 10;
+     else if (strcmp(type, "int8") == 0)
+         return 19; /* It would be 20 if it was unsigned. */
+     else if (strcmp(type, "float4") == 0)
+         return 6;
+     else if (strcmp(type, "float8") == 0)
+         return 15;
+     else if (strcmp(type, "varchar") == 0 ||
+              strcmp(type, "bpchar") == 0  ||
+              strcmp(type, "char") == 0)
+         return mod - 4;
+     else if (strcmp(type, "varbit") == 0  ||
+              strcmp(type, "bit") == 0)
+         return mod;
+
+     return -1;
+ }
+
+ int
+ PQfscale(const PGresult *res, int field_num)
+ {
+     int mod;
+     char *type;
+
+     if ((type = PQftypeName(res, field_num)) == NULL)
+         return 0;
+     mod = PQfmod(res, field_num);
+
+     if (strcmp(type, "numeric") == 0)
+         return ((0x0000FFFF) & mod) - 4;
+     else if (strcmp(type, "int2") == 0 ||
+              strcmp(type, "int4") == 0 ||
+              strcmp(type, "int8") == 0)
+         return 0;
+     else if (strcmp(type, "float4") == 0)
+         return -1;
+     else if (strcmp(type, "float8") == 0)
+         return -1;
+
+     return -1;
  }

  char *
Index: fe-misc.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/fe-misc.c,v
retrieving revision 1.60
diff -c -p -r1.60 fe-misc.c
*** fe-misc.c    2001/11/05 17:46:37    1.60
--- fe-misc.c    2001/11/07 19:00:35
*************** WSSE_GOODEXIT:
*** 896,898 ****
--- 896,974 ----
  }

  #endif
+
+ char *
+ PQinternal2common(const char *intname)
+ {
+     static char *typename;
+
+     if (intname == NULL)
+         return NULL;
+
+     if (strcmp(intname, "int8") == 0)
+         typename = "bigint";
+     else if (strcmp(intname, "bit") == 0)
+         typename = "bit";
+     else if (strcmp(intname, "varbit") == 0)
+         typename = "varbit";    /* bit varying */
+     else if (strcmp(intname, "bool") == 0)
+         typename = "boolean";
+     else if (strcmp(intname, "box") == 0)
+         typename = "box";
+     else if (strcmp(intname, "bpchar") == 0)
+         typename = "char";        /* character */
+     else if (strcmp(intname, "varchar") == 0)
+         typename = "varchar";    /* character varying */
+     else if (strcmp(intname, "cidr") == 0)
+         typename = "cidr";
+     else if (strcmp(intname, "circle") == 0)
+         typename = "circle";
+     else if (strcmp(intname, "date") == 0)
+         typename = "date";
+     else if (strcmp(intname, "float8") == 0)
+         typename = "double precision";
+     else if (strcmp(intname, "inet") == 0)
+         typename = "inet";
+     else if (strcmp(intname, "int4") == 0)
+         typename = "integer";
+     else if (strcmp(intname, "interval") == 0)
+         typename = "interval";
+     else if (strcmp(intname, "line") == 0)
+         typename = "line";
+     else if (strcmp(intname, "lseg") == 0)
+         typename = "lseg";
+     else if (strcmp(intname, "macaddr") == 0)
+         typename = "macaddr";
+     else if (strcmp(intname, "decimal") == 0)
+         typename = "numeric";
+     else if (strcmp(intname, "numeric") == 0)
+         typename = "numeric";
+     else if (strcmp(intname, "oid") == 0)
+         typename = "oid";
+     else if (strcmp(intname, "path") == 0)
+         typename = "path";
+     else if (strcmp(intname, "point") == 0)
+         typename = "point";
+     else if (strcmp(intname, "polygon") == 0)
+         typename = "polygon";
+     else if (strcmp(intname, "float4") == 0)
+         typename = "real";
+     else if (strcmp(intname, "int2") == 0)
+         typename = "smallint";
+     else if (strcmp(intname, "serial") == 0)
+         typename = "serial";
+     else if (strcmp(intname, "text") == 0)
+         typename = "text";
+     else if (strcmp(intname, "time") == 0)
+         typename = "time";
+     else if (strcmp(intname, "time with time zone") == 0)
+         typename = "time with time zone";
+     else if (strcmp(intname, "timestamp") == 0)
+         typename = "timestamp";
+     else if (strcmp(intname, "timestamp with time zone") == 0)
+         typename = "timestamp with time zone";
+     else
+         typename = NULL;
+
+     return typename;
+ }
Index: libpq-fe.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-fe.h,v
retrieving revision 1.79
diff -c -p -r1.79 libpq-fe.h
*** libpq-fe.h    2001/11/05 17:46:37    1.79
--- libpq-fe.h    2001/11/07 19:00:35
*************** extern        "C"
*** 256,261 ****
--- 256,262 ----

  /* Simple synchronous query */
      extern PGresult *PQexec(PGconn *conn, const char *query);
+     extern PGresult *PQexecIncludeMetadata(PGconn *conn, const char *query);
      extern PGnotify *PQnotifies(PGconn *conn);
      extern void PQfreeNotify(PGnotify *notify);

*************** extern        "C"
*** 303,315 ****
      extern char *PQfname(const PGresult *res, int field_num);
      extern int    PQfnumber(const PGresult *res, const char *field_name);
      extern Oid    PQftype(const PGresult *res, int field_num);
      extern int    PQfsize(const PGresult *res, int field_num);
      extern int    PQfmod(const PGresult *res, int field_num);
      extern char *PQcmdStatus(PGresult *res);
      extern char *PQoidStatus(const PGresult *res);        /* old and ugly */
      extern Oid    PQoidValue(const PGresult *res);        /* new and improved */
!     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);

--- 304,319 ----
      extern char *PQfname(const PGresult *res, int field_num);
      extern int    PQfnumber(const PGresult *res, const char *field_name);
      extern Oid    PQftype(const PGresult *res, int field_num);
+     extern char    *PQftypeName(const PGresult *res, int field_num);
      extern int    PQfsize(const PGresult *res, int field_num);
      extern int    PQfmod(const PGresult *res, int field_num);
+     extern int    PQfprecision(const PGresult *res, int field_num);
+     extern int    PQfscale(const PGresult *res, int field_num);
      extern char *PQcmdStatus(PGresult *res);
      extern char *PQoidStatus(const PGresult *res);        /* old and ugly */
      extern Oid    PQoidValue(const PGresult *res);        /* new and improved */
!     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        "C"
*** 371,376 ****
--- 375,383 ----
  /* Get encoding id from environment variable PGCLIENTENCODING */
      extern int    PQenv2encoding(void);

+     /* Convert internal type name to common type name */
+     extern char    *PQinternal2common(const char *intname);
+
  #ifdef __cplusplus
  }
  #endif
Index: libpq-int.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/interfaces/libpq/libpq-int.h,v
retrieving revision 1.44
diff -c -p -r1.44 libpq-int.h
*** libpq-int.h    2001/11/05 17:46:38    1.44
--- libpq-int.h    2001/11/07 19:00:35
*************** union pgresult_data
*** 75,88 ****
      char        space[1];        /* dummy for accessing block as bytes */
  };

! /* Data about a single attribute (column) of a query result */

  typedef struct pgresAttDesc
  {
!     char       *name;            /* type name */
      Oid            typid;            /* type id */
      int            typlen;            /* type size */
      int            atttypmod;        /* type-specific modifier info */
  }    PGresAttDesc;

  /* Data for a single attribute of a single tuple */
--- 75,91 ----
      char        space[1];        /* dummy for accessing block as bytes */
  };

! /* Data about a single attribute (column) of a query result.
!  * The type name is only available if PQexecIncludeMetadata() was used.
!  */

  typedef struct pgresAttDesc
  {
!     char       *name;            /* column name */
      Oid            typid;            /* type id */
      int            typlen;            /* type size */
      int            atttypmod;        /* type-specific modifier info */
+     char       *atttypname;        /* type name */
  }    PGresAttDesc;

  /* Data for a single attribute of a single tuple */
*************** typedef struct pgLobjfuncs
*** 191,196 ****
--- 194,208 ----
      Oid            fn_lo_write;    /* OID of backend function LOwrite        */
  }    PGlobjfuncs;

+ /* Entry in the cache of the correspondence between type Oids and type names.
+  */
+ typedef struct pgTypeCache
+ {
+     Oid                    typenum;    /* OID of type        */
+     char               *typename;    /* name of type        */
+     struct pgTypeCache *next;        /* name of type        */
+ }            PGtypecache;
+
  /* PGconn stores all the state data associated with a single connection
   * to a backend.
   */
*************** struct pg_conn
*** 240,245 ****
--- 252,258 ----
      char        cryptSalt[2];    /* password salt received from backend */
      PGlobjfuncs *lobjfuncs;        /* private state for large-object access
                                   * fns */
+     PGtypecache *typecache;        /* cached types for this connection. */

      /* Buffer for data received from backend and not yet processed */
      char       *inBuffer;        /* currently allocated buffer */
*************** extern void pqSetResultError(PGresult *r
*** 305,310 ****
--- 318,324 ----
  extern void *pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary);
  extern char *pqResultStrdup(PGresult *res, const char *str);
  extern void pqClearAsyncResult(PGconn *conn);
+ extern void pqTypeCacheClear(PGconn *conn);

  /* === in fe-misc.c === */

Index: libpq.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql/doc/src/sgml/libpq.sgml,v
retrieving revision 1.72
diff -c -p -r1.72 libpq.sgml
*** libpq.sgml    2001/09/13 15:55:23    1.72
--- libpq.sgml    2001/11/07 19:06:52
*************** PGresult *PQexec(PGconn *conn,
*** 728,733 ****
--- 728,748 ----
        <function>PQerrorMessage</function> to get more information about the error.
  </para>
  </listitem>
+
+ <listitem>
+ <para>
+ <function>PQexecIncludeMetadata</function>
+           Submit a query to the server and wait for the result;
+           include extra metadata about the result fields.
+       This makes available information such as the type name,
+       precision and scale for each field in the result.
+ <synopsis>
+ PGresult *PQexecIncludeMetadata(PGconn *conn,
+                  const char *query);
+ </synopsis>
+           Used the same way as PQexec().
+ </para>
+ </listitem>
  </itemizedlist>

  <para>
*************** You can query the system table <literal>
*** 964,969 ****
--- 979,986 ----
  the name and properties of the various data types. The <acronym>OID</acronym>s
  of the built-in data types are defined in <filename>src/include/catalog/pg_type.h</filename>
  in the source tree.
+ The function <function>PQftypename</function> can be used to retrieve the
+ type name if the result was obtained via <function>PQexecIncludeMetadata</function>.
  </para>
  </listitem>

*************** extracts data from a <acronym>BINARY</ac
*** 1010,1015 ****
--- 1027,1126 ----
  </para>
  </listitem>
  </itemizedlist>
+
+ <para>
+ The following functions only produce meaningful results if
+ <function>PQexecIncludeMetadata</function> was used
+ (as opposed to <function>PQexec</function>).
+ </para>
+
+ <itemizedlist>
+
+ <listitem>
+ <para>
+ <function>PQftypename</function>
+           Returns the name of the column type as a string.
+           Field indices start at 0.
+ <synopsis>
+ char *PQftypename(const PGresult *res,
+             int field_index);
+ </synopsis>
+       Returns the name of the column type as a string.
+           Copy the string if needed -- do not modify, free()
+           or assume its persistence.  The internal type name is
+           returned; use PQtypeint2ext() to convert to a more SQL-ish style.
+       NULL is returned if the field type name is not availble.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <function>PQfprecision</function>
+           Returns  the  precision of the field
+           associated with the given field index.
+           Field indices start at 0.
+ <synopsis>
+ int PQfprecision(const PGresult *res,
+             int field_index);
+ </synopsis>
+       Returns the precision of the field
+           associated with the given field index.
+       For numeric types (INTEGER, FLOAT, etc.), PQfprecision returns the
+       number of decimal digits in the specified field. For character and bit
+       string types, such as VARCHAR and BIT, PQfprecision returns the
+       maximum number of characters/bits allowed in the specified field.
+       PQfprecision returns 0 if precision information is not available and
+       -1 if precision is not applicable to the field in question. The latter
+       will be the case if the type of the field is POINT, for example.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <function>PQfscale</function>
+           Returns  the  scale of the field
+           associated with the given field index.
+           Field indices start at 0.
+ <synopsis>
+ int PQfscale(const PGresult *res,
+             int field_index);
+ </synopsis>
+       Returns the scale of the field
+           associated with the given field index.
+       PQfscale returns the scale of the field associated with the given
+       field index. Scale is the number of digits after the decimal point,
+       so this function is useful only with fields that are of a numeric
+       type (INTEGER, FLOAT, NUMERIC, etc.). -1 is returned if scale is not
+       applicable to the field type. 0 is returned if scale information is
+       not available.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ Use the function below to convert internal type names (like the
+ ones returned by <function>PQftypename</function>) into something
+ more user-friendly.
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ <function>PQtypeint2ext</function>
+           Converts an internal type name into a SQL-ish
+           type name.
+ <synopsis>
+ char *PQtypeint2ext(const char **intname);
+ </synopsis>
+       Converts an internal type name into a SQL-ish
+           type name.
+           NULL is returned if the internal type is not recognized
+           (which will be the case if the type is a UDT).
+ </para>
+ </listitem>
+
+ </itemizedlist>
+
  </sect2>

  <sect2 id="libpq-exec-select-values">

pgsql-patches by date:

Previous
From: Tom Lane
Date:
Subject: Re: Query preformence
Next
From: Rene Pijlman
Date:
Subject: Re: Memory leakage in jdbc Driver