1 /* +------------------------------------+
2 * | Inspire Internet Relay Chat Daemon |
3 * +------------------------------------+
5 * InspIRCd is copyright (C) 2002-2004 ChatSpike-Dev.
7 * <brain@chatspike.net>
8 * <Craig@chatspike.net>
10 * Written by Craig Edwards, Craig McLure, and others.
11 * This program is free but copyrighted software; see
12 * the file COPYING for details.
14 * ---------------------------------------------------
26 #include "helperfuncs.h"
29 /* VERSION 2 API: With nonblocking (threaded) requests */
31 /* $ModDesc: SQL Service Provider module for all other m_sql* modules */
32 /* $CompileFlags: `mysql_config --include` */
33 /* $LinkerFlags: `mysql_config --libs_r` `perl ../mysql_rpath.pl` */
38 extern InspIRCd* ServerInstance;
39 typedef std::map<std::string, SQLConnection*> ConnMap;
43 #if !defined(MYSQL_VERSION_ID) || MYSQL_VERSION_ID<32224
44 #define mysql_field_count mysql_num_fields
47 class QueryQueue : public classbase
50 typedef std::deque<SQLrequest> ReqDeque;
52 ReqDeque priority; /* The priority queue */
53 ReqDeque normal; /* The 'normal' queue */
54 enum { PRI, NOR, NON } which; /* Which queue the currently active element is at the front of */
62 void push(const SQLrequest &q)
64 log(DEBUG, "QueryQueue::push(): Adding %s query to queue: %s", ((q.pri) ? "priority" : "non-priority"), q.query.q.c_str());
67 priority.push_back(q);
74 if((which == PRI) && priority.size())
78 else if((which == NOR) && normal.size())
86 /* Silently do nothing if there was no element to pop() */
94 return priority.front();
96 return normal.front();
101 return priority.front();
107 return normal.front();
110 /* This will probably result in a segfault,
111 * but the caller should have checked totalsize()
112 * first so..meh - moron :p
115 return priority.front();
119 std::pair<int, int> size()
121 return std::make_pair(priority.size(), normal.size());
126 return priority.size() + normal.size();
129 void PurgeModule(Module* mod)
131 DoPurgeModule(mod, priority);
132 DoPurgeModule(mod, normal);
136 void DoPurgeModule(Module* mod, ReqDeque& q)
138 for(ReqDeque::iterator iter = q.begin(); iter != q.end(); iter++)
140 if(iter->GetSource() == mod)
142 if(iter->id == front().id)
144 /* It's the currently active query.. :x */
145 iter->SetSource(NULL);
149 /* It hasn't been executed yet..just remove it */
150 iter = q.erase(iter);
157 /* A mutex to wrap around queue accesses */
158 pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
160 class SQLConnection : public classbase
171 std::map<std::string,std::string> thisrow;
179 // This constructor creates an SQLConnection object with the given credentials, and creates the underlying
180 // MYSQL struct, but does not connect yet.
181 SQLConnection(std::string thishost, std::string thisuser, std::string thispass, std::string thisdb, long myid)
183 this->Enabled = true;
184 this->host = thishost;
185 this->user = thisuser;
186 this->pass = thispass;
191 // This method connects to the database using the credentials supplied to the constructor, and returns
192 // true upon success.
195 unsigned int timeout = 1;
196 mysql_init(&connection);
197 mysql_options(&connection,MYSQL_OPT_CONNECT_TIMEOUT,(char*)&timeout);
198 return mysql_real_connect(&connection, host.c_str(), user.c_str(), pass.c_str(), db.c_str(), 0, NULL, 0);
201 void DoLeadingQuery()
203 SQLrequest& query = queue.front();
204 log(DEBUG,"DO QUERY: %s",query.query.q.c_str());
207 // This method issues a query that expects multiple rows of results. Use GetRow() and QueryDone() to retrieve
209 bool QueryResult(std::string query)
211 if (!CheckConnection()) return false;
213 int r = mysql_query(&connection, query.c_str());
216 res = mysql_use_result(&connection);
221 // This method issues a query that just expects a number of 'effected' rows (e.g. UPDATE or DELETE FROM).
222 // the number of effected rows is returned in the return value.
223 long QueryCount(std::string query)
225 /* If the connection is down, we return a negative value - New to 1.1 */
226 if (!CheckConnection()) return -1;
228 int r = mysql_query(&connection, query.c_str());
231 res = mysql_store_result(&connection);
232 unsigned long rows = mysql_affected_rows(&connection);
233 mysql_free_result(res);
239 // This method fetches a row, if available from the database. You must issue a query
240 // using QueryResult() first! The row's values are returned as a map of std::string
241 // where each item is keyed by the column name.
242 std::map<std::string,std::string> GetRow()
247 row = mysql_fetch_row(res);
250 unsigned int field_count = 0;
251 MYSQL_FIELD *fields = mysql_fetch_fields(res);
252 if(mysql_field_count(&connection) == 0)
254 if (fields && mysql_field_count(&connection))
256 while (field_count < mysql_field_count(&connection))
258 std::string a = (fields[field_count].name ? fields[field_count].name : "");
259 std::string b = (row[field_count] ? row[field_count] : "");
274 mysql_free_result(res);
281 bool ConnectionLost()
284 return (mysql_ping(&connection) != 0);
289 bool CheckConnection()
291 if (ConnectionLost()) {
297 std::string GetError()
299 return mysql_error(&connection);
307 std::string GetHost()
331 void ConnectDatabases(Server* Srv)
333 for (ConnMap::iterator i = Connections.begin(); i != Connections.end(); i++)
336 if (i->second->Connect())
338 Srv->Log(DEFAULT,"SQL: Successfully connected database "+i->second->GetHost());
342 Srv->Log(DEFAULT,"SQL: Failed to connect database "+i->second->GetHost()+": Error: "+i->second->GetError());
343 i->second->Disable();
349 void LoadDatabases(ConfigReader* ThisConf, Server* Srv)
351 Srv->Log(DEFAULT,"SQL: Loading database settings");
353 Srv->Log(DEBUG,"Cleared connections");
354 for (int j =0; j < ThisConf->Enumerate("database"); j++)
356 std::string db = ThisConf->ReadValue("database","name",j);
357 std::string user = ThisConf->ReadValue("database","username",j);
358 std::string pass = ThisConf->ReadValue("database","password",j);
359 std::string host = ThisConf->ReadValue("database","hostname",j);
360 std::string id = ThisConf->ReadValue("database","id",j);
361 Srv->Log(DEBUG,"Read database settings");
362 if ((db != "") && (host != "") && (user != "") && (id != "") && (pass != ""))
364 SQLConnection* ThisSQL = new SQLConnection(host,user,pass,db,atoi(id.c_str()));
365 Srv->Log(DEFAULT,"Loaded database: "+ThisSQL->GetHost());
366 Connections[id] = ThisSQL;
367 Srv->Log(DEBUG,"Pushed back connection");
370 ConnectDatabases(Srv);
373 void* DispatcherThread(void* arg);
375 class ModuleSQL : public Module
380 pthread_t Dispatcher;
383 void Implements(char* List)
385 List[I_OnRehash] = List[I_OnRequest] = 1;
388 unsigned long NewID()
395 char* OnRequest(Request* request)
397 if(strcmp(SQLREQID, request->GetData()) == 0)
399 SQLrequest* req = (SQLrequest*)request;
402 pthread_mutex_lock(&queue_mutex);
404 ConnMap::iterator iter;
406 char* returnval = NULL;
408 log(DEBUG, "Got query: '%s' with %d replacement parameters on id '%s'", req->query.q.c_str(), req->query.p.size(), req->dbid.c_str());
410 if((iter = Connections.find(req->dbid)) != Connections.end())
412 iter->second->queue.push(*req);
414 returnval = SQLSUCCESS;
418 req->error.Id(BAD_DBID);
421 pthread_mutex_unlock(&queue_mutex);
427 log(DEBUG, "Got unsupported API version string: %s", request->GetData());
432 ModuleSQL(Server* Me)
436 Conf = new ConfigReader();
438 pthread_attr_t attribs;
439 pthread_attr_init(&attribs);
440 pthread_attr_setdetachstate(&attribs, PTHREAD_CREATE_DETACHED);
441 if (pthread_create(&this->Dispatcher, &attribs, DispatcherThread, (void *)this) != 0)
443 throw ModuleException("m_mysql: Failed to create dispatcher thread: " + std::string(strerror(errno)));
445 Srv->PublishFeature("SQL", this);
446 Srv->PublishFeature("MySQL", this);
454 virtual void OnRehash(const std::string ¶meter)
456 /* TODO: set rehash bool here, which makes the dispatcher thread rehash at next opportunity */
459 virtual Version GetVersion()
461 return Version(1,1,0,0,VF_VENDOR|VF_SERVICEPROVIDER);
466 void* DispatcherThread(void* arg)
468 ModuleSQL* thismodule = (ModuleSQL*)arg;
469 LoadDatabases(thismodule->Conf, thismodule->Srv);
473 SQLConnection* conn = NULL;
474 /* XXX: Lock here for safety */
475 pthread_mutex_lock(&queue_mutex);
476 for (ConnMap::iterator i = Connections.begin(); i != Connections.end(); i++)
478 if (i->second->queue.totalsize())
484 pthread_mutex_unlock(&queue_mutex);
487 /* Theres an item! */
490 conn->DoLeadingQuery();
493 pthread_mutex_lock(&queue_mutex);
495 pthread_mutex_unlock(&queue_mutex);
506 // stuff down here is the module-factory stuff. For basic modules you can ignore this.
508 class ModuleSQLFactory : public ModuleFactory
519 virtual Module * CreateModule(Server* Me)
521 return new ModuleSQL(Me);
527 extern "C" void * init_module( void )
529 return new ModuleSQLFactory;