]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_pgsql.cpp
Remove InspIRCd* parameters and fields
[user/henk/code/inspircd.git] / src / modules / extra / m_pgsql.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include <cstdlib>
16 #include <sstream>
17 #include <libpq-fe.h>
18 #include "m_sqlv2.h"
19
20 /* $ModDesc: PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API */
21 /* $CompileFlags: -Iexec("pg_config --includedir") eval("my $s = `pg_config --version`;$s =~ /^.*?(\d+)\.(\d+)\.(\d+).*?$/;my $v = hex(sprintf("0x%02x%02x%02x", $1, $2, $3));print "-DPGSQL_HAS_ESCAPECONN" if(($v >= 0x080104) || ($v >= 0x07030F && $v < 0x070400) || ($v >= 0x07040D && $v < 0x080000) || ($v >= 0x080008 && $v < 0x080100));") */
22 /* $LinkerFlags: -Lexec("pg_config --libdir") -lpq */
23 /* $ModDep: m_sqlv2.h */
24
25
26 /* SQLConn rewritten by peavey to
27  * use EventHandler instead of
28  * BufferedSocket. This is much neater
29  * and gives total control of destroy
30  * and delete of resources.
31  */
32
33 /* Forward declare, so we can have the typedef neatly at the top */
34 class SQLConn;
35
36 typedef std::map<std::string, SQLConn*> ConnMap;
37
38 /* CREAD,       Connecting and wants read event
39  * CWRITE,      Connecting and wants write event
40  * WREAD,       Connected/Working and wants read event
41  * WWRITE,      Connected/Working and wants write event
42  * RREAD,       Resetting and wants read event
43  * RWRITE,      Resetting and wants write event
44  */
45 enum SQLstatus { CREAD, CWRITE, WREAD, WWRITE, RREAD, RWRITE };
46
47 unsigned long count(const char * const str, char a)
48 {
49         unsigned long n = 0;
50         for (const char *p = str; *p; ++p)
51         {
52                 if (*p == '?')
53                         ++n;
54         }
55         return n;
56 }
57
58 /** SQLhost::GetDSN() - Overload to return correct DSN for PostgreSQL
59  */
60 std::string SQLhost::GetDSN()
61 {
62         std::ostringstream conninfo("connect_timeout = '5'");
63
64         if (host.length())
65                 conninfo << " host = '" << host << "'";
66
67         if (port)
68                 conninfo << " port = '" << port << "'";
69
70         if (name.length())
71                 conninfo << " dbname = '" << name << "'";
72
73         if (user.length())
74                 conninfo << " user = '" << user << "'";
75
76         if (pass.length())
77                 conninfo << " password = '" << pass << "'";
78
79         if (ssl)
80         {
81                 conninfo << " sslmode = 'require'";
82         }
83         else
84         {
85                 conninfo << " sslmode = 'disable'";
86         }
87
88         return conninfo.str();
89 }
90
91 class ReconnectTimer : public Timer
92 {
93  private:
94         Module* mod;
95  public:
96         ReconnectTimer(Module* m)
97         : Timer(5, SI->Time(), false), mod(m)
98         {
99         }
100         virtual void Tick(time_t TIME);
101 };
102
103
104 /** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult.
105  * All SQL providers must create their own subclass and define it's methods using that
106  * database library's data retriveal functions. The aim is to avoid a slow and inefficient process
107  * of converting all data to a common format before it reaches the result structure. This way
108  * data is passes to the module nearly as directly as if it was using the API directly itself.
109  */
110
111 class PgSQLresult : public SQLresult
112 {
113         PGresult* res;
114         int currentrow;
115         int rows;
116         int cols;
117
118         SQLfieldList* fieldlist;
119         SQLfieldMap* fieldmap;
120 public:
121         PgSQLresult(Module* self, Module* to, unsigned long rid, PGresult* result)
122         : SQLresult(self, to, rid), res(result), currentrow(0), fieldlist(NULL), fieldmap(NULL)
123         {
124                 rows = PQntuples(res);
125                 cols = PQnfields(res);
126         }
127
128         ~PgSQLresult()
129         {
130                 /* If we allocated these, free them... */
131                 if(fieldlist)
132                         delete fieldlist;
133
134                 if(fieldmap)
135                         delete fieldmap;
136
137                 PQclear(res);
138         }
139
140         virtual int Rows()
141         {
142                 if(!cols && !rows)
143                 {
144                         return atoi(PQcmdTuples(res));
145                 }
146                 else
147                 {
148                         return rows;
149                 }
150         }
151
152         virtual int Cols()
153         {
154                 return PQnfields(res);
155         }
156
157         virtual std::string ColName(int column)
158         {
159                 char* name = PQfname(res, column);
160
161                 return (name) ? name : "";
162         }
163
164         virtual int ColNum(const std::string &column)
165         {
166                 int n = PQfnumber(res, column.c_str());
167
168                 if(n == -1)
169                 {
170                         throw SQLbadColName();
171                 }
172                 else
173                 {
174                         return n;
175                 }
176         }
177
178         virtual SQLfield GetValue(int row, int column)
179         {
180                 char* v = PQgetvalue(res, row, column);
181
182                 if(v)
183                 {
184                         return SQLfield(std::string(v, PQgetlength(res, row, column)), PQgetisnull(res, row, column));
185                 }
186                 else
187                 {
188                         throw SQLbadColName();
189                 }
190         }
191
192         virtual SQLfieldList& GetRow()
193         {
194                 /* In an effort to reduce overhead we don't actually allocate the list
195                  * until the first time it's needed...so...
196                  */
197                 if(fieldlist)
198                 {
199                         fieldlist->clear();
200                 }
201                 else
202                 {
203                         fieldlist = new SQLfieldList;
204                 }
205
206                 if(currentrow < PQntuples(res))
207                 {
208                         int ncols = PQnfields(res);
209
210                         for(int i = 0; i < ncols; i++)
211                         {
212                                 fieldlist->push_back(GetValue(currentrow, i));
213                         }
214
215                         currentrow++;
216                 }
217
218                 return *fieldlist;
219         }
220
221         virtual SQLfieldMap& GetRowMap()
222         {
223                 /* In an effort to reduce overhead we don't actually allocate the map
224                  * until the first time it's needed...so...
225                  */
226                 if(fieldmap)
227                 {
228                         fieldmap->clear();
229                 }
230                 else
231                 {
232                         fieldmap = new SQLfieldMap;
233                 }
234
235                 if(currentrow < PQntuples(res))
236                 {
237                         int ncols = PQnfields(res);
238
239                         for(int i = 0; i < ncols; i++)
240                         {
241                                 fieldmap->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
242                         }
243
244                         currentrow++;
245                 }
246
247                 return *fieldmap;
248         }
249
250         virtual SQLfieldList* GetRowPtr()
251         {
252                 SQLfieldList* fl = new SQLfieldList;
253
254                 if(currentrow < PQntuples(res))
255                 {
256                         int ncols = PQnfields(res);
257
258                         for(int i = 0; i < ncols; i++)
259                         {
260                                 fl->push_back(GetValue(currentrow, i));
261                         }
262
263                         currentrow++;
264                 }
265
266                 return fl;
267         }
268
269         virtual SQLfieldMap* GetRowMapPtr()
270         {
271                 SQLfieldMap* fm = new SQLfieldMap;
272
273                 if(currentrow < PQntuples(res))
274                 {
275                         int ncols = PQnfields(res);
276
277                         for(int i = 0; i < ncols; i++)
278                         {
279                                 fm->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
280                         }
281
282                         currentrow++;
283                 }
284
285                 return fm;
286         }
287
288         virtual void Free(SQLfieldMap* fm)
289         {
290                 delete fm;
291         }
292
293         virtual void Free(SQLfieldList* fl)
294         {
295                 delete fl;
296         }
297 };
298
299 /** SQLConn represents one SQL session.
300  */
301 class SQLConn : public EventHandler
302 {
303  private:
304         SQLhost                 confhost;       /* The <database> entry */
305         Module*                 us;                     /* Pointer to the SQL provider itself */
306         PGconn*                 sql;            /* PgSQL database connection handle */
307         SQLstatus               status;         /* PgSQL database connection status */
308         bool                    qinprog;        /* If there is currently a query in progress */
309         QueryQueue              queue;          /* Queue of queries waiting to be executed on this connection */
310         time_t                  idle;           /* Time we last heard from the database */
311
312  public:
313         SQLConn(Module* self, const SQLhost& hi)
314         : EventHandler(), confhost(hi), us(self), sql(NULL), status(CWRITE), qinprog(false)
315         {
316                 idle = this->ServerInstance->Time();
317                 if(!DoConnect())
318                 {
319                         ServerInstance->Logs->Log("m_pgsql",DEFAULT, "WARNING: Could not connect to database with id: " + ConvToStr(hi.id));
320                         DelayReconnect();
321                 }
322         }
323
324         ~SQLConn()
325         {
326                 Close();
327         }
328
329         virtual void HandleEvent(EventType et, int errornum)
330         {
331                 switch (et)
332                 {
333                         case EVENT_READ:
334                                 OnDataReady();
335                         break;
336
337                         case EVENT_WRITE:
338                                 OnWriteReady();
339                         break;
340
341                         case EVENT_ERROR:
342                                 DelayReconnect();
343                         break;
344
345                         default:
346                         break;
347                 }
348         }
349
350         bool DoConnect()
351         {
352                 if(!(sql = PQconnectStart(confhost.GetDSN().c_str())))
353                         return false;
354
355                 if(PQstatus(sql) == CONNECTION_BAD)
356                         return false;
357
358                 if(PQsetnonblocking(sql, 1) == -1)
359                         return false;
360
361                 /* OK, we've initalised the connection, now to get it hooked into the socket engine
362                 * and then start polling it.
363                 */
364                 this->fd = PQsocket(sql);
365
366                 if(this->fd <= -1)
367                         return false;
368
369                 if (!this->ServerInstance->SE->AddFd(this))
370                 {
371                         ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Couldn't add pgsql socket to socket engine");
372                         return false;
373                 }
374
375                 /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
376                 return DoPoll();
377         }
378
379         bool DoPoll()
380         {
381                 switch(PQconnectPoll(sql))
382                 {
383                         case PGRES_POLLING_WRITING:
384                                 ServerInstance->SE->WantWrite(this);
385                                 status = CWRITE;
386                                 return true;
387                         case PGRES_POLLING_READING:
388                                 status = CREAD;
389                                 return true;
390                         case PGRES_POLLING_FAILED:
391                                 return false;
392                         case PGRES_POLLING_OK:
393                                 status = WWRITE;
394                                 return DoConnectedPoll();
395                         default:
396                                 return true;
397                 }
398         }
399
400         bool DoConnectedPoll()
401         {
402                 if(!qinprog && queue.totalsize())
403                 {
404                         /* There's no query currently in progress, and there's queries in the queue. */
405                         SQLrequest& query = queue.front();
406                         DoQuery(query);
407                 }
408
409                 if(PQconsumeInput(sql))
410                 {
411                         /* We just read stuff from the server, that counts as it being alive
412                          * so update the idle-since time :p
413                          */
414                         idle = this->ServerInstance->Time();
415
416                         if (PQisBusy(sql))
417                         {
418                                 /* Nothing happens here */
419                         }
420                         else if (qinprog)
421                         {
422                                 /* Grab the request we're processing */
423                                 SQLrequest& query = queue.front();
424
425                                 /* Get a pointer to the module we're about to return the result to */
426                                 Module* to = query.GetSource();
427
428                                 /* Fetch the result.. */
429                                 PGresult* result = PQgetResult(sql);
430
431                                 /* PgSQL would allow a query string to be sent which has multiple
432                                  * queries in it, this isn't portable across database backends and
433                                  * we don't want modules doing it. But just in case we make sure we
434                                  * drain any results there are and just use the last one.
435                                  * If the module devs are behaving there will only be one result.
436                                  */
437                                 while (PGresult* temp = PQgetResult(sql))
438                                 {
439                                         PQclear(result);
440                                         result = temp;
441                                 }
442
443                                 if(to)
444                                 {
445                                         /* ..and the result */
446                                         PgSQLresult reply(us, to, query.id, result);
447
448                                         /* Fix by brain, make sure the original query gets sent back in the reply */
449                                         reply.query = query.query.q;
450
451                                         switch(PQresultStatus(result))
452                                         {
453                                                 case PGRES_EMPTY_QUERY:
454                                                 case PGRES_BAD_RESPONSE:
455                                                 case PGRES_FATAL_ERROR:
456                                                         reply.error.Id(SQL_QREPLY_FAIL);
457                                                         reply.error.Str(PQresultErrorMessage(result));
458                                                 default:
459                                                         ;
460                                                         /* No action, other values are not errors */
461                                         }
462
463                                         reply.Send();
464
465                                         /* PgSQLresult's destructor will free the PGresult */
466                                 }
467                                 else
468                                 {
469                                         /* If the client module is unloaded partway through a query then the provider will set
470                                          * the pointer to NULL. We cannot just cancel the query as the result will still come
471                                          * through at some point...and it could get messy if we play with invalid pointers...
472                                          */
473                                         PQclear(result);
474                                 }
475                                 qinprog = false;
476                                 queue.pop();
477                                 DoConnectedPoll();
478                         }
479                         return true;
480                 }
481                 else
482                 {
483                         /* I think we'll assume this means the server died...it might not,
484                          * but I think that any error serious enough we actually get here
485                          * deserves to reconnect [/excuse]
486                          * Returning true so the core doesn't try and close the connection.
487                          */
488                         DelayReconnect();
489                         return true;
490                 }
491         }
492
493         bool DoResetPoll()
494         {
495                 switch(PQresetPoll(sql))
496                 {
497                         case PGRES_POLLING_WRITING:
498                                 ServerInstance->SE->WantWrite(this);
499                                 status = CWRITE;
500                                 return DoPoll();
501                         case PGRES_POLLING_READING:
502                                 status = CREAD;
503                                 return true;
504                         case PGRES_POLLING_FAILED:
505                                 return false;
506                         case PGRES_POLLING_OK:
507                                 status = WWRITE;
508                                 return DoConnectedPoll();
509                         default:
510                                 return true;
511                 }
512         }
513
514         bool OnDataReady()
515         {
516                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
517                 return DoEvent();
518         }
519
520         bool OnWriteReady()
521         {
522                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
523                 return DoEvent();
524         }
525
526         bool OnConnected()
527         {
528                 return DoEvent();
529         }
530
531         void DelayReconnect();
532
533         bool DoEvent()
534         {
535                 bool ret;
536
537                 if((status == CREAD) || (status == CWRITE))
538                 {
539                         ret = DoPoll();
540                 }
541                 else if((status == RREAD) || (status == RWRITE))
542                 {
543                         ret = DoResetPoll();
544                 }
545                 else
546                 {
547                         ret = DoConnectedPoll();
548                 }
549                 return ret;
550         }
551
552         SQLerror DoQuery(SQLrequest &req)
553         {
554                 if((status == WREAD) || (status == WWRITE))
555                 {
556                         if(!qinprog)
557                         {
558                                 /* Parse the command string and dispatch it */
559
560                                 /* Pointer to the buffer we screw around with substitution in */
561                                 char* query;
562                                 /* Pointer to the current end of query, where we append new stuff */
563                                 char* queryend;
564
565                                 /* Total length of the unescaped parameters */
566                                 unsigned long maxparamlen, paramcount;
567
568                                 /* The length of the longest parameter */
569                                 maxparamlen = 0;
570
571                                 for(ParamL::iterator i = req.query.p.begin(); i != req.query.p.end(); i++)
572                                 {
573                                         if (i->size() > maxparamlen)
574                                                 maxparamlen = i->size();
575                                 }
576
577                                 /* How many params are there in the query? */
578                                 paramcount = count(req.query.q.c_str(), '?');
579
580                                 /* This stores copy of params to be inserted with using numbered params 1;3B*/
581                                 ParamL paramscopy(req.query.p);
582
583                                 /* To avoid a lot of allocations, allocate enough memory for the biggest the escaped query could possibly be.
584                                  * sizeofquery + (maxtotalparamlength*2) + 1
585                                  *
586                                  * The +1 is for null-terminating the string for PQsendQuery()
587                                  */
588
589                                 query = new char[req.query.q.length() + (maxparamlen*paramcount*2) + 1];
590                                 queryend = query;
591
592                                 /* Okay, now we have a buffer large enough we need to start copying the query into it and escaping and substituting
593                                  * the parameters into it...
594                                  */
595
596                                 for(unsigned int i = 0; i < req.query.q.length(); i++)
597                                 {
598                                         if(req.query.q[i] == '?')
599                                         {
600                                                 /* We found a place to substitute..what fun.
601                                                  * Use the PgSQL calls to escape and write the
602                                                  * escaped string onto the end of our query buffer,
603                                                  * then we "just" need to make sure queryend is
604                                                  * pointing at the right place.
605                                                  */
606
607                                                 /* Is it numbered parameter?
608                                                  */
609
610                                                 bool numbered;
611                                                 numbered = false;
612
613                                                 /* Numbered parameter number :|
614                                                  */
615                                                 unsigned int paramnum;
616                                                 paramnum = 0;
617
618                                                 /* Let's check if it's a numbered param. And also calculate it's number.
619                                                  */
620
621                                                 while ((i < req.query.q.length() - 1) && (req.query.q[i+1] >= '0') && (req.query.q[i+1] <= '9'))
622                                                 {
623                                                         numbered = true;
624                                                         ++i;
625                                                         paramnum = paramnum * 10 + req.query.q[i] - '0';
626                                                 }
627
628                                                 if (paramnum > paramscopy.size() - 1)
629                                                 {
630                                                         /* index is out of range!
631                                                          */
632                                                         numbered = false;
633                                                 }
634
635                                                 if (numbered)
636                                                 {
637                                                         int error = 0;
638                                                         size_t len = 0;
639
640 #ifdef PGSQL_HAS_ESCAPECONN
641                                                         len = PQescapeStringConn(sql, queryend, paramscopy[paramnum].c_str(), paramscopy[paramnum].length(), &error);
642 #else
643                                                         len = PQescapeString         (queryend, paramscopy[paramnum].c_str(), paramscopy[paramnum].length());
644 #endif
645                                                         if (error)
646                                                         {
647                                                                 ServerInstance->Logs->Log("m_pgsql", DEBUG, "BUG: Apparently PQescapeStringConn() failed somehow...don't know how or what to do...");
648                                                         }
649
650                                                         /* Incremenet queryend to the end of the newly escaped parameter */
651                                                         queryend += len;
652                                                 }
653                                                 else if (req.query.p.size())
654                                                 {
655                                                         int error = 0;
656                                                         size_t len = 0;
657
658 #ifdef PGSQL_HAS_ESCAPECONN
659                                                         len = PQescapeStringConn(sql, queryend, req.query.p.front().c_str(), req.query.p.front().length(), &error);
660 #else
661                                                         len = PQescapeString         (queryend, req.query.p.front().c_str(), req.query.p.front().length());
662 #endif
663                                                         if(error)
664                                                         {
665                                                                 ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Apparently PQescapeStringConn() failed somehow...don't know how or what to do...");
666                                                         }
667
668                                                         /* Incremenet queryend to the end of the newly escaped parameter */
669                                                         queryend += len;
670
671                                                         /* Remove the parameter we just substituted in */
672                                                         req.query.p.pop_front();
673                                                 }
674                                                 else
675                                                 {
676                                                         ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Found a substitution location but no parameter to substitute :|");
677                                                         break;
678                                                 }
679                                         }
680                                         else
681                                         {
682                                                 *queryend = req.query.q[i];
683                                                 queryend++;
684                                         }
685                                 }
686
687                                 /* Null-terminate the query */
688                                 *queryend = 0;
689                                 req.query.q = query;
690
691                                 if(PQsendQuery(sql, query))
692                                 {
693                                         qinprog = true;
694                                         delete[] query;
695                                         return SQLerror();
696                                 }
697                                 else
698                                 {
699                                         delete[] query;
700                                         return SQLerror(SQL_QSEND_FAIL, PQerrorMessage(sql));
701                                 }
702                         }
703                 }
704                 return SQLerror(SQL_BAD_CONN, "Can't query until connection is complete");
705         }
706
707         SQLerror Query(const SQLrequest &req)
708         {
709                 queue.push(req);
710
711                 if(!qinprog && queue.totalsize())
712                 {
713                         /* There's no query currently in progress, and there's queries in the queue. */
714                         SQLrequest& query = queue.front();
715                         return DoQuery(query);
716                 }
717                 else
718                 {
719                         return SQLerror();
720                 }
721         }
722
723         void OnUnloadModule(Module* mod)
724         {
725                 queue.PurgeModule(mod);
726         }
727
728         const SQLhost GetConfHost()
729         {
730                 return confhost;
731         }
732
733         void Close()
734         {
735                 if (!this->ServerInstance->SE->DelFd(this))
736                 {
737                         if (sql && PQstatus(sql) == CONNECTION_BAD)
738                         {
739                                 this->ServerInstance->SE->DelFd(this, true);
740                         }
741                         else
742                         {
743                                 ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: PQsocket cant be removed from socket engine!");
744                         }
745                 }
746
747                 if(sql)
748                 {
749                         PQfinish(sql);
750                         sql = NULL;
751                 }
752         }
753
754 };
755
756 class ModulePgSQL : public Module
757 {
758  private:
759         ConnMap connections;
760         unsigned long currid;
761         char* sqlsuccess;
762         ReconnectTimer* retimer;
763
764  public:
765         ModulePgSQL()
766         : currid(0)
767         {
768                 ServerInstance->Modules->UseInterface("SQLutils");
769
770                 sqlsuccess = new char[strlen(SQLSUCCESS)+1];
771
772                 strlcpy(sqlsuccess, SQLSUCCESS, strlen(SQLSUCCESS));
773
774                 if (!ServerInstance->Modules->PublishFeature("SQL", this))
775                 {
776                         throw ModuleException("BUG: PgSQL Unable to publish feature 'SQL'");
777                 }
778
779                 ReadConf();
780
781                 ServerInstance->Modules->PublishInterface("SQL", this);
782                 Implementation eventlist[] = { I_OnUnloadModule, I_OnRequest, I_OnRehash };
783                 ServerInstance->Modules->Attach(eventlist, this, 3);
784         }
785
786         virtual ~ModulePgSQL()
787         {
788                 if (retimer)
789                         ServerInstance->Timers->DelTimer(retimer);
790                 ClearAllConnections();
791                 delete[] sqlsuccess;
792                 ServerInstance->Modules->UnpublishInterface("SQL", this);
793                 ServerInstance->Modules->UnpublishFeature("SQL");
794                 ServerInstance->Modules->DoneWithInterface("SQLutils");
795         }
796
797
798         virtual void OnRehash(User* user)
799         {
800                 ReadConf();
801         }
802
803         bool HasHost(const SQLhost &host)
804         {
805                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
806                 {
807                         if (host == iter->second->GetConfHost())
808                                 return true;
809                 }
810                 return false;
811         }
812
813         bool HostInConf(const SQLhost &h)
814         {
815                 ConfigReader conf;
816                 for(int i = 0; i < conf.Enumerate("database"); i++)
817                 {
818                         SQLhost host;
819                         host.id         = conf.ReadValue("database", "id", i);
820                         host.host       = conf.ReadValue("database", "hostname", i);
821                         host.port       = conf.ReadInteger("database", "port", i, true);
822                         host.name       = conf.ReadValue("database", "name", i);
823                         host.user       = conf.ReadValue("database", "username", i);
824                         host.pass       = conf.ReadValue("database", "password", i);
825                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
826                         if (h == host)
827                                 return true;
828                 }
829                 return false;
830         }
831
832         void ReadConf()
833         {
834                 ClearOldConnections();
835
836                 ConfigReader conf;
837                 for(int i = 0; i < conf.Enumerate("database"); i++)
838                 {
839                         SQLhost host;
840
841                         host.id         = conf.ReadValue("database", "id", i);
842                         host.host       = conf.ReadValue("database", "hostname", i);
843                         host.port       = conf.ReadInteger("database", "port", i, true);
844                         host.name       = conf.ReadValue("database", "name", i);
845                         host.user       = conf.ReadValue("database", "username", i);
846                         host.pass       = conf.ReadValue("database", "password", i);
847                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
848
849                         if (HasHost(host))
850                                 continue;
851
852                         this->AddConn(host);
853                 }
854         }
855
856         void ClearOldConnections()
857         {
858                 ConnMap::iterator iter,safei;
859                 for (iter = connections.begin(); iter != connections.end(); iter++)
860                 {
861                         if (!HostInConf(iter->second->GetConfHost()))
862                         {
863                                 delete iter->second;
864                                 safei = iter;
865                                 --iter;
866                                 connections.erase(safei);
867                         }
868                 }
869         }
870
871         void ClearAllConnections()
872         {
873                 ConnMap::iterator i;
874                 while ((i = connections.begin()) != connections.end())
875                 {
876                         connections.erase(i);
877                         delete i->second;
878                 }
879         }
880
881         void AddConn(const SQLhost& hi)
882         {
883                 if (HasHost(hi))
884                 {
885                         ServerInstance->Logs->Log("m_pgsql",DEFAULT, "WARNING: A pgsql connection with id: %s already exists. Aborting connection attempt.", hi.id.c_str());
886                         return;
887                 }
888
889                 SQLConn* newconn;
890
891                 newconn = new SQLConn(this, hi);
892
893                 connections.insert(std::make_pair(hi.id, newconn));
894         }
895
896         void ReconnectConn(SQLConn* conn)
897         {
898                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
899                 {
900                         if (conn == iter->second)
901                         {
902                                 delete iter->second;
903                                 connections.erase(iter);
904                                 break;
905                         }
906                 }
907                 retimer = new ReconnectTimer(this);
908                 ServerInstance->Timers->AddTimer(retimer);
909         }
910
911         virtual const char* OnRequest(Request* request)
912         {
913                 if(strcmp(SQLREQID, request->GetId()) == 0)
914                 {
915                         SQLrequest* req = (SQLrequest*)request;
916                         ConnMap::iterator iter;
917                         if((iter = connections.find(req->dbid)) != connections.end())
918                         {
919                                 /* Execute query */
920                                 req->id = NewID();
921                                 req->error = iter->second->Query(*req);
922
923                                 return (req->error.Id() == SQL_NO_ERROR) ? sqlsuccess : NULL;
924                         }
925                         else
926                         {
927                                 req->error.Id(SQL_BAD_DBID);
928                                 return NULL;
929                         }
930                 }
931                 return NULL;
932         }
933
934         virtual void OnUnloadModule(Module* mod, const std::string&     name)
935         {
936                 /* When a module unloads we have to check all the pending queries for all our connections
937                  * and set the Module* specifying where the query came from to NULL. If the query has already
938                  * been dispatched then when it is processed it will be dropped if the pointer is NULL.
939                  *
940                  * If the queries we find are not already being executed then we can simply remove them immediately.
941                  */
942                 for(ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
943                 {
944                         iter->second->OnUnloadModule(mod);
945                 }
946         }
947
948         unsigned long NewID()
949         {
950                 if (currid+1 == 0)
951                         currid++;
952
953                 return ++currid;
954         }
955
956         virtual Version GetVersion()
957         {
958                 return Version("PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API", VF_VENDOR|VF_SERVICEPROVIDER, API_VERSION);
959         }
960 };
961
962 void ReconnectTimer::Tick(time_t time)
963 {
964         ((ModulePgSQL*)mod)->ReadConf();
965 }
966
967 void SQLConn::DelayReconnect()
968 {
969         ((ModulePgSQL*)us)->ReconnectConn(this);
970 }
971
972 MODULE_INIT(ModulePgSQL)