Diff for /pgsql/contrib/dblink/dblink.c between versions 1.38 and 1.38.4.9

version 1.38, 2005/01/01 05:43:05 version 1.38.4.9, 2010/06/15 16:22:51
Line 8 Line 8
  * Darko Prenosil <[email protected]>   * Darko Prenosil <[email protected]>
  * Shridhar Daithankar <[email protected]>   * Shridhar Daithankar <[email protected]>
  *   *
  * Copyright (c) 2001-2005, PostgreSQL Global Development Group   * Copyright (c) 2001-2006, PostgreSQL Global Development Group
  * ALL RIGHTS RESERVED;   * ALL RIGHTS RESERVED;
  *   *
  * Permission to use, copy, modify, and distribute this software and its   * Permission to use, copy, modify, and distribute this software and its
Line 34 Line 34
 #include "libpq-fe.h"  #include "libpq-fe.h"
 #include "fmgr.h"  #include "fmgr.h"
 #include "funcapi.h"  #include "funcapi.h"
   #include "miscadmin.h"
 #include "access/tupdesc.h"  #include "access/tupdesc.h"
 #include "access/heapam.h"  #include "access/heapam.h"
 #include "catalog/catname.h"  #include "catalog/catname.h"
Line 47 Line 48
 #include "nodes/execnodes.h"  #include "nodes/execnodes.h"
 #include "nodes/pg_list.h"  #include "nodes/pg_list.h"
 #include "parser/parse_type.h"  #include "parser/parse_type.h"
   #include "parser/scansup.h"
 #include "tcop/tcopprot.h"  #include "tcop/tcopprot.h"
 #include "utils/builtins.h"  #include "utils/builtins.h"
 #include "utils/fmgroids.h"  #include "utils/fmgroids.h"
   #include "utils/acl.h"
 #include "utils/array.h"  #include "utils/array.h"
 #include "utils/lsyscache.h"  #include "utils/lsyscache.h"
 #include "utils/syscache.h"  #include "utils/syscache.h"
Line 73  static remoteConn *getConnectionByName(c Line 76  static remoteConn *getConnectionByName(c
 static HTAB *createConnHash(void);  static HTAB *createConnHash(void);
 static void createNewConnection(const char *name, remoteConn * con);  static void createNewConnection(const char *name, remoteConn * con);
 static void deleteConnection(const char *name);  static void deleteConnection(const char *name);
 static char **get_pkey_attnames(Oid relid, int16 *numatts);  static char **get_pkey_attnames(Relation rel, int16 *numatts);
 static char *get_sql_insert(Oid relid, int16 *pkattnums, int16 pknumatts, char **src_pkattvals, char **tgt_pkattvals);  static char *get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals);
 static char *get_sql_delete(Oid relid, int16 *pkattnums, int16 pknumatts, char **tgt_pkattvals);  static char *get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals);
 static char *get_sql_update(Oid relid, int16 *pkattnums, int16 pknumatts, char **src_pkattvals, char **tgt_pkattvals);  static char *get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals);
 static char *quote_literal_cstr(char *rawstr);  static char *quote_literal_cstr(char *rawstr);
 static char *quote_ident_cstr(char *rawstr);  static char *quote_ident_cstr(char *rawstr);
 static int16 get_attnum_pk_pos(int16 *pkattnums, int16 pknumatts, int16 key);  static int      get_attnum_pk_pos(int *pkattnums, int pknumatts, int key);
 static HeapTuple get_tuple_of_interest(Oid relid, int16 *pkattnums, int16 pknumatts, char **src_pkattvals);  static HeapTuple get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals);
 static Oid      get_relid_from_relname(text *relname_text);  static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode);
 static char *generate_relation_name(Oid relid);  static char *generate_relation_name(Relation rel);
   static char *connstr_strip_password(const char *connstr);
   static void dblink_security_check(PGconn *conn, remoteConn *rcon, const char *connstr);
   static void validate_pkattnums(Relation rel,
                                      int16 *pkattnums_arg, int32 pknumatts_arg,
                                      int **pkattnums, int *pknumatts);
   
 /* Global */  /* Global */
 List       *res_id = NIL;  List       *res_id = NIL;
Line 166  typedef struct remoteConnHashEnt Line 174  typedef struct remoteConnHashEnt
                         else \                          else \
                         { \                          { \
                                 connstr = conname_or_str; \                                  connstr = conname_or_str; \
                                   dblink_security_check(conn, rcon, connstr); \
                                 conn = PQconnectdb(connstr); \                                  conn = PQconnectdb(connstr); \
                                 if (PQstatus(conn) == CONNECTION_BAD) \                                  if (PQstatus(conn) == CONNECTION_BAD) \
                                 { \                                  { \
Line 207  dblink_connect(PG_FUNCTION_ARGS) Line 216  dblink_connect(PG_FUNCTION_ARGS)
   
         if (connname)          if (connname)
                 rcon = (remoteConn *) palloc(sizeof(remoteConn));                  rcon = (remoteConn *) palloc(sizeof(remoteConn));
   
           /* check password used if not superuser */
           dblink_security_check(conn, rcon, connstr);
         conn = PQconnectdb(connstr);          conn = PQconnectdb(connstr);
   
         MemoryContextSwitchTo(oldcontext);          MemoryContextSwitchTo(oldcontext);
Line 547  dblink_fetch(PG_FUNCTION_ARGS) Line 559  dblink_fetch(PG_FUNCTION_ARGS)
                 /* got results, keep track of them */                  /* got results, keep track of them */
                 funcctx->user_fctx = res;                  funcctx->user_fctx = res;
   
                 /* fast track when no results */  
                 if (funcctx->max_calls < 1)  
                 {  
                         if (res)  
                                 PQclear(res);  
                         SRF_RETURN_DONE(funcctx);  
                 }  
   
                 /* check typtype to see if we have a predetermined return type */                  /* check typtype to see if we have a predetermined return type */
                 functypeid = get_func_rettype(funcid);                  functypeid = get_func_rettype(funcid);
                 functyptype = get_typtype(functypeid);                  functyptype = get_typtype(functypeid);
Line 577  dblink_fetch(PG_FUNCTION_ARGS) Line 581  dblink_fetch(PG_FUNCTION_ARGS)
                         /* shouldn't happen */                          /* shouldn't happen */
                         elog(ERROR, "return type must be a row type");                          elog(ERROR, "return type must be a row type");
   
                   /* check result and tuple descriptor have the same number of columns */
                   if (PQnfields(res) != tupdesc->natts)
                           ereport(ERROR,
                                           (errcode(ERRCODE_DATATYPE_MISMATCH),
                                   errmsg("remote query result rowtype does not match "
                                                   "the specified FROM clause rowtype")));
   
                   /* fast track when no results */
                   if (funcctx->max_calls < 1)
                   {
                           if (res)
                                   PQclear(res);
                           SRF_RETURN_DONE(funcctx);
                   }
   
                 /* store needed metadata for subsequent calls */                  /* store needed metadata for subsequent calls */
                 attinmeta = TupleDescGetAttInMetadata(tupdesc);                  attinmeta = TupleDescGetAttInMetadata(tupdesc);
                 funcctx->attinmeta = attinmeta;                  funcctx->attinmeta = attinmeta;
Line 749  dblink_record(PG_FUNCTION_ARGS) Line 768  dblink_record(PG_FUNCTION_ARGS)
                 if (freeconn)                  if (freeconn)
                         PQfinish(conn);                          PQfinish(conn);
   
                 /* fast track when no results */  
                 if (funcctx->max_calls < 1)  
                 {  
                         if (res)  
                                 PQclear(res);  
                         SRF_RETURN_DONE(funcctx);  
                 }  
   
                 /* check typtype to see if we have a predetermined return type */                  /* check typtype to see if we have a predetermined return type */
                 functypeid = get_func_rettype(funcid);                  functypeid = get_func_rettype(funcid);
                 functyptype = get_typtype(functypeid);                  functyptype = get_typtype(functypeid);
Line 782  dblink_record(PG_FUNCTION_ARGS) Line 793  dblink_record(PG_FUNCTION_ARGS)
                                 elog(ERROR, "return type must be a row type");                                  elog(ERROR, "return type must be a row type");
                 }                  }
   
                   /* check result and tuple descriptor have the same number of columns */
                   if (PQnfields(res) != tupdesc->natts)
                           ereport(ERROR,
                                           (errcode(ERRCODE_DATATYPE_MISMATCH),
                                   errmsg("remote query result rowtype does not match "
                                                   "the specified FROM clause rowtype")));
   
                   /* fast track when no results */
                   if (funcctx->max_calls < 1)
                   {
                           if (res)
                                   PQclear(res);
                           SRF_RETURN_DONE(funcctx);
                   }
   
                 /* store needed metadata for subsequent calls */                  /* store needed metadata for subsequent calls */
                 attinmeta = TupleDescGetAttInMetadata(tupdesc);                  attinmeta = TupleDescGetAttInMetadata(tupdesc);
                 funcctx->attinmeta = attinmeta;                  funcctx->attinmeta = attinmeta;
Line 961  Datum Line 987  Datum
 dblink_get_pkey(PG_FUNCTION_ARGS)  dblink_get_pkey(PG_FUNCTION_ARGS)
 {  {
         int16           numatts;          int16           numatts;
         Oid                     relid;  
         char      **results;          char      **results;
         FuncCallContext *funcctx;          FuncCallContext *funcctx;
         int32           call_cntr;          int32           call_cntr;
Line 972  dblink_get_pkey(PG_FUNCTION_ARGS) Line 997  dblink_get_pkey(PG_FUNCTION_ARGS)
         /* stuff done only on the first call of the function */          /* stuff done only on the first call of the function */
         if (SRF_IS_FIRSTCALL())          if (SRF_IS_FIRSTCALL())
         {          {
                 TupleDesc       tupdesc = NULL;                  Relation        rel;
                   TupleDesc       tupdesc;
   
                 /* create a function context for cross-call persistence */                  /* create a function context for cross-call persistence */
                 funcctx = SRF_FIRSTCALL_INIT();                  funcctx = SRF_FIRSTCALL_INIT();
Line 983  dblink_get_pkey(PG_FUNCTION_ARGS) Line 1009  dblink_get_pkey(PG_FUNCTION_ARGS)
                  */                   */
                 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);                  oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
   
                 /* convert relname to rel Oid */                  /* open target relation */
                 relid = get_relid_from_relname(PG_GETARG_TEXT_P(0));                  rel = get_rel_from_relname(PG_GETARG_TEXT_P(0), AccessShareLock, ACL_SELECT);
                 if (!OidIsValid(relid))  
                         ereport(ERROR,                  /* get the array of attnums */
                                         (errcode(ERRCODE_UNDEFINED_TABLE),                  results = get_pkey_attnames(rel, &numatts);
                                          errmsg("relation \"%s\" does not exist",  
                                                         GET_STR(PG_GETARG_TEXT_P(0)))));                  relation_close(rel, AccessShareLock);
   
                 /*                  /*
                  * need a tuple descriptor representing one INT and one TEXT                   * need a tuple descriptor representing one INT and one TEXT
Line 1008  dblink_get_pkey(PG_FUNCTION_ARGS) Line 1034  dblink_get_pkey(PG_FUNCTION_ARGS)
                 attinmeta = TupleDescGetAttInMetadata(tupdesc);                  attinmeta = TupleDescGetAttInMetadata(tupdesc);
                 funcctx->attinmeta = attinmeta;                  funcctx->attinmeta = attinmeta;
   
                 /* get an array of attnums */  
                 results = get_pkey_attnames(relid, &numatts);  
   
                 if ((results != NULL) && (numatts > 0))                  if ((results != NULL) && (numatts > 0))
                 {                  {
                         funcctx->max_calls = numatts;                          funcctx->max_calls = numatts;
Line 1066  dblink_get_pkey(PG_FUNCTION_ARGS) Line 1089  dblink_get_pkey(PG_FUNCTION_ARGS)
 }  }
   
   
 #ifndef SHRT_MAX  
 #define SHRT_MAX (0x7FFF)  
 #endif  
 /*  /*
  * dblink_build_sql_insert   * dblink_build_sql_insert
  *   *
Line 1092  PG_FUNCTION_INFO_V1(dblink_build_sql_ins Line 1112  PG_FUNCTION_INFO_V1(dblink_build_sql_ins
 Datum  Datum
 dblink_build_sql_insert(PG_FUNCTION_ARGS)  dblink_build_sql_insert(PG_FUNCTION_ARGS)
 {  {
         Oid                     relid;          Relation        rel;
         text       *relname_text;          text       *relname_text;
         int16      *pkattnums;          int16      *pkattnums_arg;
         int                     pknumatts_tmp;          int32           pknumatts_arg;
         int16           pknumatts = 0;          int                *pkattnums;
           int                     pknumatts;
         char      **src_pkattvals;          char      **src_pkattvals;
         char      **tgt_pkattvals;          char      **tgt_pkattvals;
         ArrayType  *src_pkattvals_arry;          ArrayType  *src_pkattvals_arry;
Line 1117  dblink_build_sql_insert(PG_FUNCTION_ARGS Line 1138  dblink_build_sql_insert(PG_FUNCTION_ARGS
         relname_text = PG_GETARG_TEXT_P(0);          relname_text = PG_GETARG_TEXT_P(0);
   
         /*          /*
          * Convert relname to rel OID.           * Open target relation.
          */           */
         relid = get_relid_from_relname(relname_text);          rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT);
         if (!OidIsValid(relid))  
                 ereport(ERROR,  
                                 (errcode(ERRCODE_UNDEFINED_TABLE),  
                                  errmsg("relation \"%s\" does not exist",  
                                                 GET_STR(relname_text))));  
   
         pkattnums = (int16 *) PG_GETARG_POINTER(1);  
         pknumatts_tmp = PG_GETARG_INT32(2);  
         if (pknumatts_tmp <= SHRT_MAX)  
                 pknumatts = pknumatts_tmp;  
         else  
                 ereport(ERROR,  
                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),  
                                  errmsg("input for number of primary key " \  
                                                 "attributes too large")));  
   
         /*          /*
          * There should be at least one key attribute           * Process pkattnums argument.
          */           */
         if (pknumatts == 0)          pkattnums_arg = (int16 *) PG_GETARG_POINTER(1);
                 ereport(ERROR,          pknumatts_arg = PG_GETARG_INT32(2);
                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),          validate_pkattnums(rel, pkattnums_arg, pknumatts_arg,
                                  errmsg("number of key attributes must be > 0")));                                             &pkattnums, &pknumatts);
   
         src_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);          src_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);
         tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(4);          tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(4);
Line 1216  dblink_build_sql_insert(PG_FUNCTION_ARGS Line 1222  dblink_build_sql_insert(PG_FUNCTION_ARGS
         /*          /*
          * Prep work is finally done. Go get the SQL string.           * Prep work is finally done. Go get the SQL string.
          */           */
         sql = get_sql_insert(relid, pkattnums, pknumatts, src_pkattvals, tgt_pkattvals);          sql = get_sql_insert(rel, pkattnums, pknumatts, src_pkattvals, tgt_pkattvals);
   
           /*
            * Now we can close the relation.
            */
           relation_close(rel, AccessShareLock);
   
         /*          /*
          * And send it           * And send it
Line 1244  PG_FUNCTION_INFO_V1(dblink_build_sql_del Line 1255  PG_FUNCTION_INFO_V1(dblink_build_sql_del
 Datum  Datum
 dblink_build_sql_delete(PG_FUNCTION_ARGS)  dblink_build_sql_delete(PG_FUNCTION_ARGS)
 {  {
         Oid                     relid;          Relation        rel;
         text       *relname_text;          text       *relname_text;
         int16      *pkattnums;          int16      *pkattnums_arg;
         int                     pknumatts_tmp;          int32           pknumatts_arg;
         int16           pknumatts = 0;          int                *pkattnums;
           int                     pknumatts;
         char      **tgt_pkattvals;          char      **tgt_pkattvals;
         ArrayType  *tgt_pkattvals_arry;          ArrayType  *tgt_pkattvals_arry;
         int                     tgt_ndim;          int                     tgt_ndim;
Line 1264  dblink_build_sql_delete(PG_FUNCTION_ARGS Line 1276  dblink_build_sql_delete(PG_FUNCTION_ARGS
         relname_text = PG_GETARG_TEXT_P(0);          relname_text = PG_GETARG_TEXT_P(0);
   
         /*          /*
          * Convert relname to rel OID.           * Open target relation.
          */           */
         relid = get_relid_from_relname(relname_text);          rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT);
         if (!OidIsValid(relid))  
                 ereport(ERROR,  
                                 (errcode(ERRCODE_UNDEFINED_TABLE),  
                                  errmsg("relation \"%s\" does not exist",  
                                                 GET_STR(relname_text))));  
   
         pkattnums = (int16 *) PG_GETARG_POINTER(1);  
         pknumatts_tmp = PG_GETARG_INT32(2);  
         if (pknumatts_tmp <= SHRT_MAX)  
                 pknumatts = pknumatts_tmp;  
         else  
                 ereport(ERROR,  
                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),  
                                  errmsg("input for number of primary key " \  
                                                 "attributes too large")));  
   
         /*          /*
          * There should be at least one key attribute           * Process pkattnums argument.
          */           */
         if (pknumatts == 0)          pkattnums_arg = (int16 *) PG_GETARG_POINTER(1);
                 ereport(ERROR,          pknumatts_arg = PG_GETARG_INT32(2);
                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),          validate_pkattnums(rel, pkattnums_arg, pknumatts_arg,
                                  errmsg("number of key attributes must be > 0")));                                             &pkattnums, &pknumatts);
   
         tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);          tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);
   
Line 1329  dblink_build_sql_delete(PG_FUNCTION_ARGS Line 1326  dblink_build_sql_delete(PG_FUNCTION_ARGS
         /*          /*
          * Prep work is finally done. Go get the SQL string.           * Prep work is finally done. Go get the SQL string.
          */           */
         sql = get_sql_delete(relid, pkattnums, pknumatts, tgt_pkattvals);          sql = get_sql_delete(rel, pkattnums, pknumatts, tgt_pkattvals);
   
           /*
            * Now we can close the relation.
            */
           relation_close(rel, AccessShareLock);
   
         /*          /*
          * And send it           * And send it
Line 1361  PG_FUNCTION_INFO_V1(dblink_build_sql_upd Line 1363  PG_FUNCTION_INFO_V1(dblink_build_sql_upd
 Datum  Datum
 dblink_build_sql_update(PG_FUNCTION_ARGS)  dblink_build_sql_update(PG_FUNCTION_ARGS)
 {  {
         Oid                     relid;          Relation        rel;
         text       *relname_text;          text       *relname_text;
         int16      *pkattnums;          int16      *pkattnums_arg;
         int                     pknumatts_tmp;          int32           pknumatts_arg;
         int16           pknumatts = 0;          int                *pkattnums;
           int                     pknumatts;
         char      **src_pkattvals;          char      **src_pkattvals;
         char      **tgt_pkattvals;          char      **tgt_pkattvals;
         ArrayType  *src_pkattvals_arry;          ArrayType  *src_pkattvals_arry;
Line 1386  dblink_build_sql_update(PG_FUNCTION_ARGS Line 1389  dblink_build_sql_update(PG_FUNCTION_ARGS
         relname_text = PG_GETARG_TEXT_P(0);          relname_text = PG_GETARG_TEXT_P(0);
   
         /*          /*
          * Convert relname to rel OID.           * Open target relation.
          */           */
         relid = get_relid_from_relname(relname_text);          rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT);
         if (!OidIsValid(relid))  
                 ereport(ERROR,  
                                 (errcode(ERRCODE_UNDEFINED_TABLE),  
                                  errmsg("relation \"%s\" does not exist",  
                                                 GET_STR(relname_text))));  
   
         pkattnums = (int16 *) PG_GETARG_POINTER(1);  
         pknumatts_tmp = PG_GETARG_INT32(2);  
         if (pknumatts_tmp <= SHRT_MAX)  
                 pknumatts = pknumatts_tmp;  
         else  
                 ereport(ERROR,  
                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),  
                                  errmsg("input for number of primary key " \  
                                                 "attributes too large")));  
   
         /*          /*
          * There should be one source array key values for each key attnum           * Process pkattnums argument.
          */           */
         if (pknumatts == 0)          pkattnums_arg = (int16 *) PG_GETARG_POINTER(1);
                 ereport(ERROR,          pknumatts_arg = PG_GETARG_INT32(2);
                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),          validate_pkattnums(rel, pkattnums_arg, pknumatts_arg,
                                  errmsg("number of key attributes must be > 0")));                                             &pkattnums, &pknumatts);
   
         src_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);          src_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);
         tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(4);          tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(4);
Line 1485  dblink_build_sql_update(PG_FUNCTION_ARGS Line 1473  dblink_build_sql_update(PG_FUNCTION_ARGS
         /*          /*
          * Prep work is finally done. Go get the SQL string.           * Prep work is finally done. Go get the SQL string.
          */           */
         sql = get_sql_update(relid, pkattnums, pknumatts, src_pkattvals, tgt_pkattvals);          sql = get_sql_update(rel, pkattnums, pknumatts, src_pkattvals, tgt_pkattvals);
   
           /*
            * Now we can close the relation.
            */
           relation_close(rel, AccessShareLock);
   
         /*          /*
          * And send it           * And send it
Line 1519  dblink_current_query(PG_FUNCTION_ARGS) Line 1512  dblink_current_query(PG_FUNCTION_ARGS)
  * Return NULL, and set numatts = 0, if no primary key exists.   * Return NULL, and set numatts = 0, if no primary key exists.
  */   */
 static char **  static char **
 get_pkey_attnames(Oid relid, int16 *numatts)  get_pkey_attnames(Relation rel, int16 *numatts)
 {  {
         Relation        indexRelation;          Relation        indexRelation;
         ScanKeyData entry;          ScanKeyData entry;
Line 1527  get_pkey_attnames(Oid relid, int16 *numa Line 1520  get_pkey_attnames(Oid relid, int16 *numa
         HeapTuple       indexTuple;          HeapTuple       indexTuple;
         int                     i;          int                     i;
         char      **result = NULL;          char      **result = NULL;
         Relation        rel;  
         TupleDesc       tupdesc;          TupleDesc       tupdesc;
   
         /* open relation using relid, get tupdesc */  
         rel = relation_open(relid, AccessShareLock);  
         tupdesc = rel->rd_att;  
   
         /* initialize numatts to 0 in case no primary key exists */          /* initialize numatts to 0 in case no primary key exists */
         *numatts = 0;          *numatts = 0;
   
           tupdesc = rel->rd_att;
   
         /* use relid to get all related indexes */          /* use relid to get all related indexes */
         indexRelation = heap_openr(IndexRelationName, AccessShareLock);          indexRelation = heap_openr(IndexRelationName, AccessShareLock);
         ScanKeyInit(&entry,          ScanKeyInit(&entry,
                                 Anum_pg_index_indrelid,                                  Anum_pg_index_indrelid,
                                 BTEqualStrategyNumber, F_OIDEQ,                                  BTEqualStrategyNumber, F_OIDEQ,
                                 ObjectIdGetDatum(relid));                                  ObjectIdGetDatum(RelationGetRelid(rel)));
         scan = heap_beginscan(indexRelation, SnapshotNow, 1, &entry);          scan = heap_beginscan(indexRelation, SnapshotNow, 1, &entry);
   
         while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)          while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
Line 1568  get_pkey_attnames(Oid relid, int16 *numa Line 1558  get_pkey_attnames(Oid relid, int16 *numa
         }          }
         heap_endscan(scan);          heap_endscan(scan);
         heap_close(indexRelation, AccessShareLock);          heap_close(indexRelation, AccessShareLock);
         relation_close(rel, AccessShareLock);  
   
         return result;          return result;
 }  }
   
 static char *  static char *
 get_sql_insert(Oid relid, int16 *pkattnums, int16 pknumatts, char **src_pkattvals, char **tgt_pkattvals)  get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals)
 {  {
         Relation        rel;  
         char       *relname;          char       *relname;
         HeapTuple       tuple;          HeapTuple       tuple;
         TupleDesc       tupdesc;          TupleDesc       tupdesc;
Line 1584  get_sql_insert(Oid relid, int16 *pkattnu Line 1572  get_sql_insert(Oid relid, int16 *pkattnu
         StringInfo      str = makeStringInfo();          StringInfo      str = makeStringInfo();
         char       *sql;          char       *sql;
         char       *val;          char       *val;
         int16           key;          int                     key;
         int                     i;          int                     i;
         bool            needComma;          bool            needComma;
   
         /* get relation name including any needed schema prefix and quoting */          /* get relation name including any needed schema prefix and quoting */
         relname = generate_relation_name(relid);          relname = generate_relation_name(rel);
   
         /*  
          * Open relation using relid  
          */  
         rel = relation_open(relid, AccessShareLock);  
         tupdesc = rel->rd_att;          tupdesc = rel->rd_att;
         natts = tupdesc->natts;          natts = tupdesc->natts;
   
         tuple = get_tuple_of_interest(relid, pkattnums, pknumatts, src_pkattvals);          tuple = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals);
         if (!tuple)          if (!tuple)
                 ereport(ERROR,                  ereport(ERROR,
                                 (errcode(ERRCODE_CARDINALITY_VIOLATION),                                  (errcode(ERRCODE_CARDINALITY_VIOLATION),
Line 1635  get_sql_insert(Oid relid, int16 *pkattnu Line 1619  get_sql_insert(Oid relid, int16 *pkattnu
                         appendStringInfo(str, ",");                          appendStringInfo(str, ",");
   
                 if (tgt_pkattvals != NULL)                  if (tgt_pkattvals != NULL)
                         key = get_attnum_pk_pos(pkattnums, pknumatts, i + 1);                          key = get_attnum_pk_pos(pkattnums, pknumatts, i);
                 else                  else
                         key = -1;                          key = -1;
   
Line 1658  get_sql_insert(Oid relid, int16 *pkattnu Line 1642  get_sql_insert(Oid relid, int16 *pkattnu
         sql = pstrdup(str->data);          sql = pstrdup(str->data);
         pfree(str->data);          pfree(str->data);
         pfree(str);          pfree(str);
         relation_close(rel, AccessShareLock);  
   
         return (sql);          return (sql);
 }  }
   
 static char *  static char *
 get_sql_delete(Oid relid, int16 *pkattnums, int16 pknumatts, char **tgt_pkattvals)  get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals)
 {  {
         Relation        rel;  
         char       *relname;          char       *relname;
         TupleDesc       tupdesc;          TupleDesc       tupdesc;
         int                     natts;          int                     natts;
Line 1676  get_sql_delete(Oid relid, int16 *pkattnu Line 1658  get_sql_delete(Oid relid, int16 *pkattnu
         int                     i;          int                     i;
   
         /* get relation name including any needed schema prefix and quoting */          /* get relation name including any needed schema prefix and quoting */
         relname = generate_relation_name(relid);          relname = generate_relation_name(rel);
   
         /*  
          * Open relation using relid  
          */  
         rel = relation_open(relid, AccessShareLock);  
         tupdesc = rel->rd_att;          tupdesc = rel->rd_att;
         natts = tupdesc->natts;          natts = tupdesc->natts;
   
         appendStringInfo(str, "DELETE FROM %s WHERE ", relname);          appendStringInfo(str, "DELETE FROM %s WHERE ", relname);
         for (i = 0; i < pknumatts; i++)          for (i = 0; i < pknumatts; i++)
         {          {
                 int16           pkattnum = pkattnums[i];                  int                     pkattnum = pkattnums[i];
   
                 if (i > 0)                  if (i > 0)
                         appendStringInfo(str, " AND ");                          appendStringInfo(str, " AND ");
   
                 appendStringInfo(str, "%s",                  appendStringInfo(str, "%s",
                 quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum - 1]->attname)));                  quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
   
                 if (tgt_pkattvals != NULL)                  if (tgt_pkattvals != NULL)
                         val = pstrdup(tgt_pkattvals[i]);                          val = pstrdup(tgt_pkattvals[i]);
Line 1714  get_sql_delete(Oid relid, int16 *pkattnu Line 1692  get_sql_delete(Oid relid, int16 *pkattnu
         sql = pstrdup(str->data);          sql = pstrdup(str->data);
         pfree(str->data);          pfree(str->data);
         pfree(str);          pfree(str);
         relation_close(rel, AccessShareLock);  
   
         return (sql);          return (sql);
 }  }
   
 static char *  static char *
 get_sql_update(Oid relid, int16 *pkattnums, int16 pknumatts, char **src_pkattvals, char **tgt_pkattvals)  get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals)
 {  {
         Relation        rel;  
         char       *relname;          char       *relname;
         HeapTuple       tuple;          HeapTuple       tuple;
         TupleDesc       tupdesc;          TupleDesc       tupdesc;
Line 1730  get_sql_update(Oid relid, int16 *pkattnu Line 1706  get_sql_update(Oid relid, int16 *pkattnu
         StringInfo      str = makeStringInfo();          StringInfo      str = makeStringInfo();
         char       *sql;          char       *sql;
         char       *val;          char       *val;
         int16           key;          int                     key;
         int                     i;          int                     i;
         bool            needComma;          bool            needComma;
   
         /* get relation name including any needed schema prefix and quoting */          /* get relation name including any needed schema prefix and quoting */
         relname = generate_relation_name(relid);          relname = generate_relation_name(rel);
   
         /*  
          * Open relation using relid  
          */  
         rel = relation_open(relid, AccessShareLock);  
         tupdesc = rel->rd_att;          tupdesc = rel->rd_att;
         natts = tupdesc->natts;          natts = tupdesc->natts;
   
         tuple = get_tuple_of_interest(relid, pkattnums, pknumatts, src_pkattvals);          tuple = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals);
         if (!tuple)          if (!tuple)
                 ereport(ERROR,                  ereport(ERROR,
                                 (errcode(ERRCODE_CARDINALITY_VIOLATION),                                  (errcode(ERRCODE_CARDINALITY_VIOLATION),
Line 1765  get_sql_update(Oid relid, int16 *pkattnu Line 1737  get_sql_update(Oid relid, int16 *pkattnu
                                   quote_ident_cstr(NameStr(tupdesc->attrs[i]->attname)));                                    quote_ident_cstr(NameStr(tupdesc->attrs[i]->attname)));
   
                 if (tgt_pkattvals != NULL)                  if (tgt_pkattvals != NULL)
                         key = get_attnum_pk_pos(pkattnums, pknumatts, i + 1);                          key = get_attnum_pk_pos(pkattnums, pknumatts, i);
                 else                  else
                         key = -1;                          key = -1;
   
Line 1788  get_sql_update(Oid relid, int16 *pkattnu Line 1760  get_sql_update(Oid relid, int16 *pkattnu
   
         for (i = 0; i < pknumatts; i++)          for (i = 0; i < pknumatts; i++)
         {          {
                 int16           pkattnum = pkattnums[i];                  int                     pkattnum = pkattnums[i];
   
                 if (i > 0)                  if (i > 0)
                         appendStringInfo(str, " AND ");                          appendStringInfo(str, " AND ");
   
                 appendStringInfo(str, "%s",                  appendStringInfo(str, "%s",
                 quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum - 1]->attname)));                  quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
   
                 if (tgt_pkattvals != NULL)                  if (tgt_pkattvals != NULL)
                         val = pstrdup(tgt_pkattvals[i]);                          val = pstrdup(tgt_pkattvals[i]);
                 else                  else
                         val = SPI_getvalue(tuple, tupdesc, pkattnum);                          val = SPI_getvalue(tuple, tupdesc, pkattnum + 1);
   
                 if (val != NULL)                  if (val != NULL)
                 {                  {
Line 1813  get_sql_update(Oid relid, int16 *pkattnu Line 1785  get_sql_update(Oid relid, int16 *pkattnu
         sql = pstrdup(str->data);          sql = pstrdup(str->data);
         pfree(str->data);          pfree(str->data);
         pfree(str);          pfree(str);
         relation_close(rel, AccessShareLock);  
   
         return (sql);          return (sql);
 }  }
Line 1854  quote_ident_cstr(char *rawstr) Line 1825  quote_ident_cstr(char *rawstr)
         return result;          return result;
 }  }
   
 static int16  static int
 get_attnum_pk_pos(int16 *pkattnums, int16 pknumatts, int16 key)  get_attnum_pk_pos(int *pkattnums, int pknumatts, int key)
 {  {
         int                     i;          int                     i;
   
Line 1870  get_attnum_pk_pos(int16 *pkattnums, int1 Line 1841  get_attnum_pk_pos(int16 *pkattnums, int1
 }  }
   
 static HeapTuple  static HeapTuple
 get_tuple_of_interest(Oid relid, int16 *pkattnums, int16 pknumatts, char **src_pkattvals)  get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals)
 {  {
         Relation        rel;  
         char       *relname;          char       *relname;
         TupleDesc       tupdesc;          TupleDesc       tupdesc;
         StringInfo      str = makeStringInfo();          StringInfo      str = makeStringInfo();
Line 1883  get_tuple_of_interest(Oid relid, int16 * Line 1853  get_tuple_of_interest(Oid relid, int16 *
         char       *val = NULL;          char       *val = NULL;
   
         /* get relation name including any needed schema prefix and quoting */          /* get relation name including any needed schema prefix and quoting */
         relname = generate_relation_name(relid);          relname = generate_relation_name(rel);
   
         /*          tupdesc = rel->rd_att;
          * Open relation using relid  
          */  
         rel = relation_open(relid, AccessShareLock);  
         tupdesc = CreateTupleDescCopy(rel->rd_att);  
         relation_close(rel, AccessShareLock);  
   
         /*          /*
          * Connect to SPI manager           * Connect to SPI manager
Line 1907  get_tuple_of_interest(Oid relid, int16 * Line 1872  get_tuple_of_interest(Oid relid, int16 *
   
         for (i = 0; i < pknumatts; i++)          for (i = 0; i < pknumatts; i++)
         {          {
                 int16           pkattnum = pkattnums[i];                  int                     pkattnum = pkattnums[i];
   
                 if (i > 0)                  if (i > 0)
                         appendStringInfo(str, " AND ");                          appendStringInfo(str, " AND ");
   
                 appendStringInfo(str, "%s",                  appendStringInfo(str, "%s",
                 quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum - 1]->attname)));                  quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
   
                 val = pstrdup(src_pkattvals[i]);                  val = pstrdup(src_pkattvals[i]);
                 if (val != NULL)                  if (val != NULL)
Line 1968  get_tuple_of_interest(Oid relid, int16 * Line 1933  get_tuple_of_interest(Oid relid, int16 *
         return NULL;          return NULL;
 }  }
   
 static Oid  /*
 get_relid_from_relname(text *relname_text)   * Open the relation named by relname_text, acquire specified type of lock,
    * verify we have specified permissions.
    * Caller must close rel when done with it.
    */
   static Relation
   get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode)
 {  {
         RangeVar   *relvar;          RangeVar   *relvar;
         Relation        rel;          Relation        rel;
         Oid                     relid;          AclResult       aclresult;
   
         relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text, "get_relid_from_relname"));          relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text, "get_rel_from_relname"));
         rel = heap_openrv(relvar, AccessShareLock);          rel = heap_openrv(relvar, lockmode);
         relid = RelationGetRelid(rel);  
         relation_close(rel, AccessShareLock);          aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
                                                                     aclmode);
           if (aclresult != ACLCHECK_OK)
                   aclcheck_error(aclresult, ACL_KIND_CLASS,
                                              RelationGetRelationName(rel));
   
         return relid;          return rel;
 }  }
   
 /*  /*
  * generate_relation_name - copied from ruleutils.c   * generate_relation_name - copied from ruleutils.c
  *              Compute the name to display for a relation specified by OID   *              Compute the name to display for a relation
  *   *
  * The result includes all necessary quoting and schema-prefixing.   * The result includes all necessary quoting and schema-prefixing.
  */   */
 static char *  static char *
 generate_relation_name(Oid relid)  generate_relation_name(Relation rel)
 {  {
         HeapTuple       tp;  
         Form_pg_class reltup;  
         char       *nspname;          char       *nspname;
         char       *result;          char       *result;
   
         tp = SearchSysCache(RELOID,  
                                                 ObjectIdGetDatum(relid),  
                                                 0, 0, 0);  
         if (!HeapTupleIsValid(tp))  
                 elog(ERROR, "cache lookup failed for relation %u", relid);  
   
         reltup = (Form_pg_class) GETSTRUCT(tp);  
   
         /* Qualify the name if not visible in search path */          /* Qualify the name if not visible in search path */
         if (RelationIsVisible(relid))          if (RelationIsVisible(RelationGetRelid(rel)))
                 nspname = NULL;                  nspname = NULL;
         else          else
                 nspname = get_namespace_name(reltup->relnamespace);                  nspname = get_namespace_name(rel->rd_rel->relnamespace);
   
         result = quote_qualified_identifier(nspname, NameStr(reltup->relname));  
   
         ReleaseSysCache(tp);          result = quote_qualified_identifier(nspname, RelationGetRelationName(rel));
   
         return result;          return result;
 }  }
Line 2023  static remoteConn * Line 1985  static remoteConn *
 getConnectionByName(const char *name)  getConnectionByName(const char *name)
 {  {
         remoteConnHashEnt *hentry;          remoteConnHashEnt *hentry;
         char            key[NAMEDATALEN];          char       *key;
   
         if (!remoteConnHash)          if (!remoteConnHash)
                 remoteConnHash = createConnHash();                  remoteConnHash = createConnHash();
   
         MemSet(key, 0, NAMEDATALEN);          key = pstrdup(name);
         snprintf(key, NAMEDATALEN - 1, "%s", name);          truncate_identifier(key, strlen(key), true);
         hentry = (remoteConnHashEnt *) hash_search(remoteConnHash,          hentry = (remoteConnHashEnt *) hash_search(remoteConnHash,
                                                                                            key, HASH_FIND, NULL);                                                                                             key, HASH_FIND, NULL);
   
Line 2055  createNewConnection(const char *name, re Line 2017  createNewConnection(const char *name, re
 {  {
         remoteConnHashEnt *hentry;          remoteConnHashEnt *hentry;
         bool            found;          bool            found;
         char            key[NAMEDATALEN];          char       *key;
   
         if (!remoteConnHash)          if (!remoteConnHash)
                 remoteConnHash = createConnHash();                  remoteConnHash = createConnHash();
   
         MemSet(key, 0, NAMEDATALEN);          key = pstrdup(name);
         snprintf(key, NAMEDATALEN - 1, "%s", name);          truncate_identifier(key, strlen(key), true);
         hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, key,          hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, key,
                                                                                            HASH_ENTER, &found);                                                                                             HASH_ENTER, &found);
   
Line 2071  createNewConnection(const char *name, re Line 2033  createNewConnection(const char *name, re
                                  errmsg("out of memory")));                                   errmsg("out of memory")));
   
         if (found)          if (found)
           {
                   PQfinish(con->con);
                   pfree(con);
   
                 ereport(ERROR,                  ereport(ERROR,
                                 (errcode(ERRCODE_DUPLICATE_OBJECT),                                  (errcode(ERRCODE_DUPLICATE_OBJECT),
                                  errmsg("duplicate connection name")));                                   errmsg("duplicate connection name")));
           }
   
         hentry->rcon = con;          hentry->rcon = con;
         strncpy(hentry->name, name, NAMEDATALEN - 1);          strncpy(hentry->name, name, NAMEDATALEN - 1);
Line 2084  deleteConnection(const char *name) Line 2051  deleteConnection(const char *name)
 {  {
         remoteConnHashEnt *hentry;          remoteConnHashEnt *hentry;
         bool            found;          bool            found;
         char            key[NAMEDATALEN];          char       *key;
   
         if (!remoteConnHash)          if (!remoteConnHash)
                 remoteConnHash = createConnHash();                  remoteConnHash = createConnHash();
   
         MemSet(key, 0, NAMEDATALEN);          key = pstrdup(name);
         snprintf(key, NAMEDATALEN - 1, "%s", name);          truncate_identifier(key, strlen(key), true);
   
         hentry = (remoteConnHashEnt *) hash_search(remoteConnHash,          hentry = (remoteConnHashEnt *) hash_search(remoteConnHash,
                                                                                            key, HASH_REMOVE, &found);                                                                                             key, HASH_REMOVE, &found);
   
Line 2101  deleteConnection(const char *name) Line 2067  deleteConnection(const char *name)
                                  errmsg("undefined connection name")));                                   errmsg("undefined connection name")));
   
 }  }
   
   /*
    * Modified version of conninfo_parse() from fe-connect.c
    * Used to remove any password from the connection string
    * in order to test whether the server auth method will
    * require it.
    */
   static char *
   connstr_strip_password(const char *connstr)
   {
           char               *pname;
           char               *pval;
           char               *buf;
           char               *cp;
           char               *cp2;
           StringInfoData  result;
   
           /* initialize return value */
           initStringInfo(&result);
   
           /* Need a modifiable copy of the input string */
           buf = pstrdup(connstr);
           cp = buf;
   
           while (*cp)
           {
                   /* Skip blanks before the parameter name */
                   if (isspace((unsigned char) *cp))
                   {
                           cp++;
                           continue;
                   }
   
                   /* Get the parameter name */
                   pname = cp;
                   while (*cp)
                   {
                           if (*cp == '=')
                                   break;
                           if (isspace((unsigned char) *cp))
                           {
                                   *cp++ = '\0';
                                   while (*cp)
                                   {
                                           if (!isspace((unsigned char) *cp))
                                                   break;
                                           cp++;
                                   }
                                   break;
                           }
                           cp++;
                   }
   
                   /* Check that there is a following '=' */
                   if (*cp != '=')
                           ereport(ERROR,
                                           (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                           errmsg("missing \"=\" after \"%s\" in connection string", pname)));
                   *cp++ = '\0';
   
                   /* Skip blanks after the '=' */
                   while (*cp)
                   {
                           if (!isspace((unsigned char) *cp))
                                   break;
                           cp++;
                   }
   
                   /* Get the parameter value */
                   pval = cp;
   
                   if (*cp != '\'')
                   {
                           cp2 = pval;
                           while (*cp)
                           {
                                   if (isspace((unsigned char) *cp))
                                   {
                                           *cp++ = '\0';
                                           break;
                                   }
                                   if (*cp == '\\')
                                   {
                                           cp++;
                                           if (*cp != '\0')
                                                   *cp2++ = *cp++;
                                   }
                                   else
                                           *cp2++ = *cp++;
                           }
                           *cp2 = '\0';
                   }
                   else
                   {
                           cp2 = pval;
                           cp++;
                           for (;;)
                           {
                                   if (*cp == '\0')
                                           ereport(ERROR,
                                                           (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                                           errmsg("unterminated quoted string in connection string")));
                                   if (*cp == '\\')
                                   {
                                           cp++;
                                           if (*cp != '\0')
                                                   *cp2++ = *cp++;
                                           continue;
                                   }
                                   if (*cp == '\'')
                                   {
                                           *cp2 = '\0';
                                           cp++;
                                           break;
                                   }
                                   *cp2++ = *cp++;
                           }
                   }
   
                   /*
                    * Now we have the name and the value. If it is not a password,
                    * append to the return connstr.
                    */
                   if (strcmp("password", pname) != 0)
                           /* append the value */
                           appendStringInfo(&result, " %s='%s'", pname, pval);
           }
   
           return result.data;
   }
   
   static void
   dblink_security_check(PGconn *conn, remoteConn *rcon, const char *connstr)
   {
           if (!superuser())
           {
                   /* this attempt must fail */
                   conn = PQconnectdb(connstr_strip_password(connstr));
   
                   if (PQstatus(conn) == CONNECTION_OK)
                   {
                           PQfinish(conn);
                           if (rcon)
                                   pfree(rcon);
   
                           ereport(ERROR,
                                           (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
                                            errmsg("password is required"),
                                            errdetail("Non-superuser cannot connect if the server does not request a password."),
                                            errhint("Target server's authentication method must be changed.")));
                   }
                   else
                           PQfinish(conn);
           }
   }
   
   /*
    * Validate the PK-attnums argument for dblink_build_sql_insert() and related
    * functions, and translate to the internal representation.
    *
    * The user supplies an int2vector of 1-based physical attnums, plus a count
    * argument (the need for the separate count argument is historical, but we
    * still check it).  We check that each attnum corresponds to a valid,
    * non-dropped attribute of the rel.  We do *not* prevent attnums from being
    * listed twice, though the actual use-case for such things is dubious.
    *
    * The internal representation is a palloc'd int array of 0-based physical
    * attnums.
    */
   static void
   validate_pkattnums(Relation rel,
                                      int16 *pkattnums_arg, int32 pknumatts_arg,
                                      int **pkattnums, int *pknumatts)
   {
           TupleDesc       tupdesc = rel->rd_att;
           int                     natts = tupdesc->natts;
           int                     i;
   
           /* Must have at least one pk attnum selected */
           if (pknumatts_arg <= 0)
                   ereport(ERROR,
                                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                    errmsg("number of key attributes must be > 0")));
   
           /* Allocate output array */
           *pkattnums = (int *) palloc(pknumatts_arg * sizeof(int));
           *pknumatts = pknumatts_arg;
   
           /* Validate attnums and convert to internal form */
           for (i = 0; i < pknumatts_arg; i++)
           {
                   int             pkattnum = pkattnums_arg[i];
   
                   if (pkattnum <= 0 || pkattnum > natts ||
                           tupdesc->attrs[pkattnum - 1]->attisdropped)
                           ereport(ERROR,
                                           (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                            errmsg("invalid attribute number %d", pkattnum)));
                   (*pkattnums)[i] = pkattnum - 1;
           }
   }

Removed from v.1.38  
changed lines
  Added in v.1.38.4.9


PostgreSQL CVSweb <[email protected]>