diff options
Diffstat (limited to 'modules/database/db_sql_live.cpp')
-rw-r--r-- | modules/database/db_sql_live.cpp | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/modules/database/db_sql_live.cpp b/modules/database/db_sql_live.cpp new file mode 100644 index 000000000..4b939e23b --- /dev/null +++ b/modules/database/db_sql_live.cpp @@ -0,0 +1,311 @@ +#include "module.h" +#include "../extra/sql.h" +#include "../commands/os_session.h" + +class MySQLInterface : public SQLInterface +{ + public: + MySQLInterface(Module *o) : SQLInterface(o) { } + + void OnResult(const SQLResult &r) anope_override + { + Log(LOG_DEBUG) << "SQLive successfully executed query: " << r.finished_query; + } + + void OnError(const SQLResult &r) anope_override + { + if (!r.GetQuery().query.empty()) + Log(LOG_DEBUG) << "Error executing query " << r.finished_query << ": " << r.GetError(); + else + Log(LOG_DEBUG) << "Error executing query: " << r.GetError(); + } +}; + +class DBMySQL : public Module, public Pipe +{ + private: + MySQLInterface sqlinterface; + Anope::string engine; + service_reference<SQLProvider> SQL; + time_t lastwarn; + bool ro; + bool init; + std::set<dynamic_reference<Serializable> > updated_items; + + bool CheckSQL() + { + if (SQL) + { + if (readonly && this->ro) + { + readonly = this->ro = false; + const BotInfo *bi = findbot(Config->OperServ); + if (bi) + ircdproto->SendGlobops(bi, "Found SQL again, going out of readonly mode..."); + } + + return true; + } + else + { + if (Anope::CurTime - Config->UpdateTimeout > lastwarn) + { + const BotInfo *bi = findbot(Config->OperServ); + if (bi) + ircdproto->SendGlobops(bi, "Unable to locate SQL reference, going to readonly..."); + readonly = this->ro = true; + this->lastwarn = Anope::CurTime; + } + + return false; + } + } + + bool CheckInit() + { + return init && SQL; + } + + void RunQuery(const SQLQuery &query) + { + /* Can this be threaded? */ + this->RunQueryResult(query); + } + + SQLResult RunQueryResult(const SQLQuery &query) + { + if (this->CheckSQL()) + { + SQLResult res = SQL->RunQuery(query); + if (!res.GetError().empty()) + Log(LOG_DEBUG) << "SQlive got error " << res.GetError() << " for " + res.finished_query; + else + Log(LOG_DEBUG) << "SQLive got " << res.Rows() << " rows for " << res.finished_query; + return res; + } + throw SQLException("No SQL!"); + } + + SQLQuery BuildInsert(const Anope::string &table, unsigned int id, const Serialize::Data &data) + { + if (this->SQL) + { + std::vector<SQLQuery> create_queries = this->SQL->CreateTable(table, data); + for (unsigned i = 0; i < create_queries.size(); ++i) + this->RunQuery(create_queries[i]); + } + + Anope::string query_text = "INSERT INTO `" + table + "` (`id`"; + for (Serialize::Data::const_iterator it = data.begin(), it_end = data.end(); it != it_end; ++it) + query_text += ",`" + it->first + "`"; + query_text += ") VALUES (" + stringify(id); + for (Serialize::Data::const_iterator it = data.begin(), it_end = data.end(); it != it_end; ++it) + query_text += ",@" + it->first + "@"; + query_text += ") ON DUPLICATE KEY UPDATE "; + for (Serialize::Data::const_iterator it = data.begin(), it_end = data.end(); it != it_end; ++it) + query_text += "`" + it->first + "`=VALUES(`" + it->first + "`),"; + query_text.erase(query_text.end() - 1); + + SQLQuery query(query_text); + for (Serialize::Data::const_iterator it = data.begin(), it_end = data.end(); it != it_end; ++it) + query.setValue(it->first, it->second.astr()); + + return query; + } + + public: + DBMySQL(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, DATABASE), sqlinterface(this), SQL("", "") + { + this->lastwarn = 0; + this->ro = false; + this->init = false; + + Implementation i[] = { I_OnReload, I_OnShutdown, I_OnLoadDatabase, I_OnSerializableConstruct, I_OnSerializableDestruct, I_OnSerializePtrAssign, I_OnSerializeCheck, I_OnSerializableUpdate }; + ModuleManager::Attach(i, this, sizeof(i) / sizeof(Implementation)); + + OnReload(); + } + + void OnNotify() anope_override + { + if (!this->CheckInit()) + return; + + for (std::set<dynamic_reference<Serializable> >::iterator it = this->updated_items.begin(), it_end = this->updated_items.end(); it != it_end; ++it) + { + dynamic_reference<Serializable> obj = *it; + + if (obj) + { + if (obj->IsCached()) + continue; + obj->UpdateCache(); + + static std::set<Serializable *> working_objects; // XXX + if (working_objects.count(obj)) + continue; + working_objects.insert(obj); + + SQLResult res = this->RunQueryResult(BuildInsert(obj->serialize_name(), obj->id, obj->serialize())); + if (res.GetID() > 0) + obj->id = res.GetID(); + SerializeType *type = SerializeType::Find(obj->serialize_name()); + if (type) + type->objects.erase(obj->id); + + working_objects.erase(obj); + } + } + + this->updated_items.clear(); + } + + EventReturn OnLoadDatabase() anope_override + { + init = true; + return EVENT_STOP; + } + + void OnShutdown() anope_override + { + init = false; + } + + void OnReload() anope_override + { + ConfigReader config; + this->engine = config.ReadValue("db_sql", "engine", "", 0); + this->SQL = service_reference<SQLProvider>("SQLProvider", this->engine); + } + + void OnSerializableConstruct(Serializable *obj) anope_override + { + if (!this->CheckInit()) + return; + this->updated_items.insert(obj); + this->Notify(); + } + + void OnSerializableDestruct(Serializable *obj) anope_override + { + if (!this->CheckInit()) + return; + this->RunQuery("DELETE FROM `" + obj->serialize_name() + "` WHERE `id` = " + stringify(obj->id)); + SerializeType *type = SerializeType::Find(obj->serialize_name()); + if (type) + type->objects.erase(obj->id); + } + + void OnSerializePtrAssign(Serializable *obj) anope_override + { + SerializeType *stype = SerializeType::Find(obj->serialize_name()); + if (stype == NULL || !this->CheckInit() || stype->GetTimestamp() == Anope::CurTime) + return; + + if (obj->IsCached()) + return; + obj->UpdateCache(); + + SQLResult res = this->RunQueryResult("SELECT * FROM `" + obj->serialize_name() + "` WHERE `id` = " + stringify(obj->id)); + + if (res.Rows() == 0) + obj->destroy(); + else + { + const std::map<Anope::string, Anope::string> &row = res.Row(0); + + if (res.Get(0, "timestamp").empty()) + { + obj->destroy(); + stype->objects.erase(obj->id); + } + else + { + Serialize::Data data; + + for (std::map<Anope::string, Anope::string>::const_iterator it = row.begin(), it_end = row.end(); it != it_end; ++it) + data[it->first] << it->second; + + if (stype->Unserialize(obj, data) == NULL) + obj->destroy(); + } + } + } + + void OnSerializeCheck(SerializeType *obj) + { + if (!this->CheckInit() || obj->GetTimestamp() == Anope::CurTime) + return; + + SQLQuery query("SELECT * FROM `" + obj->GetName() + "` WHERE (`timestamp` > FROM_UNIXTIME(@ts@) OR `timestamp` IS NULL)"); + query.setValue("ts", obj->GetTimestamp()); + + obj->UpdateTimestamp(); + + SQLResult res = this->RunQueryResult(query); + + bool clear_null = false; + for (int i = 0; i < res.Rows(); ++i) + { + const std::map<Anope::string, Anope::string> &row = res.Row(i); + + unsigned int id; + try + { + id = convertTo<unsigned int>(res.Get(i, "id")); + } + catch (const ConvertException &) + { + Log(LOG_DEBUG) << "Unable to convert id from " << obj->GetName(); + continue; + } + + if (res.Get(i, "timestamp").empty()) + { + clear_null = true; + std::map<unsigned int, Serializable *>::iterator it = obj->objects.find(id); + if (it != obj->objects.end()) + { + it->second->destroy(); + obj->objects.erase(it); + } + } + else + { + Serialize::Data data; + + for (std::map<Anope::string, Anope::string>::const_iterator it = row.begin(), it_end = row.end(); it != it_end; ++it) + data[it->first] << it->second; + + Serializable *s = NULL; + std::map<unsigned int, Serializable *>::iterator it = obj->objects.find(id); + if (it != obj->objects.end()) + s = it->second; + + Serializable *new_s = obj->Unserialize(s, data); + if (new_s) + { + new_s->id = id; + obj->objects[id] = new_s; + } + else + s->destroy(); + } + } + + if (clear_null) + { + query = "DELETE FROM `" + obj->GetName() + "` WHERE `timestamp` IS NULL"; + this->RunQuery(query); + } + } + + void OnSerializableUpdate(Serializable *obj) + { + this->updated_items.insert(obj); + this->Notify(); + } +}; + +MODULE_INIT(DBMySQL) + |