]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/extra/m_pgsql.cpp
Okay, working PostgreSQL module, API header and example client module in /extra/...
[user/henk/code/inspircd.git] / src / modules / extra / m_pgsql.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd is copyright (C) 2002-2004 ChatSpike-Dev.
6  *                       E-mail:
7  *                <brain@chatspike.net>
8  *                <Craig@chatspike.net>
9  *                <omster@gmail.com>
10  *     
11  * Written by Craig Edwards, Craig McLure, and others.
12  * This program is free but copyrighted software; see
13  *            the file COPYING for details.
14  *
15  * ---------------------------------------------------
16  */
17
18 #include <sstream>
19 #include <string>
20 #include <deque>
21 #include <map>
22 #include <libpq-fe.h>
23
24 #include "users.h"
25 #include "channels.h"
26 #include "modules.h"
27 #include "helperfuncs.h"
28 #include "inspircd.h"
29 #include "configreader.h"
30
31 #include "m_sqlv2.h"
32
33 /* $ModDesc: PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API */
34 /* $CompileFlags: -I`pg_config --includedir` */
35 /* $LinkerFlags: -L`pg_config --libdir` -lpq */
36
37 /* UGH, UGH, UGH, UGH, UGH, UGH
38  * I'm having trouble seeing how I
39  * can avoid this. The core-defined
40  * constructors for InspSocket just
41  * aren't suitable...and if I'm
42  * reimplementing them I need this so
43  * I can access the socket engine :\
44  */
45 extern InspIRCd* ServerInstance;
46 InspSocket* socket_ref[MAX_DESCRIPTORS];
47
48 /* Forward declare, so we can have the typedef neatly at the top */
49 class SQLConn;
50 /* Also needs forward declaration, as it's used inside SQLconn */
51 class ModulePgSQL;
52
53 typedef std::map<std::string, SQLConn*> ConnMap;
54
55 /* CREAD,       Connecting and wants read event
56  * CWRITE,      Connecting and wants write event
57  * WREAD,       Connected/Working and wants read event
58  * WWRITE,      Connected/Working and wants write event
59  */
60 enum SQLstatus { CREAD, CWRITE, WREAD, WWRITE };
61
62 /** QueryQueue, a queue of queries waiting to be executed.
63  * This maintains two queues internally, one for 'priority'
64  * queries and one for less important ones. Each queue has
65  * new queries appended to it and ones to execute are popped
66  * off the front. This keeps them flowing round nicely and no
67  * query should ever get 'stuck' for too long. If there are
68  * queries in the priority queue they will be executed first,
69  * 'unimportant' queries will only be executed when the
70  * priority queue is empty.
71  *
72  * We store lists of SQLrequest's here, by value as we want to avoid storing
73  * any data allocated inside the client module (in case that module is unloaded
74  * while the query is in progress).
75  *
76  * Because we want to work on the current SQLrequest in-situ, we need a way
77  * of accessing the request we are currently processing, QueryQueue::front(),
78  * but that call needs to always return the same request until that request
79  * is removed from the queue, this is what the 'which' variable is. New queries are
80  * always added to the back of one of the two queues, but if when front()
81  * is first called then the priority queue is empty then front() will return
82  * a query from the normal queue, but if a query is then added to the priority
83  * queue then front() must continue to return the front of the *normal* queue
84  * until pop() is called.
85  */
86
87 class QueryQueue : public classbase
88 {
89 private:
90         std::deque<SQLrequest> priority;        /* The priority queue */
91         std::deque<SQLrequest> normal;  /* The 'normal' queue */
92         enum { PRI, NOR, NON } which;   /* Which queue the currently active element is at the front of */
93
94 public:
95         QueryQueue()
96         : which(NON)
97         {
98         }
99         
100         void push(const SQLrequest &q)
101         {
102                 log(DEBUG, "QueryQueue::push(): Adding %s query to queue: %s", ((q.pri) ? "priority" : "non-priority"), q.query.c_str());
103                 
104                 if(q.pri)
105                         priority.push_back(q);
106                 else
107                         normal.push_back(q);
108         }
109         
110         void pop()
111         {
112                 if((which == PRI) && priority.size())
113                 {
114                         priority.pop_front();
115                 }
116                 else if((which == NOR) && normal.size())
117                 {
118                         normal.pop_front();
119                 }
120                 
121                 /* Reset this */
122                 which = NON;
123                 
124                 /* Silently do nothing if there was no element to pop() */
125         }
126         
127         SQLrequest& front()
128         {
129                 switch(which)
130                 {
131                         case PRI:
132                                 return priority.front();
133                         case NOR:
134                                 return normal.front();
135                         default:
136                                 if(priority.size())
137                                 {
138                                         which = PRI;
139                                         return priority.front();
140                                 }
141                                 
142                                 if(normal.size())
143                                 {
144                                         which = NOR;
145                                         return normal.front();
146                                 }
147                                 
148                                 /* This will probably result in a segfault,
149                                  * but the caller should have checked totalsize()
150                                  * first so..meh - moron :p
151                                  */
152                                 
153                                 return priority.front();
154                 }
155         }
156         
157         std::pair<int, int> size()
158         {
159                 return std::make_pair(priority.size(), normal.size());
160         }
161         
162         int totalsize()
163         {
164                 return priority.size() + normal.size();
165         }
166 };
167
168 /** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult.
169  * All SQL providers must create their own subclass and define it's methods using that
170  * database library's data retriveal functions. The aim is to avoid a slow and inefficient process
171  * of converting all data to a common format before it reaches the result structure. This way
172  * data is passes to the module nearly as directly as if it was using the API directly itself.
173  */
174
175 class PgSQLresult : public SQLresult
176 {
177         PGresult* res;
178         int currentrow;
179         
180         SQLfieldList* fieldlist;
181         SQLfieldMap* fieldmap;
182 public:
183         PgSQLresult(Module* self, Module* to, PGresult* result)
184         : SQLresult(self, to), res(result), currentrow(0), fieldlist(NULL), fieldmap(NULL)
185         {
186                 int rows = PQntuples(res);
187                 int cols = PQnfields(res);
188                 
189                 log(DEBUG, "Created new PgSQL result; %d rows, %d columns", rows, cols);
190         }
191         
192         ~PgSQLresult()
193         {
194                 PQclear(res);
195         }
196         
197         virtual int Rows()
198         {
199                 return PQntuples(res);
200         }
201         
202         virtual int Cols()
203         {
204                 return PQnfields(res);
205         }
206         
207         virtual std::string ColName(int column)
208         {
209                 char* name = PQfname(res, column);
210                 
211                 return (name) ? name : "";
212         }
213         
214         virtual int ColNum(const std::string &column)
215         {
216                 int n = PQfnumber(res, column.c_str());
217                 
218                 if(n == -1)
219                 {
220                         throw SQLbadColName();
221                 }
222                 else
223                 {
224                         return n;
225                 }
226         }
227         
228         virtual SQLfield GetValue(int row, int column)
229         {
230                 char* v = PQgetvalue(res, row, column);
231                 
232                 if(v)
233                 {
234                         return SQLfield(std::string(v, PQgetlength(res, row, column)), PQgetisnull(res, row, column));
235                 }
236                 else
237                 {
238                         log(DEBUG, "PQgetvalue returned a null pointer..nobody wants to tell us what this means");
239                         throw SQLbadColName();
240                 }
241         }
242         
243         virtual SQLfieldList& GetRow()
244         {
245                 /* In an effort to reduce overhead we don't actually allocate the list
246                  * until the first time it's needed...so...
247                  */
248                 if(fieldlist)
249                 {
250                         fieldlist->clear();
251                 }
252                 else
253                 {
254                         fieldlist = new SQLfieldList;
255                 }
256                 
257                 if(currentrow < PQntuples(res))
258                 {
259                         int cols = PQnfields(res);
260                         
261                         for(int i = 0; i < cols; i++)
262                         {
263                                 fieldlist->push_back(GetValue(currentrow, i));
264                         }
265                         
266                         currentrow++;
267                 }
268                 
269                 return *fieldlist;
270         }
271         
272         virtual SQLfieldMap& GetRowMap()
273         {
274                 /* In an effort to reduce overhead we don't actually allocate the map
275                  * until the first time it's needed...so...
276                  */
277                 if(fieldmap)
278                 {
279                         fieldmap->clear();
280                 }
281                 else
282                 {
283                         fieldmap = new SQLfieldMap;
284                 }
285                 
286                 if(currentrow < PQntuples(res))
287                 {
288                         int cols = PQnfields(res);
289                         
290                         for(int i = 0; i < cols; i++)
291                         {
292                                 fieldmap->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
293                         }
294                         
295                         currentrow++;
296                 }
297                 
298                 return *fieldmap;
299         }
300         
301         virtual SQLfieldList* GetRowPtr()
302         {
303                 SQLfieldList* fl = new SQLfieldList;
304                 
305                 if(currentrow < PQntuples(res))
306                 {
307                         int cols = PQnfields(res);
308                         
309                         for(int i = 0; i < cols; i++)
310                         {
311                                 fl->push_back(GetValue(currentrow, i));
312                         }
313                         
314                         currentrow++;
315                 }
316                 
317                 return fl;
318         }
319         
320         virtual SQLfieldMap* GetRowMapPtr()
321         {
322                 SQLfieldMap* fm = new SQLfieldMap;
323                 
324                 if(currentrow < PQntuples(res))
325                 {
326                         int cols = PQnfields(res);
327                         
328                         for(int i = 0; i < cols; i++)
329                         {
330                                 fm->insert(std::make_pair(ColName(i), GetValue(currentrow, i)));
331                         }
332                         
333                         currentrow++;
334                 }
335                 
336                 return fm;
337         }
338         
339         virtual void Free(SQLfieldMap* fm)
340         {
341                 DELETE(fm);
342         }
343         
344         virtual void Free(SQLfieldList* fl)
345         {
346                 DELETE(fl);
347         }
348 };
349
350 /** SQLConn represents one SQL session.
351  * Each session has its own persistent connection to the database.
352  * This is a subclass of InspSocket so it can easily recieve read/write events from the core socket
353  * engine, unlike the original MySQL module this module does not block. Ever. It gets a mild stabbing
354  * if it dares to.
355  */
356
357 class SQLConn : public InspSocket
358 {
359 private:
360         ModulePgSQL* us;                /* Pointer to the SQL provider itself */
361         Server* Srv;                    /* Server* for..uhm..something, maybe */
362         std::string     dbhost; /* Database server hostname */
363         unsigned int    dbport; /* Database server port */
364         std::string     dbname; /* Database name */
365         std::string     dbuser; /* Database username */
366         std::string     dbpass; /* Database password */
367         bool                    ssl;    /* If we should require SSL */
368         PGconn*                 sql;    /* PgSQL database connection handle */
369         SQLstatus               status; /* PgSQL database connection status */
370         bool                    qinprog;/* If there is currently a query in progress */
371         QueryQueue              queue;  /* Queue of queries waiting to be executed on this connection */
372
373 public:
374
375         /* This class should only ever be created inside this module, using this constructor, so we don't have to worry about the default ones */
376
377         SQLConn(ModulePgSQL* self, Server* srv, const std::string &h, unsigned int p, const std::string &d, const std::string &u, const std::string &pwd, bool s);
378
379         ~SQLConn();
380
381         bool DoResolve();
382
383         bool DoConnect();
384
385         virtual void Close();
386         
387         bool DoPoll();
388         
389         bool DoConnectedPoll();
390         
391         void ShowStatus();      
392         
393         virtual bool OnDataReady();
394
395         virtual bool OnWriteReady();
396         
397         virtual bool OnConnected();
398         
399         bool DoEvent();
400         
401         std::string MkInfoStr();
402         
403         const char* StatusStr();
404         
405         SQLerror DoQuery(const SQLrequest &req);
406         
407         SQLerror Query(const SQLrequest &req);
408 };
409
410 class ModulePgSQL : public Module
411 {
412 private:
413         Server* Srv;
414         ConnMap connections;
415         unsigned long currid;
416         char* sqlsuccess;
417
418 public:
419         ModulePgSQL(Server* Me)
420         : Module::Module(Me), Srv(Me), currid(0)
421         {
422                 log(DEBUG, "%s 'SQL' feature", Srv->PublishFeature("SQL", this) ? "Published" : "Couldn't publish");
423                 log(DEBUG, "%s 'PgSQL' feature", Srv->PublishFeature("PgSQL", this) ? "Published" : "Couldn't publish");
424                 
425                 sqlsuccess = new char[strlen(SQLSUCCESS)+1];
426                 
427                 strcpy(sqlsuccess, SQLSUCCESS);
428
429                 OnRehash("");
430         }
431
432         void Implements(char* List)
433         {
434                 List[I_OnRequest] = List[I_OnRehash] = List[I_OnUserRegister] = List[I_OnCheckReady] = List[I_OnUserDisconnect] = 1;
435         }
436
437         virtual void OnRehash(const std::string &parameter)
438         {
439                 ConfigReader conf;
440                 
441                 /* Delete all the SQLConn objects in the connection lists,
442                  * this will call their destructors where they can handle
443                  * closing connections and such.
444                  */
445                 for(ConnMap::iterator iter = connections.begin(); iter != connections.end(); iter++)
446                 {
447                         DELETE(iter->second);
448                 }
449                 
450                 /* Empty out our list of connections */
451                 connections.clear();
452
453                 for(int i = 0; i < conf.Enumerate("database"); i++)
454                 {
455                         std::string id;
456                         SQLConn* newconn;
457                         
458                         id = conf.ReadValue("database", "id", i);
459                         newconn = new SQLConn(this, Srv,
460                                                                                 conf.ReadValue("database", "hostname", i),
461                                                                                 conf.ReadInteger("database", "port", i, true),
462                                                                                 conf.ReadValue("database", "name", i),
463                                                                                 conf.ReadValue("database", "username", i),
464                                                                                 conf.ReadValue("database", "password", i),
465                                                                                 conf.ReadFlag("database", "ssl", i));
466                         
467                         connections.insert(std::make_pair(id, newconn));
468                 }       
469         }
470         
471         virtual char* OnRequest(Request* request)
472         {
473                 if(strcmp(SQLREQID, request->GetData()) == 0)
474                 {
475                         SQLrequest* req = (SQLrequest*)request;
476                         ConnMap::iterator iter;
477                 
478                         log(DEBUG, "Got query: '%s' on id '%s'", req->query.c_str(), req->dbid.c_str());
479
480                         if((iter = connections.find(req->dbid)) != connections.end())
481                         {
482                                 /* Execute query */
483                                 req->error = iter->second->Query(*req);
484                                 req->id = NewID();
485                                 
486                                 return (req->error.Id() == NO_ERROR) ? sqlsuccess : NULL;
487                         }
488                         else
489                         {
490                                 req->error.Id(BAD_DBID);
491                                 return NULL;
492                         }
493                 }
494
495                 log(DEBUG, "Got unsupported API version string: %s", request->GetData());
496                 
497                 return NULL;
498         }
499         
500         unsigned long NewID()
501         {
502                 if (currid+1 == 0)
503                         currid++;
504                 
505                 return ++currid;
506         }
507                 
508         virtual Version GetVersion()
509         {
510                 return Version(1, 0, 0, 0, VF_VENDOR|VF_SERVICEPROVIDER);
511         }
512         
513         virtual ~ModulePgSQL()
514         {
515                 DELETE(sqlsuccess);
516         }       
517 };
518
519 SQLConn::SQLConn(ModulePgSQL* self, Server* srv, const std::string &h, unsigned int p, const std::string &d, const std::string &u, const std::string &pwd, bool s)
520 : InspSocket::InspSocket(), us(self), Srv(srv), dbhost(h), dbport(p), dbname(d), dbuser(u), dbpass(pwd), ssl(s), sql(NULL), status(CWRITE), qinprog(false)
521 {
522         log(DEBUG, "Creating new PgSQL connection to database %s on %s:%u (%s/%s)", dbname.c_str(), dbhost.c_str(), dbport, dbuser.c_str(), dbpass.c_str());
523
524         /* Some of this could be reviewed, unsure if I need to fill 'host' etc...
525          * just copied this over from the InspSocket constructor.
526          */
527         strlcpy(this->host, dbhost.c_str(), MAXBUF);
528         this->port = dbport;
529         
530         this->ClosePending = false;
531         
532         if(!inet_aton(this->host, &this->addy))
533         {
534                 /* Its not an ip, spawn the resolver.
535                  * PgSQL doesn't do nonblocking DNS 
536                  * lookups, so we do it for it.
537                  */
538                 
539                 log(DEBUG,"Attempting to resolve %s", this->host);
540                 
541                 this->dns.SetNS(Srv->GetConfig()->DNSServer);
542                 this->dns.ForwardLookupWithFD(this->host, fd);
543                 
544                 this->state = I_RESOLVING;
545                 socket_ref[this->fd] = this;
546                 
547                 return;
548         }
549         else
550         {
551                 log(DEBUG,"No need to resolve %s", this->host);
552                 strlcpy(this->IP, this->host, MAXBUF);
553                 
554                 if(!this->DoConnect())
555                 {
556                         throw ModuleException("Connect failed");
557                 }
558         }
559 }
560
561 SQLConn::~SQLConn()
562 {
563         Close();
564 }
565
566 bool SQLConn::DoResolve()
567 {       
568         log(DEBUG, "Checking for DNS lookup result");
569         
570         if(this->dns.HasResult())
571         {
572                 std::string res_ip = dns.GetResultIP();
573                 
574                 if(res_ip.length())
575                 {
576                         log(DEBUG, "Got result: %s", res_ip.c_str());
577                         
578                         strlcpy(this->IP, res_ip.c_str(), MAXBUF);
579                         dbhost = res_ip;
580                         
581                         socket_ref[this->fd] = NULL;
582                         
583                         return this->DoConnect();
584                 }
585                 else
586                 {
587                         log(DEBUG, "DNS lookup failed, dying horribly");
588                         Close();
589                         return false;
590                 }
591         }
592         else
593         {
594                 log(DEBUG, "No result for lookup yet!");
595                 return true;
596         }
597 }
598
599 bool SQLConn::DoConnect()
600 {
601         log(DEBUG, "SQLConn::DoConnect()");
602         
603         if(!(sql = PQconnectStart(MkInfoStr().c_str())))
604         {
605                 log(DEBUG, "Couldn't allocate PGconn structure, aborting: %s", PQerrorMessage(sql));
606                 Close();
607                 return false;
608         }
609         
610         if(PQstatus(sql) == CONNECTION_BAD)
611         {
612                 log(DEBUG, "PQconnectStart failed: %s", PQerrorMessage(sql));
613                 Close();
614                 return false;
615         }
616         
617         ShowStatus();
618         
619         if(PQsetnonblocking(sql, 1) == -1)
620         {
621                 log(DEBUG, "Couldn't set connection nonblocking: %s", PQerrorMessage(sql));
622                 Close();
623                 return false;
624         }
625         
626         /* OK, we've initalised the connection, now to get it hooked into the socket engine
627          * and then start polling it.
628          */
629         
630         log(DEBUG, "Old DNS socket: %d", this->fd);
631         this->fd = PQsocket(sql);
632         log(DEBUG, "New SQL socket: %d", this->fd);
633         
634         if(this->fd <= -1)
635         {
636                 log(DEBUG, "PQsocket says we have an invalid FD: %d", this->fd);
637                 Close();
638                 return false;
639         }
640         
641         this->state = I_CONNECTING;
642         ServerInstance->SE->AddFd(this->fd,false,X_ESTAB_MODULE);
643         socket_ref[this->fd] = this;
644         
645         /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
646         
647         return DoPoll();
648 }
649
650 void SQLConn::Close()
651 {
652         log(DEBUG,"SQLConn::Close");
653         
654         if(this->fd > 01)
655                 socket_ref[this->fd] = NULL;
656         this->fd = -1;
657         this->state = I_ERROR;
658         this->OnError(I_ERR_SOCKET);
659         this->ClosePending = true;
660         
661         if(sql)
662         {
663                 PQfinish(sql);
664                 sql = NULL;
665         }
666         
667         return;
668 }
669
670 bool SQLConn::DoPoll()
671 {
672         switch(PQconnectPoll(sql))
673         {
674                 case PGRES_POLLING_WRITING:
675                         log(DEBUG, "PGconnectPoll: PGRES_POLLING_WRITING");
676                         WantWrite();
677                         status = CWRITE;
678                         return DoPoll();
679                 case PGRES_POLLING_READING:
680                         log(DEBUG, "PGconnectPoll: PGRES_POLLING_READING");
681                         status = CREAD;
682                         break;
683                 case PGRES_POLLING_FAILED:
684                         log(DEBUG, "PGconnectPoll: PGRES_POLLING_FAILED: %s", PQerrorMessage(sql));
685                         return false;
686                 case PGRES_POLLING_OK:
687                         log(DEBUG, "PGconnectPoll: PGRES_POLLING_OK");
688                         status = WWRITE;
689                         return DoConnectedPoll();
690                 default:
691                         log(DEBUG, "PGconnectPoll: wtf?");
692                         break;
693         }
694         
695         return true;
696 }
697
698 bool SQLConn::DoConnectedPoll()
699 {
700         if(!qinprog && queue.totalsize())
701         {
702                 /* There's no query currently in progress, and there's queries in the queue. */
703                 SQLrequest& query = queue.front();
704                 DoQuery(query);
705         }
706         
707         if(PQconsumeInput(sql))
708         {
709                 log(DEBUG, "PQconsumeInput succeeded");
710                         
711                 if(PQisBusy(sql))
712                 {
713                         log(DEBUG, "Still busy processing command though");
714                 }
715                 else if(qinprog)
716                 {
717                         log(DEBUG, "Looks like we have a result to process!");
718                         
719                         /* Grab the request we're processing */
720                         SQLrequest& query = queue.front();
721                         
722                         /* Get a pointer to the module we're about to return the result to */
723                         Module* to = query.GetSource();
724                         
725                         /* Fetch the result.. */
726                         PGresult* result = PQgetResult(sql);
727                         
728                         /* PgSQL would allow a query string to be sent which has multiple
729                          * queries in it, this isn't portable across database backends and
730                          * we don't want modules doing it. But just in case we make sure we
731                          * drain any results there are and just use the last one.
732                          * If the module devs are behaving there will only be one result.
733                          */
734                         while (PGresult* temp = PQgetResult(sql))
735                         {
736                                 PQclear(result);
737                                 result = temp;
738                         }
739                         
740                         if(to)
741                         {
742                                 /* ..and the result */
743                                 log(DEBUG, "Got result, status code: %s; error message: %s", PQresStatus(PQresultStatus(result)), PQresultErrorMessage(result));
744                                         
745                                 PgSQLresult reply(us, to, result);
746                                 
747                                 reply.Send();
748                                 
749                                 /* PgSQLresult's destructor will free the PGresult */
750                         }
751                         else
752                         {
753                                 /* If the client module is unloaded partway through a query then the provider will set
754                                  * the pointer to NULL. We cannot just cancel the query as the result will still come
755                                  * through at some point...and it could get messy if we play with invalid pointers...
756                                  */
757                                 log(DEBUG, "Looks like we're handling a zombie query from a module which unloaded before it got a result..fun. ID: %lu", query.id);
758                                 PQclear(result);
759                         }
760                         
761                         qinprog = false;
762                         queue.pop();                            
763                         DoConnectedPoll();
764                 }
765                 
766                 return true;
767         }
768         
769         log(DEBUG, "PQconsumeInput failed: %s", PQerrorMessage(sql));
770         return false;
771 }
772
773 void SQLConn::ShowStatus()
774 {
775         switch(PQstatus(sql))
776         {
777                 case CONNECTION_STARTED:
778                         log(DEBUG, "PQstatus: CONNECTION_STARTED: Waiting for connection to be made.");
779                         break;
780
781                 case CONNECTION_MADE:
782                         log(DEBUG, "PQstatus: CONNECTION_MADE: Connection OK; waiting to send.");
783                         break;
784                 
785                 case CONNECTION_AWAITING_RESPONSE:
786                         log(DEBUG, "PQstatus: CONNECTION_AWAITING_RESPONSE: Waiting for a response from the server.");
787                         break;
788                 
789                 case CONNECTION_AUTH_OK:
790                         log(DEBUG, "PQstatus: CONNECTION_AUTH_OK: Received authentication; waiting for backend start-up to finish.");
791                         break;
792                 
793                 case CONNECTION_SSL_STARTUP:
794                         log(DEBUG, "PQstatus: CONNECTION_SSL_STARTUP: Negotiating SSL encryption.");
795                         break;
796                 
797                 case CONNECTION_SETENV:
798                         log(DEBUG, "PQstatus: CONNECTION_SETENV: Negotiating environment-driven parameter settings.");
799                         break;
800                 
801                 default:
802                         log(DEBUG, "PQstatus: ???");
803         }
804 }
805
806 bool SQLConn::OnDataReady()
807 {
808         /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
809         log(DEBUG, "OnDataReady(): status = %s", StatusStr());
810         
811         return DoEvent();
812 }
813
814 bool SQLConn::OnWriteReady()
815 {
816         /* Always return true here, false would close the socket - we need to do that ourselves with the pgsql API */
817         log(DEBUG, "OnWriteReady(): status = %s", StatusStr());
818         
819         return DoEvent();
820 }
821
822 bool SQLConn::OnConnected()
823 {
824         log(DEBUG, "OnConnected(): status = %s", StatusStr());
825         
826         return DoEvent();
827 }
828
829 bool SQLConn::DoEvent()
830 {
831         bool ret;
832         
833         if((status == CREAD) || (status == CWRITE))
834         {
835                 ret = DoPoll();
836         }
837         else
838         {
839                 ret = DoConnectedPoll();
840         }
841         
842         switch(PQflush(sql))
843         {
844                 case -1:
845                         log(DEBUG, "Error flushing write queue: %s", PQerrorMessage(sql));
846                         break;
847                 case 0:
848                         log(DEBUG, "Successfully flushed write queue (or there was nothing to write)");
849                         break;
850                 case 1:
851                         log(DEBUG, "Not all of the write queue written, triggering write event so we can have another go");
852                         WantWrite();
853                         break;
854         }
855
856         return ret;
857 }
858
859 std::string SQLConn::MkInfoStr()
860 {                       
861         std::ostringstream conninfo("connect_timeout = '2'");
862         
863         if(dbhost.length())
864                 conninfo << " hostaddr = '" << dbhost << "'";
865         
866         if(dbport)
867                 conninfo << " port = '" << dbport << "'";
868         
869         if(dbname.length())
870                 conninfo << " dbname = '" << dbname << "'";
871         
872         if(dbuser.length())
873                 conninfo << " user = '" << dbuser << "'";
874         
875         if(dbpass.length())
876                 conninfo << " password = '" << dbpass << "'";
877         
878         if(ssl)
879                 conninfo << " sslmode = 'require'";
880         
881         return conninfo.str();
882 }
883
884 const char* SQLConn::StatusStr()
885 {
886         if(status == CREAD) return "CREAD";
887         if(status == CWRITE) return "CWRITE";
888         if(status == WREAD) return "WREAD";
889         if(status == WWRITE) return "WWRITE";
890         return "Err...what, erm..BUG!";
891 }
892
893 SQLerror SQLConn::DoQuery(const SQLrequest &req)
894 {
895         if((status == WREAD) || (status == WWRITE))
896         {
897                 if(!qinprog)
898                 {
899                         if(PQsendQuery(sql, req.query.c_str()))
900                         {
901                                 log(DEBUG, "Dispatched query: %s", req.query.c_str());
902                                 qinprog = true;
903                                 return SQLerror();
904                         }
905                         else
906                         {
907                                 log(DEBUG, "Failed to dispatch query: %s", PQerrorMessage(sql));
908                                 return SQLerror(QSEND_FAIL, PQerrorMessage(sql));
909                         }
910                 }
911         }
912
913         log(DEBUG, "Can't query until connection is complete");
914         return SQLerror(BAD_CONN, "Can't query until connection is complete");
915 }
916
917 SQLerror SQLConn::Query(const SQLrequest &req)
918 {
919         queue.push(req);
920         
921         if(!qinprog && queue.totalsize())
922         {
923                 /* There's no query currently in progress, and there's queries in the queue. */
924                 SQLrequest& query = queue.front();
925                 return DoQuery(query);
926         }
927         else
928         {
929                 return SQLerror();
930         }
931 }
932
933 class ModulePgSQLFactory : public ModuleFactory
934 {
935  public:
936         ModulePgSQLFactory()
937         {
938         }
939         
940         ~ModulePgSQLFactory()
941         {
942         }
943         
944         virtual Module * CreateModule(Server* Me)
945         {
946                 return new ModulePgSQL(Me);
947         }
948 };
949
950
951 extern "C" void * init_module( void )
952 {
953         return new ModulePgSQLFactory;
954 }