]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_pgsql.cpp
...because every now and again, i have to do a massive commit.
[user/henk/code/inspircd.git] / src / modules / extra / m_pgsql.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2010 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 static 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* const mod;
95  public:
96         ReconnectTimer(Module* m) : Timer(5, ServerInstance->Time(), false), mod(m)
97         {
98         }
99         virtual void Tick(time_t TIME);
100 };
101
102
103 /** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult.
104  * All SQL providers must create their own subclass and define it's methods using that
105  * database library's data retriveal functions. The aim is to avoid a slow and inefficient process
106  * of converting all data to a common format before it reaches the result structure. This way
107  * data is passes to the module nearly as directly as if it was using the API directly itself.
108  */
109
110 class PgSQLresult : public SQLresult
111 {
112         PGresult* res;
113         int currentrow;
114         int rows;
115         int cols;
116
117         SQLfieldList* fieldlist;
118         SQLfieldMap* fieldmap;
119 public:
120         PgSQLresult(Module* self, Module* to, unsigned long rid, PGresult* result)
121         : SQLresult(self, to, rid), res(result), currentrow(0), fieldlist(NULL), fieldmap(NULL)
122         {
123                 rows = PQntuples(res);
124                 cols = PQnfields(res);
125         }
126
127         ~PgSQLresult()
128         {
129                 /* If we allocated these, free them... */
130                 if(fieldlist)
131                         delete fieldlist;
132
133                 if(fieldmap)
134                         delete fieldmap;
135
136                 PQclear(res);
137         }
138
139         virtual int Rows()
140         {
141                 if(!cols && !rows)
142                 {
143                         return atoi(PQcmdTuples(res));
144                 }
145                 else
146                 {
147                         return rows;
148                 }
149         }
150
151         virtual int Cols()
152         {
153                 return PQnfields(res);
154         }
155
156         virtual std::string ColName(int column)
157         {
158                 char* name = PQfname(res, column);
159
160                 return (name) ? name : "";
161         }
162
163         virtual int ColNum(const std::string &column)
164         {
165                 int n = PQfnumber(res, column.c_str());
166
167                 if(n == -1)
168                 {
169                         throw SQLbadColName();
170                 }
171                 else
172                 {
173                         return n;
174                 }
175         }
176
177         virtual SQLfield GetValue(int row, int column)
178         {
179                 char* v = PQgetvalue(res, row, column);
180
181                 if(v)
182                 {
183                         return SQLfield(std::string(v, PQgetlength(res, row, column)), PQgetisnull(res, row, column));
184                 }
185                 else
186                 {
187                         throw SQLbadColName();
188                 }
189         }
190
191         virtual SQLfieldList& GetRow()
192         {
193                 /* In an effort to reduce overhead we don't actually allocate the list
194                  * until the first time it's needed...so...
195                  */
196                 if(fieldlist)
197                 {
198                         fieldlist->clear();
199                 }
200                 else
201                 {
202                         fieldlist = new SQLfieldList;
203                 }
204
205                 if(currentrow < PQntuples(res))
206                 {
207                         int ncols = PQnfields(res);
208
209                         for(int i = 0; i < ncols; i++)
210                         {
211                                 fieldlist->push_back(GetValue(currentrow, i));
212                         }
213
214                         currentrow++;
215                 }
216
217                 return *fieldlist;
218         }
219
220         virtual SQLfieldMap& GetRowMap()
221         {
222                 /* In an effort to reduce overhead we don't actually allocate the map
223                  * until the first time it's needed...so...
224                  */
225                 if(fieldmap)
226                 {
227                         fieldmap->clear();
228                 }
229                 else
230                 {
231                         fieldmap = new SQLfieldMap;
232                 }
233
234                 if(currentrow < PQntuples(res))
235                 {
236                         int ncols = PQnfields(res);
237
238                         for(int i = 0; i < ncols; i++)
239                         {
240                                 fieldmap->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
241                         }
242
243                         currentrow++;
244                 }
245
246                 return *fieldmap;
247         }
248
249         virtual SQLfieldList* GetRowPtr()
250         {
251                 SQLfieldList* fl = new SQLfieldList;
252
253                 if(currentrow < PQntuples(res))
254                 {
255                         int ncols = PQnfields(res);
256
257                         for(int i = 0; i < ncols; i++)
258                         {
259                                 fl->push_back(GetValue(currentrow, i));
260                         }
261
262                         currentrow++;
263                 }
264
265                 return fl;
266         }
267
268         virtual SQLfieldMap* GetRowMapPtr()
269         {
270                 SQLfieldMap* fm = new SQLfieldMap;
271
272                 if(currentrow < PQntuples(res))
273                 {
274                         int ncols = PQnfields(res);
275
276                         for(int i = 0; i < ncols; i++)
277                         {
278                                 fm->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
279                         }
280
281                         currentrow++;
282                 }
283
284                 return fm;
285         }
286
287         virtual void Free(SQLfieldMap* fm)
288         {
289                 delete fm;
290         }
291
292         virtual void Free(SQLfieldList* fl)
293         {
294                 delete fl;
295         }
296 };
297
298 /** SQLConn represents one SQL session.
299  */
300 class SQLConn : public EventHandler
301 {
302  private:
303         SQLhost                 confhost;       /* The <database> entry */
304         Module*                 us;                     /* Pointer to the SQL provider itself */
305         PGconn*                 sql;            /* PgSQL database connection handle */
306         SQLstatus               status;         /* PgSQL database connection status */
307         bool                    qinprog;        /* If there is currently a query in progress */
308         QueryQueue              queue;          /* Queue of queries waiting to be executed on this connection */
309         time_t                  idle;           /* Time we last heard from the database */
310
311  public:
312         SQLConn(Module* self, const SQLhost& hi)
313         : EventHandler(), confhost(hi), us(self), sql(NULL), status(CWRITE), qinprog(false)
314         {
315                 idle = ServerInstance->Time();
316                 if(!DoConnect())
317                 {
318                         ServerInstance->Logs->Log("m_pgsql",DEFAULT, "WARNING: Could not connect to database with id: " + ConvToStr(hi.id));
319                         DelayReconnect();
320                 }
321         }
322
323         ~SQLConn()
324         {
325                 Close();
326         }
327
328         virtual void HandleEvent(EventType et, int errornum)
329         {
330                 switch (et)
331                 {
332                         case EVENT_READ:
333                                 OnDataReady();
334                         break;
335
336                         case EVENT_WRITE:
337                                 OnWriteReady();
338                         break;
339
340                         case EVENT_ERROR:
341                                 DelayReconnect();
342                         break;
343
344                         default:
345                         break;
346                 }
347         }
348
349         bool DoConnect()
350         {
351                 if(!(sql = PQconnectStart(confhost.GetDSN().c_str())))
352                         return false;
353
354                 if(PQstatus(sql) == CONNECTION_BAD)
355                         return false;
356
357                 if(PQsetnonblocking(sql, 1) == -1)
358                         return false;
359
360                 /* OK, we've initalised the connection, now to get it hooked into the socket engine
361                 * and then start polling it.
362                 */
363                 this->fd = PQsocket(sql);
364
365                 if(this->fd <= -1)
366                         return false;
367
368                 if (!ServerInstance->SE->AddFd(this, FD_WANT_NO_WRITE | FD_WANT_NO_READ))
369                 {
370                         ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Couldn't add pgsql socket to socket engine");
371                         return false;
372                 }
373
374                 /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
375                 return DoPoll();
376         }
377
378         bool DoPoll()
379         {
380                 switch(PQconnectPoll(sql))
381                 {
382                         case PGRES_POLLING_WRITING:
383                                 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ);
384                                 status = CWRITE;
385                                 return true;
386                         case PGRES_POLLING_READING:
387                                 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
388                                 status = CREAD;
389                                 return true;
390                         case PGRES_POLLING_FAILED:
391                                 return false;
392                         case PGRES_POLLING_OK:
393                                 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
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 = 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->source;
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->ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ);
500                                 status = CWRITE;
501                                 return DoPoll();
502                         case PGRES_POLLING_READING:
503                                 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
504                                 status = CREAD;
505                                 return true;
506                         case PGRES_POLLING_FAILED:
507                                 return false;
508                         case PGRES_POLLING_OK:
509                                 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
510                                 status = WWRITE;
511                                 return DoConnectedPoll();
512                         default:
513                                 return true;
514                 }
515         }
516
517         bool OnDataReady()
518         {
519                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
520                 return DoEvent();
521         }
522
523         bool OnWriteReady()
524         {
525                 /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
526                 return DoEvent();
527         }
528
529         bool OnConnected()
530         {
531                 return DoEvent();
532         }
533
534         void DelayReconnect();
535
536         bool DoEvent()
537         {
538                 bool ret;
539
540                 if((status == CREAD) || (status == CWRITE))
541                 {
542                         ret = DoPoll();
543                 }
544                 else if((status == RREAD) || (status == RWRITE))
545                 {
546                         ret = DoResetPoll();
547                 }
548                 else
549                 {
550                         ret = DoConnectedPoll();
551                 }
552                 return ret;
553         }
554
555         SQLerror DoQuery(SQLrequest &req)
556         {
557                 if((status == WREAD) || (status == WWRITE))
558                 {
559                         if(!qinprog)
560                         {
561                                 /* Parse the command string and dispatch it */
562
563                                 /* Pointer to the buffer we screw around with substitution in */
564                                 char* query;
565                                 /* Pointer to the current end of query, where we append new stuff */
566                                 char* queryend;
567
568                                 /* Total length of the unescaped parameters */
569                                 unsigned long maxparamlen, paramcount;
570
571                                 /* The length of the longest parameter */
572                                 maxparamlen = 0;
573
574                                 for(ParamL::iterator i = req.query.p.begin(); i != req.query.p.end(); i++)
575                                 {
576                                         if (i->size() > maxparamlen)
577                                                 maxparamlen = i->size();
578                                 }
579
580                                 /* How many params are there in the query? */
581                                 paramcount = count(req.query.q.c_str(), '?');
582
583                                 /* This stores copy of params to be inserted with using numbered params 1;3B*/
584                                 ParamL paramscopy(req.query.p);
585
586                                 /* To avoid a lot of allocations, allocate enough memory for the biggest the escaped query could possibly be.
587                                  * sizeofquery + (maxtotalparamlength*2) + 1
588                                  *
589                                  * The +1 is for null-terminating the string for PQsendQuery()
590                                  */
591
592                                 query = new char[req.query.q.length() + (maxparamlen*paramcount*2) + 1];
593                                 queryend = query;
594
595                                 /* Okay, now we have a buffer large enough we need to start copying the query into it and escaping and substituting
596                                  * the parameters into it...
597                                  */
598
599                                 for(unsigned int i = 0; i < req.query.q.length(); i++)
600                                 {
601                                         if(req.query.q[i] == '?')
602                                         {
603                                                 /* We found a place to substitute..what fun.
604                                                  * Use the PgSQL calls to escape and write the
605                                                  * escaped string onto the end of our query buffer,
606                                                  * then we "just" need to make sure queryend is
607                                                  * pointing at the right place.
608                                                  */
609
610                                                 /* Is it numbered parameter?
611                                                  */
612
613                                                 bool numbered;
614                                                 numbered = false;
615
616                                                 /* Numbered parameter number :|
617                                                  */
618                                                 unsigned int paramnum;
619                                                 paramnum = 0;
620
621                                                 /* Let's check if it's a numbered param. And also calculate it's number.
622                                                  */
623
624                                                 while ((i < req.query.q.length() - 1) && (req.query.q[i+1] >= '0') && (req.query.q[i+1] <= '9'))
625                                                 {
626                                                         numbered = true;
627                                                         ++i;
628                                                         paramnum = paramnum * 10 + req.query.q[i] - '0';
629                                                 }
630
631                                                 if (paramnum > paramscopy.size() - 1)
632                                                 {
633                                                         /* index is out of range!
634                                                          */
635                                                         numbered = false;
636                                                 }
637
638                                                 if (numbered)
639                                                 {
640                                                         int error = 0;
641                                                         size_t len = 0;
642
643 #ifdef PGSQL_HAS_ESCAPECONN
644                                                         len = PQescapeStringConn(sql, queryend, paramscopy[paramnum].c_str(), paramscopy[paramnum].length(), &error);
645 #else
646                                                         len = PQescapeString         (queryend, paramscopy[paramnum].c_str(), paramscopy[paramnum].length());
647 #endif
648                                                         if (error)
649                                                         {
650                                                                 ServerInstance->Logs->Log("m_pgsql", DEBUG, "BUG: Apparently PQescapeStringConn() failed somehow...don't know how or what to do...");
651                                                         }
652
653                                                         /* Incremenet queryend to the end of the newly escaped parameter */
654                                                         queryend += len;
655                                                 }
656                                                 else if (req.query.p.size())
657                                                 {
658                                                         int error = 0;
659                                                         size_t len = 0;
660
661 #ifdef PGSQL_HAS_ESCAPECONN
662                                                         len = PQescapeStringConn(sql, queryend, req.query.p.front().c_str(), req.query.p.front().length(), &error);
663 #else
664                                                         len = PQescapeString         (queryend, req.query.p.front().c_str(), req.query.p.front().length());
665 #endif
666                                                         if(error)
667                                                         {
668                                                                 ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Apparently PQescapeStringConn() failed somehow...don't know how or what to do...");
669                                                         }
670
671                                                         /* Incremenet queryend to the end of the newly escaped parameter */
672                                                         queryend += len;
673
674                                                         /* Remove the parameter we just substituted in */
675                                                         req.query.p.pop_front();
676                                                 }
677                                                 else
678                                                 {
679                                                         ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Found a substitution location but no parameter to substitute :|");
680                                                         break;
681                                                 }
682                                         }
683                                         else
684                                         {
685                                                 *queryend = req.query.q[i];
686                                                 queryend++;
687                                         }
688                                 }
689
690                                 /* Null-terminate the query */
691                                 *queryend = 0;
692                                 req.query.q = query;
693
694                                 if(PQsendQuery(sql, query))
695                                 {
696                                         qinprog = true;
697                                         delete[] query;
698                                         return SQLerror();
699                                 }
700                                 else
701                                 {
702                                         delete[] query;
703                                         return SQLerror(SQL_QSEND_FAIL, PQerrorMessage(sql));
704                                 }
705                         }
706                 }
707                 return SQLerror(SQL_BAD_CONN, "Can't query until connection is complete");
708         }
709
710         SQLerror Query(const SQLrequest &req)
711         {
712                 queue.push(new SQLrequest(req));
713
714                 if(!qinprog && queue.totalsize())
715                 {
716                         /* There's no query currently in progress, and there's queries in the queue. */
717                         SQLrequest* query = queue.front();
718                         return DoQuery(*query);
719                 }
720                 else
721                 {
722                         return SQLerror();
723                 }
724         }
725
726         void OnUnloadModule(Module* mod)
727         {
728                 queue.PurgeModule(mod);
729         }
730
731         const SQLhost GetConfHost()
732         {
733                 return confhost;
734         }
735
736         void Close()
737         {
738                 if (!ServerInstance->SE->DelFd(this))
739                 {
740                         if (sql && PQstatus(sql) == CONNECTION_BAD)
741                         {
742                                 ServerInstance->SE->DelFd(this, true);
743                         }
744                         else
745                         {
746                                 ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: PQsocket cant be removed from socket engine!");
747                         }
748                 }
749
750                 if(sql)
751                 {
752                         PQfinish(sql);
753                         sql = NULL;
754                 }
755         }
756
757 };
758
759 class ModulePgSQL : public Module
760 {
761  private:
762         ConnMap connections;
763         unsigned long currid;
764         char* sqlsuccess;
765         ReconnectTimer* retimer;
766         ServiceProvider sqlserv;
767  public:
768         ModulePgSQL()
769         : currid(0), sqlserv(this, "SQL/pgsql", SERVICE_DATA)
770         {
771                 sqlsuccess = new char[strlen(SQLSUCCESS)+1];
772
773                 strlcpy(sqlsuccess, SQLSUCCESS, strlen(SQLSUCCESS));
774
775                 ReadConf();
776
777                 ServerInstance->Modules->AddService(sqlserv);
778                 Implementation eventlist[] = { I_OnUnloadModule, I_OnRehash };
779                 ServerInstance->Modules->Attach(eventlist, this, 2);
780         }
781
782         virtual ~ModulePgSQL()
783         {
784                 if (retimer)
785                         ServerInstance->Timers->DelTimer(retimer);
786                 ClearAllConnections();
787                 delete[] sqlsuccess;
788         }
789
790
791         virtual void OnRehash(User* user)
792         {
793                 ReadConf();
794         }
795
796         bool HasHost(const SQLhost &host)
797         {
798                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
799                 {
800                         if (host == iter->second->GetConfHost())
801                                 return true;
802                 }
803                 return false;
804         }
805
806         bool HostInConf(const SQLhost &h)
807         {
808                 ConfigReader conf;
809                 for(int i = 0; i < conf.Enumerate("database"); i++)
810                 {
811                         SQLhost host;
812                         host.id         = conf.ReadValue("database", "id", i);
813                         host.host       = conf.ReadValue("database", "hostname", i);
814                         host.port       = conf.ReadInteger("database", "port", i, true);
815                         host.name       = conf.ReadValue("database", "name", i);
816                         host.user       = conf.ReadValue("database", "username", i);
817                         host.pass       = conf.ReadValue("database", "password", i);
818                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
819                         if (h == host)
820                                 return true;
821                 }
822                 return false;
823         }
824
825         void ReadConf()
826         {
827                 ClearOldConnections();
828
829                 ConfigReader conf;
830                 for(int i = 0; i < conf.Enumerate("database"); i++)
831                 {
832                         SQLhost host;
833
834                         host.id         = conf.ReadValue("database", "id", i);
835                         host.host       = conf.ReadValue("database", "hostname", i);
836                         host.port       = conf.ReadInteger("database", "port", i, true);
837                         host.name       = conf.ReadValue("database", "name", i);
838                         host.user       = conf.ReadValue("database", "username", i);
839                         host.pass       = conf.ReadValue("database", "password", i);
840                         host.ssl        = conf.ReadFlag("database", "ssl", "0", i);
841
842                         if (HasHost(host))
843                                 continue;
844
845                         this->AddConn(host);
846                 }
847         }
848
849         void ClearOldConnections()
850         {
851                 ConnMap::iterator iter,safei;
852                 for (iter = connections.begin(); iter != connections.end(); iter++)
853                 {
854                         if (!HostInConf(iter->second->GetConfHost()))
855                         {
856                                 delete iter->second;
857                                 safei = iter;
858                                 --iter;
859                                 connections.erase(safei);
860                         }
861                 }
862         }
863
864         void ClearAllConnections()
865         {
866                 ConnMap::iterator i;
867                 while ((i = connections.begin()) != connections.end())
868                 {
869                         connections.erase(i);
870                         delete i->second;
871                 }
872         }
873
874         void AddConn(const SQLhost& hi)
875         {
876                 if (HasHost(hi))
877                 {
878                         ServerInstance->Logs->Log("m_pgsql",DEFAULT, "WARNING: A pgsql connection with id: %s already exists. Aborting connection attempt.", hi.id.c_str());
879                         return;
880                 }
881
882                 SQLConn* newconn;
883
884                 newconn = new SQLConn(this, hi);
885
886                 connections.insert(std::make_pair(hi.id, newconn));
887         }
888
889         void ReconnectConn(SQLConn* conn)
890         {
891                 for (ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
892                 {
893                         if (conn == iter->second)
894                         {
895                                 delete iter->second;
896                                 connections.erase(iter);
897                                 break;
898                         }
899                 }
900                 retimer = new ReconnectTimer(this);
901                 ServerInstance->Timers->AddTimer(retimer);
902         }
903
904         void OnRequest(Request& request)
905         {
906                 if(strcmp(SQLREQID, request.id) == 0)
907                 {
908                         SQLrequest* req = (SQLrequest*)&request;
909                         ConnMap::iterator iter;
910                         if((iter = connections.find(req->dbid)) != connections.end())
911                         {
912                                 /* Execute query */
913                                 req->id = NewID();
914                                 req->error = iter->second->Query(*req);
915                         }
916                         else
917                         {
918                                 req->error.Id(SQL_BAD_DBID);
919                         }
920                 }
921         }
922
923         virtual void OnUnloadModule(Module* mod)
924         {
925                 /* When a module unloads we have to check all the pending queries for all our connections
926                  * and set the Module* specifying where the query came from to NULL. If the query has already
927                  * been dispatched then when it is processed it will be dropped if the pointer is NULL.
928                  *
929                  * If the queries we find are not already being executed then we can simply remove them immediately.
930                  */
931                 for(ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
932                 {
933                         iter->second->OnUnloadModule(mod);
934                 }
935         }
936
937         unsigned long NewID()
938         {
939                 if (currid+1 == 0)
940                         currid++;
941
942                 return ++currid;
943         }
944
945         virtual Version GetVersion()
946         {
947                 return Version("PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API", VF_VENDOR);
948         }
949 };
950
951 void ReconnectTimer::Tick(time_t time)
952 {
953         ((ModulePgSQL*)mod)->ReadConf();
954 }
955
956 void SQLConn::DelayReconnect()
957 {
958         ((ModulePgSQL*)us)->ReconnectConn(this);
959 }
960
961 MODULE_INIT(ModulePgSQL)