/*
* InspIRCd -- Internet Relay Chat Daemon
*
+ * Copyright (C) 2016 Adam <Adam@anope.org>
+ * Copyright (C) 2015 Daniel Vassdal <shutter@canternet.org>
+ * Copyright (C) 2013, 2016-2020 Sadie Powell <sadie@witchery.services>
+ * Copyright (C) 2012-2015 Attila Molnar <attilamolnar@hush.com>
+ * Copyright (C) 2012 Robby <robby@chatbelgie.be>
* Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
- * Copyright (C) 2006-2007, 2009 Dennis Friis <peavey@inspircd.org>
- * Copyright (C) 2006-2007, 2009 Craig Edwards <craigedwards@brainbox.cc>
- * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
* Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
- * Copyright (C) 2006 Oliver Lupton <oliverlupton@gmail.com>
+ * Copyright (C) 2007, 2009-2010 Craig Edwards <brain@inspircd.org>
+ * Copyright (C) 2007, 2009 Dennis Friis <peavey@inspircd.org>
+ * Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2006 Oliver Lupton <om@inspircd.org>
*
* This file is part of InspIRCd. InspIRCd is free software: you can
* redistribute it and/or modify it under the terms of the GNU General Public
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+/// $CompilerFlags: -Iexecute("pg_config --includedir" "POSTGRESQL_INCLUDE_DIR")
+/// $LinkerFlags: -Lexecute("pg_config --libdir" "POSTGRESQL_LIBRARY_DIR") -lpq
+
+/// $PackageInfo: require_system("arch") postgresql-libs
+/// $PackageInfo: require_system("centos") postgresql-devel
+/// $PackageInfo: require_system("darwin") postgresql
+/// $PackageInfo: require_system("debian") libpq-dev
+/// $PackageInfo: require_system("ubuntu") libpq-dev
+
#include "inspircd.h"
#include <cstdlib>
-#include <sstream>
#include <libpq-fe.h>
#include "modules/sql.h"
-/* $ModDesc: PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API */
-/* $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));") */
-/* $LinkerFlags: -Lexec("pg_config --libdir") -lpq */
-
/* SQLConn rewritten by peavey to
* use EventHandler instead of
* BufferedSocket. This is much neater
class SQLConn;
class ModulePgSQL;
-typedef std::map<std::string, SQLConn*> ConnMap;
+typedef insp::flat_map<std::string, SQLConn*> ConnMap;
-/* CREAD, Connecting and wants read event
- * CWRITE, Connecting and wants write event
- * WREAD, Connected/Working and wants read event
- * WWRITE, Connected/Working and wants write event
- * RREAD, Resetting and wants read event
- * RWRITE, Resetting and wants write event
- */
-enum SQLstatus { CREAD, CWRITE, WREAD, WWRITE, RREAD, RWRITE };
+enum SQLstatus
+{
+ // The connection has died.
+ DEAD,
+
+ // Connecting and wants read event.
+ CREAD,
+
+ // Connecting and wants write event.
+ CWRITE,
+
+ // Connected/working and wants read event.
+ WREAD,
+
+ // Connected/working and wants write event.
+ WWRITE
+};
class ReconnectTimer : public Timer
{
private:
ModulePgSQL* mod;
public:
- ReconnectTimer(ModulePgSQL* m) : Timer(5, ServerInstance->Time(), false), mod(m)
+ ReconnectTimer(ModulePgSQL* m) : Timer(5, false), mod(m)
{
}
- bool Tick(time_t TIME);
+ bool Tick(time_t TIME) CXX11_OVERRIDE;
};
struct QueueItem
{
- SQLQuery* c;
+ SQL::Query* c;
std::string q;
- QueueItem(SQLQuery* C, const std::string& Q) : c(C), q(Q) {}
+ QueueItem(SQL::Query* C, const std::string& Q) : c(C), q(Q) {}
};
/** PgSQLresult is a subclass of the mostly-pure-virtual class SQLresult.
* data is passes to the module nearly as directly as if it was using the API directly itself.
*/
-class PgSQLresult : public SQLResult
+class PgSQLresult : public SQL::Result
{
PGresult* res;
int currentrow;
int rows;
+ std::vector<std::string> colnames;
+
+ void getColNames()
+ {
+ colnames.resize(PQnfields(res));
+ for(unsigned int i=0; i < colnames.size(); i++)
+ {
+ colnames[i] = PQfname(res, i);
+ }
+ }
public:
PgSQLresult(PGresult* result) : res(result), currentrow(0)
{
rows = PQntuples(res);
if (!rows)
- rows = atoi(PQcmdTuples(res));
+ rows = ConvToNum<int>(PQcmdTuples(res));
}
~PgSQLresult()
PQclear(res);
}
- int Rows()
+ int Rows() CXX11_OVERRIDE
{
return rows;
}
- void GetCols(std::vector<std::string>& result)
+ void GetCols(std::vector<std::string>& result) CXX11_OVERRIDE
{
- result.resize(PQnfields(res));
- for(unsigned int i=0; i < result.size(); i++)
+ if (colnames.empty())
+ getColNames();
+ result = colnames;
+ }
+
+ bool HasColumn(const std::string& column, size_t& index) CXX11_OVERRIDE
+ {
+ if (colnames.empty())
+ getColNames();
+
+ for (size_t i = 0; i < colnames.size(); ++i)
{
- result[i] = PQfname(res, i);
+ if (colnames[i] == column)
+ {
+ index = i;
+ return true;
+ }
}
+ return false;
}
- SQLEntry GetValue(int row, int column)
+ SQL::Field GetValue(int row, int column)
{
char* v = PQgetvalue(res, row, column);
if (!v || PQgetisnull(res, row, column))
- return SQLEntry();
+ return SQL::Field();
- return SQLEntry(std::string(v, PQgetlength(res, row, column)));
+ return SQL::Field(std::string(v, PQgetlength(res, row, column)));
}
- bool GetRow(SQLEntries& result)
+ bool GetRow(SQL::Row& result) CXX11_OVERRIDE
{
if (currentrow >= PQntuples(res))
return false;
/** SQLConn represents one SQL session.
*/
-class SQLConn : public SQLProvider, public EventHandler
+class SQLConn : public SQL::Provider, public EventHandler
{
public:
reference<ConfigTag> conf; /* The <database> entry */
std::deque<QueueItem> queue;
- PGconn* sql; /* PgSQL database connection handle */
+ PGconn* sql; /* PgSQL database connection handle */
SQLstatus status; /* PgSQL database connection status */
QueueItem qinprog; /* If there is currently a query in progress */
SQLConn(Module* Creator, ConfigTag* tag)
- : SQLProvider(Creator, "SQL/" + tag->getString("id")), conf(tag), sql(NULL), status(CWRITE), qinprog(NULL, "")
+ : SQL::Provider(Creator, tag->getString("id"))
+ , conf(tag)
+ , sql(NULL)
+ , status(CWRITE)
+ , qinprog(NULL, "")
{
if (!DoConnect())
- {
- ServerInstance->Logs->Log("m_pgsql",LOG_DEFAULT, "WARNING: Could not connect to database " + tag->getString("id"));
DelayReconnect();
- }
}
- CullResult cull()
+ CullResult cull() CXX11_OVERRIDE
{
- this->SQLProvider::cull();
+ this->SQL::Provider::cull();
ServerInstance->Modules->DelService(*this);
return this->EventHandler::cull();
}
~SQLConn()
{
- SQLerror err(SQL_BAD_DBID);
+ SQL::Error err(SQL::BAD_DBID);
if (qinprog.c)
{
qinprog.c->OnError(err);
}
for(std::deque<QueueItem>::iterator i = queue.begin(); i != queue.end(); i++)
{
- SQLQuery* q = i->c;
+ SQL::Query* q = i->c;
q->OnError(err);
delete q;
}
+ Close();
}
- void HandleEvent(EventType et, int errornum)
+ void OnEventHandlerRead() CXX11_OVERRIDE
{
- switch (et)
- {
- case EVENT_READ:
- case EVENT_WRITE:
- DoEvent();
- break;
+ DoEvent();
+ }
- case EVENT_ERROR:
- DelayReconnect();
- }
+ void OnEventHandlerWrite() CXX11_OVERRIDE
+ {
+ DoEvent();
+ }
+
+ void OnEventHandlerError(int errornum) CXX11_OVERRIDE
+ {
+ DelayReconnect();
}
std::string GetDSN()
return conninfo.str();
}
+ bool HandleConnectError(const char* reason)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Could not connect to the \"%s\" database: %s",
+ GetId().c_str(), reason);
+ return false;
+ }
+
bool DoConnect()
{
sql = PQconnectStart(GetDSN().c_str());
if (!sql)
- return false;
+ return HandleConnectError("PQconnectStart returned NULL");
if(PQstatus(sql) == CONNECTION_BAD)
- return false;
+ return HandleConnectError("connection status is bad");
if(PQsetnonblocking(sql, 1) == -1)
- return false;
+ return HandleConnectError("unable to mark fd as non-blocking");
- /* OK, we've initalised the connection, now to get it hooked into the socket engine
+ /* OK, we've initialised the connection, now to get it hooked into the socket engine
* and then start polling it.
*/
- this->fd = PQsocket(sql);
-
- if(this->fd <= -1)
- return false;
+ SetFd(PQsocket(sql));
+ if(!HasFd())
+ return HandleConnectError("PQsocket returned an invalid fd");
- if (!ServerInstance->SE->AddFd(this, FD_WANT_NO_WRITE | FD_WANT_NO_READ))
- {
- ServerInstance->Logs->Log("m_pgsql",LOG_DEBUG, "BUG: Couldn't add pgsql socket to socket engine");
- return false;
- }
+ if (!SocketEngine::AddFd(this, FD_WANT_NO_WRITE | FD_WANT_NO_READ))
+ return HandleConnectError("could not add the pgsql socket to the socket engine");
/* Socket all hooked into the engine, now to tell PgSQL to start connecting */
- return DoPoll();
+ if (!DoPoll())
+ return HandleConnectError("could not poll the connection state");
+
+ return true;
}
bool DoPoll()
switch(PQconnectPoll(sql))
{
case PGRES_POLLING_WRITING:
- ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ);
+ SocketEngine::ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ);
status = CWRITE;
return true;
case PGRES_POLLING_READING:
- ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
+ SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
status = CREAD;
return true;
case PGRES_POLLING_FAILED:
+ SocketEngine::ChangeEventMask(this, FD_WANT_NO_READ | FD_WANT_NO_WRITE);
+ status = DEAD;
return false;
case PGRES_POLLING_OK:
- ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
+ SocketEngine::ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
status = WWRITE;
DoConnectedPoll();
+ return true;
default:
return true;
}
case PGRES_BAD_RESPONSE:
case PGRES_FATAL_ERROR:
{
- SQLerror err(SQL_QREPLY_FAIL, PQresultErrorMessage(result));
+ SQL::Error err(SQL::QREPLY_FAIL, PQresultErrorMessage(result));
qinprog.c->OnError(err);
break;
}
}
}
- bool DoResetPoll()
- {
- switch(PQresetPoll(sql))
- {
- case PGRES_POLLING_WRITING:
- ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_WRITE | FD_WANT_NO_READ);
- status = CWRITE;
- return DoPoll();
- case PGRES_POLLING_READING:
- ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
- status = CREAD;
- return true;
- case PGRES_POLLING_FAILED:
- return false;
- case PGRES_POLLING_OK:
- ServerInstance->SE->ChangeEventMask(this, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
- status = WWRITE;
- DoConnectedPoll();
- default:
- return true;
- }
- }
-
void DelayReconnect();
void DoEvent()
{
DoPoll();
}
- else if((status == RREAD) || (status == RWRITE))
- {
- DoResetPoll();
- }
- else
+ else if (status == WREAD || status == WWRITE)
{
DoConnectedPoll();
}
}
- void submit(SQLQuery *req, const std::string& q)
+ void Submit(SQL::Query *req, const std::string& q) CXX11_OVERRIDE
{
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Executing PostgreSQL query: " + q);
if (qinprog.q.empty())
{
DoQuery(QueueItem(req,q));
}
}
- void submit(SQLQuery *req, const std::string& q, const ParamL& p)
+ void Submit(SQL::Query *req, const std::string& q, const SQL::ParamList& p) CXX11_OVERRIDE
{
std::string res;
unsigned int param = 0;
if (param < p.size())
{
std::string parm = p[param++];
- char buffer[MAXBUF];
-#ifdef PGSQL_HAS_ESCAPECONN
+ std::vector<char> buffer(parm.length() * 2 + 1);
int error;
- PQescapeStringConn(sql, buffer, parm.c_str(), parm.length(), &error);
+ size_t escapedsize = PQescapeStringConn(sql, &buffer[0], parm.data(), parm.length(), &error);
if (error)
- ServerInstance->Logs->Log("m_pgsql", LOG_DEBUG, "BUG: Apparently PQescapeStringConn() failed");
-#else
- PQescapeString (buffer, parm.c_str(), parm.length());
-#endif
- res.append(buffer);
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BUG: Apparently PQescapeStringConn() failed");
+ res.append(&buffer[0], escapedsize);
}
}
}
- submit(req, res);
+ Submit(req, res);
}
- void submit(SQLQuery *req, const std::string& q, const ParamM& p)
+ void Submit(SQL::Query *req, const std::string& q, const SQL::ParamMap& p) CXX11_OVERRIDE
{
std::string res;
for(std::string::size_type i = 0; i < q.length(); i++)
field.push_back(q[i++]);
i--;
- ParamM::const_iterator it = p.find(field);
+ SQL::ParamMap::const_iterator it = p.find(field);
if (it != p.end())
{
std::string parm = it->second;
- char buffer[MAXBUF];
-#ifdef PGSQL_HAS_ESCAPECONN
+ std::vector<char> buffer(parm.length() * 2 + 1);
int error;
- PQescapeStringConn(sql, buffer, parm.c_str(), parm.length(), &error);
+ size_t escapedsize = PQescapeStringConn(sql, &buffer[0], parm.data(), parm.length(), &error);
if (error)
- ServerInstance->Logs->Log("m_pgsql", LOG_DEBUG, "BUG: Apparently PQescapeStringConn() failed");
-#else
- PQescapeString (buffer, parm.c_str(), parm.length());
-#endif
- res.append(buffer);
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "BUG: Apparently PQescapeStringConn() failed");
+ res.append(&buffer[0], escapedsize);
}
}
}
- submit(req, res);
+ Submit(req, res);
}
void DoQuery(const QueueItem& req)
if (status != WREAD && status != WWRITE)
{
// whoops, not connected...
- SQLerror err(SQL_BAD_CONN);
+ SQL::Error err(SQL::BAD_CONN);
req.c->OnError(err);
delete req.c;
return;
}
else
{
- SQLerror err(SQL_QSEND_FAIL, PQerrorMessage(sql));
+ SQL::Error err(SQL::QSEND_FAIL, PQerrorMessage(sql));
req.c->OnError(err);
delete req.c;
}
void Close()
{
- ServerInstance->SE->DelFd(this);
+ status = DEAD;
+
+ if (HasFd() && SocketEngine::HasFd(GetFd()))
+ SocketEngine::DelFd(this);
if(sql)
{
{
}
- void init() CXX11_OVERRIDE
- {
- ReadConf();
-
- Implementation eventlist[] = { I_OnUnloadModule, I_OnRehash };
- ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
- }
-
~ModulePgSQL()
{
delete retimer;
ClearAllConnections();
}
- void OnRehash(User* user) CXX11_OVERRIDE
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
{
ReadConf();
}
ConfigTagList tags = ServerInstance->Config->ConfTags("database");
for(ConfigIter i = tags.first; i != tags.second; i++)
{
- if (i->second->getString("module", "pgsql") != "pgsql")
+ if (!stdalgo::string::equalsci(i->second->getString("module"), "pgsql"))
continue;
std::string id = i->second->getString("id");
ConnMap::iterator curr = connections.find(id);
if (curr == connections.end())
{
SQLConn* conn = new SQLConn(this, i->second);
- conns.insert(std::make_pair(id, conn));
- ServerInstance->Modules->AddService(*conn);
+ if (conn->status != DEAD)
+ {
+ conns.insert(std::make_pair(id, conn));
+ ServerInstance->Modules->AddService(*conn);
+ }
+ // If the connection is dead it has already been queued for culling
+ // at the end of the main loop so we don't need to delete it here.
}
else
{
void OnUnloadModule(Module* mod) CXX11_OVERRIDE
{
- SQLerror err(SQL_BAD_DBID);
+ SQL::Error err(SQL::BAD_DBID);
for(ConnMap::iterator i = connections.begin(); i != connections.end(); i++)
{
SQLConn* conn = i->second;
std::deque<QueueItem>::iterator j = conn->queue.begin();
while (j != conn->queue.end())
{
- SQLQuery* q = j->c;
+ SQL::Query* q = j->c;
if (q->creator == mod)
{
q->OnError(err);
Version GetVersion() CXX11_OVERRIDE
{
- return Version("PostgreSQL Service Provider module for all other m_sql* modules, uses v2 of the SQL API", VF_VENDOR);
+ return Version("Provides the ability for SQL modules to query a PostgreSQL database.", VF_VENDOR);
}
};
{
mod->retimer = NULL;
mod->ReadConf();
+ delete this;
return false;
}
void SQLConn::DelayReconnect()
{
+ status = DEAD;
ModulePgSQL* mod = (ModulePgSQL*)(Module*)creator;
+
ConnMap::iterator it = mod->connections.find(conf->getString("id"));
if (it != mod->connections.end())
- {
mod->connections.erase(it);
- ServerInstance->GlobalCulls.AddItem((EventHandler*)this);
- if (!mod->retimer)
- {
- mod->retimer = new ReconnectTimer(mod);
- ServerInstance->Timers->AddTimer(mod->retimer);
- }
+ ServerInstance->GlobalCulls.AddItem((EventHandler*)this);
+ if (!mod->retimer)
+ {
+ mod->retimer = new ReconnectTimer(mod);
+ ServerInstance->Timers.AddTimer(mod->retimer);
}
}