From e334e5f7e7b20c78692845c15d3d86dbebfdf7b0 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 1 Jun 2007 02:30:29 +0000 Subject: [PATCH] -initial import of database natives + config -initial import of completed database layer -CreateIdentity now requires a non-optional second pointer. This breaks backwards compatibility for CreateIdentity(), however, this is not a function extension authors are supposed to be calling --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40874 --- configs/databases.cfg | 15 + core/Database.cpp | 272 ++++++++++- core/Database.h | 43 +- core/msvc8/sourcemod_mm.vcproj | 4 + core/smn_database.cpp | 860 +++++++++++++++++++++++++++++++++ core/systems/ExtensionSys.cpp | 18 +- core/systems/ExtensionSys.h | 2 + core/systems/PluginSys.cpp | 2 +- core/systems/ShareSys.cpp | 6 +- core/systems/ShareSys.h | 4 +- plugins/include/dbi.inc | 445 +++++++++++++++++ plugins/include/sourcemod.inc | 1 + public/IDBDriver.h | 27 +- public/IShareSys.h | 10 +- 14 files changed, 1684 insertions(+), 25 deletions(-) create mode 100644 configs/databases.cfg create mode 100644 core/smn_database.cpp create mode 100644 plugins/include/dbi.inc diff --git a/configs/databases.cfg b/configs/databases.cfg new file mode 100644 index 00000000..5328927d --- /dev/null +++ b/configs/databases.cfg @@ -0,0 +1,15 @@ +"Databases" +{ + "driver_default" "mysql" + + "default" + { + "driver" "default" + "host" "localhost" + "database" "sourcemod" + "user" "root" + "pass" "" + //"timeout" "0" + //"port" "0" + } +} diff --git a/core/Database.cpp b/core/Database.cpp index 1a778a9c..d0ffbac7 100644 --- a/core/Database.cpp +++ b/core/Database.cpp @@ -16,9 +16,23 @@ #include "HandleSys.h" #include "ShareSys.h" #include "sourcemod.h" +#include "sm_stringutil.h" +#include "TextParsers.h" +#include "Logger.h" +#include "ExtensionSys.h" +#include + +#define DBPARSE_LEVEL_NONE 0 +#define DBPARSE_LEVEL_MAIN 1 +#define DBPARSE_LEVEL_DATABASE 2 DBManager g_DBMan; +DBManager::DBManager() +: m_StrTab(512), m_ParseLevel(0), m_ParseState(0), m_pDefault(NULL) +{ +} + void DBManager::OnSourceModAllInitialized() { HandleAccess sec; @@ -31,6 +45,23 @@ void DBManager::OnSourceModAllInitialized() m_DatabaseType = g_HandleSys.CreateType("IDatabase", this, 0, NULL, NULL, g_pCoreIdent, NULL); g_ShareSys.AddInterface(NULL, this); + + g_SourceMod.BuildPath(Path_SM, m_Filename, sizeof(m_Filename), "configs/databases.cfg"); +} + +void DBManager::OnSourceModLevelChange(const char *mapName) +{ + SMCParseError err; + unsigned int line = 0; + if ((err = g_TextParser.ParseFile_SMC(m_Filename, this, &line, NULL)) != SMCParse_Okay) + { + g_Logger.LogError("[SM] Detected parse error(s) in file \"%s\"", m_Filename); + if (err != SMCParse_Custom) + { + const char *txt = g_TextParser.GetSMCErrorString(err); + g_Logger.LogError("[SM] Line %d: %s", line, txt); + } + } } void DBManager::OnSourceModShutdown() @@ -64,23 +95,173 @@ void DBManager::OnHandleDestroy(HandleType_t type, void *object) } } -bool DBManager::Connect(const char *name, IDBDriver **pdr, IDatabase **pdb, bool persistent, char *error, size_t maxlength) +void DBManager::ReadSMC_ParseStart() { - const DatabaseInfo *pInfo = FindDatabaseConf(name); + m_confs.clear(); + m_ParseLevel = 0; + m_ParseState = DBPARSE_LEVEL_NONE; + m_StrTab.Reset(); + m_DefDriver.clear(); +} - for (size_t i=0; idriver, m_drivers[i]->GetIdentifier()) == 0) + m_ParseLevel++; + return SMCParse_Continue; + } + + if (m_ParseState == DBPARSE_LEVEL_NONE) + { + if (strcmp(name, "Databases") == 0) { - *pdr = m_drivers[i]; - *pdb = m_drivers[i]->Connect(pInfo, persistent, error, maxlength); - return (*pdb == NULL); + m_ParseState = DBPARSE_LEVEL_MAIN; + } else { + m_ParseLevel++; + } + } else if (m_ParseState == DBPARSE_LEVEL_MAIN) { + s_CurInfo = ConfDbInfo(); + s_CurInfo.database = m_StrTab.AddString(name); + m_ParseState = DBPARSE_LEVEL_DATABASE; + } else if (m_ParseState == DBPARSE_LEVEL_DATABASE) { + m_ParseLevel++; + } + + return SMCParse_Continue; +} + +SMCParseResult DBManager::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes) +{ + if (m_ParseLevel) + { + return SMCParse_Continue; + } + + if (m_ParseState == DBPARSE_LEVEL_MAIN) + { + if (strcmp(key, "driver_default") == 0) + { + m_DefDriver.assign(value); + } + } else if (m_ParseState == DBPARSE_LEVEL_DATABASE) { + if (strcmp(key, "driver") == 0) + { + if (strcmp(value, "default") != 0) + { + s_CurInfo.driver = m_StrTab.AddString(value); + } + } else if (strcmp(key, "database") == 0) { + s_CurInfo.database = m_StrTab.AddString(value); + } else if (strcmp(key, "host") == 0) { + s_CurInfo.host = m_StrTab.AddString(value); + } else if (strcmp(key, "user") == 0) { + s_CurInfo.user = m_StrTab.AddString(value); + } else if (strcmp(key, "pass") == 0) { + s_CurInfo.pass = m_StrTab.AddString(value); + } else if (strcmp(key, "timeout") == 0) { + s_CurInfo.info.maxTimeout = atoi(value); + } else if (strcmp(key, "port") == 0) { + s_CurInfo.info.port = atoi(value); } } - *pdr = NULL; + return SMCParse_Continue; +} + +#define ASSIGN_VAR(var) \ + if (s_CurInfo.var == -1) { \ + s_CurInfo.info.var = ""; \ + } else { \ + s_CurInfo.info.var = m_StrTab.GetString(s_CurInfo.var); \ + } + +SMCParseResult DBManager::ReadSMC_LeavingSection() +{ + if (m_ParseLevel) + { + m_ParseLevel--; + return SMCParse_Continue; + } + + if (m_ParseState == DBPARSE_LEVEL_DATABASE) + { + /* Set all of the info members to either a blank string + * or the string pointer from the string table. + */ + ASSIGN_VAR(driver); + ASSIGN_VAR(database); + ASSIGN_VAR(host); + ASSIGN_VAR(user); + ASSIGN_VAR(pass); + + /* Save it.. */ + m_confs.push_back(s_CurInfo); + + /* Go up one level */ + m_ParseState = DBPARSE_LEVEL_MAIN; + } else if (m_ParseState == DBPARSE_LEVEL_MAIN) { + m_ParseState = DBPARSE_LEVEL_NONE; + return SMCParse_Halt; + } + + return SMCParse_Continue; +} +#undef ASSIGN_VAR + +void DBManager::ReadSMC_ParseEnd(bool halted, bool failed) +{ +} + +bool DBManager::Connect(const char *name, IDBDriver **pdr, IDatabase **pdb, bool persistent, char *error, size_t maxlength) +{ + ConfDbInfo *pInfo = GetDatabaseConf(name); + + if (!pInfo) + { + if (pdr) + { + *pdr = NULL; + } + *pdb = NULL; + UTIL_Format(error, maxlength, "Configuration \"%s\" not found", name); + return false; + } + + if (!pInfo->realDriver) + { + /* Try to assign a real driver pointer */ + if (pInfo->info.driver[0] == '\0') + { + if (!m_pDefault && m_DefDriver.size() > 0) + { + m_pDefault = FindOrLoadDriver(m_DefDriver.c_str()); + } + pInfo->realDriver = m_pDefault; + } else { + pInfo->realDriver = FindOrLoadDriver(pInfo->info.driver); + } + } + + if (pInfo->realDriver) + { + if (pdr) + { + *pdr = pInfo->realDriver; + } + *pdb = pInfo->realDriver->Connect(&pInfo->info, persistent, error, maxlength); + return (*pdb != NULL); + } + + if (pdr) + { + *pdr = NULL; + } *pdb = NULL; + UTIL_Format(error, maxlength, "Driver \"%s\" not found", pInfo->driver); + return false; } @@ -99,6 +280,27 @@ void DBManager::RemoveDriver(IDBDriver *pDriver) return; } } + + /* Make sure NOTHING references this! */ + List::iterator iter; + for (iter=m_confs.begin(); iter!=m_confs.end(); iter++) + { + ConfDbInfo &db = (*iter); + if (db.realDriver == pDriver) + { + db.realDriver = NULL; + } + } +} + +IDBDriver *DBManager::GetDefaultDriver() +{ + if (!m_pDefault && m_DefDriver.size() > 0) + { + m_pDefault = FindOrLoadDriver(m_DefDriver.c_str()); + } + + return m_pDefault; } Handle_t DBManager::CreateHandle(DBHandleType dtype, void *ptr, IdentityToken_t *pToken) @@ -158,6 +360,58 @@ IDBDriver *DBManager::GetDriver(unsigned int index) const DatabaseInfo *DBManager::FindDatabaseConf(const char *name) { - /* :TODO: */ + ConfDbInfo *info = GetDatabaseConf(name); + if (!info) + { + return NULL; + } + + return &info->info; +} + +ConfDbInfo *DBManager::GetDatabaseConf(const char *name) +{ + List::iterator iter; + + for (iter=m_confs.begin(); iter!=m_confs.end(); iter++) + { + ConfDbInfo &conf = (*iter); + if (strcmp(m_StrTab.GetString(conf.name), name) == 0) + { + return &conf; + } + } + + return NULL; +} + +IDBDriver *DBManager::FindOrLoadDriver(const char *name) +{ + size_t last_size = m_drivers.size(); + for (size_t i=0; iGetIdentifier(), name) == 0) + { + return m_drivers[i]; + } + } + + char filename[PLATFORM_MAX_PATH]; + UTIL_Format(filename, sizeof(filename), "dbi.%s", name); + + IExtension *pExt = g_Extensions.LoadAutoExtension(filename); + if (!pExt || !pExt->IsLoaded() || m_drivers.size() <= last_size) + { + return NULL; + } + + /* last_size is now gauranteed to be a valid index. + * The identifier must match the name. + */ + if (strcmp(m_drivers[last_size]->GetIdentifier(), name) == 0) + { + return m_drivers[last_size]; + } + return NULL; } diff --git a/core/Database.h b/core/Database.h index 47796e39..fa14c553 100644 --- a/core/Database.h +++ b/core/Database.h @@ -18,19 +18,43 @@ #include #include "sm_globals.h" #include +#include +#include +#include +#include "sm_memtable.h" using namespace SourceHook; +struct ConfDbInfo +{ + ConfDbInfo() : name(-1), driver(-1), host(-1), user(-1), pass(-1), + database(-1), realDriver(NULL) + { + } + int name; + int driver; + int host; + int user; + int pass; + int database; + IDBDriver *realDriver; + DatabaseInfo info; +}; + class DBManager : public IDBManager, public SMGlobalClass, - public IHandleTypeDispatch + public IHandleTypeDispatch, + public ITextListener_SMC { +public: + DBManager(); public: const char *GetInterfaceName(); unsigned int GetInterfaceVersion(); public: //SMGlobalClass void OnSourceModAllInitialized(); + void OnSourceModLevelChange(const char *mapName); void OnSourceModShutdown(); public: //IHandleTypeDispatch void OnHandleDestroy(HandleType_t type, void *object); @@ -44,10 +68,27 @@ public: //IDBManager Handle_t CreateHandle(DBHandleType type, void *ptr, IdentityToken_t *pToken); HandleError ReadHandle(Handle_t hndl, DBHandleType type, void **ptr); HandleError ReleaseHandle(Handle_t hndl, DBHandleType type, IdentityToken_t *token); +public: //ITextListener_SMC + void ReadSMC_ParseStart(); + SMCParseResult ReadSMC_NewSection(const char *name, bool opt_quotes); + SMCParseResult ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes); + SMCParseResult ReadSMC_LeavingSection(); + void ReadSMC_ParseEnd(bool halted, bool failed); +public: + ConfDbInfo *GetDatabaseConf(const char *name); + IDBDriver *FindOrLoadDriver(const char *name); + IDBDriver *GetDefaultDriver(); private: CVector m_drivers; + List m_confs; HandleType_t m_DriverType; HandleType_t m_DatabaseType; + String m_DefDriver; + BaseStringTable m_StrTab; + char m_Filename[PLATFORM_MAX_PATH]; + unsigned int m_ParseLevel; + unsigned int m_ParseState; + IDBDriver *m_pDefault; }; extern DBManager g_DBMan; diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj index 07277577..d00b86ea 100644 --- a/core/msvc8/sourcemod_mm.vcproj +++ b/core/msvc8/sourcemod_mm.vcproj @@ -796,6 +796,10 @@ RelativePath="..\smn_core.cpp" > + + diff --git a/core/smn_database.cpp b/core/smn_database.cpp new file mode 100644 index 00000000..d1b8c84b --- /dev/null +++ b/core/smn_database.cpp @@ -0,0 +1,860 @@ +#include "sm_globals.h" +#include "HandleSys.h" +#include "Database.h" +#include "ExtensionSys.h" +#include "PluginSys.h" + +HandleType_t hQueryType; +HandleType_t hStmtType; + +class DatabaseHelpers : + public SMGlobalClass, + public IHandleTypeDispatch +{ +public: + virtual void OnSourceModAllInitialized() + { + HandleAccess acc; + + /* Disable cloning */ + g_HandleSys.InitAccessDefaults(NULL, &acc); + acc.access[HandleAccess_Clone] = HANDLE_RESTRICT_OWNER|HANDLE_RESTRICT_IDENTITY; + + TypeAccess tacc; + + g_HandleSys.InitAccessDefaults(&tacc, NULL); + tacc.ident = g_pCoreIdent; + + hQueryType = g_HandleSys.CreateType("IQuery", this, 0, &tacc, &acc, g_pCoreIdent, NULL); + hStmtType = g_HandleSys.CreateType("IPreparedQuery", this, hQueryType, &tacc, &acc, NULL, NULL); + } + + virtual void OnSourceModShutdown() + { + g_HandleSys.RemoveType(hStmtType, g_pCoreIdent); + g_HandleSys.RemoveType(hQueryType, g_pCoreIdent); + } + + virtual void OnHandleDestroy(HandleType_t type, void *object) + { + if (type == hQueryType) + { + IQuery *query = (IQuery *)object; + query->Destroy(); + } else if (type == hStmtType) { + IPreparedQuery *query = (IPreparedQuery *)object; + query->Destroy(); + } + } +} s_DatabaseNativeHelpers; + +//is this safe for stmt handles? i think since it's single inheritance, it always will be. +inline HandleError ReadQueryHndl(Handle_t hndl, IPluginContext *pContext, IQuery **query) +{ + HandleSecurity sec; + sec.pOwner = pContext->GetIdentity(); + sec.pIdentity = g_pCoreIdent; + + return g_HandleSys.ReadHandle(hndl, hQueryType, &sec, (void **)query); +} + +inline HandleError ReadStmtHndl(Handle_t hndl, IPluginContext *pContext, IPreparedQuery **query) +{ + HandleSecurity sec; + sec.pOwner = pContext->GetIdentity(); + sec.pIdentity = g_pCoreIdent; + + return g_HandleSys.ReadHandle(hndl, hStmtType, &sec, (void **)query); +} + +inline HandleError ReadDbOrStmtHndl(Handle_t hndl, IPluginContext *pContext, IDatabase **db, IPreparedQuery **query) +{ + HandleError err; + if ((err = g_DBMan.ReadHandle(hndl, DBHandle_Database, (void **)db)) == HandleError_Type) + { + *db = NULL; + return ReadStmtHndl(hndl, pContext, query); + } + return err; +} + +static cell_t SQL_Connect(IPluginContext *pContext, const cell_t *params) +{ + char *conf, *err; + + size_t maxlength = (size_t)params[4]; + bool persistent = params[2] ? true : false; + pContext->LocalToString(params[1], &conf); + pContext->LocalToString(params[3], &err); + + IDBDriver *driver; + IDatabase *db; + if (!g_DBMan.Connect(conf, &driver, &db, persistent, err, maxlength)) + { + return BAD_HANDLE; + } + + /* HACK! Add us to the dependency list */ + CExtension *pExt = g_Extensions.GetExtensionFromIdent(driver->GetIdentity()); + if (pExt) + { + g_Extensions.BindChildPlugin(pExt, g_PluginSys.FindPluginByContext(pContext->GetContext())); + } + + return db->GetHandle(); +} + +static cell_t SQL_ConnectEx(IPluginContext *pContext, const cell_t *params) +{ + IDBDriver *driver; + if (params[1] == BAD_HANDLE) + { + if ((driver = g_DBMan.GetDefaultDriver()) == NULL) + { + return pContext->ThrowNativeError("Could not find any default driver"); + } + } else { + HandleError err; + if ((err = g_DBMan.ReadHandle(params[1], DBHandle_Driver, (void **)&driver)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid driver Handle %x (error: %d)", params[1], err); + } + } + + char *host, *user, *pass, *database, *error; + size_t maxlength = (size_t)params[7]; + bool persistent = params[8] ? true : false; + unsigned int port = params[9]; + unsigned int maxTimeout = params[10]; + pContext->LocalToString(params[2], &host); + pContext->LocalToString(params[3], &user); + pContext->LocalToString(params[4], &pass); + pContext->LocalToString(params[5], &database); + pContext->LocalToString(params[6], &error); + + DatabaseInfo info; + info.database = database; + info.driver = driver->GetIdentifier(); + info.host = host; + info.maxTimeout = maxTimeout; + info.pass = pass; + info.port = port; + info.user = user; + + IDatabase *db = driver->Connect(&info, persistent, error, maxlength); + + if (db) + { + /* HACK! Add us to the dependency list */ + CExtension *pExt = g_Extensions.GetExtensionFromIdent(driver->GetIdentity()); + if (pExt) + { + g_Extensions.BindChildPlugin(pExt, g_PluginSys.FindPluginByContext(pContext->GetContext())); + } + + return db->GetHandle(); + } + + return BAD_HANDLE; +} + +static cell_t SQL_GetDriverIdent(IPluginContext *pContext, const cell_t *params) +{ + IDBDriver *driver; + if (params[1] == BAD_HANDLE) + { + if ((driver = g_DBMan.GetDefaultDriver()) == NULL) + { + return pContext->ThrowNativeError("Could not find any default driver"); + } + } else { + HandleError err; + if ((err = g_DBMan.ReadHandle(params[1], DBHandle_Driver, (void **)&driver)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid driver Handle %x (error: %d)", params[1], err); + } + } + + pContext->StringToLocalUTF8(params[2], params[3], driver->GetIdentifier(), NULL); + + return 1; +} + +static cell_t SQL_GetDriver(IPluginContext *pContext, const cell_t *params) +{ + char *name; + pContext->LocalToString(params[1], &name); + + IDBDriver *driver = NULL; + if (name[0] == '\0') + { + driver = g_DBMan.GetDefaultDriver(); + } else { + driver = g_DBMan.FindOrLoadDriver(name); + } + + return (driver != NULL) ? driver->GetHandle() : BAD_HANDLE; +} + +static cell_t SQL_GetDriverProduct(IPluginContext *pContext, const cell_t *params) +{ + IDBDriver *driver; + if (params[1] == BAD_HANDLE) + { + if ((driver = g_DBMan.GetDefaultDriver()) == NULL) + { + return pContext->ThrowNativeError("Could not find any default driver"); + } + } else { + HandleError err; + if ((err = g_DBMan.ReadHandle(params[1], DBHandle_Driver, (void **)&driver)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid driver Handle %x (error: %d)", params[1], err); + } + } + + pContext->StringToLocalUTF8(params[2], params[3], driver->GetProductName(), NULL); + + return 1; +} + +static cell_t SQL_GetAffectedRows(IPluginContext *pContext, const cell_t *params) +{ + IDatabase *db = NULL; + IPreparedQuery *stmt = NULL; + HandleError err; + + if ((err = ReadDbOrStmtHndl(params[1], pContext, &db, &stmt)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid statement or db Handle %x (error: %d)", params[1], err); + } + + if (db) + { + return db->GetAffectedRows(); + } else if (stmt) { + return stmt->GetAffectedRows(); + } + + return pContext->ThrowNativeError("Unknown error reading db/stmt handles"); +} + +static cell_t SQL_GetInsertId(IPluginContext *pContext, const cell_t *params) +{ + IDatabase *db = NULL; + IPreparedQuery *stmt = NULL; + HandleError err; + + if ((err = ReadDbOrStmtHndl(params[1], pContext, &db, &stmt)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid statement or db Handle %x (error: %d)", params[1], err); + } + + if (db) + { + return db->GetInsertID(); + } else if (stmt) { + return stmt->GetInsertID(); + } + + return pContext->ThrowNativeError("Unknown error reading db/stmt handles"); +} + +static cell_t SQL_GetError(IPluginContext *pContext, const cell_t *params) +{ + IDatabase *db = NULL; + IPreparedQuery *stmt = NULL; + HandleError err; + + if ((err = ReadDbOrStmtHndl(params[1], pContext, &db, &stmt)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid statement or db Handle %x (error: %d)", params[1], err); + } + + const char *error = ""; + if (db) + { + error = db->GetError(); + } else if (stmt) { + error = stmt->GetError(); + } + + if (error[0] == '\0') + { + return false; + } + + pContext->StringToLocalUTF8(params[2], params[3], error, NULL); + + return 1; +} + +static cell_t SQL_QuoteString(IPluginContext *pContext, const cell_t *params) +{ + IDatabase *db = NULL; + HandleError err; + + if ((err = g_DBMan.ReadHandle(params[1], DBHandle_Database, (void **)&db)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid database Handle %x (error: %d)", params[1], err); + } + + char *input, *output; + size_t maxlength = (size_t)params[4]; + pContext->LocalToString(params[2], &input); + pContext->LocalToString(params[3], &output); + + size_t written; + bool s = db->QuoteString(input, output, maxlength, &written); + + cell_t *addr; + pContext->LocalToPhysAddr(params[5], &addr); + *addr = (cell_t)written; + + return s ? 1 : 0; +} + +static cell_t SQL_FastQuery(IPluginContext *pContext, const cell_t *params) +{ + IDatabase *db = NULL; + HandleError err; + + if ((err = g_DBMan.ReadHandle(params[1], DBHandle_Database, (void **)&db)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid database Handle %x (error: %d)", params[1], err); + } + + char *query; + pContext->LocalToString(params[2], &query); + + return db->DoSimpleQuery(query) ? 1 : 0; +} + +static cell_t SQL_Query(IPluginContext *pContext, const cell_t *params) +{ + IDatabase *db = NULL; + HandleError err; + + if ((err = g_DBMan.ReadHandle(params[1], DBHandle_Database, (void **)&db)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid database Handle %x (error: %d)", params[1], err); + } + + char *query; + pContext->LocalToString(params[2], &query); + + IQuery *qr = db->DoQuery(query); + + if (!qr) + { + return BAD_HANDLE; + } + + Handle_t hndl = g_HandleSys.CreateHandle(hQueryType, qr, pContext->GetIdentity(), g_pCoreIdent, NULL); + if (hndl == BAD_HANDLE) + { + qr->Destroy(); + return BAD_HANDLE; + } + + return hndl; +} + +static cell_t SQL_PrepareQuery(IPluginContext *pContext, const cell_t *params) +{ + IDatabase *db = NULL; + HandleError err; + + if ((err = g_DBMan.ReadHandle(params[1], DBHandle_Database, (void **)&db)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid database Handle %x (error: %d)", params[1], err); + } + + char *query, *error; + size_t maxlength = (size_t)params[4]; + pContext->LocalToString(params[2], &query); + pContext->LocalToString(params[3], &error); + + IPreparedQuery *qr = db->PrepareQuery(query, error, maxlength); + + if (!qr) + { + return BAD_HANDLE; + } + + Handle_t hndl = g_HandleSys.CreateHandle(hStmtType, qr, pContext->GetIdentity(), g_pCoreIdent, NULL); + if (hndl == BAD_HANDLE) + { + qr->Destroy(); + return BAD_HANDLE; + } + + return hndl; +} + +static cell_t SQL_FetchMoreResults(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + return query->FetchMoreResults() ? 1 : 0; +} + +static cell_t SQL_HasResultSet(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + return query->GetResultSet() != NULL ? true : false; +} + +static cell_t SQL_GetRowCount(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return 0; + } + + return rs->GetRowCount(); +} + +static cell_t SQL_GetFieldCount(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return 0; + } + + return rs->GetFieldCount(); +} + +static cell_t SQL_FieldNumToName(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + unsigned int field = params[2]; + + const char *fldname; + if ((fldname = rs->FieldNumToName(field)) == NULL) + { + return pContext->ThrowNativeError("Invalid field index %d", field); + } + + pContext->StringToLocalUTF8(params[3], params[4], fldname, NULL); + + return 1; +} + +static cell_t SQL_FieldNameToNum(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + char *field; + pContext->LocalToString(params[2], &field); + + cell_t *num; + pContext->LocalToPhysAddr(params[3], &num); + + return rs->FieldNameToNum(field, (unsigned int *)num) ? 1 : 0; +} + +static cell_t SQL_FetchRow(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + return (rs->FetchRow() != NULL) ? true : false; +} + +static cell_t SQL_MoreRows(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + return rs->MoreRows() ? 1 : 0; +} + +static cell_t SQL_Rewind(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + return rs->Rewind() ? 1 : 0; +} + +static cell_t SQL_FetchString(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + IResultRow *row = rs->CurrentRow(); + if (!row) + { + return pContext->ThrowNativeError("Current result set has no fetched rows"); + } + + const char *str; + size_t length; + DBResult res = row->GetString(params[2], &str, &length); + + if (res == DBVal_Error) + { + return pContext->ThrowNativeError("Error fetching data from field %d", params[2]); + } else if (res == DBVal_TypeMismatch) { + return pContext->ThrowNativeError("Could not fetch data in field %d as a string", params[2]); + } + + pContext->StringToLocalUTF8(params[3], params[4], str, &length); + + cell_t *addr; + pContext->LocalToPhysAddr(params[5], &addr); + *addr = (cell_t)res; + + return (cell_t)length; +} + +static cell_t SQL_FetchFloat(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + IResultRow *row = rs->CurrentRow(); + if (!row) + { + return pContext->ThrowNativeError("Current result set has no fetched rows"); + } + + float f; + DBResult res = row->GetFloat(params[2], &f); + + if (res == DBVal_Error) + { + return pContext->ThrowNativeError("Error fetching data from field %d", params[2]); + } else if (res == DBVal_TypeMismatch) { + return pContext->ThrowNativeError("Could not fetch data in field %d as a float", params[2]); + } + + cell_t *addr; + pContext->LocalToPhysAddr(params[5], &addr); + *addr = (cell_t)res; + + return sp_ftoc(f); +} + +static cell_t SQL_FetchInt(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + IResultRow *row = rs->CurrentRow(); + if (!row) + { + return pContext->ThrowNativeError("Current result set has no fetched rows"); + } + + int iv; + DBResult res = row->GetInt(params[2], &iv); + + if (res == DBVal_Error) + { + return pContext->ThrowNativeError("Error fetching data from field %d", params[2]); + } else if (res == DBVal_TypeMismatch) { + return pContext->ThrowNativeError("Could not fetch data in field %d as an integer", params[2]); + } + + cell_t *addr; + pContext->LocalToPhysAddr(params[5], &addr); + *addr = (cell_t)res; + + return iv; +} + +static cell_t SQL_IsFieldNull(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + IResultRow *row = rs->CurrentRow(); + if (!row) + { + return pContext->ThrowNativeError("Current result set has no fetched rows"); + } + + if ((unsigned)params[2] >= rs->GetFieldCount()) + { + return pContext->ThrowNativeError("Invalid field index %d", params[2]); + } + + return row->IsNull(params[2]) ? 1 : 0; +} + +static cell_t SQL_FetchSize(IPluginContext *pContext, const cell_t *params) +{ + IQuery *query; + HandleError err; + + if ((err = ReadQueryHndl(params[1], pContext, &query)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid query Handle %x (error: %d)", params[1], err); + } + + IResultSet *rs = query->GetResultSet(); + if (!rs) + { + return pContext->ThrowNativeError("No current result set"); + } + + IResultRow *row = rs->CurrentRow(); + if (!row) + { + return pContext->ThrowNativeError("Current result set has no fetched rows"); + } + + if ((unsigned)params[2] >= rs->GetFieldCount()) + { + return pContext->ThrowNativeError("Invalid field index %d", params[2]); + } + + return row->GetDataSize(params[2]); +} + +static cell_t SQL_BindParamInt(IPluginContext *pContext, const cell_t *params) +{ + IPreparedQuery *stmt; + HandleError err; + + if ((err = ReadStmtHndl(params[1], pContext, &stmt)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid statement Handle %x (error: %d)", params[1], err); + } + + if (!stmt->BindParamInt(params[2], params[3], params[4] ? true : false)) + { + return pContext->ThrowNativeError("Could not bind parameter %d as an integer", params[2]); + } + + return 1; +} + +static cell_t SQL_BindParamFloat(IPluginContext *pContext, const cell_t *params) +{ + IPreparedQuery *stmt; + HandleError err; + + if ((err = ReadStmtHndl(params[1], pContext, &stmt)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid statement Handle %x (error: %d)", params[1], err); + } + + if (!stmt->BindParamFloat(params[2], sp_ctof(params[3]))) + { + return pContext->ThrowNativeError("Could not bind parameter %d as a float", params[2]); + } + + return 1; +} + +static cell_t SQL_BindParamString(IPluginContext *pContext, const cell_t *params) +{ + IPreparedQuery *stmt; + HandleError err; + + if ((err = ReadStmtHndl(params[1], pContext, &stmt)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid statement Handle %x (error: %d)", params[1], err); + } + + char *str; + pContext->LocalToString(params[3], &str); + + if (!stmt->BindParamString(params[2], str, params[4] ? true : false)) + { + return pContext->ThrowNativeError("Could not bind parameter %d as a string", params[2]); + } + + return 1; +} + +static cell_t SQL_Execute(IPluginContext *pContext, const cell_t *params) +{ + IPreparedQuery *stmt; + HandleError err; + + if ((err = ReadStmtHndl(params[1], pContext, &stmt)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid statement Handle %x (error: %d)", params[1], err); + } + + return stmt->Execute() ? 1 : 0; +} + +REGISTER_NATIVES(dbNatives) +{ + {"SQL_BindParamInt", SQL_BindParamInt}, + {"SQL_BindParamFloat", SQL_BindParamFloat}, + {"SQL_BindParamString", SQL_BindParamString}, + {"SQL_Connect", SQL_Connect}, + {"SQL_ConnectEx", SQL_ConnectEx}, + {"SQL_Execute", SQL_Execute}, + {"SQL_FastQuery", SQL_FastQuery}, + {"SQL_FetchFloat", SQL_FetchFloat}, + {"SQL_FetchInt", SQL_FetchInt}, + {"SQL_FetchMoreResults", SQL_FetchMoreResults}, + {"SQL_FetchRow", SQL_FetchRow}, + {"SQL_FetchSize", SQL_FetchSize}, + {"SQL_FetchString", SQL_FetchString}, + {"SQL_FieldNameToNum", SQL_FieldNameToNum}, + {"SQL_FieldNumToName", SQL_FieldNumToName}, + {"SQL_GetAffectedRows", SQL_GetAffectedRows}, + {"SQL_GetDriver", SQL_GetDriver}, + {"SQL_GetDriverIdent", SQL_GetDriverIdent}, + {"SQL_GetDriverProduct", SQL_GetDriverProduct}, + {"SQL_GetError", SQL_GetError}, + {"SQL_GetFieldCount", SQL_GetFieldCount}, + {"SQL_GetInsertId", SQL_GetInsertId}, + {"SQL_GetRowCount", SQL_GetRowCount}, + {"SQL_HasResultSet", SQL_HasResultSet}, + {"SQL_IsFieldNull", SQL_IsFieldNull}, + {"SQL_MoreRows", SQL_MoreRows}, + {"SQL_Query", SQL_Query}, + {"SQL_QuoteString", SQL_QuoteString}, + {"SQL_PrepareQuery", SQL_PrepareQuery}, + {"SQL_Rewind", SQL_Rewind}, + {NULL, NULL}, +}; diff --git a/core/systems/ExtensionSys.cpp b/core/systems/ExtensionSys.cpp index b09aefd8..3c59add2 100644 --- a/core/systems/ExtensionSys.cpp +++ b/core/systems/ExtensionSys.cpp @@ -70,7 +70,7 @@ CExtension::CExtension(const char *filename, char *error, size_t maxlength) m_PlId = g_pMMPlugins->Load(path, g_PLID, already, error, maxlength); } - m_pIdentToken = g_ShareSys.CreateIdentity(g_ExtType); + m_pIdentToken = g_ShareSys.CreateIdentity(g_ExtType, this); if (!m_pAPI->OnExtensionLoad(this, &g_ShareSys, error, maxlength, !g_SourceMod.IsMapLoading())) { @@ -132,7 +132,11 @@ void CExtension::MarkAllLoaded() void CExtension::AddPlugin(IPlugin *pPlugin) { - m_Plugins.push_back(pPlugin); + /* Unfortunately we have to do this :( */ + if (m_Plugins.find(pPlugin) != m_Plugins.end()) + { + m_Plugins.push_back(pPlugin); + } } void CExtension::RemovePlugin(IPlugin *pPlugin) @@ -866,3 +870,13 @@ void CExtensionManager::OnRootConsoleCommand(const char *cmd, unsigned int argco g_RootMenu.DrawGenericOption("list", "List extensions"); g_RootMenu.DrawGenericOption("unload", "Unload an extension"); } + +CExtension *CExtensionManager::GetExtensionFromIdent(IdentityToken_t *ptr) +{ + if (ptr->type == g_ExtType) + { + return (CExtension *)(ptr->ptr); + } + + return NULL; +} diff --git a/core/systems/ExtensionSys.h b/core/systems/ExtensionSys.h index e714cb8b..a2336c92 100644 --- a/core/systems/ExtensionSys.h +++ b/core/systems/ExtensionSys.h @@ -96,6 +96,8 @@ public: void MarkAllLoaded(); void AddDependency(IExtension *pSource, const char *file, bool required, bool autoload); void TryAutoload(); +public: + CExtension *GetExtensionFromIdent(IdentityToken_t *ptr); private: CExtension *FindByOrder(unsigned int num); private: diff --git a/core/systems/PluginSys.cpp b/core/systems/PluginSys.cpp index 7b4bb201..d268c474 100644 --- a/core/systems/PluginSys.cpp +++ b/core/systems/PluginSys.cpp @@ -91,7 +91,7 @@ void CPlugin::InitIdentity() { if (!m_handle) { - m_ident = g_ShareSys.CreateIdentity(g_PluginIdent); + m_ident = g_ShareSys.CreateIdentity(g_PluginIdent, this); m_handle = g_HandleSys.CreateHandle(g_PluginType, this, g_PluginSys.GetIdentity(), g_PluginSys.GetIdentity(), NULL); m_ctx.base->SetIdentity(m_ident); } diff --git a/core/systems/ShareSys.cpp b/core/systems/ShareSys.cpp index 3dea7ecd..7dc3b70d 100644 --- a/core/systems/ShareSys.cpp +++ b/core/systems/ShareSys.cpp @@ -34,7 +34,7 @@ IdentityToken_t *ShareSystem::CreateCoreIdentity() m_CoreType = CreateIdentType("CORE"); } - return CreateIdentity(m_CoreType); + return CreateIdentity(m_CoreType, this); } void ShareSystem::OnSourceModStartup(bool late) @@ -96,7 +96,7 @@ void ShareSystem::OnHandleDestroy(HandleType_t type, void *object) /* THIS WILL NEVER BE CALLED FOR ANYTHING WITH THE IDENTITY TYPE */ } -IdentityToken_t *ShareSystem::CreateIdentity(IdentityType_t type) +IdentityToken_t *ShareSystem::CreateIdentity(IdentityType_t type, void *ptr) { if (!m_TypeRoot) { @@ -110,6 +110,8 @@ IdentityToken_t *ShareSystem::CreateIdentity(IdentityType_t type) sec.pOwner = sec.pIdentity = GetIdentRoot(); pToken->ident = g_HandleSys.CreateHandleInt(type, NULL, &sec, NULL, NULL, true); + pToken->ptr = ptr; + pToken->type = type; return pToken; } diff --git a/core/systems/ShareSys.h b/core/systems/ShareSys.h index 4627ad35..439623f0 100644 --- a/core/systems/ShareSys.h +++ b/core/systems/ShareSys.h @@ -28,6 +28,8 @@ namespace SourceMod struct IdentityToken_t { Handle_t ident; + void *ptr; + IdentityType_t type; }; }; @@ -53,7 +55,7 @@ public: //IShareSys void AddNatives(IExtension *myself, const sp_nativeinfo_t *natives); IdentityType_t CreateIdentType(const char *name); IdentityType_t FindIdentType(const char *name); - IdentityToken_t *CreateIdentity(IdentityType_t type); + IdentityToken_t *CreateIdentity(IdentityType_t type, void *ptr); void DestroyIdentType(IdentityType_t type); void DestroyIdentity(IdentityToken_t *identity); void AddDependency(IExtension *myself, const char *filename, bool require, bool autoload); diff --git a/plugins/include/dbi.inc b/plugins/include/dbi.inc new file mode 100644 index 00000000..3393e5e0 --- /dev/null +++ b/plugins/include/dbi.inc @@ -0,0 +1,445 @@ +/** + * vim: set ts=4 : + * =============================================================== + * SourceMod (C)2004-2007 AlliedModders LLC. All rights reserved. + * =============================================================== + * + * This file is part of the SourceMod/SourcePawn SDK. This file may only be used + * or modified under the Terms and Conditions of its License Agreement, which is found + * in LICENSE.txt. The Terms and Conditions for making SourceMod extensions/plugins + * may change at any time. To view the latest information, see: + * http://www.sourcemod.net/license.php + * + * Version: $Id$ + */ + +#if defined _dbi_included + #endinput +#endif +#define _dbi_included + +/** + * @handle Driver + * + * Contains information about an SQL driver. + */ + +/** + * @handle Database + * + * Contains information about a database connection. + */ + +/** + * @handle Query + * + * Contains information about an active query and its + * result sets. + */ + +/** + * @handle Statement : Query + * + * Extends a Query Handle and can be used as a Query Handle. + * Statement Handles are for prepared queries and contain + * their own function for binding parameters. Statement + * Handles can be used instead of database Handles in a few + * select functions. + */ + +/** + * Describes a database field fetch status. + */ +enum DBResult +{ + DBVal_Error = 0, /**< Column number/field is invalid. */ + DBVal_TypeMismatch = 1, /**< You cannot retrieve this data with this type. */ + DBVal_Null = 2, /**< Field has no data (NULL) */ + DBVal_Data = 3, /**< Field has data */ +} + +/** + * Creates an SQL connection from a named configuration. + * + * @param confname Named configuration. + * @param persistent True to re-use a previous persistent connection if + * possible, false otherwise. + * @param error Error buffer. + * @param maxlength Maximum length of the error buffer. + * @return A database connection Handle, or INVALID_HANDLE on failure. + */ +native Handle:SQL_Connect(const String:confname[], bool:persistent, String:error[], maxlength); + +/** + * Creates a default SQL connection. + * + * @param error Error buffer. + * @param maxlength Maximum length of the error buffer. + * @param persistent True to re-use a previous persistent connection + * if possible, false otherwise. + * @return A database connection Handle, or INVALID_HANDLE on failure. + */ +stock Handle:SQL_DefConnect(String:error[], maxlength, bool:persistent=true) +{ + return SQL_Connect("default", persistent, error, maxlength); +} + +/** + * Creates an SQL connection from specific parameters. + * + * @param driver Driver Handle, or INVALID_HANDLE for default. + * @param host Host name. + * @param user User name. + * @param pass User password. + * @param database Database name. + * @param error Error buffer. + * @param maxlength Maximum length of the error buffer. + * @param persistent True to re-use a previous persistent connection + * if possible, false otherwise. + * @param port Optional port to specify. + * @param maxTimeout Maximum timeout in seconds if applicable. + * @return A database connection Handle, or INVALID_HANDLE on failure. + * @error Invalid driver Handle other than INVALID_HANDLE. + */ +native Handle:SQL_ConnectEx(Handle:driver, + const String:host[], + const String:user[], + const String:pass[], + const String:database[], + String:error[], + maxlength, + bool:persistent=true, + port=0, + maxTimeout=0); + +/** + * Returns a driver Handle from a name string. + * + * If the driver is not found, SourceMod will attempt + * to load an extension named dbi..ext.[dll|so]. + * + * @param name Driver identification string, or an empty + * string to return the default driver. + * @return Driver Handle, or INVALID_HANDLE on failure. + */ +native Handle:SQL_GetDriver(const String:name[]=""); + +/** + * Retrieves a driver's identification string. + * + * @param driver Driver Handle, or INVALID_HANDLE for the default driver. + * @param ident Identification string buffer. + * @param maxlength Maximum length of the buffer. + * @noreturn + * @error Invalid Handle other than INVALID_HANDLE. + */ +native SQL_GetDriverIdent(Handle:driver, String:ident[], maxlength); + +/** + * Retrieves a driver's product string. + * + * @param driver Driver Handle, or INVALID_HANDLE for the default driver. + * @param product Product string buffer. + * @param maxlength Maximum length of the buffer. + * @noreturn + * @error Invalid Handle other than INVALID_HANDLE. + */ +native SQL_GetDriverProduct(Handle:driver, String:product[], maxlength); + +/** + * Returns the number of affected rows from the last query. + * + * @param hndl A database OR statement Handle. + * @return Number of rows affected by the last query. + * @error Invalid database or statement Handle. + */ +native SQL_GetAffectedRows(Handle:hndl); + +/** + * Returns the last query's insertion id. + * + * @param hndl A database OR statement Handle. + * @return Last query's insertion id. + * @error Invalid database or statement Handle. + */ +native SQL_GetInsertId(Handle:hndl); + +/** + * Returns the error reported by the last query. + * + * @param hndl A database OR statement Handle. + * @param error Error buffer. + * @param maxlength Maximum length of the buffer. + * @return True if there was an error, false otherwise. + * @error Invalid database or statement Handle. + */ +native bool:SQL_GetError(Handle:hndl, String:error[], maxlength); + +/** + * Quotes a database string for literal insertion. This is not needed + * for binding strings in prepared statements. + * + * @param hndl A database Handle. + * @param string String to quote. + * @param buffer Buffer to store quoted string in. + * @param maxlength Maximum length of the buffer. + * @param written Optionally returns the number of bytes written. + * @return True on success, false if buffer is not big enough. + * The buffer must be at least 2*strlen(string)+1. + * @error Invalid database or statement Handle. + */ +native bool:SQL_QuoteString(Handle:database, const String:string[], String:buffer[], maxlength, &written=0); + +/** + * Executes a query and ignores the result set. + * + * @param database A database Handle. + * @param query Query string. + * @return True if query succeeded, false otherwise. Use + * SQL_GetError to find the last error. + * @error Invalid database Handle. + */ +native bool:SQL_FastQuery(Handle:database, const String:query[]); + +/** + * Executes a simple query and returns a new query Handle for + * receiving the results. + * + * @param database A database Handle. + * @param query Query string. + * @return A new Query Handle on success, INVALID_HANDLE + * otherwise. The Handle must be freed with CloseHandle(). + * @error Invalid database Handle. + */ +native Handle:SQL_Query(Handle:database, const String:query[]); + +/** + * Creates a new prepared statement query. Prepared statements can + * be executed any number of times. They can also have placeholder + * parameters, similar to variables, which can be bound safely and + * securely (for example, you do not need to quote bound strings). + * + * Statement handles will work in any function that accepts a Query handle. + * + * @param database A database Handle. + * @param query Query string. + * @param error Error buffer. + * @param maxlength Maximum size of the error buffer. + * @return A new statement Handle on success, INVALID_HANDLE + * otherwise. The Handle must be freed with CloseHandle(). + * @error Invalid database Handle. + */ +native Handle:SQL_PrepareQuery(Handle:database, const String:query[], String:error[], maxlength); + +/** + * Advances to the next set of results. + * + * In some SQL implementations, multiple result sets can exist on one query. + * This is possible in MySQL with simple queries when executing a CALL + * query. If this is the case, all result sets must be processed before + * another query is made. + * + * @param query A query Handle. + * @return True if there was another result set, false otherwise. + * @error Invalid query Handle. + */ +native bool:SQL_FetchMoreResults(Handle:query); + +/** + * Returns whether or not a result set exists. This will + * return true even if 0 results were returned, but false + * on queries like UPDATE, INSERT, or DELETE. + * + * @param query A query (or statement) Handle. + * @return True if there is a result set, false otherwise. + * @error Invalid query Handle. + */ +native bool:SQL_HasResultSet(Handle:query); + +/** + * Retrieves the number of rows in the last result set. + * + * @param query A query (or statement) Handle. + * @error Invalid query Handle. + */ +native SQL_GetRowCount(Handle:query); + +/** + * Retrieves the name of a field by index. + * + * @param query A query (or statement) Handle. + * @param field Field number (starting from 0). + * @param name Name buffer. + * @param maxlength Maximum length of the name buffer. + * @noreturn + * @error Invalid query Handle, invalid field index, or + * no current result set. + */ +native SQL_FieldNumToName(Handle:query, field, String:name[], maxlength); + +/** + * Retrieves a field index by name. + * + * @param query A query (or statement) Handle. + * @param name Name of the field (case sensitive). + * @param field Variable to store field index in. + * @return True if found, false if not found. + * @error Invalid query Handle or no current result set. + */ +native bool:SQL_FieldNameToNum(Handle:query, const String:name[], &field); + +/** + * Fetches a row from the current result set. This must be + * successfully called before any results are fetched. + * + * If this function fails, SQL_MoreResults() can be used to + * tell if there was an error or the result set is finished. + * + * @param query A query (or statement) Handle. + * @return True if a row was fetched, false otherwise. + * @error Invalid query Handle. + */ +native bool:SQL_FetchRow(Handle:query); + +/** + * Returns if there are more rows. + * + * @param query A query (or statement) Handle. + * @return True if there are more rows, false otherwise. + * @error Invalid query Handle. + */ +native bool:SQL_MoreRows(Handle:query); + +/** + * Rewinds a result set back to the first result. + * + * @param query A query (or statement) Handle. + * @return True on success, false otherwise. + * @error Invalid query Handle or no current result set. + */ +native bool:SQL_Rewind(Handle:query); + +/** + * Fetches a string from a field in the current row of a result set. + * If the result is NULL, an empty string will be returned. A NULL + * check can be done with the result parameter, or SQL_IsFieldNull(). + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @param buffer String buffer. + * @param maxlength Maximum size of the string buffer. + * @param result Optional variable to store the status of the return value. + * @return Number of bytes written. + * @error Invalid query Handle or field index, invalid + * type conversion requested from the database, + * or no current result set. + */ +native SQL_FetchString(Handle:query, field, String:buffer[], maxlength, &DBResult:result=DBVal_Error); + +/** + * Fetches a float from a field in the current row of a result set. + * If the result is NULL, a value of 0.0 will be returned. A NULL + * check can be done with the result parameter, or SQL_IsFieldNull(). + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @param result Optional variable to store the status of the return value. + * @return A float value. + * @error Invalid query Handle or field index, invalid + * type conversion requested from the database, + * or no current result set. + */ +native Float:SQL_FetchFloat(Handle:query, field, &DBResult:result=DBVal_Error); + +/** + * Fetches an integer from a field in the current row of a result set. + * If the result is NULL, a value of 0 will be returned. A NULL + * check can be done with the result parameter, or SQL_IsFieldNull(). + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @param result Optional variable to store the status of the return value. + * @return An integer value. + * @error Invalid query Handle or field index, invalid + * type conversion requested from the database, + * or no current result set. + */ +native SQL_FetchInt(Handle:query, field, &DBResult:result=DBVal_Error); + +/** + * Returns whether a field's data in the current row of a result set is + * NULL or not. NULL is an SQL type which means "no data." + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @return True if data is NULL, false otherwise. + * @error Invalid query Handle or field index, or no + * current result set. + */ +native bool:SQL_IsFieldNull(Handle:query, field); + +/** + * Returns the length of a field's data in the current row of a result + * set. This only needs to be called for strings to determine how many + * bytes to use. Note that the return value does not include the null + * terminator. + * + * @param query A query (or statement) Handle. + * @param field The field index (starting from 0). + * @return Number of bytes for the field's data size. + * @error Invalid query Handle or field index or no + * current result set. + */ +native SQL_FetchSize(Handle:query, field); + +/** + * Binds a parameter in a prepared statement to a given integer value. + * + * @param statement A statement (prepared query) Handle. + * @param param The parameter index (starting from 0). + * @param number The number to bind. + * @param signed True to bind the number as signed, false to + * bind it as unsigned. + * @noreturn + * @error Invalid statement Handle or parameter index, or + * SQL error. + */ +native SQL_BindParamInt(Handle:statement, param, number, bool:signed=true); + +/** + * Binds a parameter in a prepared statement to a given float value. + * + * @param statement A statement (prepared query) Handle. + * @param param The parameter index (starting from 0). + * @param float The float number to bind. + * @noreturn + * @error Invalid statement Handle or parameter index, or + * SQL error. + */ +native SQL_BindParamFloat(Handle:statement, param, Float:value); + +/** + * Binds a parameter in a prepared statement to a given string value. + * + * @param statement A statement (prepared query) Handle. + * @param param The parameter index (starting from 0). + * @param value The string to bind. + * @param copy Whether or not SourceMod should copy the value + * locally if necessary. If the string contents + * won't change before calling SQL_Execute(), this + * can be set to false for optimization. + * @noreturn + * @error Invalid statement Handle or parameter index, or + * SQL error. + */ +native SQL_BindParamString(Handle:statement, param, const String:value[], bool:copy); + +/** + * Executes a prepared statement. All parameters must be bound beforehand. + * + * @param statement A statement (prepared query) Handle. + * @return True on success, false on failure. + * @error Invalid statement Handle. + */ +native bool:SQL_Execute(Handle:statement); + diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 790f03e1..d49d112a 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -44,6 +44,7 @@ struct Plugin #include #include #include +#include enum DialogType { diff --git a/public/IDBDriver.h b/public/IDBDriver.h index 88d3a31f..b7f77b6b 100644 --- a/public/IDBDriver.h +++ b/public/IDBDriver.h @@ -24,7 +24,7 @@ #include #define SMINTERFACE_DBI_NAME "IDBI" -#define SMINTERFACE_DBI_VERSION 1 +#define SMINTERFACE_DBI_VERSION 2 namespace SourceMod { @@ -561,7 +561,8 @@ namespace SourceMod virtual Handle_t GetHandle() =0; /** - * @brief Returns the driver's controlling identity. + * @brief Returns the driver's controlling identity (must be the same + * as from IExtension::GetIdentity). * * @return An IdentityToken_t identity. */ @@ -601,7 +602,8 @@ namespace SourceMod virtual void RemoveDriver(IDBDriver *pDriver) =0; /** - * @brief Searches for database info by name. + * @brief Searches for database info by name. Both the return pointer + * and all pointers contained therein should be considered volatile. * * @param name Named database info. * @return DatabaseInfo pointer. @@ -648,7 +650,8 @@ namespace SourceMod * @brief Creates a Handle_t of the IDBDriver type. * * @param type A DBHandleType value. - * @param ptr A pointer corrresponding to a DBHandleType object. + * @param ptr A pointer corrresponding to a DBHandleType + * object. * @param token Identity pointer of the owning identity. * @return A new Handle_t handle, or 0 on failure. */ @@ -673,6 +676,22 @@ namespace SourceMod * @return HandleError value. */ virtual HandleError ReleaseHandle(Handle_t hndl, DBHandleType type, IdentityToken_t *token) =0; + + /** + * @brief Given a driver name, attempts to find it. If it is not found, SourceMod + * will attempt to load it. + * + * @param name Driver identifier name. + * @return IDBDriver pointer on success, NULL otherwise. + */ + virtual IDBDriver *FindOrLoadDriver(const char *driver) =0; + + /** + * @brief Returns the default driver, or NULL if none is set. + * + * @return IDBDriver pointer on success, NULL otherwise. + */ + virtual IDBDriver *GetDefaultDriver() =0; }; } diff --git a/public/IShareSys.h b/public/IShareSys.h index c70912b2..dbb35db0 100644 --- a/public/IShareSys.h +++ b/public/IShareSys.h @@ -137,13 +137,14 @@ namespace SourceMod virtual IdentityType_t FindIdentType(const char *name) =0; /** - * @brief Creates a new identity token. This token is guaranteed to be unique - * amongst all other open identities. + * @brief Creates a new identity token. This token is guaranteed to be + * unique amongst all other open identities. * * @param type Identity type. - * @return A new IdentityToken_t identifier. + * @param ptr Private data pointer (cannot be NULL). + * @return A new IdentityToken_t pointer, or NULL on failure. */ - virtual IdentityToken_t *CreateIdentity(IdentityType_t type) =0; + virtual IdentityToken_t *CreateIdentity(IdentityType_t type, void *ptr) =0; /** * @brief Destroys an identity type. Note that this will delete any identities @@ -161,7 +162,6 @@ namespace SourceMod */ virtual void DestroyIdentity(IdentityToken_t *identity) =0; - /** * @brief Requires an extension. This tells SourceMod that without this extension, * your extension should not be loaded. The name should not include the ".dll" or