1 /* +------------------------------------+
2 * | Inspire Internet Relay Chat Daemon |
3 * +------------------------------------+
5 * InspIRCd: (C) 2002-2010 InspIRCd Development Team
6 * See: http://wiki.inspircd.org/Credits
8 * This program is free but copyrighted software; see
9 * the file COPYING for details.
11 * ---------------------------------------------------
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 */
24 /* SQLConn rewritten by peavey to
25 * use EventHandler instead of
26 * BufferedSocket. This is much neater
27 * and gives total control of destroy
28 * and delete of resources.
31 /* Forward declare, so we can have the typedef neatly at the top */
35 typedef std::map<std::string, SQLConn*> ConnMap;
37 /* CREAD, Connecting and wants read event
38 * CWRITE, Connecting and wants write event
39 * WREAD, Connected/Working and wants read event
40 * WWRITE, Connected/Working and wants write event
41 * RREAD, Resetting and wants read event
42 * RWRITE, Resetting and wants write event
44 enum SQLstatus { CREAD, CWRITE, WREAD, WWRITE, RREAD, RWRITE };
46 class ReconnectTimer : public Timer
51 ReconnectTimer(ModulePgSQL* m) : Timer(5, ServerInstance->Time(), false), mod(m)
54 virtual void Tick(time_t TIME);
61 QueueItem(SQLQuery* C, const std::string& Q) : c(C), q(Q) {}
64 /** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult.
65 * All SQL providers must create their own subclass and define it's methods using that
66 * database library's data retriveal functions. The aim is to avoid a slow and inefficient process
67 * of converting all data to a common format before it reaches the result structure. This way
68 * data is passes to the module nearly as directly as if it was using the API directly itself.
71 class PgSQLresult : public SQLResult
77 PgSQLresult(PGresult* result) : res(result), currentrow(0)
79 rows = PQntuples(res);
81 rows = atoi(PQcmdTuples(res));
94 virtual void GetCols(std::vector<std::string>& result)
96 result.resize(PQnfields(res));
97 for(unsigned int i=0; i < result.size(); i++)
99 result[i] = PQfname(res, i);
103 virtual SQLEntry GetValue(int row, int column)
105 char* v = PQgetvalue(res, row, column);
106 if (!v || PQgetisnull(res, row, column))
109 return SQLEntry(std::string(v, PQgetlength(res, row, column)));
112 virtual bool GetRow(SQLEntries& result)
114 if (currentrow >= PQntuples(res))
116 int ncols = PQnfields(res);
118 for(int i = 0; i < ncols; i++)
120 result.push_back(GetValue(currentrow, i));
128 /** SQLConn represents one SQL session.
130 class SQLConn : public SQLProvider, public EventHandler
133 reference<ConfigTag> conf; /* The <database> entry */
134 std::deque<QueueItem> queue;
135 PGconn* sql; /* PgSQL database connection handle */
136 SQLstatus status; /* PgSQL database connection status */
137 QueueItem qinprog; /* If there is currently a query in progress */
139 SQLConn(Module* Creator, ConfigTag* tag)
140 : SQLProvider(Creator, "SQL/" + tag->getString("id")), conf(tag), sql(NULL), status(CWRITE), qinprog(NULL, "")
144 ServerInstance->Logs->Log("m_pgsql",DEFAULT, "WARNING: Could not connect to database " + tag->getString("id"));
151 this->SQLProvider::cull();
152 ServerInstance->Modules->DelService(*this);
153 return this->EventHandler::cull();
158 SQLerror err(SQL_BAD_DBID);
161 qinprog.c->OnError(err);
164 for(std::deque<QueueItem>::iterator i = queue.begin(); i != queue.end(); i++)
172 virtual void HandleEvent(EventType et, int errornum)
188 std::ostringstream conninfo("connect_timeout = '5'");
191 if (conf->readString("host", item))
192 conninfo << " host = '" << item << "'";
194 if (conf->readString("port", item))
195 conninfo << " port = '" << item << "'";
197 if (conf->readString("name", item))
198 conninfo << " dbname = '" << item << "'";
200 if (conf->readString("user", item))
201 conninfo << " user = '" << item << "'";
203 if (conf->readString("pass", item))
204 conninfo << " password = '" << item << "'";
206 if (conf->getBool("ssl"))
207 conninfo << " sslmode = 'require'";
209 conninfo << " sslmode = 'disable'";
211 return conninfo.str();
216 sql = PQconnectStart(GetDSN().c_str());
220 if(PQstatus(sql) == CONNECTION_BAD)
223 if(PQsetnonblocking(sql, 1) == -1)
226 /* OK, we've initalised the connection, now to get it hooked into the socket engine
227 * and then start polling it.
229 this->fd = PQsocket(sql);
234 if (!ServerInstance->SE->AddFd(this, FD_WANT_NO_WRITE | FD_WANT_NO_READ))
236 ServerInstance->Logs->Log("m_pgsql",DEBUG, "BUG: Couldn't add pgsql socket to socket engine");
240 /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
246 switch(PQconnectPoll(sql))
248 case PGRES_POLLING_WRITING:
249 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ);
252 case PGRES_POLLING_READING:
253 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
256 case PGRES_POLLING_FAILED:
258 case PGRES_POLLING_OK:
259 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
267 void DoConnectedPoll()
270 while (qinprog.q.empty() && !queue.empty())
272 /* There's no query currently in progress, and there's queries in the queue. */
273 DoQuery(queue.front());
277 if (PQconsumeInput(sql))
281 /* Nothing happens here */
285 /* Fetch the result.. */
286 PGresult* result = PQgetResult(sql);
288 /* PgSQL would allow a query string to be sent which has multiple
289 * queries in it, this isn't portable across database backends and
290 * we don't want modules doing it. But just in case we make sure we
291 * drain any results there are and just use the last one.
292 * If the module devs are behaving there will only be one result.
294 while (PGresult* temp = PQgetResult(sql))
300 /* ..and the result */
301 PgSQLresult reply(result);
302 switch(PQresultStatus(result))
304 case PGRES_EMPTY_QUERY:
305 case PGRES_BAD_RESPONSE:
306 case PGRES_FATAL_ERROR:
308 SQLerror err(SQL_QREPLY_FAIL, PQresultErrorMessage(result));
309 qinprog.c->OnError(err);
313 /* Other values are not errors */
314 qinprog.c->OnResult(reply);
318 qinprog = QueueItem(NULL, "");
328 /* I think we'll assume this means the server died...it might not,
329 * but I think that any error serious enough we actually get here
330 * deserves to reconnect [/excuse]
331 * Returning true so the core doesn't try and close the connection.
339 switch(PQresetPoll(sql))
341 case PGRES_POLLING_WRITING:
342 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ);
345 case PGRES_POLLING_READING:
346 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
349 case PGRES_POLLING_FAILED:
351 case PGRES_POLLING_OK:
352 ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
360 void DelayReconnect();
364 if((status == CREAD) || (status == CWRITE))
368 else if((status == RREAD) || (status == RWRITE))
378 void submit(SQLQuery *req, const std::string& q)
380 if (qinprog.q.empty())
382 DoQuery(QueueItem(req,q));
387 queue.push_back(QueueItem(req,q));
391 void submit(SQLQuery *req, const std::string& q, const ParamL& p)
394 unsigned int param = 0;
395 for(std::string::size_type i = 0; i < q.length(); i++)
401 if (param < p.size())
403 std::string parm = p[param++];
405 #ifdef PGSQL_HAS_ESCAPECONN
407 PQescapeStringConn(sql, buffer, parm.c_str(), parm.length(), &error);
409 ServerInstance->Logs->Log("m_pgsql", DEBUG, "BUG: Apparently PQescapeStringConn() failed");
411 PQescapeString (buffer, parm.c_str(), parm.length());
420 void submit(SQLQuery *req, const std::string& q, const ParamM& p)
423 for(std::string::size_type i = 0; i < q.length(); i++)
431 while (i < q.length() && isalnum(q[i]))
432 field.push_back(q[i++]);
435 ParamM::const_iterator it = p.find(field);
438 std::string parm = it->second;
440 #ifdef PGSQL_HAS_ESCAPECONN
442 PQescapeStringConn(sql, buffer, parm.c_str(), parm.length(), &error);
444 ServerInstance->Logs->Log("m_pgsql", DEBUG, "BUG: Apparently PQescapeStringConn() failed");
446 PQescapeString (buffer, parm.c_str(), parm.length());
455 void DoQuery(const QueueItem& req)
457 if (status != WREAD && status != WWRITE)
459 // whoops, not connected...
460 SQLerror err(SQL_BAD_CONN);
466 if(PQsendQuery(sql, req.q.c_str()))
472 SQLerror err(SQL_QSEND_FAIL, PQerrorMessage(sql));
480 ServerInstance->SE->DelFd(this);
490 class ModulePgSQL : public Module
494 ReconnectTimer* retimer;
504 Implementation eventlist[] = { I_OnUnloadModule, I_OnRehash };
505 ServerInstance->Modules->Attach(eventlist, this, 2);
508 virtual ~ModulePgSQL()
511 ServerInstance->Timers->DelTimer(retimer);
512 ClearAllConnections();
515 virtual void OnRehash(User* user)
523 ConfigTagList tags = ServerInstance->Config->ConfTags("database");
524 for(ConfigIter i = tags.first; i != tags.second; i++)
526 if (i->second->getString("module", "pgsql") != "pgsql")
528 std::string id = i->second->getString("id");
529 ConnMap::iterator curr = connections.find(id);
530 if (curr == connections.end())
532 SQLConn* conn = new SQLConn(this, i->second);
533 conns.insert(std::make_pair(id, conn));
534 ServerInstance->Modules->AddService(*conn);
539 connections.erase(curr);
542 ClearAllConnections();
543 conns.swap(connections);
546 void ClearAllConnections()
548 for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++)
556 void OnUnloadModule(Module* mod)
558 SQLerror err(SQL_BAD_DBID);
559 for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++)
561 SQLConn* conn = i->second;
562 if (conn->qinprog.c && conn->qinprog.c->creator == mod)
564 conn->qinprog.c->OnError(err);
565 delete conn->qinprog.c;
566 conn->qinprog.c = NULL;
568 std::deque<QueueItem>::iterator j = conn->queue.begin();
569 while (j != conn->queue.end())
572 if (q->creator == mod)
576 j = conn->queue.erase(j);
586 return Version("PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API", VF_VENDOR);
590 void ReconnectTimer::Tick(time_t time)
596 void SQLConn::DelayReconnect()
598 ModulePgSQL* mod = (ModulePgSQL*)(Module*)creator;
599 ConnMap::iterator it = mod->connections.find(conf->getString("id"));
600 if (it != mod->connections.end())
602 mod->connections.erase(it);
603 ServerInstance->GlobalCulls.AddItem((EventHandler*)this);
606 mod->retimer = new ReconnectTimer(mod);
607 ServerInstance->Timers->AddTimer(mod->retimer);
612 MODULE_INIT(ModulePgSQL)