2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2016 Adam <Adam@anope.org>
5 * Copyright (C) 2015 Daniel Vassdal <shutter@canternet.org>
6 * Copyright (C) 2013, 2016-2019 Sadie Powell <sadie@witchery.services>
7 * Copyright (C) 2012-2015 Attila Molnar <attilamolnar@hush.com>
8 * Copyright (C) 2012 Robby <robby@chatbelgie.be>
9 * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
10 * Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
11 * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
12 * Copyright (C) 2007, 2009-2010 Craig Edwards <brain@inspircd.org>
13 * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
14 * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
15 * Copyright (C) 2006 Oliver Lupton <om@inspircd.org>
17 * This file is part of InspIRCd. InspIRCd is free software: you can
18 * redistribute it and/or modify it under the terms of the GNU General Public
19 * License as published by the Free Software Foundation, version 2.
21 * This program is distributed in the hope that it will be useful, but WITHOUT
22 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
23 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
26 * You should have received a copy of the GNU General Public License
27 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 /// $CompilerFlags: -Iexecute("pg_config --includedir" "POSTGRESQL_INCLUDE_DIR")
31 /// $LinkerFlags: -Lexecute("pg_config --libdir" "POSTGRESQL_LIBRARY_DIR") -lpq
33 /// $PackageInfo: require_system("arch") postgresql-libs
34 /// $PackageInfo: require_system("centos") postgresql-devel
35 /// $PackageInfo: require_system("darwin") postgresql
36 /// $PackageInfo: require_system("debian") libpq-dev
37 /// $PackageInfo: require_system("ubuntu") libpq-dev
43 #include "modules/sql.h"
45 /* SQLConn rewritten by peavey to
46 * use EventHandler instead of
47 * BufferedSocket. This is much neater
48 * and gives total control of destroy
49 * and delete of resources.
52 /* Forward declare, so we can have the typedef neatly at the top */
56 typedef insp::flat_map<std::string, SQLConn*> ConnMap;
60 // The connection has died.
63 // Connecting and wants read event.
66 // Connecting and wants write event.
69 // Connected/working and wants read event.
72 // Connected/working and wants write event.
76 class ReconnectTimer : public Timer
81 ReconnectTimer(ModulePgSQL* m) : Timer(5, false), mod(m)
84 bool Tick(time_t TIME) CXX11_OVERRIDE;
91 QueueItem(SQL::Query* C, const std::string& Q) : c(C), q(Q) {}
94 /** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult.
95 * All SQL providers must create their own subclass and define it's methods using that
96 * database library's data retriveal functions. The aim is to avoid a slow and inefficient process
97 * of converting all data to a common format before it reaches the result structure. This way
98 * data is passes to the module nearly as directly as if it was using the API directly itself.
101 class PgSQLresult : public SQL::Result
106 std::vector<std::string> colnames;
110 colnames.resize(PQnfields(res));
111 for(unsigned int i=0; i < colnames.size(); i++)
113 colnames[i] = PQfname(res, i);
117 PgSQLresult(PGresult* result) : res(result), currentrow(0)
119 rows = PQntuples(res);
121 rows = ConvToNum<int>(PQcmdTuples(res));
129 int Rows() CXX11_OVERRIDE
134 void GetCols(std::vector<std::string>& result) CXX11_OVERRIDE
136 if (colnames.empty())
141 bool HasColumn(const std::string& column, size_t& index) CXX11_OVERRIDE
143 if (colnames.empty())
146 for (size_t i = 0; i < colnames.size(); ++i)
148 if (colnames[i] == column)
157 SQL::Field GetValue(int row, int column)
159 char* v = PQgetvalue(res, row, column);
160 if (!v || PQgetisnull(res, row, column))
163 return SQL::Field(std::string(v, PQgetlength(res, row, column)));
166 bool GetRow(SQL::Row& result) CXX11_OVERRIDE
168 if (currentrow >= PQntuples(res))
170 int ncols = PQnfields(res);
172 for(int i = 0; i < ncols; i++)
174 result.push_back(GetValue(currentrow, i));
182 /** SQLConn represents one SQL session.
184 class SQLConn : public SQL::Provider, public EventHandler
187 reference<ConfigTag> conf; /* The <database> entry */
188 std::deque<QueueItem> queue;
189 PGconn* sql; /* PgSQL database connection handle */
190 SQLstatus status; /* PgSQL database connection status */
191 QueueItem qinprog; /* If there is currently a query in progress */
193 SQLConn(Module* Creator, ConfigTag* tag)
194 : SQL::Provider(Creator, tag->getString("id"))
202 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: Could not connect to database " + tag->getString("id"));
207 CullResult cull() CXX11_OVERRIDE
209 this->SQL::Provider::cull();
210 ServerInstance->Modules->DelService(*this);
211 return this->EventHandler::cull();
216 SQL::Error err(SQL::BAD_DBID);
219 qinprog.c->OnError(err);
222 for(std::deque<QueueItem>::iterator i = queue.begin(); i != queue.end(); i++)
224 SQL::Query* q = i->c;
231 void OnEventHandlerRead() CXX11_OVERRIDE
236 void OnEventHandlerWrite() CXX11_OVERRIDE
241 void OnEventHandlerError(int errornum) CXX11_OVERRIDE
248 std::ostringstream conninfo("connect_timeout = '5'");
251 if (conf->readString("host", item))
252 conninfo << " host = '" << item << "'";
254 if (conf->readString("port", item))
255 conninfo << " port = '" << item << "'";
257 if (conf->readString("name", item))
258 conninfo << " dbname = '" << item << "'";
260 if (conf->readString("user", item))
261 conninfo << " user = '" << item << "'";
263 if (conf->readString("pass", item))
264 conninfo << " password = '" << item << "'";
266 if (conf->getBool("ssl"))
267 conninfo << " sslmode = 'require'";
269 conninfo << " sslmode = 'disable'";
271 return conninfo.str();
276 sql = PQconnectStart(GetDSN().c_str());
280 if(PQstatus(sql) == CONNECTION_BAD)
283 if(PQsetnonblocking(sql, 1) == -1)
286 /* OK, we've initialised the connection, now to get it hooked into the socket engine
287 * and then start polling it.
289 this->fd = PQsocket(sql);
294 if (!SocketEngine::AddFd(this, FD_WANT_NO_WRITE | FD_WANT_NO_READ))
296 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BUG: Couldn't add pgsql socket to socket engine");
300 /* Socket all hooked into the engine, now to tell PgSQL to start connecting */
306 switch(PQconnectPoll(sql))
308 case PGRES_POLLING_WRITING:
309 SocketEngine::ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ);
312 case PGRES_POLLING_READING:
313 SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
316 case PGRES_POLLING_FAILED:
317 SocketEngine::ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE);
320 case PGRES_POLLING_OK:
321 SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
330 void DoConnectedPoll()
333 while (qinprog.q.empty() && !queue.empty())
335 /* There's no query currently in progress, and there's queries in the queue. */
336 DoQuery(queue.front());
340 if (PQconsumeInput(sql))
344 /* Nothing happens here */
348 /* Fetch the result.. */
349 PGresult* result = PQgetResult(sql);
351 /* PgSQL would allow a query string to be sent which has multiple
352 * queries in it, this isn't portable across database backends and
353 * we don't want modules doing it. But just in case we make sure we
354 * drain any results there are and just use the last one.
355 * If the module devs are behaving there will only be one result.
357 while (PGresult* temp = PQgetResult(sql))
363 /* ..and the result */
364 PgSQLresult reply(result);
365 switch(PQresultStatus(result))
367 case PGRES_EMPTY_QUERY:
368 case PGRES_BAD_RESPONSE:
369 case PGRES_FATAL_ERROR:
371 SQL::Error err(SQL::QREPLY_FAIL, PQresultErrorMessage(result));
372 qinprog.c->OnError(err);
376 /* Other values are not errors */
377 qinprog.c->OnResult(reply);
381 qinprog = QueueItem(NULL, "");
391 /* I think we'll assume this means the server died...it might not,
392 * but I think that any error serious enough we actually get here
393 * deserves to reconnect [/excuse]
394 * Returning true so the core doesn't try and close the connection.
400 void DelayReconnect();
404 if((status == CREAD) || (status == CWRITE))
408 else if (status == WREAD || status == WWRITE)
414 void Submit(SQL::Query *req, const std::string& q) CXX11_OVERRIDE
416 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Executing PostgreSQL query: " + q);
417 if (qinprog.q.empty())
419 DoQuery(QueueItem(req,q));
424 queue.push_back(QueueItem(req,q));
428 void Submit(SQL::Query *req, const std::string& q, const SQL::ParamList& p) CXX11_OVERRIDE
431 unsigned int param = 0;
432 for(std::string::size_type i = 0; i < q.length(); i++)
438 if (param < p.size())
440 std::string parm = p[param++];
441 std::vector<char> buffer(parm.length() * 2 + 1);
443 size_t escapedsize = PQescapeStringConn(sql, &buffer[0], parm.data(), parm.length(), &error);
445 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BUG: Apparently PQescapeStringConn() failed");
446 res.append(&buffer[0], escapedsize);
453 void Submit(SQL::Query *req, const std::string& q, const SQL::ParamMap& p) CXX11_OVERRIDE
456 for(std::string::size_type i = 0; i < q.length(); i++)
464 while (i < q.length() && isalnum(q[i]))
465 field.push_back(q[i++]);
468 SQL::ParamMap::const_iterator it = p.find(field);
471 std::string parm = it->second;
472 std::vector<char> buffer(parm.length() * 2 + 1);
474 size_t escapedsize = PQescapeStringConn(sql, &buffer[0], parm.data(), parm.length(), &error);
476 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BUG: Apparently PQescapeStringConn() failed");
477 res.append(&buffer[0], escapedsize);
484 void DoQuery(const QueueItem& req)
486 if (status != WREAD && status != WWRITE)
488 // whoops, not connected...
489 SQL::Error err(SQL::BAD_CONN);
495 if(PQsendQuery(sql, req.q.c_str()))
501 SQL::Error err(SQL::QSEND_FAIL, PQerrorMessage(sql));
511 if (HasFd() && SocketEngine::HasFd(GetFd()))
512 SocketEngine::DelFd(this);
522 class ModulePgSQL : public Module
526 ReconnectTimer* retimer;
536 ClearAllConnections();
539 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
547 ConfigTagList tags = ServerInstance->Config->ConfTags("database");
548 for(ConfigIter i = tags.first; i != tags.second; i++)
550 if (!stdalgo::string::equalsci(i->second->getString("module"), "pgsql"))
552 std::string id = i->second->getString("id");
553 ConnMap::iterator curr = connections.find(id);
554 if (curr == connections.end())
556 SQLConn* conn = new SQLConn(this, i->second);
557 conns.insert(std::make_pair(id, conn));
558 ServerInstance->Modules->AddService(*conn);
563 connections.erase(curr);
566 ClearAllConnections();
567 conns.swap(connections);
570 void ClearAllConnections()
572 for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++)
580 void OnUnloadModule(Module* mod) CXX11_OVERRIDE
582 SQL::Error err(SQL::BAD_DBID);
583 for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++)
585 SQLConn* conn = i->second;
586 if (conn->qinprog.c && conn->qinprog.c->creator == mod)
588 conn->qinprog.c->OnError(err);
589 delete conn->qinprog.c;
590 conn->qinprog.c = NULL;
592 std::deque<QueueItem>::iterator j = conn->queue.begin();
593 while (j != conn->queue.end())
595 SQL::Query* q = j->c;
596 if (q->creator == mod)
600 j = conn->queue.erase(j);
608 Version GetVersion() CXX11_OVERRIDE
610 return Version("Provides the ability for SQL modules to query a PostgreSQL database.", VF_VENDOR);
614 bool ReconnectTimer::Tick(time_t time)
622 void SQLConn::DelayReconnect()
625 ModulePgSQL* mod = (ModulePgSQL*)(Module*)creator;
626 ConnMap::iterator it = mod->connections.find(conf->getString("id"));
627 if (it != mod->connections.end())
629 mod->connections.erase(it);
630 ServerInstance->GlobalCulls.AddItem((EventHandler*)this);
633 mod->retimer = new ReconnectTimer(mod);
634 ServerInstance->Timers.AddTimer(mod->retimer);
639 MODULE_INIT(ModulePgSQL)