Next version of JABA
[jabaws.git] / binaries / src / fasta34 / pgsql_lib.c
1
2 /* pgsql_lib.c copyright (c) 2004 William R. Pearson */
3
4 /* $Name: fa_34_26_5 $ - $Id: pgsql_lib.c,v 1.3 2006/04/12 18:00:02 wrp Exp $ */
5
6 /* functions for opening, reading, seeking a pgsql database */
7
8 /*
9   For the moment, this interface assumes that the file to be searched will
10   be specified in a single, long, string with 4 parts:
11
12   (1) a database open string. This string has four fields, separated by
13       whitespace (' \t'):
14         hostname:port dbname user password
15
16    '--' dashes at the beginning of lines are ignored -
17    thus the first line could be:
18    -- hostname:port dbname user password
19
20   (2) a database query string that will return an unique ID (not
21       necessarily numberic, but it must be < 12 characters as libstr[12]
22       is used) and a sequence string
23
24   (2a) a series of pgsql commands that do not generate results
25        starting with 'DO', followed by a select() statement.
26
27   (3) a database select string that will return a description
28       given a unique ID
29
30   (4) a database select string that well return a sequence given a
31       unique ID
32
33    Lines (3) and (4) are not required for pv34comp* libraries, but
34    line (2) must generate a complete description as well as a sequence.
35
36
37    18-July-2001
38    Additional syntax has been added to support multiline SQL queries.
39
40    If the host line begins with '+', then the SQL is openned on the same
41    connection as the previous SQL file.
42
43    If the host line contains '-' just before the terminal ';', then
44    the file will not produce any output.
45
46    This string can contain "\n". ";" are used to separate the four
47    functions, which must be specified in the order shown above.
48    The last (fourth) query must terminate with a ';'
49
50    19-July-2004
51
52    This file is designed for PostgreSQL, which uses a different syntax
53    for getting rows of data.  Specifically, a select statement must be
54    associated with a "cursor", so that one can fetch a single row.
55
56    This can be simply done with the statment:
57
58    DECLARE next_seq CURSOR FOR "select statement ..."
59
60    The need for a CURSOR complicates the getlib()/ranlib() design, which
61    assumes that ranlib() can set something up that getlib() can read.
62    This can be avoided by setting up an otherwise unnecessary cursor for
63    the ranlib statement that gets a sequence.
64
65 */
66
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #include <ctype.h>
71
72 #include <libpq-fe.h>
73 #define PGSQL_LIB 17
74
75 #include "defs.h"
76 #include "mm_file.h"
77
78 #define XTERNAL
79 #include "uascii.h"
80 #define EOSEQ 0
81 /* #include "upam.h" */
82
83 #ifdef SUPERFAMNUM
84 int sfnum[10], nsfnum;
85 #endif
86
87 int pgsql_getlib(unsigned char *, int, char *, int, fseek_t *, int *, struct lmf_str *, long *);
88 void pgsql_ranlib(char *, int, fseek_t, char *, struct lmf_str *m_fd);
89
90 #define PGSQL_BUF 4096
91
92 struct lmf_str *
93 pgsql_openlib(char *sname, int ldnaseq, int *sascii) {
94   FILE *sql_file;
95   PGconn *conn;
96   PGresult *res;
97   char *tmp_str, *ttmp_str;
98   int tmp_str_len;
99   char *bp, *bps, *bdp, *tp, tchar;
100   int i, qs_len, qqs_len;
101   char *sql_db, *sql_host, *sql_dbname, *sql_user, *sql_pass;
102   char *sql_port;
103   char *sql_do;
104   int sql_do_cnt;
105   struct lmf_str *m_fptr;
106
107   /*  if (sql_reopen) return NULL; - should not be called for re-open */
108
109   tmp_str_len = PGSQL_BUF;
110   if ((tmp_str=(char *)calloc(tmp_str_len,sizeof(char)))==NULL) {
111     fprintf(stderr,"cannot allocate %d for pgSQL buffer\n",tmp_str_len);
112     return NULL;
113   }
114
115   if (sname[0] == '%') {
116     strncpy(tmp_str,sname+1,tmp_str_len);
117     tmp_str[sizeof(tmp_str)-1]='\0';
118   }
119   else {
120     if ((sql_file=fopen(sname,"r"))==NULL) {
121       fprintf(stderr," cannot open pgSQL file: %s\n",sname);
122       return NULL;
123     }
124
125     if ((qs_len=fread(tmp_str,sizeof(char),tmp_str_len-1,sql_file))<=0) {
126       fprintf(stderr," cannot read pgSQL file: %s\n",sname);
127       return NULL;
128     }
129     else  {
130       tmp_str[qs_len]='\0';
131       qqs_len = qs_len;
132       while (qqs_len >= tmp_str_len-1) {
133         tmp_str_len += PGSQL_BUF;
134         if ((tmp_str=(char *)realloc(tmp_str,tmp_str_len))==NULL) {
135           fprintf(stderr,
136                   " cannot reallocate %d for pgSQL buffer\n",tmp_str_len);
137           return NULL;
138         }
139         ttmp_str = &tmp_str[qqs_len];
140         if ((qs_len=fread(ttmp_str,sizeof(char),PGSQL_BUF,sql_file))<0) {
141           fprintf(stderr," cannot read pgSQL file: %s\n",sname);
142           return NULL;
143         }
144         ttmp_str[qs_len]='\0';
145         qqs_len += qs_len;
146       }
147     }
148     fclose(sql_file);
149   }
150
151   bps = tmp_str;
152   if ((bp=strchr(bps,';'))!=NULL) {
153     *bp='\0';
154     if ((sql_db=calloc(strlen(bps)+1,sizeof(char)))==NULL) {
155       fprintf(stderr, " cannot allocate space for database name [%d], %s\n",
156               strlen(bps),bps);
157       return NULL;
158     }
159     /* have database name, parse the fields */
160     else {
161       strcpy(sql_db,bps);       /* strcpy OK because allocated strlen(bps) */
162       bps = bp+1;       /* points to next char after ';' */
163       while (isspace(*bps)) bps++;
164       *bp=';'; /* replace ; */
165       bp = sql_db;
166       while (*bp=='-') {*bp++ = ' ';}
167       sql_host = strtok(bp," \t\n");
168       if (sql_host[0]=='@') sql_host="";
169       sql_dbname = strtok(NULL," \t\n");
170       sql_user = strtok(NULL," \t\n");
171       if (sql_user[0]=='@') sql_user="";
172       sql_pass = strtok(NULL," \t\n");
173       if (sql_pass[0]=='@') sql_pass="";
174       if ((tp=strchr(sql_host,':'))!=NULL) {
175         sql_port = tp+1;
176         *tp='\0';
177       }
178       else sql_port = "";
179     }
180   }
181   else {
182     fprintf(stderr," cannot find database fields:\n%s\n",tmp_str);
183     return NULL;
184   }
185
186   /* we have all the info we need to open a database, allocate lmf_str */
187   if ((m_fptr = (struct lmf_str *)calloc(1,sizeof(struct lmf_str)))==NULL) {
188     fprintf(stderr," cannot allocate lmf_str (%ld) for %s\n",
189             sizeof(struct lmf_str),sname);
190     return NULL;
191   }
192
193   /* have our struct, initialize it */
194
195   strncpy(m_fptr->lb_name,sname,MAX_FN);
196   m_fptr->lb_name[MAX_FN-1]='\0';
197
198   m_fptr->sascii = sascii;
199
200   m_fptr->sql_db = sql_db;
201   m_fptr->getlib = pgsql_getlib;
202   m_fptr->ranlib = pgsql_ranlib;
203   m_fptr->mm_flg = 0;
204   m_fptr->sql_reopen = 0;
205   m_fptr->lb_type = PGSQL_LIB;
206
207   /* now open the database, if necessary */
208   conn = PQsetdbLogin(sql_host,
209                       sql_port,
210                       NULL,
211                       NULL,
212                       sql_dbname,
213                       sql_user,
214                       sql_pass);
215
216   if (PQstatus(conn) != CONNECTION_OK)     {
217     fprintf(stderr, "Connection to database '%s' failed.\n", PQdb(conn));
218     fprintf(stderr, "%s", PQerrorMessage(conn));
219     PQfinish(conn);
220     goto error_r;
221   }
222   else {
223     m_fptr->pgsql_conn = conn;
224     fprintf(stderr," Database %s opened on %s\n",sql_dbname,sql_host);
225   }
226
227   /* check for 'DO' command - copy to 'DO' string */
228   while (*bps == '-') { *bps++=' ';}
229   if (isspace(bps[-1]) && toupper(bps[0])=='D' &&
230       toupper(bps[1])=='O' && isspace(bps[2])) {
231     /* have some 'DO' commands */
232     /* check where the end of the last DO statement is */
233
234     sql_do_cnt = 1;     /* count up the number of 'DO' statements for later */
235     bdp=bps+3;
236     while ((bp=strchr(bdp,';'))!=NULL) {
237       tp = bp+2; /* skip ;\n */
238       while (isspace(*tp) || *tp == '-') {*tp++ = ' ';}
239       if (toupper(*tp)=='D' && toupper(tp[1])=='O' && isspace(tp[2])) {
240         sql_do_cnt++;           /* count the DO statements */
241         bdp = tp+3;             /* move to the next DO statement */
242       }
243       else break;
244     }
245     if (bp != NULL) {   /* end of the last DO, begin of select */
246       tchar = *(bp+1);
247       *(bp+1)='\0';             /* terminate DO strings */
248       if ((sql_do = calloc(strlen(bps)+1, sizeof(char)))==NULL) {
249         fprintf(stderr," cannot allocate %d for sql_do\n",strlen(bps));
250         goto error_r;
251       }
252       else {
253         strcpy(sql_do,bps);
254         *(bp+1)=tchar;  /* replace missing ';' */
255       }
256       bps = bp+1;
257       while (isspace(*bps)) bps++;
258     }
259     else {
260       fprintf(stderr," terminal ';' not found: %s\n",bps);
261       goto error_r;
262     }
263     /* all the DO commands are in m_fptr->sql_do in the form: 
264      DO command1; DO command2; DO command3; */
265     bdp = sql_do;
266     while (sql_do_cnt-- && (bp=strchr(bdp,';'))!=NULL) {
267       /* do the pgsql statement on bdp+3 */
268       /* check for error */
269       *bp='\0';
270       res = PQexec(m_fptr->pgsql_conn,bdp+3);
271       if (PQresultStatus(res) != PGRES_COMMAND_OK) {
272         fprintf(stderr,"*** Error %s - query failed:\n%s\n",
273                 PQerrorMessage(m_fptr->pgsql_conn), bdp+3);
274         PQclear(res);
275         goto error_r;
276       }
277       PQclear(res);
278
279       *bp=';';
280       bdp = bp+1;
281       while (isspace(*bdp)) bdp++;
282     }
283   }
284
285   /* copy 1st query field */
286   if ((bp=strchr(bps,';'))!=NULL) {
287     *bp='\0';
288     if ((m_fptr->sql_query=calloc(strlen(bps)+41,sizeof(char)))==NULL) {
289       fprintf(stderr, " cannot allocate space for query string [%d], %s\n",
290               strlen(bps),bps);
291       goto error_r;
292     }
293     /* have query, copy it */
294     else {
295       strncpy(m_fptr->sql_query,"DECLARE next_seq CURSOR FOR ",40);
296       strcat(m_fptr->sql_query,bps);
297       *bp=';'; /* replace ; */
298       bps = bp+1;
299       while(isspace(*bps)) bps++;
300     }
301   }
302   else {
303     fprintf(stderr," cannot find database query field:\n%s\n",tmp_str);
304     goto error_r;
305   }
306
307   /* copy get_desc field */
308   if ((bp=strchr(bps,';'))!=NULL) {
309     *bp='\0';
310     if ((m_fptr->sql_getdesc=calloc(strlen(bps)+1,sizeof(char)))==NULL) {
311       fprintf(stderr, " cannot allocate space for database name [%d], %s\n",
312               strlen(bps),bps);
313       goto error_r;
314     }
315     /* have get_desc, copy it */
316     else {
317       strcpy(m_fptr->sql_getdesc,bps);
318       *bp=';'; /* replace ; */
319       bps = bp+1;
320       while(isspace(*bps)) bps++;
321     }
322   }
323   else {
324     fprintf(stderr," cannot find getdesc field:\n%s\n",tmp_str);
325     goto error_r;
326   }
327
328   if ((bp=strchr(bps,';'))!=NULL) { *bp='\0';}
329
330   if ((m_fptr->sql_getseq=calloc(strlen(bps)+1,sizeof(char)))==NULL) {
331     fprintf(stderr, " cannot allocate space for database name [%d], %s\n",
332             strlen(bps),bps);
333     goto error_r;
334   }
335
336   if (strlen(bps) > 0) {
337     strcpy(m_fptr->sql_getseq,bps);
338   }
339   else {
340     fprintf(stderr," cannot find getseq field:\n%s\n",tmp_str);
341     return NULL;
342   }
343   if (bp!=NULL) *bp=';';
344
345   /* now do the fetch */
346
347   res = PQexec(m_fptr->pgsql_conn,"BEGIN;");
348   if (PQresultStatus(res) != PGRES_COMMAND_OK) {
349     fprintf(stderr,"*** Error %s - BEGIN failed:\n",
350             PQerrorMessage(conn));
351     PQclear(res);
352     goto error_r;
353   }
354   PQclear(res);
355
356   res = PQexec(m_fptr->pgsql_conn, m_fptr->sql_query);
357   if (PQresultStatus(res) != PGRES_COMMAND_OK) {
358     fprintf(stderr,"*** Error %d:%s - query failed:\n%s\n",
359             PQresultStatus(res),PQerrorMessage(conn), m_fptr->sql_query);
360     PQclear(res);
361     goto error_r;
362   }
363   PQclear(res);
364   m_fptr->pgsql_res=NULL;
365
366   return m_fptr;
367
368  error_r:
369   free(m_fptr->sql_getseq);
370   free(m_fptr->sql_getdesc);
371   free(m_fptr->sql_query);
372   free(m_fptr);
373   free(sql_db);
374   return NULL;
375 }
376
377 struct lmf_str *
378 pgsql_reopen(struct lmf_str *m_fptr) {
379   m_fptr->sql_reopen = 1;
380   return m_fptr;
381 }
382
383 void
384 pgsql_closelib(struct lmf_str *m_fptr) {
385
386   if (m_fptr == NULL) return;
387   if (m_fptr->pgsql_res != NULL) PQclear(m_fptr->pgsql_res);
388   PQfinish(m_fptr->pgsql_conn);
389   m_fptr->sql_reopen=0;
390 }
391
392 /*
393 static char *sql_seq = NULL, *sql_seqp;
394 static int sql_seq_len;
395 */
396
397 int
398 pgsql_getlib( unsigned char *seq,
399               int maxs,
400               char *libstr,
401               int n_libstr,
402               fseek_t *libpos,
403               int *lcont,
404               struct lmf_str *lm_fd,
405               long *l_off)
406 {
407   register unsigned char *cp, *seqp;
408   register int *ap;
409   unsigned char *seqm, *seqm1;
410   PGresult *res;
411
412   char *bp;
413   /*   int l_start, l_stop, len; */
414
415   seqp = seq;
416   seqm = &seq[maxs-9];
417   seqm1 = seqm-1;
418
419   ap = lm_fd->sascii;
420
421 #ifdef SUPERFAMNUM
422   sfnum[0]=nsfnum = 0;
423 #endif
424
425   if (*lcont==0) {
426     /* get a row, with UID, sequence */
427     *l_off = 1;
428
429     /* check to see if we already have a valid result */
430     if (lm_fd->pgsql_res==NULL) {
431       res = PQexec(lm_fd->pgsql_conn,"FETCH next_seq");
432       if (PQresultStatus(res) != PGRES_TUPLES_OK) {
433         fprintf(stderr,"*** Error %s - getlib FETCH failed:\n%s\n",
434                 PQerrorMessage(lm_fd->pgsql_conn), lm_fd->sql_query);
435         PQclear(res);
436         lm_fd->pgsql_res = NULL;
437         *lcont = 0;
438         *seqp = EOSEQ;
439         return -1;
440       }
441     }
442     else {res = lm_fd->pgsql_res;}
443
444     if (PQntuples(res)>0) {
445       lm_fd->pgsql_res = res;
446       *libpos=(fseek_t)atol(PQgetvalue(res,0,0));
447         
448       *l_off = 1;
449       if (PQnfields(res) > 2 && (bp=strchr(PQgetvalue(res,0,2),'@'))!=NULL &&
450           !strncmp(bp+1,"C:",2)) sscanf(bp+3,"%ld",l_off);
451
452       lm_fd->sql_seqp = PQgetvalue(res,0,1);
453     
454       /* because of changes in pgsql_ranlib(), it is essential that
455          libstr return the unique identifier; thus we must use
456          sql_row[0], not sql_row[2]. Using libstr as the UID allows
457          one to use any UID, not just numeric ones.  *libpos is not
458          used for pgsql libraries.
459       */
460
461       if (n_libstr <= MAX_UID) {
462         /* the normal case returns only GID/sequence */
463         strncpy(libstr,PQgetvalue(res,0,0),MAX_UID-1);
464         libstr[MAX_UID-1]='\0';
465       }
466       else {
467         /* here we do not use the UID in libstr, because we are not
468            going back into the db */
469         /* the PVM case also returns a long description */
470         if (PQnfields(res)>2) {
471           strncpy(libstr,PQgetvalue(res,0,2),n_libstr-1);
472         }
473         else {
474           strncpy(libstr,PQgetvalue(res,0,0),n_libstr-1);
475         }
476         libstr[n_libstr-1]='\0';
477       }
478     }
479     else {
480       PQclear(lm_fd->pgsql_res);
481       lm_fd->pgsql_res=NULL;
482       *lcont = 0;
483       *seqp = EOSEQ;
484       return -1;
485     }
486   }
487
488   for (cp=(unsigned char *)lm_fd->sql_seqp; seqp<seqm1 && *cp; ) {
489     if ((*seqp++=ap[*cp++])<NA &&
490         (*seqp++=ap[*cp++])<NA &&
491         (*seqp++=ap[*cp++])<NA &&
492         (*seqp++=ap[*cp++])<NA &&
493         (*seqp++=ap[*cp++])<NA &&
494         (*seqp++=ap[*cp++])<NA &&
495         (*seqp++=ap[*cp++])<NA &&
496         (*seqp++=ap[*cp++])<NA &&
497         (*seqp++=ap[*cp++])<NA &&
498         (*seqp++=ap[*cp++])<NA) continue;
499     --seqp;
500     if (*(cp-1)==0) break;
501   }
502   lm_fd->sql_seqp = (char *)cp;
503
504   if (seqp>=seqm1) (*lcont)++;
505   else {
506     *lcont=0;
507     PQclear(lm_fd->pgsql_res);
508     lm_fd->pgsql_res = NULL;
509   }
510
511   *seqp = EOSEQ;
512   /*   if ((int)(seqp-seq)==0) return 1; */
513   return (int)(seqp-seq);
514 }
515
516 void
517 pgsql_ranlib(char *str,
518              int cnt,
519              fseek_t libpos,
520              char *libstr,
521              struct lmf_str *lm_fd
522              )
523 {
524   char tmp_query[1024], tmp_val[20];
525   PGresult *res;
526   char *bp;
527
528   str[0]='\0';
529
530   /* put the UID into the query string - cannot use sprintf because of
531      "%' etc */
532
533   /*   sprintf(tmp_query,lm_fd->sql_getdesc,libpos); */
534
535   if ((bp=strchr(lm_fd->sql_getdesc,'#'))==NULL) {
536     fprintf(stderr, "no KEY position in %s\n",lm_fd->sql_getdesc);
537     goto next1;
538   }
539   else {
540     *bp = '\0';
541     strncpy(tmp_query,lm_fd->sql_getdesc,sizeof(tmp_query));
542     tmp_query[sizeof(tmp_query)-1]='\0';
543     /*    sprintf(tmp_val,"%ld",(long)libpos); */
544     strncat(tmp_query,libstr,sizeof(tmp_query)-1);
545     strncat(tmp_query,bp+1,sizeof(tmp_query)-1);
546     *bp='#';
547     lm_fd->lpos = libpos;
548   }
549
550   /*  fprintf(stderr," requesting: %s\n",tmp_query); */
551
552   if (lm_fd->pgsql_res !=NULL) {
553     PQclear(lm_fd->pgsql_res);
554     lm_fd->pgsql_res = NULL;
555   }
556
557   res = PQexec(lm_fd->pgsql_conn,tmp_query);
558   if (PQresultStatus(res) != PGRES_TUPLES_OK) {
559     lm_fd->pgsql_res = NULL;
560
561     sprintf(str,"gi|%ld ***Error - query failed***",(long)libpos);
562     fprintf(stderr,"*** Error %s - ranlib DESC failed:\n%s\n",
563             PQerrorMessage(lm_fd->pgsql_conn), tmp_query);
564     PQclear(res);
565     goto next1;
566   }
567
568   if (PQntuples(res)<=0) {
569 /*     fprintf(stderr,"*** Error = use result failed\n%s\n", 
570            pgsql_error(lm_fd->pgsql_conn)); */
571     sprintf(str,"gi|%ld ***use result failed***",(long)libpos);
572     goto next0;
573   }
574
575   if (PQgetvalue(res,0,1)!= NULL) strncpy(str,PQgetvalue(res,0,1),cnt-1);
576   else strncpy(str,PQgetvalue(res,0,0),cnt-1);
577   str[cnt-1]='\0';
578   /* change this later to support multiple row returns */
579   /*
580   while (strlen(str) < cnt-1 &&
581          (lm_fd->sql_row = pgsql_fetch_row(lm_fd->pgsql_res))!=NULL) {
582     strncat(str," ",cnt-2-strlen(str));
583     if (lm_fd->sql_row[1]!=NULL) 
584       strncat(str,lm_fd->sql_row[1],cnt-2-strlen(str));
585     else break;
586   }
587   */
588
589   str[cnt-1]='\0';
590   if ((bp = strchr(str,'\r'))!=NULL) *bp='\0';
591   if ((bp = strchr(str,'\n'))!=NULL) *bp='\0';
592
593  next0:
594   PQclear(res);
595  next1: 
596   lm_fd->pgsql_res = NULL;
597
598   /* get the sequence, set up for pgsql_getseq() */
599   /* put the UID into the query string */
600
601   if ((bp=strchr(lm_fd->sql_getseq,'#'))==NULL) {
602     fprintf(stderr, "no GID position in %s\n",lm_fd->sql_getseq);
603     return;
604   }
605   else {
606     *bp = '\0';
607     strncpy(tmp_query,lm_fd->sql_getseq,sizeof(tmp_query));
608     tmp_query[sizeof(tmp_query)-1]='\0';
609     /*    sprintf(tmp_val,"%ld",(long)libpos); */
610     strncat(tmp_query,libstr,sizeof(tmp_query));
611     strncat(tmp_query,bp+1,sizeof(tmp_query));
612     *bp='#';
613   }
614
615   res = PQexec(lm_fd->pgsql_conn,tmp_query);
616   if (PQresultStatus(res) != PGRES_TUPLES_OK) {
617     PQclear(res);
618     lm_fd->pgsql_res = NULL;
619     fprintf(stderr,"*** Error - ranlib SEQ failed:\n%s\n%s\n",tmp_query,
620             PQerrorMessage(lm_fd->pgsql_conn));
621     exit(1);
622   }
623   else {
624     lm_fd->pgsql_res = res;
625   }
626 }