m_sqlite.cpp

Go to the documentation of this file.
00001 /* RequiredLibraries: sqlite3 */
00002 
00003 #include "module.h"
00004 #include <sqlite3.h>
00005 #include "sql.h"
00006 
00007 using namespace SQL;
00008 
00009 /* SQLite3 API, based from InspiRCd */
00010 
00013 class SQLiteResult : public Result
00014 {
00015  public:
00016         SQLiteResult(unsigned int i, const Query &q, const Anope::string &fq) : Result(i, q, fq)
00017         {
00018         }
00019 
00020         SQLiteResult(const Query &q, const Anope::string &fq, const Anope::string &err) : Result(0, q, fq, err)
00021         {
00022         }
00023 
00024         void AddRow(const std::map<Anope::string, Anope::string> &data)
00025         {
00026                 this->entries.push_back(data);
00027         }
00028 };
00029 
00032 class SQLiteService : public Provider
00033 {
00034         std::map<Anope::string, std::set<Anope::string> > active_schema;
00035 
00036         Anope::string database;
00037 
00038         sqlite3 *sql;
00039 
00040         Anope::string Escape(const Anope::string &query);
00041 
00042  public:
00043         SQLiteService(Module *o, const Anope::string &n, const Anope::string &d);
00044 
00045         ~SQLiteService();
00046 
00047         void Run(Interface *i, const Query &query) anope_override;
00048 
00049         Result RunQuery(const Query &query);
00050 
00051         std::vector<Query> CreateTable(const Anope::string &table, const Data &data) anope_override;
00052 
00053         Query BuildInsert(const Anope::string &table, unsigned int id, Data &data);
00054 
00055         Query GetTables(const Anope::string &prefix);
00056 
00057         Anope::string BuildQuery(const Query &q);
00058 
00059         Anope::string FromUnixtime(time_t);
00060 };
00061 
00062 class ModuleSQLite : public Module
00063 {
00064         /* SQL connections */
00065         std::map<Anope::string, SQLiteService *> SQLiteServices;
00066  public:
00067         ModuleSQLite(const Anope::string &modname, const Anope::string &creator) : Module(modname, creator, SUPPORTED)
00068         {
00069                 Implementation i[] = { I_OnReload };
00070                 ModuleManager::Attach(i, this,  sizeof(i) / sizeof(Implementation));
00071 
00072                 OnReload();
00073         }
00074 
00075         ~ModuleSQLite()
00076         {
00077                 for (std::map<Anope::string, SQLiteService *>::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end(); ++it)
00078                         delete it->second;
00079                 SQLiteServices.clear();
00080         }
00081 
00082         void OnReload() anope_override
00083         {
00084                 ConfigReader config;
00085                 int i, num;
00086 
00087                 for (std::map<Anope::string, SQLiteService *>::iterator it = this->SQLiteServices.begin(); it != this->SQLiteServices.end();)
00088                 {
00089                         const Anope::string &cname = it->first;
00090                         SQLiteService *s = it->second;
00091                         ++it;
00092 
00093                         for (i = 0, num = config.Enumerate("sqlite"); i < num; ++i)
00094                                 if (config.ReadValue("sqlite", "name", "sqlite/main", i) == cname)
00095                                         break;
00096 
00097                         if (i == num)
00098                         {
00099                                 Log(LOG_NORMAL, "sqlite") << "SQLite: Removing server connection " << cname;
00100 
00101                                 delete s;
00102                                 this->SQLiteServices.erase(cname);
00103                         }
00104                 }
00105 
00106                 for (i = 0, num = config.Enumerate("sqlite"); i < num; ++i)
00107                 {
00108                         Anope::string connname = config.ReadValue("sqlite", "name", "sqlite/main", i);
00109 
00110                         if (this->SQLiteServices.find(connname) == this->SQLiteServices.end())
00111                         {
00112                                 Anope::string database = Anope::DataDir + "/" + config.ReadValue("sqlite", "database", "anope", i);
00113 
00114                                 try
00115                                 {
00116                                         SQLiteService *ss = new SQLiteService(this, connname, database);
00117                                         this->SQLiteServices[connname] = ss;
00118 
00119                                         Log(LOG_NORMAL, "sqlite") << "SQLite: Successfully added database " << database;
00120                                 }
00121                                 catch (const SQL::Exception &ex)
00122                                 {
00123                                         Log(LOG_NORMAL, "sqlite") << "SQLite: " << ex.GetReason();
00124                                 }
00125                         }
00126                 }
00127         }
00128 };
00129 
00130 SQLiteService::SQLiteService(Module *o, const Anope::string &n, const Anope::string &d)
00131 : Provider(o, n), database(d), sql(NULL)
00132 {
00133         int db = sqlite3_open_v2(database.c_str(), &this->sql, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0);
00134         if (db != SQLITE_OK)
00135                 throw SQL::Exception("Unable to open SQLite database " + database + ": " + sqlite3_errmsg(this->sql));
00136 }
00137 
00138 SQLiteService::~SQLiteService()
00139 {
00140         sqlite3_interrupt(this->sql);
00141         sqlite3_close(this->sql);
00142 }
00143 
00144 void SQLiteService::Run(Interface *i, const Query &query)
00145 {
00146         Result res = this->RunQuery(query);
00147         if (!res.GetError().empty())
00148                 i->OnError(res);
00149         else
00150                 i->OnResult(res);
00151 }
00152 
00153 Result SQLiteService::RunQuery(const Query &query)
00154 {
00155         Anope::string real_query = this->BuildQuery(query);
00156         sqlite3_stmt *stmt;
00157         int err = sqlite3_prepare_v2(this->sql, real_query.c_str(), real_query.length(), &stmt, NULL);
00158         if (err != SQLITE_OK)
00159                 return SQLiteResult(query, real_query, sqlite3_errmsg(this->sql));
00160 
00161         std::vector<Anope::string> columns;
00162         int cols = sqlite3_column_count(stmt);
00163         columns.resize(cols);
00164         for (int i = 0; i < cols; ++i)
00165                 columns[i] = sqlite3_column_name(stmt, i);
00166 
00167         SQLiteResult result(0, query, real_query);
00168 
00169         while ((err = sqlite3_step(stmt)) == SQLITE_ROW)
00170         {
00171                 std::map<Anope::string, Anope::string> items;
00172                 for (int i = 0; i < cols; ++i)
00173                 {
00174                         const char *data = reinterpret_cast<const char *>(sqlite3_column_text(stmt, i));
00175                         if (data && *data)
00176                                 items[columns[i]] = data;
00177                 }
00178                 result.AddRow(items);
00179         }
00180 
00181         result.id = sqlite3_last_insert_rowid(this->sql);
00182 
00183         sqlite3_finalize(stmt);
00184 
00185         if (err != SQLITE_DONE)
00186                 return SQLiteResult(query, real_query, sqlite3_errmsg(this->sql));
00187 
00188         return result;
00189 }
00190 
00191 std::vector<Query> SQLiteService::CreateTable(const Anope::string &table, const Data &data)
00192 {
00193         std::vector<Query> queries;
00194         std::set<Anope::string> &known_cols = this->active_schema[table];
00195 
00196         if (known_cols.empty())
00197         {
00198                 Log(LOG_DEBUG) << "m_sqlite: Fetching columns for " << table;
00199 
00200                 Result columns = this->RunQuery("PRAGMA table_info(" + table + ")");
00201                 for (int i = 0; i < columns.Rows(); ++i)
00202                 {
00203                         const Anope::string &column = columns.Get(i, "name");
00204 
00205                         Log(LOG_DEBUG) << "m_sqlite: Column #" << i << " for " << table << ": " << column;
00206                         known_cols.insert(column);
00207                 }
00208         }
00209 
00210         if (known_cols.empty())
00211         {
00212                 Anope::string query_text = "CREATE TABLE `" + table + "` (`id` INTEGER PRIMARY KEY, `timestamp` timestamp DEFAULT CURRENT_TIMESTAMP";
00213 
00214                 for (Data::Map::const_iterator it = data.data.begin(), it_end = data.data.end(); it != it_end; ++it)
00215                 {
00216                         known_cols.insert(it->first);
00217 
00218                         query_text += ", `" + it->first + "` ";
00219                         if (data.GetType(it->first) == Serialize::Data::DT_INT)
00220                                 query_text += "int(11)";
00221                         else
00222                                 query_text += "text";
00223                 }
00224 
00225                 query_text += ")";
00226 
00227                 queries.push_back(query_text);
00228 
00229                 query_text = "CREATE UNIQUE INDEX `" + table + "_id_idx` ON `" + table + "` (`id`)";
00230                 queries.push_back(query_text);
00231 
00232                 query_text = "CREATE INDEX `" + table + "_timestamp_idx` ON `" + table + "` (`timestamp`)";
00233                 queries.push_back(query_text);
00234 
00235                 query_text = "CREATE TRIGGER `" + table + "_trigger` AFTER UPDATE ON `" + table + "` FOR EACH ROW BEGIN UPDATE `" + table + "` SET `timestamp` = CURRENT_TIMESTAMP WHERE `id` = `old.id`; end;";
00236                 queries.push_back(query_text);
00237         }
00238         else
00239                 for (Data::Map::const_iterator it = data.data.begin(), it_end = data.data.end(); it != it_end; ++it)
00240                 {
00241                         if (known_cols.count(it->first) > 0)
00242                                 continue;
00243 
00244                         known_cols.insert(it->first);
00245 
00246                         Anope::string query_text = "ALTER TABLE `" + table + "` ADD `" + it->first + "` ";
00247                         if (data.GetType(it->first) == Serialize::Data::DT_INT)
00248                                 query_text += "int(11)";
00249                         else
00250                                 query_text += "text";
00251 
00252                         queries.push_back(query_text);
00253                 }
00254 
00255         return queries;
00256 }
00257 
00258 Query SQLiteService::BuildInsert(const Anope::string &table, unsigned int id, Data &data)
00259 {
00260         /* Empty columns not present in the data set */
00261         const std::set<Anope::string> &known_cols = this->active_schema[table];
00262         for (std::set<Anope::string>::iterator it = known_cols.begin(), it_end = known_cols.end(); it != it_end; ++it)
00263                 if (*it != "id" && *it != "timestamp" && data.data.count(*it) == 0)
00264                         data[*it] << "";
00265 
00266         Anope::string query_text = "REPLACE INTO `" + table + "` (";
00267         if (id > 0)
00268                 query_text += "`id`,";
00269         for (Data::Map::const_iterator it = data.data.begin(), it_end = data.data.end(); it != it_end; ++it)
00270                 query_text += "`" + it->first + "`,";
00271         query_text.erase(query_text.length() - 1);
00272         query_text += ") VALUES (";
00273         if (id > 0)
00274                 query_text += stringify(id) + ",";
00275         for (Data::Map::const_iterator it = data.data.begin(), it_end = data.data.end(); it != it_end; ++it)
00276                 query_text += "@" + it->first + "@,";
00277         query_text.erase(query_text.length() - 1);
00278         query_text += ")";
00279 
00280         Query query(query_text);
00281         for (Data::Map::const_iterator it = data.data.begin(), it_end = data.data.end(); it != it_end; ++it)
00282         {
00283                 Anope::string buf;
00284                 *it->second >> buf;
00285                 query.SetValue(it->first, buf);
00286         }
00287         
00288         return query;
00289 }
00290 
00291 Query SQLiteService::GetTables(const Anope::string &prefix)
00292 {
00293         return Query("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '" + prefix + "%';");
00294 }
00295 
00296 Anope::string SQLiteService::Escape(const Anope::string &query)
00297 {
00298         char *e = sqlite3_mprintf("%q", query.c_str());
00299         Anope::string buffer = e;
00300         sqlite3_free(e);
00301         return buffer;
00302 }
00303 
00304 Anope::string SQLiteService::BuildQuery(const Query &q)
00305 {
00306         Anope::string real_query = q.query;
00307 
00308         for (std::map<Anope::string, QueryData>::const_iterator it = q.parameters.begin(), it_end = q.parameters.end(); it != it_end; ++it)
00309                 real_query = real_query.replace_all_cs("@" + it->first + "@", (it->second.escape ? ("'" + this->Escape(it->second.data) + "'") : it->second.data));
00310 
00311         return real_query;
00312 }
00313 
00314 Anope::string SQLiteService::FromUnixtime(time_t t)
00315 {
00316         return "datetime('" + stringify(t) + "', 'unixepoch')";
00317 }
00318 
00319 MODULE_INIT(ModuleSQLite)
00320