Re: Libpq support for precision and scale - Mailing list pgsql-patches
From | Bruce Momjian |
---|---|
Subject | Re: Libpq support for precision and scale |
Date | |
Msg-id | 200202222005.g1MK50g14262@candle.pha.pa.us Whole thread Raw |
In response to | Libpq support for precision and scale (Fernando Nasser <fnasser@redhat.com>) |
List | pgsql-patches |
Your patch has been added to the PostgreSQL unapplied patches list at: http://candle.pha.pa.us/cgi-bin/pgpatches I will try to apply it within the next 48 hours. --------------------------------------------------------------------------- Fernando Nasser wrote: > 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"> > > ---------------------------(end of broadcast)--------------------------- > TIP 6: Have you searched our list archives? > > http://archives.postgresql.org -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 853-3000 + If your life is a hard drive, | 830 Blythe Avenue + Christ can be your backup. | Drexel Hill, Pennsylvania 19026
pgsql-patches by date: