diff options
author | danieldg <danieldg@e03df62e-2008-0410-955e-edbf42e46eb7> | 2010-03-06 16:58:13 +0000 |
---|---|---|
committer | danieldg <danieldg@e03df62e-2008-0410-955e-edbf42e46eb7> | 2010-03-06 16:58:13 +0000 |
commit | 410de52634ddeabfe9a57aa15131311d16abb42c (patch) | |
tree | b774062ae69631585835f206210a7d82b5a356d5 /src | |
parent | a1fab90c8040280a6a812a9454f085c062990708 (diff) |
SQL API v3, drop all the ugly complexity
git-svn-id: http://svn.inspircd.org/repository/trunk/inspircd@12602 e03df62e-2008-0410-955e-edbf42e46eb7
Diffstat (limited to 'src')
-rw-r--r-- | src/modules/m_sqlauth.cpp | 189 | ||||
-rw-r--r-- | src/modules/m_sqloper.cpp | 259 | ||||
-rw-r--r-- | src/modules/m_sqlutils.cpp | 223 | ||||
-rw-r--r-- | src/modules/m_sqlutils.h | 143 | ||||
-rw-r--r-- | src/modules/m_sqlv2.h | 604 | ||||
-rw-r--r-- | src/modules/m_testclient.cpp | 103 | ||||
-rw-r--r-- | src/modules/sql.h | 154 |
7 files changed, 321 insertions, 1354 deletions
diff --git a/src/modules/m_sqlauth.cpp b/src/modules/m_sqlauth.cpp index be6e50e52..df2dd0190 100644 --- a/src/modules/m_sqlauth.cpp +++ b/src/modules/m_sqlauth.cpp @@ -12,50 +12,78 @@ */ #include "inspircd.h" -#include "m_sqlv2.h" -#include "m_sqlutils.h" +#include "sql.h" #include "hash.h" /* $ModDesc: Allow/Deny connections based upon an arbitary SQL table */ +enum AuthState { + AUTH_STATE_NONE = 0, + AUTH_STATE_BUSY = 1, + AUTH_STATE_FAIL = 2 +}; + +class AuthQuery : public SQLQuery +{ + public: + const std::string uid; + LocalIntExt& pendingExt; + bool verbose; + AuthQuery(Module* me, const std::string& db, const std::string& q, const std::string& u, LocalIntExt& e, bool v) + : SQLQuery(me, db, q), uid(u), pendingExt(e), verbose(v) {} + + void OnResult(SQLResult& res) + { + User* user = ServerInstance->FindNick(uid); + if (!user) + return; + if (res.Rows()) + { + pendingExt.set(user, AUTH_STATE_NONE); + } + else + { + if (verbose) + ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s!%s@%s (SQL query returned no matches)", user->nick.c_str(), user->ident.c_str(), user->host.c_str()); + pendingExt.set(user, AUTH_STATE_FAIL); + } + } + + void OnError(SQLerror& error) + { + User* user = ServerInstance->FindNick(uid); + if (!user) + return; + pendingExt.set(user, AUTH_STATE_FAIL); + if (verbose) + ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s!%s@%s (SQL query failed: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), error.Str()); + } +}; + class ModuleSQLAuth : public Module { - LocalIntExt sqlAuthed; - Module* SQLutils; - Module* SQLprovider; + LocalIntExt pendingExt; + dynamic_reference<SQLProvider> SQL; std::string freeformquery; std::string killreason; std::string allowpattern; std::string databaseid; - bool verbose; -public: - ModuleSQLAuth() : sqlAuthed("sqlauth", this) + public: + ModuleSQLAuth() : pendingExt("sqlauth-wait", this), SQL(this, "SQL") { } void init() { - SQLutils = ServerInstance->Modules->Find("m_sqlutils.so"); - if (!SQLutils) - throw ModuleException("Can't find m_sqlutils.so. Please load m_sqlutils.so before m_sqlauth.so."); - - ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_DATA, "SQL"); - if (!prov) - throw ModuleException("Can't find an SQL provider module. Please load one before attempting to load m_sqlauth."); - SQLprovider = prov->creator; - + ServerInstance->Modules->AddService(pendingExt); OnRehash(NULL); Implementation eventlist[] = { I_OnUserDisconnect, I_OnCheckReady, I_OnRehash, I_OnUserRegister }; ServerInstance->Modules->Attach(eventlist, this, 4); } - virtual ~ModuleSQLAuth() - { - } - void OnRehash(User* user) { ConfigReader Conf; @@ -69,115 +97,62 @@ public: ModResult OnUserRegister(LocalUser* user) { - if ((!allowpattern.empty()) && (InspIRCd::Match(user->nick,allowpattern))) - { - sqlAuthed.set(user, 1); + // Note this is their initial (unresolved) connect block + ConfigTag* tag = user->MyClass->config; + if (!tag->getBool("usesqlauth", true)) return MOD_RES_PASSTHRU; - } - - if (!CheckCredentials(user)) - { - ServerInstance->Users->QuitUser(user, killreason); - return MOD_RES_DENY; - } - return MOD_RES_PASSTHRU; - } - bool CheckCredentials(LocalUser* user) - { - std::string thisquery = freeformquery; - std::string safepass = user->password; - std::string safegecos = user->fullname; + if (!allowpattern.empty() && InspIRCd::Match(user->nick,allowpattern)) + return MOD_RES_PASSTHRU; - /* Search and replace the escaped nick and escaped pass into the query */ + if (pendingExt.get(user)) + return MOD_RES_PASSTHRU; - SearchAndReplace(safepass, std::string("\""), std::string("\\\"")); - SearchAndReplace(safegecos, std::string("\""), std::string("\\\"")); + pendingExt.set(user, AUTH_STATE_BUSY); - SearchAndReplace(thisquery, std::string("$nick"), user->nick); - SearchAndReplace(thisquery, std::string("$pass"), safepass); - SearchAndReplace(thisquery, std::string("$host"), user->host); - SearchAndReplace(thisquery, std::string("$ip"), std::string(user->GetIPString())); - SearchAndReplace(thisquery, std::string("$gecos"), safegecos); - SearchAndReplace(thisquery, std::string("$ident"), user->ident); - SearchAndReplace(thisquery, std::string("$server"), std::string(user->server)); - SearchAndReplace(thisquery, std::string("$uuid"), user->uuid); + std::string thisquery = freeformquery; + ParamM userinfo; + userinfo["nick"] = user->nick; + userinfo["pass"] = user->password; + userinfo["host"] = user->host; + userinfo["ip"] = user->GetIPString(); + userinfo["gecos"] = user->fullname; + userinfo["ident"] = user->ident; + userinfo["server"] = user->server; + userinfo["uuid"] = user->uuid; HashProvider* md5 = ServerInstance->Modules->FindDataService<HashProvider>("hash/md5"); if (md5) - SearchAndReplace(thisquery, std::string("$md5pass"), md5->hexsum(user->password)); + userinfo["md5pass"] = md5->hexsum(user->password); HashProvider* sha256 = ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"); if (sha256) - SearchAndReplace(thisquery, std::string("$sha256pass"), sha256->hexsum(user->password)); - - /* Build the query */ - SQLrequest req = SQLrequest(this, SQLprovider, databaseid, SQLquery(thisquery)); - - req.Send(); - /* When we get the query response from the service provider we will be given an ID to play with, - * just an ID number which is unique to this query. We need a way of associating that ID with a User - * so we insert it into a map mapping the IDs to users. - * Thankfully m_sqlutils provides this, it will associate a ID with a user or channel, and if the user quits it removes the - * association. This means that if the user quits during a query we will just get a failed lookup from m_sqlutils - telling - * us to discard the query. - */ - AssociateUser(this, SQLutils, req.id, user).Send(); - - return true; + userinfo["$sha256pass"] = sha256->hexsum(user->password); + + SQL->submit(new AuthQuery(this, databaseid, SQL->FormatQuery(freeformquery, userinfo), user->uuid, pendingExt, verbose)); + + return MOD_RES_PASSTHRU; } - void OnRequest(Request& request) + ModResult OnCheckReady(LocalUser* user) { - if(strcmp(SQLRESID, request.id) == 0) + switch (pendingExt.get(user)) { - SQLresult* res = static_cast<SQLresult*>(&request); - - User* user = GetAssocUser(this, SQLutils, res->id).S().user; - UnAssociate(this, SQLutils, res->id).S(); - - if(user) - { - if(res->error.Id() == SQL_NO_ERROR) - { - if(res->Rows()) - { - /* We got a row in the result, this is enough really */ - sqlAuthed.set(user, 1); - } - else if (verbose) - { - /* No rows in result, this means there was no record matching the user */ - ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s!%s@%s (SQL query returned no matches)", user->nick.c_str(), user->ident.c_str(), user->host.c_str()); - } - } - else if (verbose) - { - ServerInstance->SNO->WriteGlobalSno('a', "Forbidden connection from %s!%s@%s (SQL query failed: %s)", user->nick.c_str(), user->ident.c_str(), user->host.c_str(), res->error.Str()); - } - } - else - { - return; - } - - if (!sqlAuthed.get(user)) - { + case AUTH_STATE_NONE: + return MOD_RES_PASSTHRU; + case AUTH_STATE_BUSY: + return MOD_RES_DENY; + case AUTH_STATE_FAIL: ServerInstance->Users->QuitUser(user, killreason); - } + return MOD_RES_DENY; } - } - - ModResult OnCheckReady(LocalUser* user) - { - return sqlAuthed.get(user) ? MOD_RES_PASSTHRU : MOD_RES_DENY; + return MOD_RES_PASSTHRU; } Version GetVersion() { return Version("Allow/Deny connections based upon an arbitary SQL table", VF_VENDOR); } - }; MODULE_INIT(ModuleSQLAuth) diff --git a/src/modules/m_sqloper.cpp b/src/modules/m_sqloper.cpp index cca391cf5..281593cfa 100644 --- a/src/modules/m_sqloper.cpp +++ b/src/modules/m_sqloper.cpp @@ -12,211 +12,72 @@ */ #include "inspircd.h" -#include "m_sqlv2.h" -#include "m_sqlutils.h" +#include "sql.h" #include "hash.h" /* $ModDesc: Allows storage of oper credentials in an SQL table */ -typedef std::map<irc::string, Module*> hashymodules; - -class ModuleSQLOper : public Module +static bool OneOfMatches(const char* host, const char* ip, const std::string& hostlist) { - LocalStringExt saved_user; - LocalStringExt saved_pass; - Module* SQLutils; - std::string databaseid; - std::string hashtype; - parameterlist names; - -public: - ModuleSQLOper() : saved_user("sqloper_user", this), saved_pass("sqloper_pass", this) - { - } - - void init() + std::stringstream hl(hostlist); + std::string xhost; + while (hl >> xhost) { - OnRehash(NULL); - - SQLutils = ServerInstance->Modules->Find("m_sqlutils.so"); - if (!SQLutils) - throw ModuleException("Can't find m_sqlutils.so. Please load m_sqlutils.so before m_sqloper.so."); - - Implementation eventlist[] = { I_OnRehash, I_OnPreCommand, I_OnLoadModule }; - ServerInstance->Modules->Attach(eventlist, this, 3); - ServerInstance->Modules->AddService(saved_user); - ServerInstance->Modules->AddService(saved_pass); - } - - bool OneOfMatches(const char* host, const char* ip, const char* hostlist) - { - std::stringstream hl(hostlist); - std::string xhost; - while (hl >> xhost) + if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map)) { - if (InspIRCd::Match(host, xhost, ascii_case_insensitive_map) || InspIRCd::MatchCIDR(ip, xhost, ascii_case_insensitive_map)) - { - return true; - } + return true; } - return false; } + return false; +} - virtual void OnRehash(User* user) - { - ConfigReader Conf; - - databaseid = Conf.ReadValue("sqloper", "dbid", 0); /* Database ID of a database configured for the service provider module */ - hashtype = Conf.ReadValue("sqloper", "hash", 0); - } - - virtual ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) - { - if ((validated) && (command == "OPER")) - { - if (LookupOper(user, parameters[0], parameters[1])) - { - /* Returning true here just means the query is in progress, or on it's way to being - * in progress. Nothing about the /oper actually being successful.. - * If the oper lookup fails later, we pass the command to the original handler - * for /oper by calling its Handle method directly. - */ - return MOD_RES_DENY; - } - } - return MOD_RES_PASSTHRU; - } +class OpMeQuery : public SQLQuery +{ + public: + const std::string uid, username, password; + OpMeQuery(Module* me, const std::string& db, const std::string& q, const std::string& u, const std::string& un, const std::string& pw) + : SQLQuery(me, db, q), uid(u), username(un), password(pw) {} - bool LookupOper(User* user, const std::string &username, const std::string &password) + void OnResult(SQLResult& res) { - ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_DATA, "SQL"); - if (prov) - { - Module* target = prov->creator; - HashProvider* hash = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + hashtype); - - /* Make an MD5 hash of the password for using in the query */ - std::string md5_pass_hash = hash ? hash->hexsum(password) : password; - - /* We generate our own sum here because some database providers (e.g. SQLite) dont have a builtin md5/sha256 function, - * also hashing it in the module and only passing a remote query containing a hash is more secure. - */ - SQLrequest req = SQLrequest(this, target, databaseid, - SQLquery("SELECT username, password, hostname, type FROM ircd_opers WHERE username = '?' AND password='?'") % username % md5_pass_hash); - - /* When we get the query response from the service provider we will be given an ID to play with, - * just an ID number which is unique to this query. We need a way of associating that ID with a User - * so we insert it into a map mapping the IDs to users. - * Thankfully m_sqlutils provides this, it will associate a ID with a user or channel, and if the user quits it removes the - * association. This means that if the user quits during a query we will just get a failed lookup from m_sqlutils - telling - * us to discard the query. - */ - AssociateUser(this, SQLutils, req.id, user).Send(); - - saved_user.set(user, username); - saved_pass.set(user, password); + User* user = ServerInstance->FindNick(uid); + if (!user) + return; - return true; - } - else + // multiple rows may exist for multiple hosts + parameterlist row; + while (res.GetRow(row)) { - ServerInstance->Logs->Log("m_sqloper",SPARSE, "WARNING: Couldn't find SQL provider module. NOBODY will be able to oper up unless their o:line is statically configured"); - return false; + if (OperUser(user, row[2], row[3])) + return; } + // nobody succeeded... fall back to OPER + fallback(); } - void OnRequest(Request& request) + void OnError(SQLerror& error) { - if (strcmp(SQLRESID, request.id) == 0) - { - SQLresult* res = static_cast<SQLresult*>(&request); - - User* user = GetAssocUser(this, SQLutils, res->id).S().user; - UnAssociate(this, SQLutils, res->id).S(); - - if (user) - { - std::string* tried_user = saved_user.get(user); - std::string* tried_pass = saved_pass.get(user); - if (res->error.Id() == SQL_NO_ERROR) - { - if (res->Rows()) - { - /* We got a row in the result, this means there was a record for the oper.. - * now we just need to check if their host matches, and if it does then - * oper them up. - * - * We now (previous versions of the module didn't) support multiple SQL - * rows per-oper in the same way the config file does, all rows will be tried - * until one is found which matches. This is useful to define several different - * hosts for a single oper. - * - * The for() loop works as SQLresult::GetRowMap() returns an empty map when there - * are no more rows to return. - */ - - for (SQLfieldMap& row = res->GetRowMap(); row.size(); row = res->GetRowMap()) - { - if (OperUser(user, row["hostname"].d, row["type"].d)) - { - /* If/when one of the rows matches, stop checking and return */ - saved_user.unset(user); - saved_pass.unset(user); - } - if (tried_user && tried_pass) - { - LoginFail(user, *tried_user, *tried_pass); - saved_user.unset(user); - saved_pass.unset(user); - } - } - } - else - { - /* No rows in result, this means there was no oper line for the user, - * we should have already checked the o:lines so now we need an - * "insufficient awesomeness" (invalid credentials) error - */ - if (tried_user && tried_pass) - { - LoginFail(user, *tried_user, *tried_pass); - saved_user.unset(user); - saved_pass.unset(user); - } - } - } - else - { - /* This one shouldn't happen, the query failed for some reason. - * We have to fail the /oper request and give them the same error - * as above. - */ - if (tried_user && tried_pass) - { - LoginFail(user, *tried_user, *tried_pass); - saved_user.unset(user); - saved_pass.unset(user); - } - - } - } - } + fallback(); } - void LoginFail(User* user, const std::string &username, const std::string &pass) + void fallback() { + User* user = ServerInstance->FindNick(uid); + if (!user) + return; + Command* oper_command = ServerInstance->Parser->GetHandler("OPER"); if (oper_command) { std::vector<std::string> params; params.push_back(username); - params.push_back(pass); + params.push_back(password); oper_command->Handle(params, user); } else { - ServerInstance->Logs->Log("m_sqloper",DEBUG, "BUG: WHAT?! Why do we have no OPER command?!"); + ServerInstance->Logs->Log("m_sqloper",SPARSE, "BUG: WHAT?! Why do we have no OPER command?!"); } } @@ -241,6 +102,56 @@ public: return false; } +}; + +class ModuleSQLOper : public Module +{ + std::string databaseid; + std::string hashtype; + dynamic_reference<SQLProvider> SQL; + +public: + ModuleSQLOper() : SQL(this, "SQL") {} + + void init() + { + OnRehash(NULL); + + Implementation eventlist[] = { I_OnRehash, I_OnPreCommand }; + ServerInstance->Modules->Attach(eventlist, this, 2); + } + + void OnRehash(User* user) + { + ConfigReader Conf; + + databaseid = Conf.ReadValue("sqloper", "dbid", 0); /* Database ID of a database configured for the service provider module */ + hashtype = Conf.ReadValue("sqloper", "hash", 0); + } + + ModResult OnPreCommand(std::string &command, std::vector<std::string> ¶meters, LocalUser *user, bool validated, const std::string &original_line) + { + if (validated && command == "OPER" && parameters.size() == 2 && SQL) + { + LookupOper(user, parameters[0], parameters[1]); + /* Query is in progress, it will re-invoke OPER if needed */ + return MOD_RES_DENY; + } + return MOD_RES_PASSTHRU; + } + + void LookupOper(User* user, const std::string &username, const std::string &password) + { + HashProvider* hash = ServerInstance->Modules->FindDataService<HashProvider>("hash/" + hashtype); + + parameterlist params; + params.push_back(username); + params.push_back(hash ? hash->hexsum(password) : password); + + SQL->submit(new OpMeQuery(this, databaseid, SQL->FormatQuery( + "SELECT username, password, hostname, type FROM ircd_opers WHERE username = '?' AND password='?'", params + ), user->uuid, username, password)); + } Version GetVersion() { diff --git a/src/modules/m_sqlutils.cpp b/src/modules/m_sqlutils.cpp deleted file mode 100644 index 3071f5242..000000000 --- a/src/modules/m_sqlutils.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/* +------------------------------------+ - * | Inspire Internet Relay Chat Daemon | - * +------------------------------------+ - * - * InspIRCd: (C) 2002-2010 InspIRCd Development Team - * See: http://wiki.inspircd.org/Credits - * - * This program is free but copyrighted software; see - * the file COPYING for details. - * - * --------------------------------------------------- - */ - -#include "inspircd.h" -#include <sstream> -#include <list> -#include "m_sqlutils.h" - -/* $ModDesc: Provides some utilities to SQL client modules, such as mapping queries to users and channels */ -/* $ModDep: m_sqlutils.h */ - -typedef std::map<unsigned long, User*> IdUserMap; -typedef std::map<unsigned long, Channel*> IdChanMap; -typedef std::list<unsigned long> AssocIdList; - -class ModuleSQLutils : public Module -{ -private: - IdUserMap iduser; - IdChanMap idchan; - SimpleExtItem<AssocIdList> idExt; - -public: - ModuleSQLutils() : idExt("sqlutils_list", this) - { - } - - void init() - { - Implementation eventlist[] = { I_OnChannelDelete, I_OnUnloadModule, I_OnUserDisconnect }; - ServerInstance->Modules->Attach(eventlist, this, 3); - } - - void OnRequest(Request& request) - { - if(strcmp(SQLUTILAU, request.id) == 0) - { - AssociateUser* req = (AssociateUser*)&request; - - iduser.insert(std::make_pair(req->id, req->user)); - - AttachList(req->user, req->id); - } - else if(strcmp(SQLUTILAC, request.id) == 0) - { - AssociateChan* req = (AssociateChan*)&request; - - idchan.insert(std::make_pair(req->id, req->chan)); - - AttachList(req->chan, req->id); - } - else if(strcmp(SQLUTILUA, request.id) == 0) - { - UnAssociate* req = (UnAssociate*)&request; - - /* Unassociate a given query ID with all users and channels - * it is associated with. - */ - - DoUnAssociate(iduser, req->id); - DoUnAssociate(idchan, req->id); - } - else if(strcmp(SQLUTILGU, request.id) == 0) - { - GetAssocUser* req = (GetAssocUser*)&request; - - IdUserMap::iterator iter = iduser.find(req->id); - - if(iter != iduser.end()) - { - req->user = iter->second; - } - } - else if(strcmp(SQLUTILGC, request.id) == 0) - { - GetAssocChan* req = (GetAssocChan*)&request; - - IdChanMap::iterator iter = idchan.find(req->id); - - if(iter != idchan.end()) - { - req->chan = iter->second; - } - } - } - - void OnUserDisconnect(LocalUser* user) - { - /* A user is disconnecting, first we need to check if they have a list of queries associated with them. - * Then, if they do, we need to erase each of them from our IdUserMap (iduser) so when the module that - * associated them asks to look them up then it gets a NULL result and knows to discard the query. - */ - AssocIdList* il = idExt.get(user); - - if(il) - { - for(AssocIdList::iterator listiter = il->begin(); listiter != il->end(); listiter++) - { - IdUserMap::iterator iter; - - iter = iduser.find(*listiter); - - if(iter != iduser.end()) - { - if(iter->second != user) - { - ServerInstance->Logs->Log("m_sqlutils",DEBUG, "BUG: ID associated with user %s doesn't have the same User* associated with it in the map (erasing anyway)", user->nick.c_str()); - } - - iduser.erase(iter); - } - else - { - ServerInstance->Logs->Log("m_sqlutils",DEBUG, "BUG: user %s was extended with sqlutils_queryids but there was nothing matching in the map", user->nick.c_str()); - } - } - - idExt.unset(user); - } - } - - void AttachList(Extensible* obj, unsigned long id) - { - AssocIdList* il = idExt.get(obj); - - if (!il) - { - /* Doesn't already exist, create a new list and attach it. */ - il = new AssocIdList; - idExt.set(obj, il); - } - - /* Now either way we have a valid list in il, attached. */ - il->push_back(id); - } - - void RemoveFromList(Extensible* obj, unsigned long id) - { - AssocIdList* il = idExt.get(obj); - - if (il) - { - /* Only do anything if the list exists... (which it ought to) */ - il->remove(id); - - if(il->empty()) - { - /* If we just emptied it.. */ - idExt.unset(obj); - } - } - } - - template <class T> void DoUnAssociate(T &map, unsigned long id) - { - /* For each occurence of 'id' (well, only one..it's not a multimap) in 'map' - * remove it from the map, take an Extensible* value from the map and remove - * 'id' from the list of query IDs attached to it. - */ - typename T::iterator iter = map.find(id); - - if(iter != map.end()) - { - /* Found a value indexed by 'id', call RemoveFromList() - * on it with 'id' to remove 'id' from the list attached - * to the value. - */ - RemoveFromList(iter->second, id); - } - } - - void OnChannelDelete(Channel* chan) - { - /* A channel is being destroyed, first we need to check if it has a list of queries associated with it. - * Then, if it does, we need to erase each of them from our IdChanMap (idchan) so when the module that - * associated them asks to look them up then it gets a NULL result and knows to discard the query. - */ - AssocIdList* il = idExt.get(chan); - - if (il) - { - for(AssocIdList::iterator listiter = il->begin(); listiter != il->end(); listiter++) - { - IdChanMap::iterator iter; - - iter = idchan.find(*listiter); - - if(iter != idchan.end()) - { - if(iter->second != chan) - { - ServerInstance->Logs->Log("m_sqlutils",DEBUG, "BUG: ID associated with channel %s doesn't have the same Channel* associated with it in the map (erasing anyway)", chan->name.c_str()); - } - idchan.erase(iter); - } - else - { - ServerInstance->Logs->Log("m_sqlutils",DEBUG, "BUG: channel %s was extended with sqlutils_queryids but there was nothing matching in the map", chan->name.c_str()); - } - } - - idExt.unset(chan); - } - } - - Version GetVersion() - { - return Version("Provides some utilities to SQL client modules, such as mapping queries to users and channels", VF_VENDOR); - } - -}; - -MODULE_INIT(ModuleSQLutils) diff --git a/src/modules/m_sqlutils.h b/src/modules/m_sqlutils.h deleted file mode 100644 index 81c3c2d3b..000000000 --- a/src/modules/m_sqlutils.h +++ /dev/null @@ -1,143 +0,0 @@ -/* +------------------------------------+ - * | Inspire Internet Relay Chat Daemon | - * +------------------------------------+ - * - * InspIRCd: (C) 2002-2010 InspIRCd Development Team - * See: http://wiki.inspircd.org/Credits - * - * This program is free but copyrighted software; see - * the file COPYING for details. - * - * --------------------------------------------------- - */ - -#ifndef INSPIRCD_SQLUTILS -#define INSPIRCD_SQLUTILS - -#include "modules.h" - -#define SQLUTILAU "SQLutil AssociateUser" -#define SQLUTILAC "SQLutil AssociateChan" -#define SQLUTILUA "SQLutil UnAssociate" -#define SQLUTILGU "SQLutil GetAssocUser" -#define SQLUTILGC "SQLutil GetAssocChan" -#define SQLUTILSUCCESS "You shouldn't be reading this (success)" - -/** Used to associate an SQL query with a user - */ -class AssociateUser : public Request -{ -public: - /** Query ID - */ - unsigned long id; - /** User - */ - User* user; - - AssociateUser(Module* s, Module* d, unsigned long i, User* u) - : Request(s, d, SQLUTILAU), id(i), user(u) - { - } - - AssociateUser& S() - { - Send(); - return *this; - } -}; - -/** Used to associate an SQL query with a channel - */ -class AssociateChan : public Request -{ -public: - /** Query ID - */ - unsigned long id; - /** Channel - */ - Channel* chan; - - AssociateChan(Module* s, Module* d, unsigned long i, Channel* u) - : Request(s, d, SQLUTILAC), id(i), chan(u) - { - } - - AssociateChan& S() - { - Send(); - return *this; - } -}; - -/** Unassociate a user or class from an SQL query - */ -class UnAssociate : public Request -{ -public: - /** The query ID - */ - unsigned long id; - - UnAssociate(Module* s, Module* d, unsigned long i) - : Request(s, d, SQLUTILUA), id(i) - { - } - - UnAssociate& S() - { - Send(); - return *this; - } -}; - -/** Get the user associated with an SQL query ID - */ -class GetAssocUser : public Request -{ -public: - /** The query id - */ - unsigned long id; - /** The user - */ - User* user; - - GetAssocUser(Module* s, Module* d, unsigned long i) - : Request(s, d, SQLUTILGU), id(i), user(NULL) - { - } - - GetAssocUser& S() - { - Send(); - return *this; - } -}; - -/** Get the channel associated with an SQL query ID - */ -class GetAssocChan : public Request -{ -public: - /** The query id - */ - unsigned long id; - /** The channel - */ - Channel* chan; - - GetAssocChan(Module* s, Module* d, unsigned long i) - : Request(s, d, SQLUTILGC), id(i), chan(NULL) - { - } - - GetAssocChan& S() - { - Send(); - return *this; - } -}; - -#endif diff --git a/src/modules/m_sqlv2.h b/src/modules/m_sqlv2.h deleted file mode 100644 index 9b6bd36b3..000000000 --- a/src/modules/m_sqlv2.h +++ /dev/null @@ -1,604 +0,0 @@ -/* +------------------------------------+ - * | Inspire Internet Relay Chat Daemon | - * +------------------------------------+ - * - * InspIRCd: (C) 2002-2010 InspIRCd Development Team - * See: http://wiki.inspircd.org/Credits - * - * This program is free but copyrighted software; see - * the file COPYING for details. - * - * --------------------------------------------------- - */ - -#ifndef INSPIRCD_SQLAPI_2 -#define INSPIRCD_SQLAPI_2 - -#include <string> -#include <deque> -#include <map> -#include "modules.h" - -/** Identifiers used to identify Request types - */ -#define SQLREQID "SQLv2 Request" -#define SQLRESID "SQLv2 Result" -#define SQLSUCCESS "You shouldn't be reading this (success)" - -/** Defines the error types which SQLerror may be set to - */ -enum SQLerrorNum { SQL_NO_ERROR, SQL_BAD_DBID, SQL_BAD_CONN, SQL_QSEND_FAIL, SQL_QREPLY_FAIL }; - -/** A list of format parameters for an SQLquery object. - */ -typedef std::deque<std::string> ParamL; - -/** The base class of SQL exceptions - */ -class SQLexception : public ModuleException -{ - public: - SQLexception(const std::string &reason) : ModuleException(reason) - { - } - - SQLexception() : ModuleException("SQLv2: Undefined exception") - { - } -}; - -/** An exception thrown when a bad column or row name or id is requested - */ -class SQLbadColName : public SQLexception -{ -public: - SQLbadColName() : SQLexception("SQLv2: Bad column name") - { - } -}; - -/** SQLerror holds the error state of any SQLrequest or SQLresult. - * The error string varies from database software to database software - * and should be used to display informational error messages to users. - */ -class SQLerror -{ - /** The error id - */ - SQLerrorNum id; - /** The error string - */ - std::string str; -public: - /** Initialize an SQLerror - * @param i The error ID to set - * @param s The (optional) error string to set - */ - SQLerror(SQLerrorNum i = SQL_NO_ERROR, const std::string &s = "") - : id(i), str(s) - { - } - - /** Return the ID of the error - */ - SQLerrorNum Id() - { - return id; - } - - /** Set the ID of an error - * @param i The new error ID to set - * @return the ID which was set - */ - SQLerrorNum Id(SQLerrorNum i) - { - id = i; - return id; - } - - /** Set the error string for an error - * @param s The new error string to set - */ - void Str(const std::string &s) - { - str = s; - } - - /** Return the error string for an error - */ - const char* Str() - { - if(str.length()) - return str.c_str(); - - switch(id) - { - case SQL_NO_ERROR: - return "No error"; - case SQL_BAD_DBID: - return "Invalid database ID"; - case SQL_BAD_CONN: - return "Invalid connection"; - case SQL_QSEND_FAIL: - return "Sending query failed"; - case SQL_QREPLY_FAIL: - return "Getting query result failed"; - default: - return "Unknown error"; - } - } -}; - -/** SQLquery provides a way to represent a query string, and its parameters in a type-safe way. - * C++ has no native type-safe way of having a variable number of arguments to a function, - * the workaround for this isn't easy to describe simply, but in a nutshell what's really - * happening when - from the above example - you do this: - * - * SQLrequest foo = SQLrequest(this, target, "databaseid", SQLquery("SELECT (foo, bar) FROM rawr WHERE foo = '?' AND bar = ?", "Hello", "42")); - * - * what's actually happening is functionally this: - * - * SQLrequest foo = SQLrequest(this, target, "databaseid", query("SELECT (foo, bar) FROM rawr WHERE foo = '?' AND bar = ?").addparam("Hello").addparam("42")); - * - * with 'query()' returning a reference to an object with a 'addparam()' member function which - * in turn returns a reference to that object. There are actually four ways you can create a - * SQLrequest..all have their disadvantages and advantages. In the real implementations the - * 'query()' function is replaced by the constructor of another class 'SQLquery' which holds - * the query string and a ParamL (std::deque<std::string>) of query parameters. - * This is essentially the same as the above example except 'addparam()' is replaced by operator,(). The full syntax for this method is: - * - * SQLrequest foo = SQLrequest(this, target, "databaseid", (SQLquery("SELECT.. ?"), parameter, parameter)); - */ -class SQLquery -{ -public: - /** The query 'format string' - */ - std::string q; - /** The query parameter list - * There should be one parameter for every ? character - * within the format string shown above. - */ - ParamL p; - - /** Initialize an SQLquery with a given format string only - */ - SQLquery(const std::string &query) - : q(query) - { - } - - /** Initialize an SQLquery with a format string and parameters. - * If you provide parameters, you must initialize the list yourself - * if you choose to do it via this method, using std::deque::push_back(). - */ - SQLquery(const std::string &query, const ParamL ¶ms) - : q(query), p(params) - { - } - - /** An overloaded operator for pushing parameters onto the parameter list - */ - template<typename T> SQLquery& operator,(const T &foo) - { - p.push_back(ConvToStr(foo)); - return *this; - } - - /** An overloaded operator for pushing parameters onto the parameter list. - * This has higher precedence than 'operator,' and can save on parenthesis. - */ - template<typename T> SQLquery& operator%(const T &foo) - { - p.push_back(ConvToStr(foo)); - return *this; - } -}; - -/** SQLrequest is sent to the SQL API to command it to run a query and return the result. - * You must instantiate this object with a valid SQLquery object and its parameters, then - * send it using its Send() method to the module providing the 'SQL' feature. To find this - * module, use Server::FindFeature(). - */ -class SQLrequest : public Request -{ -public: - /** The fully parsed and expanded query string - * This is initialized from the SQLquery parameter given in the constructor. - */ - SQLquery query; - /** The database ID to apply the request to - */ - std::string dbid; - /** True if this is a priority query. - * Priority queries may 'queue jump' in the request queue. - */ - bool pri; - /** True if this query has been cancelled; send no response */ - bool cancel; - /** The query ID, assigned by the SQL api. - * After your request is processed, this will - * be initialized for you by the API to a valid request ID, - * except in the case of an error. - */ - unsigned long id; - /** If an error occured, error.id will be any other value than SQL_NO_ERROR. - */ - SQLerror error; - - /** Initialize an SQLrequest. - * For example: - * - * SQLrequest req = SQLrequest(MyMod, SQLModule, dbid, SQLquery("INSERT INTO ircd_log_actors VALUES('','?')" % nick)); - * - * @param s A pointer to the sending module, where the result should be routed - * @param d A pointer to the receiving module, identified as implementing the 'SQL' feature - * @param databaseid The database ID to perform the query on. This must match a valid - * database ID from the configuration of the SQL module. - * @param q A properly initialized SQLquery object. - */ - SQLrequest(Module* s, Module* d, const std::string &databaseid, const SQLquery &q) - : Request(s, d, SQLREQID), query(q), dbid(databaseid), pri(false), id(0) - { - } - - // Copy constructor - XXX probably shouldn't be needed - SQLrequest(const SQLrequest& o) - : Request(o.source, o.dest, SQLREQID), query(o.query), dbid(o.dbid), pri(o.pri), cancel(o.cancel), - id(o.id), error(o.error) {} - - /** Set the priority of a request. - */ - void Priority(bool p = true) - { - pri = p; - } -}; - -/** - * This class contains a field's data plus a way to determine if the field - * is NULL or not without having to mess around with NULL pointers. - */ -class SQLfield -{ -public: - /** - * The data itself - */ - std::string d; - - /** - * If the field was null - */ - bool null; - - /** Initialize an SQLfield - */ - SQLfield(const std::string &data = "", bool n = false) - : d(data), null(n) - { - - } -}; - -/** A list of items which make up a row of a result or table (tuple) - * This does not include field names. - */ -typedef std::vector<SQLfield> SQLfieldList; -/** A list of items which make up a row of a result or table (tuple) - * This also includes the field names. - */ -typedef std::map<std::string, SQLfield> SQLfieldMap; - -/** SQLresult is a reply to a previous query. - * If you send a query to the SQL api, the response will arrive at your - * OnRequest method of your module at some later time, depending on the - * congestion of the SQL server and complexity of the query. The ID of - * this result will match the ID assigned to your original request. - * SQLresult contains its own internal cursor (row counter) which is - * incremented with each method call which retrieves a single row. - */ -class SQLresult : public Request -{ -public: - /** The original query string passed initially to the SQL API - */ - std::string query; - /** The database ID the query was executed on - */ - std::string dbid; - /** - * The error (if any) which occured. - * If an error occured the value of error.id will be any - * other value than SQL_NO_ERROR. - */ - SQLerror error; - /** - * This will match query ID you were given when sending - * the request at an earlier time. - */ - unsigned long id; - - /** Used by the SQL API to instantiate an SQLrequest - */ - SQLresult(Module* s, Module* d, unsigned long i) - : Request(s, d, SQLRESID), id(i) - { - } - - /** - * Return the number of rows in the result - * Note that if you have perfomed an INSERT - * or UPDATE query or other query which will - * not return rows, this will return the - * number of affected rows, and SQLresult::Cols() - * will contain 0. In this case you SHOULD NEVER - * access any of the result set rows, as there arent any! - * @returns Number of rows in the result set. - */ - virtual int Rows() = 0; - - /** - * Return the number of columns in the result. - * If you performed an UPDATE or INSERT which - * does not return a dataset, this value will - * be 0. - * @returns Number of columns in the result set. - */ - virtual int Cols() = 0; - - /** - * Get a string name of the column by an index number - * @param column The id number of a column - * @returns The column name associated with the given ID - */ - virtual std::string ColName(int column) = 0; - - /** - * Get an index number for a column from a string name. - * An exception of type SQLbadColName will be thrown if - * the name given is invalid. - * @param column The column name to get the ID of - * @returns The ID number of the column provided - */ - virtual int ColNum(const std::string &column) = 0; - - /** - * Get a string value in a given row and column - * This does not effect the internal cursor. - * @returns The value stored at [row,column] in the table - */ - virtual SQLfield GetValue(int row, int column) = 0; - - /** - * Return a list of values in a row, this should - * increment an internal counter so you can repeatedly - * call it until it returns an empty vector. - * This returns a reference to an internal object, - * the same object is used for all calls to this function - * and therefore the return value is only valid until - * you call this function again. It is also invalid if - * the SQLresult object is destroyed. - * The internal cursor (row counter) is incremented by one. - * @returns A reference to the current row's SQLfieldList - */ - virtual SQLfieldList& GetRow() = 0; - - /** - * As above, but return a map indexed by key name. - * The internal cursor (row counter) is incremented by one. - * @returns A reference to the current row's SQLfieldMap - */ - virtual SQLfieldMap& GetRowMap() = 0; - - /** - * Like GetRow(), but returns a pointer to a dynamically - * allocated object which must be explicitly freed. For - * portability reasons this must be freed with SQLresult::Free() - * The internal cursor (row counter) is incremented by one. - * @returns A newly-allocated SQLfieldList - */ - virtual SQLfieldList* GetRowPtr() = 0; - - /** - * As above, but return a map indexed by key name - * The internal cursor (row counter) is incremented by one. - * @returns A newly-allocated SQLfieldMap - */ - virtual SQLfieldMap* GetRowMapPtr() = 0; - - /** - * Overloaded function for freeing the lists and maps - * returned by GetRowPtr or GetRowMapPtr. - * @param fm The SQLfieldMap to free - */ - virtual void Free(SQLfieldMap* fm) = 0; - - /** - * Overloaded function for freeing the lists and maps - * returned by GetRowPtr or GetRowMapPtr. - * @param fl The SQLfieldList to free - */ - virtual void Free(SQLfieldList* fl) = 0; -}; - - -/** SQLHost represents a <database> config line and is useful - * for storing in a map and iterating on rehash to see which - * <database> tags was added/removed/unchanged. - */ -class SQLhost -{ - public: - std::string id; /* Database handle id */ - std::string host; /* Database server hostname */ - std::string ip; /* resolved IP, needed for at least pgsql.so */ - unsigned int port; /* Database server port */ - std::string name; /* Database name */ - std::string user; /* Database username */ - std::string pass; /* Database password */ - bool ssl; /* If we should require SSL */ - - SQLhost() - : id(""), host(""), ip(""), port(0), name(""), user(""), pass(""), ssl(0) - { - } - - SQLhost(const std::string& i, const std::string& h, unsigned int p, const std::string& n, const std::string& u, const std::string& pa, bool s) - : id(i), host(h), ip(""), port(p), name(n), user(u), pass(pa), ssl(s) - { - } - - /** Overload this to return a correct Data source Name (DSN) for - * the current SQL module. - */ - std::string GetDSN(); -}; - -/** Overload operator== for two SQLhost objects for easy comparison. - */ -inline bool operator== (const SQLhost& l, const SQLhost& r) -{ - return (l.id == r.id && l.host == r.host && l.port == r.port && l.name == r.name && l.user == r.user && l.pass == r.pass && l.ssl == r.ssl); -} -/** Overload operator!= for two SQLhost objects for easy comparison. - */ -inline bool operator!= (const SQLhost& l, const SQLhost& r) -{ - return (l.id != r.id || l.host != r.host || l.port != r.port || l.name != r.name || l.user != r.user || l.pass != r.pass || l.ssl != r.ssl); -} - - -/** QueryQueue, a queue of queries waiting to be executed. - * This maintains two queues internally, one for 'priority' - * queries and one for less important ones. Each queue has - * new queries appended to it and ones to execute are popped - * off the front. This keeps them flowing round nicely and no - * query should ever get 'stuck' for too long. If there are - * queries in the priority queue they will be executed first, - * 'unimportant' queries will only be executed when the - * priority queue is empty. - * - * We store lists of SQLrequest's here, by value as we want to avoid storing - * any data allocated inside the client module (in case that module is unloaded - * while the query is in progress). - * - * Because we want to work on the current SQLrequest in-situ, we need a way - * of accessing the request we are currently processing, QueryQueue::front(), - * but that call needs to always return the same request until that request - * is removed from the queue, this is what the 'which' variable is. New queries are - * always added to the back of one of the two queues, but if when front() - * is first called then the priority queue is empty then front() will return - * a query from the normal queue, but if a query is then added to the priority - * queue then front() must continue to return the front of the *normal* queue - * until pop() is called. - */ - -class QueryQueue -{ -private: - typedef std::deque<SQLrequest*> ReqDeque; - - ReqDeque priority; /* The priority queue */ - ReqDeque normal; /* The 'normal' queue */ - enum { PRI, NOR, NON } which; /* Which queue the currently active element is at the front of */ - -public: - QueryQueue() - : which(NON) - { - } - - void push(SQLrequest *q) - { - if(q->pri) - priority.push_back(q); - else - normal.push_back(q); - } - - void pop() - { - if((which == PRI) && priority.size()) - { - priority.pop_front(); - } - else if((which == NOR) && normal.size()) - { - normal.pop_front(); - } - - /* Reset this */ - which = NON; - - /* Silently do nothing if there was no element to pop() */ - } - - SQLrequest* front() - { - switch(which) - { - case PRI: - return priority.front(); - case NOR: - return normal.front(); - default: - if(priority.size()) - { - which = PRI; - return priority.front(); - } - - if(normal.size()) - { - which = NOR; - return normal.front(); - } - } - return NULL; - } - - std::pair<int, int> size() - { - return std::make_pair(priority.size(), normal.size()); - } - - int totalsize() - { - return priority.size() + normal.size(); - } - - void PurgeModule(Module* mod) - { - DoPurgeModule(mod, priority); - DoPurgeModule(mod, normal); - } - -private: - void DoPurgeModule(Module* mod, ReqDeque& q) - { - ReqDeque::iterator iter = q.begin(); - while (iter != q.end()) - { - if((**iter).source == mod) - { - if (*iter == front()) - { - /* It's the currently active query.. :x */ - (**iter).cancel = true; - iter++; - } - else - { - /* It hasn't been executed yet..just remove it */ - iter = q.erase(iter); - } - } - else - iter++; - } - } -}; - - -#endif diff --git a/src/modules/m_testclient.cpp b/src/modules/m_testclient.cpp deleted file mode 100644 index 7c47ce390..000000000 --- a/src/modules/m_testclient.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* +------------------------------------+ - * | Inspire Internet Relay Chat Daemon | - * +------------------------------------+ - * - * InspIRCd: (C) 2002-2010 InspIRCd Development Team - * See: http://wiki.inspircd.org/Credits - * - * This program is free but copyrighted software; see - * the file COPYING for details. - * - * --------------------------------------------------- - */ - -#include "inspircd.h" -#include "m_sqlv2.h" - -class ModuleTestClient : public Module -{ -private: - - -public: - ModuleTestClient() - { - Implementation eventlist[] = { I_OnBackgroundTimer }; - ServerInstance->Modules->Attach(eventlist, this, 1); - } - - - virtual Version GetVersion() - { - return Version("SQL test module", VF_VENDOR); - } - - virtual void OnBackgroundTimer(time_t) - { - ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_DATA, "SQL"); - if (!prov) - return; - Module* target = prov->creator; - - if(target) - { - SQLrequest foo = SQLrequest(this, target, "foo", - SQLquery("UPDATE rawr SET foo = '?' WHERE bar = 42") % ServerInstance->Time()); - - foo.Send(); - if (foo.cancel) - { - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "SQLrequest failed: %s", foo.error.Str()); - } - else - { - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Sent query, got given ID %lu", foo.id); - } - } - } - - void OnRequest(Request& request) - { - if(strcmp(SQLRESID, request.id) == 0) - { - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Got SQL result (%s)", request.id); - - SQLresult* res = (SQLresult*)&request; - - if (res->error.Id() == SQL_NO_ERROR) - { - if(res->Cols()) - { - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Got result with %d rows and %d columns", res->Rows(), res->Cols()); - - for (int r = 0; r < res->Rows(); r++) - { - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Row %d:", r); - - for(int i = 0; i < res->Cols(); i++) - { - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "\t[%s]: %s", res->ColName(i).c_str(), res->GetValue(r, i).d.c_str()); - } - } - } - else - { - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "%d rows affected in query", res->Rows()); - } - } - else - { - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "SQLrequest failed: %s", res->error.Str()); - } - } - - ServerInstance->Logs->Log("m_testclient.so", DEBUG, "Got unsupported API version string: %s", request.id); - } - - virtual ~ModuleTestClient() - { - } -}; - -MODULE_INIT(ModuleTestClient) - diff --git a/src/modules/sql.h b/src/modules/sql.h new file mode 100644 index 000000000..2d44584fa --- /dev/null +++ b/src/modules/sql.h @@ -0,0 +1,154 @@ +/* +------------------------------------+ + * | Inspire Internet Relay Chat Daemon | + * +------------------------------------+ + * + * InspIRCd: (C) 2002-2010 InspIRCd Development Team + * See: http://wiki.inspircd.org/Credits + * + * This program is free but copyrighted software; see + * the file COPYING for details. + * + * --------------------------------------------------- + */ + +#ifndef INSPIRCD_SQLAPI_3 +#define INSPIRCD_SQLAPI_3 + +/** Defines the error types which SQLerror may be set to + */ +enum SQLerrorNum { SQL_BAD_DBID, SQL_BAD_CONN, SQL_QSEND_FAIL, SQL_QREPLY_FAIL }; + +/** A list of format parameters for an SQLquery object. + */ +typedef std::vector<std::string> ParamL; + +typedef std::map<std::string, std::string> ParamM; + +/** + * Result of an SQL query. Only valid inside OnResult + */ +class SQLResult : public interfacebase +{ + public: + /** + * Return the number of rows in the result. + * + * Note that if you have perfomed an INSERT or UPDATE query or other + * query which will not return rows, this will return the number of + * affected rows. In this case you SHOULD NEVER access any of the result + * set rows, as there aren't any! + * @returns Number of rows in the result set. + */ + virtual int Rows() = 0; + + /** + * Return a single row (result of the query). The internal row counter + * is incremented by one. + * + * @param result Storage for the result data. + * @returns true if there was a row, false if no row exists (end of + * iteration) + */ + virtual bool GetRow(std::vector<std::string>& result) = 0; +}; + +/** SQLerror holds the error state of a request. + * The error string varies from database software to database software + * and should be used to display informational error messages to users. + */ +class SQLerror +{ + public: + /** The error id + */ + SQLerrorNum id; + + /** The error string + */ + std::string str; + + /** Initialize an SQLerror + * @param i The error ID to set + * @param s The (optional) error string to set + */ + SQLerror(SQLerrorNum i, const std::string &s = "") + : id(i), str(s) + { + } + + /** Return the error string for an error + */ + const char* Str() + { + if(str.length()) + return str.c_str(); + + switch(id) + { + case SQL_BAD_DBID: + return "Invalid database ID"; + case SQL_BAD_CONN: + return "Invalid connection"; + case SQL_QSEND_FAIL: + return "Sending query failed"; + case SQL_QREPLY_FAIL: + return "Getting query result failed"; + default: + return "Unknown error"; + } + } +}; + +/** + * Object representing an SQL query. This should be allocated on the heap and + * passed to an SQLProvider, which will free it when the query is complete or + * when the querying module is unloaded. + * + * You should store whatever information is needed to have the callbacks work in + * this object (UID of user, channel name, etc). + */ +class SQLQuery : public classbase +{ + public: + ModuleRef creator; + const std::string dbid; + const std::string query; + + SQLQuery(Module* Creator, const std::string& db, const std::string& q) + : creator(Creator), dbid(db), query(q) {} + virtual ~SQLQuery() {} + + virtual void OnResult(SQLResult& result) = 0; + /** + * Called when the query fails + */ + virtual void OnError(SQLerror& error) { } +}; + +/** + * Provider object for SQL servers + */ +class SQLProvider : public DataProvider +{ + public: + /** Submit an asynchronous SQL request + * @param dbid The database ID to apply the request to + * @param query The query string + * @param callback The callback that the result is sent to + */ + virtual void submit(SQLQuery* query) = 0; + + /** Format a parameterized query string using proper SQL escaping. + * @param q The query string, with '?' parameters + * @param p The parameters to fill in in the '?' slots + */ + virtual std::string FormatQuery(std::string q, ParamL p); + + /** Format a parameterized query string using proper SQL escaping. + * @param q The query string, with '$foo' parameters + * @param p The map to look up parameters in + */ + virtual std::string FormatQuery(std::string q, ParamM p); +}; + +#endif |