]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_pgsql.cpp
c61e0983cf103f53c505a98e2a0a6298b1956709
[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(InspIRCd* SI, 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         InspIRCd*               ServerInstance;
305         SQLhost                 confhost;       /* The <database> entry */
306         Module*                 us;                     /* Pointer to the SQL provider itself */
307         PGconn*                 sql;            /* PgSQL database connection handle */
308         SQLstatus               status;         /* PgSQL database connection status */
309         bool                    qinprog;        /* If there is currently a query in progress */
310         QueryQueue              queue;          /* Queue of queries waiting to be executed on this connection */
311         time_t                  idle;           /* Time we last heard from the database */
312
313  public:
314         SQLConn(InspIRCd* SI, Module* self, const SQLhost& hi)
315         : EventHandler(), ServerInstance(SI), confhost(hi), us(self), sql(NULL), status(CWRITE), qinprog(false)
316         {
317                 idle = this->ServerInstance->Time();
318                 if(!DoConnect())
319                 {
320                         ServerInstance->Logs->Log("m_pgsql",DEFAULT, "WARNING: Could not connect to database with id: " + ConvToStr(hi.id));
321                         DelayReconnect();
322                 }
323         }
324
325         ~SQLConn()
326         {
327                 Close();
328         }
329
330         virtual void HandleEvent(EventType et, int errornum)
331         {
332                 switch (et)
333                 {
334                         case EVENT_READ:
335                                 OnDataReady();
336                         break;
337
338                         case EVENT_WRITE:
339                                 OnWriteReady();
340                         break;
341
342                         case EVENT_ERROR:
343                                 DelayReconnect();
344                         break;
345
346                         default:
347                         break;
348                 }
349         }
350
351         bool DoConnect()
352         {
353                 if(!(sql = PQconnectStart(confhost.GetDSN().c_str())))
354                         return false;
355
356                 if(PQstatus(sql) == CONNECTION_BAD)
357                         return false;
358
359                 if(PQsetnonblocking(sql, 1) == -1)
360                         return false;
361
362                 /* OK, we've initalised the connection, now to get it hooked into the socket engine
363                 * and then start polling it.
364                 */
365                 this->fd = PQsocket(sql);
366
367                 if(this->fd <= -1)
368                         return false;
369
370                 if (!this->ServerInstance->SE->AddFd(this))
371                 {
372                         ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Couldn't add pgsql socket to socket engine");
373                         return false;
374                 }
375
376                 /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
377                 return DoPoll();
378         }
379
380         bool DoPoll()
381         {
382                 switch(PQconnectPoll(sql))
383                 {
384                         case PGRES_POLLING_WRITING:
385                                 ServerInstance->SE->WantWrite(this);
386                                 status = CWRITE;
387                                 return true;
388                         case PGRES_POLLING_READING:
389                                 status = CREAD;
390                                 return true;
391                         case PGRES_POLLING_FAILED:
392                                 return false;
393                         case PGRES_POLLING_OK:
394                                 status = WWRITE;
395                                 return DoConnectedPoll();
396                         default:
397                                 return true;
398                 }
399         }
400
401         bool DoConnectedPoll()
402         {
403                 if(!qinprog && queue.totalsize())
404                 {
405                         /* There's no query currently in progress, and there's queries in the queue. */
406                         SQLrequest& query = queue.front();
407                         DoQuery(query);
408                 }
409
410                 if(PQconsumeInput(sql))
411                 {
412                         /* We just read stuff from the server, that counts as it being alive
413                          * so update the idle-since time :p
414                          */
415                         idle = this->ServerInstance->Time();
416
417                         if (PQisBusy(sql))
418                         {
419                                 /* Nothing happens here */
420                         }
421                         else if (qinprog)
422                         {
423                                 /* Grab the request we're processing */
424                                 SQLrequest& query = queue.front();
425
426                                 /* Get a pointer to the module we're about to return the result to */
427                                 Module* to = query.GetSource();
428
429                                 /* Fetch the result.. */
430                                 PGresult* result = PQgetResult(sql);
431
432                                 /* PgSQL would allow a query string to be sent which has multiple
433                                  * queries in it, this isn't portable across database backends and
434                                  * we don't want modules doing it. But just in case we make sure we
435                                  * drain any results there are and just use the last one.
436                                  * If the module devs are behaving there will only be one result.
437                                  */
438                                 while (PGresult* temp = PQgetResult(sql))
439                                 {
440                                         PQclear(result);
441                                         result = temp;
442                                 }
443
444                                 if(to)
445                                 {
446                                         /* ..and the result */
447                                         PgSQLresult reply(us, to, query.id, result);
448
449                                         /* Fix by brain, make sure the original query gets sent back in the reply */
450                                         reply.query = query.query.q;
451
452                                         switch(PQresultStatus(result))
453                                         {
454                                                 case PGRES_EMPTY_QUERY:
455                                                 case PGRES_BAD_RESPONSE:
456                                                 case PGRES_FATAL_ERROR:
457                                                         reply.error.Id(SQL_QREPLY_FAIL);
458                                                         reply.error.Str(PQresultErrorMessage(result));
459                                                 default:
460                                                         ;
461                                                         /* No action, other values are not errors */
462                                         }
463
464                                         reply.Send();
465
466                                         /* PgSQLresult's destructor will free the PGresult */
467                                 }
468                                 else
469                                 {
470                                         /* If the client module is unloaded partway through a query then the provider will set
471                                          * the pointer to NULL. We cannot just cancel the query as the result will still come
472                                          * through at some point...and it could get messy if we play with invalid pointers...
473                                          */
474                                         PQclear(result);
475                                 }
476                                 qinprog = false;
477                                 queue.pop();
478                                 DoConnectedPoll();
479                         }
480                         return true;
481                 }
482                 else
483                 {
484                         /* I think we'll assume this means the server died...it might not,
485                          * but I think that any error serious enough we actually get here
486                          * deserves to reconnect [/excuse]
487                          * Returning true so the core doesn't try and close the connection.
488                          */
489                         DelayReconnect();
490                         return true;
491                 }
492         }
493
494         bool DoResetPoll()
495         {
496                 switch(PQresetPoll(sql))
497                 {
498                         case PGRES_POLLING_WRITING:
499                                 ServerInstance->SE->WantWrite(this);
500                                 status = CWRITE;
501                                 return DoPoll();
502                         case PGRES_POLLING_READING:
503                                 status = CREAD;
504                                 return true;
505                         case PGRES_POLLING_FAILED:
506                                 return false;
507                         case PGRES_POLLING_OK:
508                                 status = WWRITE;
509                                 return DoConnectedPoll();
510                         default:
511                                 return true;
512                 }
513         }
514
515         bool OnDataReady()
516         {
517                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
518                 return DoEvent();
519         }
520
521         bool OnWriteReady()
522         {
523                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
524                 return DoEvent();
525         }
526
527         bool OnConnected()
528         {
529                 return DoEvent();
530         }
531
532         void DelayReconnect();
533
534         bool DoEvent()
535         {
536                 bool ret;
537
538                 if((status == CREAD) || (status == CWRITE))
539                 {
540                         ret = DoPoll();
541                 }
542                 else if((status == RREAD) || (status == RWRITE))
543                 {
544                         ret = DoResetPoll();
545                 }
546                 else
547                 {
548                         ret = DoConnectedPoll();
549                 }
550                 return ret;
551         }
552
553         SQLerror DoQuery(SQLrequest &req)
554         {
555                 if((status == WREAD) || (status == WWRITE))
556                 {
557                         if(!qinprog)
558                         {
559                                 /* Parse the command string and dispatch it */
560
561                                 /* Pointer to the buffer we screw around with substitution in */
562                                 char* query;
563                                 /* Pointer to the current end of query, where we append new stuff */
564                                 char* queryend;
565
566                                 /* Total length of the unescaped parameters */
567                                 unsigned long maxparamlen, paramcount;
568
569                                 /* The length of the longest parameter */
570                                 maxparamlen = 0;
571
572                                 for(ParamL::iterator i = req.query.p.begin(); i != req.query.p.end(); i++)
573                                 {
574                                         if (i->size() > maxparamlen)
575                                                 maxparamlen = i->size();
576                                 }
577
578                                 /* How many params are there in the query? */
579                                 paramcount = count(req.query.q.c_str(), '?');
580
581                                 /* This stores copy of params to be inserted with using numbered params 1;3B*/
582                                 ParamL paramscopy(req.query.p);
583
584                                 /* To avoid a lot of allocations, allocate enough memory for the biggest the escaped query could possibly be.
585                                  * sizeofquery + (maxtotalparamlength*2) + 1
586                                  *
587                                  * The +1 is for null-terminating the string for PQsendQuery()
588                                  */
589
590                                 query = new char[req.query.q.length() + (maxparamlen*paramcount*2) + 1];
591                                 queryend = query;
592
593                                 /* Okay, now we have a buffer large enough we need to start copying the query into it and escaping and substituting
594                                  * the parameters into it...
595                                  */
596
597                                 for(unsigned int i = 0; i < req.query.q.length(); i++)
598                                 {
599                                         if(req.query.q[i] == '?')
600                                         {
601                                                 /* We found a place to substitute..what fun.
602                                                  * Use the PgSQL calls to escape and write the
603                                                  * escaped string onto the end of our query buffer,
604                                                  * then we "just" need to make sure queryend is
605                                                  * pointing at the right place.
606                                                  */
607
608                                                 /* Is it numbered parameter?
609                                                  */
610
611                                                 bool numbered;
612                                                 numbered = false;
613
614                                                 /* Numbered parameter number :|
615                                                  */
616                                                 unsigned int paramnum;
617                                                 paramnum = 0;
618
619                                                 /* Let's check if it's a numbered param. And also calculate it's number.
620                                                  */
621
622                                                 while ((i < req.query.q.length() - 1) && (req.query.q[i+1] >= '0') && (req.query.q[i+1] <= '9'))
623                                                 {
624                                                         numbered = true;
625                                                         ++i;
626                                                         paramnum = paramnum * 10 + req.query.q[i] - '0';
627                                                 }
628
629                                                 if (paramnum > paramscopy.size() - 1)
630                                                 {
631                                                         /* index is out of range!
632                                                          */
633                                                         numbered = false;
634                                                 }
635
636                                                 if (numbered)
637                                                 {
638                                                         int error = 0;
639                                                         size_t len = 0;
640
641 #ifdef PGSQL_HAS_ESCAPECONN
642                                                         len = PQescapeStringConn(sql, queryend, paramscopy[paramnum].c_str(), paramscopy[paramnum].length(), &error);
643 #else
644                                                         len = PQescapeString         (queryend, paramscopy[paramnum].c_str(), paramscopy[paramnum].length());
645 #endif
646                                                         if (error)
647                                                         {
648                                                                 ServerInstance->Logs->Log("m_pgsql", DEBUG, "BUG: Apparently PQescapeStringConn() failed somehow...don't know how or what to do...");
649                                                         }
650
651                                                         /* Incremenet queryend to the end of the newly escaped parameter */
652                                                         queryend += len;
653                                                 }
654                                                 else if (req.query.p.size())
655                                                 {
656                                                         int error = 0;
657                                                         size_t len = 0;
658
659 #ifdef PGSQL_HAS_ESCAPECONN
660                                                         len = PQescapeStringConn(sql, queryend, req.query.p.front().c_str(), req.query.p.front().length(), &error);
661 #else
662                                                         len = PQescapeString         (queryend, req.query.p.front().c_str(), req.query.p.front().length());
663 #endif
664                                                         if(error)
665                                                         {
666                                                                 ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Apparently PQescapeStringConn() failed somehow...don't know how or what to do...");
667                                                         }
668
669                                                         /* Incremenet queryend to the end of the newly escaped parameter */
670                                                         queryend += len;
671
672                                                         /* Remove the parameter we just substituted in */
673                                                         req.query.p.pop_front();
674                                                 }
675                                                 else
676                                                 {
677                                                         ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Found a substitution location but no parameter to substitute :|");
678                                                         break;
679                                                 }
680                                         }
681                                         else
682                                         {
683                                                 *queryend = req.query.q[i];
684                                                 queryend++;
685                                         }
686                                 }
687
688                                 /* Null-terminate the query */
689                                 *queryend = 0;
690                                 req.query.q = query;
691
692                                 if(PQsendQuery(sql, query))
693                                 {
694                                         qinprog = true;
695                                         delete[] query;
696                                         return SQLerror();
697                                 }
698                                 else
699                                 {
700                                         delete[] query;
701                                         return SQLerror(SQL_QSEND_FAIL, PQerrorMessage(sql));
702                                 }
703                         }
704                 }
705                 return SQLerror(SQL_BAD_CONN, "Can't query until connection is complete");
706         }
707
708         SQLerror Query(const SQLrequest &req)
709         {
710                 queue.push(req);
711
712                 if(!qinprog && queue.totalsize())
713                 {
714                         /* There's no query currently in progress, and there's queries in the queue. */
715                         SQLrequest& query = queue.front();
716                         return DoQuery(query);
717                 }
718                 else
719                 {
720                         return SQLerror();
721                 }
722         }
723
724         void OnUnloadModule(Module* mod)
725         {
726                 queue.PurgeModule(mod);
727         }
728
729         const SQLhost GetConfHost()
730         {
731                 return confhost;
732         }
733
734         void Close()
735         {
736                 if (!this->ServerInstance->SE->DelFd(this))
737                 {
738                         if (sql && PQstatus(sql) == CONNECTION_BAD)
739                         {
740                                 this->ServerInstance->SE->DelFd(this, true);
741                         }
742                         else
743                         {
744                                 ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: PQsocket cant be removed from socket engine!");
745                         }
746                 }
747
748                 if(sql)
749                 {
750                         PQfinish(sql);
751                         sql = NULL;
752                 }
753         }
754
755 };
756
757 class ModulePgSQL : public Module
758 {
759  private:
760         ConnMap connections;
761         unsigned long currid;
762         char* sqlsuccess;
763         ReconnectTimer* retimer;
764
765  public:
766         ModulePgSQL(InspIRCd* Me)
767         : Module(Me), currid(0)
768         {
769                 ServerInstance->Modules->UseInterface("SQLutils");
770
771                 sqlsuccess = new char[strlen(SQLSUCCESS)+1];
772
773                 strlcpy(sqlsuccess, SQLSUCCESS, strlen(SQLSUCCESS));
774
775                 if (!ServerInstance->Modules->PublishFeature("SQL", this))
776                 {
777                         throw ModuleException("BUG: PgSQL Unable to publish feature 'SQL'");
778                 }
779
780                 ReadConf();
781
782                 ServerInstance->Modules->PublishInterface("SQL", this);
783                 Implementation eventlist[] = { I_OnUnloadModule, I_OnRequest, I_OnRehash };
784                 ServerInstance->Modules->Attach(eventlist, this, 3);
785         }
786
787         virtual ~ModulePgSQL()
788         {
789                 if (retimer)
790                         ServerInstance->Timers->DelTimer(retimer);
791                 ClearAllConnections();
792                 delete[] sqlsuccess;
793                 ServerInstance->Modules->UnpublishInterface("SQL", this);
794                 ServerInstance->Modules->UnpublishFeature("SQL");
795                 ServerInstance->Modules->DoneWithInterface("SQLutils");
796         }
797
798
799         virtual void OnRehash(User* user)
800         {
801                 ReadConf();
802         }
803
804         bool HasHost(const SQLhost &host)
805         {
806                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
807                 {
808                         if (host == iter->second->GetConfHost())
809                                 return true;
810                 }
811                 return false;
812         }
813
814         bool HostInConf(const SQLhost &h)
815         {
816                 ConfigReader conf(ServerInstance);
817                 for(int i = 0; i < conf.Enumerate("database"); i++)
818                 {
819                         SQLhost host;
820                         host.id         = conf.ReadValue("database", "id", i);
821                         host.host       = conf.ReadValue("database", "hostname", i);
822                         host.port       = conf.ReadInteger("database", "port", i, true);
823                         host.name       = conf.ReadValue("database", "name", i);
824                         host.user       = conf.ReadValue("database", "username", i);
825                         host.pass       = conf.ReadValue("database", "password", i);
826                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
827                         if (h == host)
828                                 return true;
829                 }
830                 return false;
831         }
832
833         void ReadConf()
834         {
835                 ClearOldConnections();
836
837                 ConfigReader conf(ServerInstance);
838                 for(int i = 0; i < conf.Enumerate("database"); i++)
839                 {
840                         SQLhost host;
841
842                         host.id         = conf.ReadValue("database", "id", i);
843                         host.host       = conf.ReadValue("database", "hostname", i);
844                         host.port       = conf.ReadInteger("database", "port", i, true);
845                         host.name       = conf.ReadValue("database", "name", i);
846                         host.user       = conf.ReadValue("database", "username", i);
847                         host.pass       = conf.ReadValue("database", "password", i);
848                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
849
850                         if (HasHost(host))
851                                 continue;
852
853                         this->AddConn(host);
854                 }
855         }
856
857         void ClearOldConnections()
858         {
859                 ConnMap::iterator iter,safei;
860                 for (iter = connections.begin(); iter != connections.end(); iter++)
861                 {
862                         if (!HostInConf(iter->second->GetConfHost()))
863                         {
864                                 delete iter->second;
865                                 safei = iter;
866                                 --iter;
867                                 connections.erase(safei);
868                         }
869                 }
870         }
871
872         void ClearAllConnections()
873         {
874                 ConnMap::iterator i;
875                 while ((i = connections.begin()) != connections.end())
876                 {
877                         connections.erase(i);
878                         delete i->second;
879                 }
880         }
881
882         void AddConn(const SQLhost& hi)
883         {
884                 if (HasHost(hi))
885                 {
886                         ServerInstance->Logs->Log("m_pgsql",DEFAULT, "WARNING: A pgsql connection with id: %s already exists. Aborting connection attempt.", hi.id.c_str());
887                         return;
888                 }
889
890                 SQLConn* newconn;
891
892                 newconn = new SQLConn(ServerInstance, this, hi);
893
894                 connections.insert(std::make_pair(hi.id, newconn));
895         }
896
897         void ReconnectConn(SQLConn* conn)
898         {
899                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
900                 {
901                         if (conn == iter->second)
902                         {
903                                 delete iter->second;
904                                 connections.erase(iter);
905                                 break;
906                         }
907                 }
908                 retimer = new ReconnectTimer(ServerInstance, this);
909                 ServerInstance->Timers->AddTimer(retimer);
910         }
911
912         virtual const char* OnRequest(Request* request)
913         {
914                 if(strcmp(SQLREQID, request->GetId()) == 0)
915                 {
916                         SQLrequest* req = (SQLrequest*)request;
917                         ConnMap::iterator iter;
918                         if((iter = connections.find(req->dbid)) != connections.end())
919                         {
920                                 /* Execute query */
921                                 req->id = NewID();
922                                 req->error = iter->second->Query(*req);
923
924                                 return (req->error.Id() == SQL_NO_ERROR) ? sqlsuccess : NULL;
925                         }
926                         else
927                         {
928                                 req->error.Id(SQL_BAD_DBID);
929                                 return NULL;
930                         }
931                 }
932                 return NULL;
933         }
934
935         virtual void OnUnloadModule(Module* mod, const std::string&     name)
936         {
937                 /* When a module unloads we have to check all the pending queries for all our connections
938                  * and set the Module* specifying where the query came from to NULL. If the query has already
939                  * been dispatched then when it is processed it will be dropped if the pointer is NULL.
940                  *
941                  * If the queries we find are not already being executed then we can simply remove them immediately.
942                  */
943                 for(ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
944                 {
945                         iter->second->OnUnloadModule(mod);
946                 }
947         }
948
949         unsigned long NewID()
950         {
951                 if (currid+1 == 0)
952                         currid++;
953
954                 return ++currid;
955         }
956
957         virtual Version GetVersion()
958         {
959                 return Version("PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API", VF_VENDOR|VF_SERVICEPROVIDER, API_VERSION);
960         }
961 };
962
963 void ReconnectTimer::Tick(time_t time)
964 {
965         ((ModulePgSQL*)mod)->ReadConf();
966 }
967
968 void SQLConn::DelayReconnect()
969 {
970         ((ModulePgSQL*)us)->ReconnectConn(this);
971 }
972
973 MODULE_INIT(ModulePgSQL)