#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 SQL; time_t lastwarn; bool ro; bool init; std::set > 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 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 >::iterator it = this->updated_items.begin(), it_end = this->updated_items.end(); it != it_end; ++it) { dynamic_reference obj = *it; if (obj) { if (obj->IsCached()) continue; obj->UpdateCache(); static std::set 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", 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 &row = res.Row(0); if (res.Get(0, "timestamp").empty()) { obj->destroy(); stype->objects.erase(obj->id); } else { Serialize::Data data; for (std::map::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 &row = res.Row(i); unsigned int id; try { id = convertTo(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::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::const_iterator it = row.begin(), it_end = row.end(); it != it_end; ++it) data[it->first] << it->second; Serializable *s = NULL; std::map::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)