diff --git a/core/Database.cpp b/core/Database.cpp index 40b182d8..1a778a9c 100644 --- a/core/Database.cpp +++ b/core/Database.cpp @@ -14,6 +14,7 @@ #include "Database.h" #include "HandleSys.h" +#include "ShareSys.h" #include "sourcemod.h" DBManager g_DBMan; @@ -28,6 +29,8 @@ void DBManager::OnSourceModAllInitialized() m_DriverType = g_HandleSys.CreateType("IDriver", this, 0, NULL, &sec, g_pCoreIdent, NULL); m_DatabaseType = g_HandleSys.CreateType("IDatabase", this, 0, NULL, NULL, g_pCoreIdent, NULL); + + g_ShareSys.AddInterface(NULL, this); } void DBManager::OnSourceModShutdown() diff --git a/extensions/mysql/Makefile b/extensions/mysql/Makefile new file mode 100644 index 00000000..8c735d7d --- /dev/null +++ b/extensions/mysql/Makefile @@ -0,0 +1,87 @@ +#(C)2004-2006 SourceMM Development Team +# Makefile written by David "BAILOPAN" Anderson + +SMSDK = ../.. +SRCDS = ~/srcds +SOURCEMM = ../../../../sourcemm + +##################################### +### EDIT BELOW FOR OTHER PROJECTS ### +##################################### + +PROJECT = sample + +#Uncomment for SourceMM-enabled extensions +#LINK_HL2 = $(HL2LIB)/tier1_i486.a vstdlib_i486.so tier0_i486.so + +OBJECTS = sdk/smsdk_ext.cpp extension.cpp + +############################################## +### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### +############################################## + +C_OPT_FLAGS = -O3 -funroll-loops -s -pipe -fno-strict-aliasing +C_DEBUG_FLAGS = -g -ggdb3 +CPP_GCC4_FLAGS = -fvisibility=hidden -fvisibility-inlines-hidden +CPP = gcc-4.1 + +HL2PUB = $(HL2SDK)/public +HL2LIB = $(HL2SDK)/linux_sdk +HL2SDK = $(SOURCEMM)/hl2sdk +SMM_TRUNK = $(SOURCEMM)/trunk + +LINK = $(LINK_HL2) -static-libgcc + +INCLUDE = -I. -I.. -Isdk -I$(HL2PUB) -I$(HL2PUB)/dlls -I$(HL2PUB)/engine -I$(HL2PUB)/tier0 -I$(HL2PUB)/tier1 \ + -I$(HL2PUB)/vstdlib -I$(HL2SDK)/tier1 -I$(SMM_TRUNK) -I$(SMM_TRUNK)/sourcehook -I$(SMM_TRUNK)/sourcemm \ + -I$(SMSDK)/public -I$(SMSDK)/public/sourcepawn -I$(SMSDK)/public/extensions \ + +CFLAGS = -D_LINUX -DNDEBUG -Dstricmp=strcasecmp -D_stricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp -D_snprintf=snprintf -D_vsnprintf=vsnprintf -D_alloca=alloca -Dstrcmpi=strcasecmp -Wall -Werror -fPIC -msse -DSOURCEMOD_BUILD -DHAVE_STDINT_H +CPPFLAGS = -Wno-non-virtual-dtor -fno-exceptions -fno-rtti + +################################################ +### DO NOT EDIT BELOW HERE FOR MOST PROJECTS ### +################################################ + +ifeq "$(DEBUG)" "true" + BIN_DIR = Debug + CFLAGS += $(C_DEBUG_FLAGS) +else + BIN_DIR = Release + CFLAGS += $(C_OPT_FLAGS) +endif + + +GCC_VERSION := $(shell $(CPP) -dumpversion >&1 | cut -b1) +ifeq "$(GCC_VERSION)" "4" + CPPFLAGS += $(CPP_GCC4_FLAGS) +endif + +BINARY = $(PROJECT).ext.so + +OBJ_LINUX := $(OBJECTS:%.cpp=$(BIN_DIR)/%.o) + +$(BIN_DIR)/%.o: %.cpp + $(CPP) $(INCLUDE) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< + +all: + mkdir -p $(BIN_DIR)/sdk + ln -sf $(SRCDS)/bin/vstdlib_i486.so vstdlib_i486.so + ln -sf $(SRCDS)/bin/tier0_i486.so tier0_i486.so + $(MAKE) extension + +extension: $(OBJ_LINUX) + $(CPP) $(INCLUDE) $(CFLAGS) $(CPPFLAGS) $(OBJ_LINUX) $(LINK) -shared -ldl -lm -o$(BIN_DIR)/$(BINARY) + +debug: + $(MAKE) all DEBUG=true + +default: all + +clean: + rm -rf Release/*.o + rm -rf Release/sdk/*.o + rm -rf Release/$(BINARY) + rm -rf Debug/*.o + rm -rf Debug/sdk/*.o + rm -rf Debug/$(BINARY) diff --git a/extensions/mysql/extension.cpp b/extensions/mysql/extension.cpp new file mode 100644 index 00000000..fe0de158 --- /dev/null +++ b/extensions/mysql/extension.cpp @@ -0,0 +1,48 @@ +/** + * vim: set ts=4 : + * =============================================================== + * Sample SourceMod Extension + * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. + * =============================================================== + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Version: $Id: extension.cpp 763 2007-05-09 05:20:03Z damagedsoul $ + */ + +#include "extension.h" +#include "mysql/MyDriver.h" +#include <assert.h> +#include <stdlib.h> + +/** + * @file extension.cpp + * @brief Implement extension code here. + */ + +DBI_MySQL g_MySqlDBI; /**< Global singleton for extension's main interface */ + +SMEXT_LINK(&g_MySqlDBI); + +bool DBI_MySQL::SDK_OnLoad(char *error, size_t maxlength, bool late) +{ + dbi->AddDriver(&g_MyDriver); + + return true; +} + +void DBI_MySQL::SDK_OnUnload() +{ + dbi->RemoveDriver(&g_MyDriver); +} diff --git a/extensions/mysql/extension.h b/extensions/mysql/extension.h new file mode 100644 index 00000000..37888837 --- /dev/null +++ b/extensions/mysql/extension.h @@ -0,0 +1,111 @@ +/** + * vim: set ts=4 : + * =============================================================== + * Sample SourceMod Extension + * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. + * =============================================================== + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Version: $Id: extension.h 763 2007-05-09 05:20:03Z damagedsoul $ + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ + +/** + * @file extension.h + * @brief Sample extension code header. + */ + +#include "smsdk_ext.h" + + +/** + * @brief Sample implementation of the SDK Extension. + * Note: Uncomment one of the pre-defined virtual functions in order to use it. + */ +class DBI_MySQL : public SDKExtension +{ +public: + /** + * @brief This is called after the initial loading sequence has been processed. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @param late Whether or not the module was loaded after map load. + * @return True to succeed loading, false to fail. + */ + virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late); + + /** + * @brief This is called right before the extension is unloaded. + */ + virtual void SDK_OnUnload(); + + /** + * @brief This is called once all known extensions have been loaded. + * Note: It is is a good idea to add natives here, if any are provided. + */ + //virtual void SDK_OnAllLoaded(); + + /** + * @brief Called when the pause state is changed. + */ + //virtual void SDK_OnPauseChange(bool paused); + + /** + * @brief this is called when Core wants to know if your extension is working. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @return True if working, false otherwise. + */ + //virtual bool QueryRunning(char *error, size_t maxlength); +public: +#if defined SMEXT_CONF_METAMOD + /** + * @brief Called when Metamod is attached, before the extension version is called. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @param late Whether or not Metamod considers this a late load. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late); + + /** + * @brief Called when Metamod is detaching, after the extension version is called. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodUnload(char *error, size_t maxlength); + + /** + * @brief Called when Metamod's pause state is changing. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param paused Pause state being set. + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + //virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength); +#endif +}; + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_PROPER_H_ diff --git a/extensions/mysql/msvc8/sm_mysql.sln b/extensions/mysql/msvc8/sm_mysql.sln new file mode 100644 index 00000000..25736ce5 --- /dev/null +++ b/extensions/mysql/msvc8/sm_mysql.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sm_mysql", "sm_mysql.vcproj", "{B3E797CF-4E77-4C9D-B8A8-7589B6902206}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B3E797CF-4E77-4C9D-B8A8-7589B6902206}.Debug|Win32.ActiveCfg = Debug|Win32 + {B3E797CF-4E77-4C9D-B8A8-7589B6902206}.Debug|Win32.Build.0 = Debug|Win32 + {B3E797CF-4E77-4C9D-B8A8-7589B6902206}.Release|Win32.ActiveCfg = Release|Win32 + {B3E797CF-4E77-4C9D-B8A8-7589B6902206}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/extensions/mysql/msvc8/sm_mysql.vcproj b/extensions/mysql/msvc8/sm_mysql.vcproj new file mode 100644 index 00000000..efe81320 --- /dev/null +++ b/extensions/mysql/msvc8/sm_mysql.vcproj @@ -0,0 +1,279 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="sm_mysql" + ProjectGUID="{B3E797CF-4E77-4C9D-B8A8-7589B6902206}" + RootNamespace="sm_mysql" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$(SolutionDir)$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..;..\sdk;..\..\..\public;..\..\..\public\sourcepawn" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;SDK_EXPORTS;_CRT_SECURE_NO_DEPRECATE;SOURCEMOD_BUILD" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + StructMemberAlignment="3" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="false" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="wsock32.lib mysqlclient.lib zlib.lib" + OutputFile="$(OutDir)\dbi.mysql.ext.dll" + LinkIncremental="2" + IgnoreDefaultLibraryNames="LIBCMT" + GenerateDebugInformation="true" + SubSystem="2" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$(SolutionDir)$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalIncludeDirectories="..;..\sdk;..\..\..\public;..\..\..\public\sourcepawn" + PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;SDK_EXPORTS;_CRT_SECURE_NO_DEPRECATE;SOURCEMOD_BUILD" + RuntimeLibrary="0" + StructMemberAlignment="3" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="false" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="wsock32.lib mysqlclient.lib" + OutputFile="$(OutDir)\dbi.mysql.ext.dll" + LinkIncremental="1" + GenerateDebugInformation="true" + SubSystem="2" + OptimizeReferences="2" + EnableCOMDATFolding="2" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <File + RelativePath="..\extension.cpp" + > + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <File + RelativePath="..\extension.h" + > + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + </Filter> + <Filter + Name="SourceMod SDK" + UniqueIdentifier="{31958233-BB2D-4e41-A8F9-CE8A4684F436}" + > + <File + RelativePath="..\sdk\smsdk_config.h" + > + </File> + <File + RelativePath="..\sdk\smsdk_ext.cpp" + > + </File> + <File + RelativePath="..\sdk\smsdk_ext.h" + > + </File> + </Filter> + <Filter + Name="MySQL Driver" + > + <Filter + Name="Headers" + > + <File + RelativePath="..\mysql\MyBasicResults.h" + > + </File> + <File + RelativePath="..\mysql\MyBoundResults.h" + > + </File> + <File + RelativePath="..\mysql\MyDatabase.h" + > + </File> + <File + RelativePath="..\mysql\MyDriver.h" + > + </File> + <File + RelativePath="..\mysql\MyStatement.h" + > + </File> + </Filter> + <Filter + Name="Source" + > + <File + RelativePath="..\mysql\MyBasicResults.cpp" + > + </File> + <File + RelativePath="..\mysql\MyBoundResults.cpp" + > + </File> + <File + RelativePath="..\mysql\MyDatabase.cpp" + > + </File> + <File + RelativePath="..\mysql\MyDriver.cpp" + > + </File> + <File + RelativePath="..\mysql\MyStatement.cpp" + > + </File> + </Filter> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/extensions/mysql/mysql/MyBasicResults.cpp b/extensions/mysql/mysql/MyBasicResults.cpp new file mode 100644 index 00000000..96d75c5e --- /dev/null +++ b/extensions/mysql/mysql/MyBasicResults.cpp @@ -0,0 +1,337 @@ +#include <stdlib.h> +#include "MyBasicResults.h" + +MyBasicResults::MyBasicResults(MYSQL_RES *res) +: m_pRes(res) +{ + Update(); +} + +MyBasicResults::~MyBasicResults() +{ +} + +void MyBasicResults::Update() +{ + if (m_pRes) + { + m_ColCount = (unsigned int)mysql_num_fields(m_pRes); + m_RowCount = (unsigned int)mysql_num_rows(m_pRes); + m_CurRow = 0; + m_Row = NULL; + } +} + +unsigned int MyBasicResults::GetRowCount() +{ + return m_RowCount; +} + +unsigned int MyBasicResults::GetFieldCount() +{ + return m_ColCount; +} + +bool MyBasicResults::FieldNameToNum(const char *name, unsigned int *columnId) +{ + unsigned int total = GetFieldCount(); + + for (unsigned int i=0; i<total; i++) + { + if (strcmp(FieldNumToName(i), name) == 0) + { + *columnId = i; + return true; + } + } + + return false; +} + +const char *MyBasicResults::FieldNumToName(unsigned int colId) +{ + if (colId >= GetFieldCount()) + { + return NULL; + } + + MYSQL_FIELD *field = mysql_fetch_field_direct(m_pRes, colId); + return field ? (field->name ? field->name : "") : ""; +} + +bool MyBasicResults::MoreRows() +{ + return (m_CurRow < m_RowCount); +} + +IResultRow *MyBasicResults::FetchRow() +{ + if (m_CurRow >= m_RowCount) + { + /* Put us one after so we know to block CurrentRow() */ + m_CurRow = m_RowCount + 1; + return NULL; + } + m_Row = mysql_fetch_row(m_pRes); + m_Lengths = mysql_fetch_lengths(m_pRes); + m_CurRow++; + return this; +} + +IResultRow *MyBasicResults::CurrentRow() +{ + if (!m_pRes + || !m_CurRow + || m_CurRow > m_RowCount) + { + return NULL; + } + + return this; +} + +bool MyBasicResults::Rewind() +{ + mysql_data_seek(m_pRes, 0); + m_CurRow = 0; + return true; +} + +DBType MyBasicResults::GetFieldType(unsigned int field) +{ + if (field >= m_ColCount) + { + return DBType_Unknown; + } + + MYSQL_FIELD *fld = mysql_fetch_field_direct(m_pRes, field); + if (!fld) + { + return DBType_Unknown; + } + + return GetOurType(fld->type); +} + +DBType MyBasicResults::GetFieldDataType(unsigned int field) +{ + DBType type = GetFieldType(field); + if (type == DBType_Blob) + { + return DBType_Blob; + } else { + return DBType_String; + } +} + +bool MyBasicResults::IsNull(unsigned int columnId) +{ + if (columnId >= m_ColCount) + { + return true; + } + + return (m_Row[columnId] == NULL); +} + +DBResult MyBasicResults::GetString(unsigned int columnId, const char **pString, size_t *length) +{ + if (columnId >= m_ColCount) + { + return DBVal_Error; + } else if (m_Row[columnId] == NULL) { + *pString = ""; + if (length) + { + *length = 0; + } + return DBVal_Null; + } + + *pString = m_Row[columnId]; + + if (length) + { + *length = (size_t)m_Lengths[columnId]; + } + + return DBVal_Data; +} + +DBResult MyBasicResults::CopyString(unsigned int columnId, + char *buffer, + size_t maxlength, + size_t *written) +{ + DBResult res; + const char *str; + if ((res=GetString(columnId, &str, NULL)) == DBVal_Error) + { + return DBVal_Error; + } + + size_t wr = strncopy(buffer, str, maxlength); + if (written) + { + *written = wr; + } + + return res; +} + +size_t MyBasicResults::GetDataSize(unsigned int columnId) +{ + if (columnId >= m_ColCount) + { + return 0; + } + + return (size_t)m_Lengths[columnId]; +} + +DBResult MyBasicResults::GetFloat(unsigned int col, float *fval) +{ + if (col >= m_ColCount) + { + return DBVal_Error; + } else if (m_Row[col] == NULL) { + *fval = 0.0f; + return DBVal_Null; + } + + *fval = (float)atof(m_Row[col]); + + return DBVal_Data; +} + +DBResult MyBasicResults::GetInt(unsigned int col, int *val) +{ + if (col >= m_ColCount) + { + return DBVal_Error; + } else if (m_Row[col] == NULL) { + *val = 0; + return DBVal_Null; + } + + *val = atoi(m_Row[col]); + + return DBVal_Data; +} + +DBResult MyBasicResults::GetBlob(unsigned int col, const void **pData, size_t *length) +{ + if (col >= m_ColCount) + { + return DBVal_Error; + } else if (m_Row[col] == NULL) { + *pData = NULL; + if (length) + { + *length = 0; + } + return DBVal_Null; + } + + *pData = m_Row[col]; + + if (length) + { + *length = (size_t)m_Lengths[col]; + } + + return DBVal_Data; +} + +DBResult MyBasicResults::CopyBlob(unsigned int columnId, void *buffer, size_t maxlength, size_t *written) +{ + const void *addr; + size_t length; + DBResult res; + + if ((res=GetBlob(columnId, &addr, &length)) == DBVal_Error) + { + return DBVal_Error; + } + + if (addr == NULL) + { + return DBVal_Null; + } + + if (length > maxlength) + { + length = maxlength; + } + + memcpy(buffer, addr, length); + if (written) + { + *written = length; + } + + return res; +} + +MyQuery::MyQuery(MyDatabase *db, MYSQL_RES *res) +: m_pParent(db), m_rs(res) +{ + m_pParent->IncRefCount(); +} + +IResultSet *MyQuery::GetResultSet() +{ + if (m_rs.m_pRes == NULL) + { + return NULL; + } + + return &m_rs; +} + +bool MyQuery::FetchMoreResults() +{ + if (m_rs.m_pRes == NULL) + { + return false; + } else if (!mysql_more_results(m_pParent->m_mysql)) { + return false; + } + + mysql_free_result(m_rs.m_pRes); + m_rs.m_pRes = NULL; + + if (mysql_next_result(m_pParent->m_mysql) != 0) + { + return false; + } + + m_rs.m_pRes = mysql_store_result(m_pParent->m_mysql); + m_rs.Update(); + + return (m_rs.m_pRes != NULL); +} + +void MyQuery::Destroy() +{ + /* :TODO: All this rot should be moved into the destructor, + * and the Update() function needs to not be so stupid. + */ + + while (FetchMoreResults()) + { + /* Spin until all are gone */ + } + + /* Free the last, if any */ + if (m_rs.m_pRes != NULL) + { + mysql_free_result(m_rs.m_pRes); + } + + /* Tell our parent we're done */ + m_pParent->Close(); + + /* Self destruct */ + delete this; +} diff --git a/extensions/mysql/mysql/MyBasicResults.h b/extensions/mysql/mysql/MyBasicResults.h new file mode 100644 index 00000000..ea5be32c --- /dev/null +++ b/extensions/mysql/mysql/MyBasicResults.h @@ -0,0 +1,64 @@ +#ifndef _INCLUDE_SM_MYSQL_BASIC_RESULTS_H_ +#define _INCLUDE_SM_MYSQL_BASIC_RESULTS_H_ + +#include "MyDatabase.h" + +class MyQuery; + +class MyBasicResults : + public IResultSet, + public IResultRow +{ + friend class MyQuery; +public: + MyBasicResults(MYSQL_RES *res); + ~MyBasicResults(); +public: //IResultSet + unsigned int GetRowCount(); + unsigned int GetFieldCount(); + const char *FieldNumToName(unsigned int columnId); + bool FieldNameToNum(const char *name, unsigned int *columnId); + bool MoreRows(); + IResultRow *FetchRow(); + bool Rewind(); + DBType GetFieldType(unsigned int field); + DBType GetFieldDataType(unsigned int field); + IResultRow *CurrentRow(); +public: //IResultRow + DBResult GetString(unsigned int columnId, const char **pString, size_t *length); + DBResult GetFloat(unsigned int columnId, float *pFloat); + DBResult GetInt(unsigned int columnId, int *pInt); + bool IsNull(unsigned int columnId); + DBResult GetBlob(unsigned int columnId, const void **pData, size_t *length); + DBResult CopyBlob(unsigned int columnId, void *buffer, size_t maxlength, size_t *written); + DBResult CopyString(unsigned int columnId, + char *buffer, + size_t maxlength, + size_t *written); + size_t GetDataSize(unsigned int columnId); +protected: + void Update(); +private: + MYSQL_RES *m_pRes; + unsigned int m_CurRow; + MYSQL_ROW m_Row; + unsigned long *m_Lengths; + unsigned int m_ColCount; + unsigned int m_RowCount; +}; + +class MyQuery : public IQuery +{ + friend class MyBasicResults; +public: + MyQuery(MyDatabase *db, MYSQL_RES *res); +public: + IResultSet *GetResultSet(); + bool FetchMoreResults(); + void Destroy(); +private: + MyDatabase *m_pParent; + MyBasicResults m_rs; +}; + +#endif //_INCLUDE_SM_MYSQL_BASIC_RESULTS_H_ diff --git a/extensions/mysql/mysql/MyBoundResults.cpp b/extensions/mysql/mysql/MyBoundResults.cpp new file mode 100644 index 00000000..d1a54c99 --- /dev/null +++ b/extensions/mysql/mysql/MyBoundResults.cpp @@ -0,0 +1,633 @@ +#include "MyBoundResults.h" + +#define DEFAULT_BUFFER_SIZE 5 + +/* :IDEA: When we have to refetch a buffer to do type changes, should we rebind + * the buffer so the next fetch will predict the proper cast? Probably yes since + * these things are done in standard iterations, but maybe users should be punished + * for not using the API as it was intended? Maybe it should be an option set to + * on by default to catch the bad users? + */ + +enum_field_types GetTheirType(DBType type) +{ + switch (type) + { + case DBType_Float: + { + return MYSQL_TYPE_FLOAT; + } + case DBType_Integer: + { + return MYSQL_TYPE_LONG; + } + case DBType_String: + { + return MYSQL_TYPE_STRING; + } + case DBType_Blob: + { + return MYSQL_TYPE_BLOB; + } + } + + return MYSQL_TYPE_STRING; +} + +MyBoundResults::MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res) +: m_stmt(stmt), m_pRes(res), m_Initialized(false), m_RowCount(0), m_CurRow(0) +{ + /** + * Important things to note here: + * 1) We're guaranteed at least one field. + * 2) The field information should never change, and thus we + * never rebuild it. If someone ALTERs the table during + * a prepared query's lifetime, it's their own death. + */ + + m_ColCount = (unsigned int)mysql_num_fields(m_pRes); + + /* Allocate buffers */ + m_bind = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * m_ColCount); + m_pull = (ResultBind *)malloc(sizeof(ResultBind) * m_ColCount); + + /* Zero data */ + memset(m_bind, 0, sizeof(MYSQL_BIND) * m_ColCount); + memset(m_pull, 0, sizeof(ResultBind) * m_ColCount); +} + +MyBoundResults::~MyBoundResults() +{ + if (m_Initialized) + { + /* Make sure we free our internal buffers */ + for (unsigned int i=0; i<m_ColCount; i++) + { + delete [] m_pull[i].blob; + } + } + + free(m_pull); + free(m_bind); +} + +void MyBoundResults::Update() +{ + m_RowCount = (unsigned int)mysql_stmt_num_rows(m_stmt); + m_CurRow = 0; +} + +bool MyBoundResults::Initialize() +{ + /* Check if we need to build our result binding information */ + if (!m_Initialized) + { + for (unsigned int i=0; i<m_ColCount; i++) + { + MYSQL_FIELD *field = mysql_fetch_field_direct(m_pRes, i); + DBType type = GetOurType(field->type); + + m_bind[i].length = &(m_pull[i].my_length); + m_bind[i].is_null = &(m_pull[i].my_null); + + if (type == DBType_Integer) + { + m_bind[i].buffer_type = MYSQL_TYPE_LONG; + m_bind[i].buffer = &(m_pull[i].data.ival); + } else if (type == DBType_Float) { + m_bind[i].buffer_type = MYSQL_TYPE_FLOAT; + m_bind[i].buffer = &(m_pull[i].data.ival); + } else if (type == DBType_String || type == DBType_Blob) { + m_bind[i].buffer_type = GetTheirType(type); + + /* We bound this to 2048 bytes. Otherwise a MEDIUMBLOB + * or something could allocate horrible amounts of memory + * because MySQL is incompetent. + */ + size_t creat_length = (size_t)field->length; + if (!creat_length || creat_length > DEFAULT_BUFFER_SIZE) + { + creat_length = DEFAULT_BUFFER_SIZE; + } + m_pull[i].blob = new unsigned char[creat_length]; + m_pull[i].length = creat_length; + + m_bind[i].buffer = m_pull[i].blob; + m_bind[i].buffer_length = (unsigned long)creat_length; + } else { + return false; + } + } + m_Initialized = true; + } + + /* Do the actual bind */ + return (mysql_stmt_bind_result(m_stmt, m_bind) == 0); +} + +unsigned int MyBoundResults::GetRowCount() +{ + return m_RowCount; +} + +unsigned int MyBoundResults::GetFieldCount() +{ + return m_ColCount; +} + +const char *MyBoundResults::FieldNumToName(unsigned int columnId) +{ + if (columnId >= m_ColCount) + { + return NULL; + } + + MYSQL_FIELD *field = mysql_fetch_field_direct(m_pRes, columnId); + + return field ? (field->name ? field->name : "") : ""; +} + +bool MyBoundResults::FieldNameToNum(const char *name, unsigned int *columnId) +{ + for (unsigned int i=0; i<m_ColCount; i++) + { + if (strcmp(name, FieldNumToName(i)) == 0) + { + *columnId = i; + return true; + } + } + + return false; +} + +bool MyBoundResults::MoreRows() +{ + return (m_CurRow < m_RowCount); +} + +IResultRow *MyBoundResults::FetchRow() +{ + if (!MoreRows()) + { + m_CurRow = m_RowCount + 1; + NULL; + } + + m_CurRow++; + + /* We should be able to get another row */ + int err = mysql_stmt_fetch(m_stmt); + if (err == 0 || err == MYSQL_DATA_TRUNCATED) + { + return this; + } + + if (err == MYSQL_NO_DATA && m_CurRow == m_RowCount) + { + return this; + } + + /* Some sort of error occurred */ + return NULL; +} + +IResultRow *MyBoundResults::CurrentRow() +{ + if (!m_CurRow || m_CurRow > m_RowCount) + { + return NULL; + } + + return this; +} + +bool MyBoundResults::Rewind() +{ + mysql_stmt_data_seek(m_stmt, 0); + m_CurRow = 0; + return true; +} + +DBType MyBoundResults::GetFieldType(unsigned int field) +{ + if (field >= m_ColCount) + { + return DBType_Unknown; + } + + MYSQL_FIELD *fld = mysql_fetch_field_direct(m_pRes, field); + return GetOurType(fld->type); +} + +DBType MyBoundResults::GetFieldDataType(unsigned int field) +{ + return GetFieldType(field); +} + +void ResizeBuffer(ResultBind *bind, size_t len) +{ + if (!bind->blob) + { + bind->blob = new unsigned char[len]; + bind->length = len; + } else if (bind->length < len) { + delete [] bind->blob; + bind->blob = new unsigned char[len]; + bind->length = len; + } +} + +bool RefetchField(MYSQL_STMT *stmt, + ResultBind *rbind, + unsigned int id, + size_t initSize, + enum_field_types type) +{ + /* Make sure there is a buffer to pull into */ + ResizeBuffer(rbind, initSize); + + MYSQL_BIND bind; + + /* Initialize bind info */ + memset(&bind, 0, sizeof(MYSQL_BIND)); + bind.buffer = rbind->blob; + bind.buffer_type = type; + bind.buffer_length = (unsigned long)rbind->length; + bind.length = &(rbind->my_length); + bind.is_null = &(rbind->my_null); + + /* Attempt to fetch */ + return (mysql_stmt_fetch_column(stmt, &bind, id, 0) == 0); +} + +DBResult RefetchSize4Field(MYSQL_STMT *stmt, + unsigned int id, + void *buffer, + enum_field_types type) +{ + MYSQL_BIND bind; + my_bool is_null; + + /* Initialize bind info */ + memset(&bind, 0, sizeof(MYSQL_BIND)); + bind.buffer = buffer; + bind.buffer_type = type; + bind.is_null = &is_null; + + /* Attempt to fetch */ + if (mysql_stmt_fetch_column(stmt, &bind, id, 0) != 0) + { + return DBVal_TypeMismatch; + } + + return is_null ? DBVal_Null : DBVal_Data; +} + + +bool RefetchUserField(MYSQL_STMT *stmt, + unsigned int id, + void *userbuf, + size_t userlen, + enum_field_types type, + my_bool &is_null, + size_t *written) +{ + MYSQL_BIND bind; + unsigned long length; + + /* Initialize bind info */ + memset(&bind, 0, sizeof(MYSQL_BIND)); + bind.buffer = userbuf; + bind.buffer_type = type; + bind.length = &length; + bind.is_null = &is_null; + bind.buffer_length = (unsigned long)userlen; + + if (mysql_stmt_fetch_column(stmt, &bind, id, 0) != 0) + { + return false; + } + + if (is_null) + { + return true; + } + + if (type == MYSQL_TYPE_STRING && (size_t)length == userlen) + { + /* Enforce null termination in case MySQL forgot. + * Note we subtract one from the length (which must be >= 1) + * so we can pass the number of bytes written below. + */ + char *data = (char *)userbuf; + data[--userlen] = '\0'; + } + + if (written) + { + /* In the case of strings, they will never be equal */ + *written = (userlen < length) ? userlen : length; + } + + return true; +} + +#define BAD_COL_CHECK() \ + if (id >= m_ColCount) \ + return DBVal_Error; + +#define STR_NULL_CHECK_0(var) \ + if (var) { \ + *pString = NULL; \ + if (length) \ + *length = 0; \ + return DBVal_Null; \ + } + +DBResult MyBoundResults::GetString(unsigned int id, const char **pString, size_t *length) +{ + BAD_COL_CHECK(); + + STR_NULL_CHECK_0(m_pull[id].my_null); + + if (m_bind[id].buffer_type != MYSQL_TYPE_STRING) + { + /* Ugh, we have to re-get this as a string. Sigh, stupid user. + * We're going to disallow conversions from blobs. + */ + if (m_bind[id].buffer_type == MYSQL_TYPE_BLOB) + { + return DBVal_TypeMismatch; + } + + /* Attempt to refetch the string */ + if (!RefetchField(m_stmt, &m_pull[id], id, 128, MYSQL_TYPE_STRING)) + { + return DBVal_TypeMismatch; + } + + /* Check if we have a new null */ + STR_NULL_CHECK_0(m_pull[id].my_null); + } + + /* Okay, we should now have a blob type whether we originally wanted one or not. */ + + /* Check if the size is too small. Note that MySQL will not null terminate small buffers, + * and it returns the size without the null terminator. This means we need to add an extra + * byte onto the end to accept the terminator until there is a workaround. + * + * Note that we do an >= check because MySQL appears to want the null terminator included, + * so just to be safe and avoid its inconsistencies, we make sure we'll always have room. + */ + if ((size_t)(m_pull[id].my_length) >= m_pull[id].length) + { + /* Yes, we need to refetch. */ + if (!RefetchField(m_stmt, &m_pull[id], id, m_pull[id].my_length + 1, MYSQL_TYPE_STRING)) + { + return DBVal_Error; + } + } + + /* Finally, we can return. We're guaranteed to have a properly NULL-terminated string + * here because we have refetched the string to a bigger length. + */ + *pString = (const char *)m_pull[id].blob; + if (length) + { + *length = (size_t)m_pull[id].my_length; + } + + return DBVal_Data; +} + +#define STR_NULL_CHECK_1(var) \ + if (var) { \ + buffer[0] = '\0'; \ + if (written) \ + *written = 0; \ + return DBVal_Null; \ + } + +DBResult MyBoundResults::CopyString(unsigned int id, char *buffer, size_t maxlength, size_t *written) +{ + BAD_COL_CHECK(); + + STR_NULL_CHECK_1(m_pull[id].my_null); + + if (!buffer || !maxlength) + { + return DBVal_Error; + } + + if (m_bind[id].buffer_type != MYSQL_TYPE_STRING) + { + /* We're going to disallow conversions from blobs. */ + if (m_bind[id].buffer_type == MYSQL_TYPE_BLOB) + { + return DBVal_TypeMismatch; + } + + /* Re-fetch this for the user. This call will guarantee NULL termination. */ + my_bool is_null; + if (!RefetchUserField(m_stmt, id, buffer, maxlength, MYSQL_TYPE_STRING, is_null, written)) + { + return DBVal_TypeMismatch; + } + + STR_NULL_CHECK_1(is_null); + + return DBVal_Data; + } + + size_t pull_length = (size_t)m_pull[id].my_length; + size_t orig_length = m_pull[id].length; + + /* If there's more data in the buffer, we have to look at two cases. */ + if (pull_length >= orig_length) + { + /* If the user supplied a bigger buffer, just refetch for them. */ + if (maxlength > orig_length) + { + my_bool is_null; + RefetchUserField(m_stmt, id, buffer, maxlength, MYSQL_TYPE_STRING, is_null, written); + STR_NULL_CHECK_1(is_null); + return DBVal_Data; + } + /* Otherwise, we should enforce null termination from MySQL. */ + else if (pull_length == orig_length) + { + char *data = (char *)m_pull[id].blob; + data[pull_length] = '\0'; + } + } + + /* If we got here, we need to copy the resultant string to the user and be done with it. + * Null termination is guaranteed from the pulled string. + */ + size_t wr = strncopy(buffer, (const char *)m_pull[id].blob, maxlength); + if (written) + { + *written = wr; + } + + return DBVal_Data; +} + +DBResult MyBoundResults::GetFloat(unsigned int id, float *pFloat) +{ + BAD_COL_CHECK(); + + if (m_pull[id].my_null) + { + *pFloat = 0.0f; + return DBVal_Null; + } + + if (m_bind[id].buffer_type != MYSQL_TYPE_FLOAT) + { + if (m_bind[id].buffer_type == MYSQL_TYPE_BLOB) + { + return DBVal_TypeMismatch; + } + /* We have to convert... */ + return RefetchSize4Field(m_stmt, id, pFloat, MYSQL_TYPE_FLOAT); + } + + *pFloat = m_pull[id].data.fval; + + return DBVal_Data; +} + +DBResult MyBoundResults::GetInt(unsigned int id, int *pInt) +{ + BAD_COL_CHECK(); + + if (m_pull[id].my_null) + { + *pInt = 0; + return DBVal_Null; + } + + if (m_bind[id].buffer_type != MYSQL_TYPE_LONG) + { + if (m_bind[id].buffer_type == MYSQL_TYPE_BLOB) + { + return DBVal_TypeMismatch; + } + /* We have to convert... */ + return RefetchSize4Field(m_stmt, id, pInt, MYSQL_TYPE_LONG); + } + + *pInt = m_pull[id].data.ival; + + return DBVal_Data; +} + +bool MyBoundResults::IsNull(unsigned int id) +{ + if (id >= m_ColCount) + { + return true; + } + + return m_pull[id].my_null ? true : false; +} + +#define BLOB_CHECK_NULL_0() \ + if (m_pull[id].my_null) { \ + *pData = NULL; \ + if (length) \ + *length = 0; \ + return DBVal_Null; \ + } + +DBResult MyBoundResults::GetBlob(unsigned int id, const void **pData, size_t *length) +{ + BAD_COL_CHECK(); + + BLOB_CHECK_NULL_0(); + + /* We only want blobs to be read as blobs */ + if (m_bind[id].buffer_type != MYSQL_TYPE_BLOB) + { + return DBVal_TypeMismatch; + } + + if ((size_t)m_pull[id].my_length > m_pull[id].length) + { + if (!RefetchField(m_stmt, &m_pull[id], id, m_pull[id].my_length, MYSQL_TYPE_BLOB)) + { + return DBVal_TypeMismatch; + } + BLOB_CHECK_NULL_0(); + } + + *pData = m_pull[id].blob; + if (length) + { + *length = (size_t)m_pull[id].my_length; + } + + return DBVal_Data; +} + +#define BLOB_CHECK_NULL_1(var) \ + if (var) { \ + if (written) \ + *written = 0; \ + return DBVal_Null; \ + } + +DBResult MyBoundResults::CopyBlob(unsigned int id, void *buffer, size_t maxlength, size_t *written) +{ + BAD_COL_CHECK(); + + /* We only want blobs to be read as blobs */ + if (m_bind[id].buffer_type != MYSQL_TYPE_BLOB) + { + return DBVal_TypeMismatch; + } + + BLOB_CHECK_NULL_1(m_pull[id].my_null); + + size_t pull_size = (size_t)m_pull[id].my_length; + size_t push_size = m_pull[id].length; + + /* Check if we can do a resize and copy in one step */ + if (pull_size > push_size + && maxlength > push_size) + { + my_bool is_null; + if (!RefetchUserField(m_stmt, id, buffer, maxlength, MYSQL_TYPE_BLOB, is_null, written)) + { + return DBVal_TypeMismatch; + } + BLOB_CHECK_NULL_1(is_null); + return DBVal_Data; + } + + /* If we got here, either there is no more data to refetch, + * or our buffer is too small to receive the refetched data. + */ + size_t buf_bytes = pull_size > push_size ? push_size : pull_size; + size_t to_copy = buf_bytes > maxlength ? maxlength : buf_bytes; + + memcpy(buffer, m_pull[id].blob, to_copy); + if (written) + { + *written = to_copy; + } + + return DBVal_Data; +} + +size_t MyBoundResults::GetDataSize(unsigned int id) +{ + if (id >= m_ColCount) + { + return 0; + } + + return (size_t)m_pull[id].my_length; +} diff --git a/extensions/mysql/mysql/MyBoundResults.h b/extensions/mysql/mysql/MyBoundResults.h new file mode 100644 index 00000000..74772668 --- /dev/null +++ b/extensions/mysql/mysql/MyBoundResults.h @@ -0,0 +1,66 @@ +#ifndef _INCLUDE_SM_MYSQL_BOUND_RESULTS_H_ +#define _INCLUDE_SM_MYSQL_BOUND_RESULTS_H_ + +#include "MyDatabase.h" + +class MyStatement; + +struct ResultBind +{ + my_bool my_null; + unsigned long my_length; + union + { + int ival; + float fval; + } data; + unsigned char *blob; + size_t length; +}; + +class MyBoundResults : + public IResultSet, + public IResultRow +{ + friend class MyStatement; +public: + MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res); + ~MyBoundResults(); +public: //IResultSet + unsigned int GetRowCount(); + unsigned int GetFieldCount(); + const char *FieldNumToName(unsigned int columnId); + bool FieldNameToNum(const char *name, unsigned int *columnId); + bool MoreRows(); + IResultRow *FetchRow(); + bool Rewind(); + DBType GetFieldType(unsigned int field); + DBType GetFieldDataType(unsigned int field); + IResultRow *CurrentRow(); +public: //IResultRow + DBResult GetString(unsigned int id, const char **pString, size_t *length); + DBResult CopyString(unsigned int id, + char *buffer, + size_t maxlength, + size_t *written); + DBResult GetFloat(unsigned int id, float *pFloat); + DBResult GetInt(unsigned int id, int *pInt); + bool IsNull(unsigned int id); + size_t GetDataSize(unsigned int id); + DBResult GetBlob(unsigned int id, const void **pData, size_t *length); + DBResult CopyBlob(unsigned int id, void *buffer, size_t maxlength, size_t *written); +public: + bool Initialize(); + void Update(); +private: + MYSQL_STMT *m_stmt; + MYSQL_RES *m_pRes; + MYSQL_BIND *m_bind; + ResultBind *m_pull; + unsigned int m_ColCount; + bool m_Initialized; + unsigned int m_RowCount; + unsigned int m_CurRow; +}; + +#endif //_INCLUDE_SM_MYSQL_BOUND_RESULTS_H_ diff --git a/extensions/mysql/mysql/MyDatabase.cpp b/extensions/mysql/mysql/MyDatabase.cpp new file mode 100644 index 00000000..500eacef --- /dev/null +++ b/extensions/mysql/mysql/MyDatabase.cpp @@ -0,0 +1,244 @@ +#include "MyDatabase.h" +#include "smsdk_ext.h" +#include "MyBasicResults.h" +#include "MyStatement.h" + +DBType GetOurType(enum_field_types type) +{ + switch (type) + { + case MYSQL_TYPE_DOUBLE: + case MYSQL_TYPE_FLOAT: + { + return DBType_Float; + } + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_BIT: + { + return DBType_Integer; + } + case MYSQL_TYPE_LONGLONG: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + { + return DBType_String; + } + + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_GEOMETRY: + { + return DBType_Blob; + } + default: + { + return DBType_String; + } + } + + return DBType_Unknown; +} + +MyDatabase::MyDatabase(MYSQL *mysql, const DatabaseInfo *info, bool persistent) +: m_mysql(mysql), m_refcount(1), m_handle(BAD_HANDLE), m_bPersistent(persistent) +{ + m_Host.assign(info->host); + m_Database.assign(info->database); + m_User.assign(info->user); + m_Pass.assign(info->pass); + + m_Info.database = m_Database.c_str(); + m_Info.host = m_Host.c_str(); + m_Info.user = m_User.c_str(); + m_Info.pass = m_Pass.c_str(); + m_Info.driver = NULL; + m_Info.maxTimeout = info->maxTimeout; + m_Info.port = info->port; +} + +MyDatabase::~MyDatabase() +{ + mysql_close(m_mysql); + m_mysql = NULL; +} + +void MyDatabase::IncRefCount() +{ + m_refcount++; +} + +bool MyDatabase::Close(bool fromHndlSys) +{ + if (m_refcount > 1) + { + m_refcount--; + return false; + } + + /* If we don't have a Handle and the Handle is + * is from the Handle System, it means we need + * to block a re-entrant call from our own + * FreeHandle(). + */ + if (fromHndlSys && (m_handle == BAD_HANDLE)) + { + return false; + } + + /* Remove us from the search list */ + if (m_bPersistent) + { + g_MyDriver.RemoveFromList(this, true); + } + + /* If we're not from the Handle system, and + * we have a Handle, we need to free it first. + */ + if (!fromHndlSys && m_handle != BAD_HANDLE) + { + Handle_t hndl = m_handle; + m_handle = BAD_HANDLE; + dbi->ReleaseHandle(hndl, DBHandle_Database, myself->GetIdentity()); + } + + /* Finally, free our resource(s) */ + delete this; + + return true; +} + +Handle_t MyDatabase::GetHandle() +{ + if (m_handle == BAD_HANDLE) + { + m_handle = dbi->CreateHandle(DBHandle_Database, this, myself->GetIdentity()); + } + + return m_handle; +} + +const DatabaseInfo &MyDatabase::GetInfo() +{ + return m_Info; +} + +unsigned int MyDatabase::GetInsertID() +{ + return (unsigned int)mysql_insert_id(m_mysql); +} + +unsigned int MyDatabase::GetAffectedRows() +{ + return (unsigned int)mysql_affected_rows(m_mysql); +} + +const char *MyDatabase::GetError(int *errCode) +{ + if (errCode) + { + *errCode = mysql_errno(m_mysql); + } + + return mysql_error(m_mysql); +} + +bool MyDatabase::QuoteString(const char *str, char buffer[], size_t maxlength, size_t *newSize) +{ + unsigned long size = static_cast<unsigned long>(strlen(str)); + unsigned long needed = size * 2 + 1; + + if (maxlength < needed) + { + if (newSize) + { + *newSize = (size_t)needed; + } + return false; + } + + needed = mysql_real_escape_string(m_mysql, buffer, str, size); + if (newSize) + { + *newSize = (size_t)needed; + } + + return true; +} + +bool MyDatabase::DoSimpleQuery(const char *query) +{ + IQuery *pQuery = DoQuery(query); + if (!pQuery) + { + return false; + } + pQuery->Destroy(); + return true; +} + +IQuery *MyDatabase::DoQuery(const char *query) +{ + if (mysql_real_query(m_mysql, query, strlen(query)) != 0) + { + return NULL; + } + + MYSQL_RES *res = NULL; + if (mysql_field_count(m_mysql)) + { + res = mysql_store_result(m_mysql); + if (!res) + { + return NULL; + } + } + + return new MyQuery(this, res); +} + +IPreparedQuery *MyDatabase::PrepareQuery(const char *query, char *error, size_t maxlength, int *errCode) +{ + MYSQL_STMT *stmt = mysql_stmt_init(m_mysql); + if (!stmt) + { + if (error) + { + strncopy(error, GetError(errCode), maxlength); + } else if (errCode) { + *errCode = mysql_errno(m_mysql); + } + return NULL; + } + + if (mysql_stmt_prepare(stmt, query, strlen(query)) != 0) + { + if (error) + { + strncopy(error, mysql_stmt_error(stmt), maxlength); + } + if (errCode) + { + *errCode = mysql_stmt_errno(stmt); + } + mysql_stmt_close(stmt); + return NULL; + } + + return new MyStatement(this, stmt); +} diff --git a/extensions/mysql/mysql/MyDatabase.h b/extensions/mysql/mysql/MyDatabase.h new file mode 100644 index 00000000..d45a00be --- /dev/null +++ b/extensions/mysql/mysql/MyDatabase.h @@ -0,0 +1,45 @@ +#ifndef _INCLUDE_SM_MYSQL_DATABASE_H_ +#define _INCLUDE_SM_MYSQL_DATABASE_H_ + +#include "MyDriver.h" + +class MyQuery; +class MyStatement; + +class MyDatabase : public IDatabase +{ + friend class MyQuery; + friend class MyStatement; +public: + MyDatabase(MYSQL *mysql, const DatabaseInfo *info, bool persistent); + ~MyDatabase(); +public: //IDatabase + bool Close(bool fromHndlSys=false); + const char *GetError(int *errorCode=NULL); + bool DoSimpleQuery(const char *query); + IQuery *DoQuery(const char *query); + IPreparedQuery *PrepareQuery(const char *query, char *error, size_t maxlength, int *errCode=NULL); + bool QuoteString(const char *str, char buffer[], size_t maxlen, size_t *newSize); + unsigned int GetAffectedRows(); + unsigned int GetInsertID(); + Handle_t GetHandle(); +public: + const DatabaseInfo &GetInfo(); + void IncRefCount(); +private: + MYSQL *m_mysql; + unsigned int m_refcount; + Handle_t m_handle; + + /* ---------- */ + DatabaseInfo m_Info; + String m_Host; + String m_Database; + String m_User; + String m_Pass; + bool m_bPersistent; +}; + +DBType GetOurType(enum_field_types type); + +#endif //_INCLUDE_SM_MYSQL_DATABASE_H_ diff --git a/extensions/mysql/mysql/MyDriver.cpp b/extensions/mysql/mysql/MyDriver.cpp new file mode 100644 index 00000000..7f006c67 --- /dev/null +++ b/extensions/mysql/mysql/MyDriver.cpp @@ -0,0 +1,169 @@ +#include "MyDriver.h" +#include "MyDatabase.h" +#include "sdk/smsdk_ext.h" + +MyDriver g_MyDriver; + +MyDriver::MyDriver() +{ + m_MyHandle = BAD_HANDLE; +} + +void CloseDBList(List<MyDatabase *> &l) +{ + List<MyDatabase *>::iterator iter; + for (iter=l.begin(); iter!=l.end(); iter++) + { + MyDatabase *db = (*iter); + while (!db->Close()) + { + /* Spool until it closes */ + } + } + l.clear(); +} + +void MyDriver::Shutdown() +{ + List<MyDatabase *>::iterator iter; + CloseDBList(m_PermDbs); + + if (m_MyHandle != BAD_HANDLE) + { + dbi->ReleaseHandle(m_MyHandle, DBHandle_Driver, myself->GetIdentity()); + m_MyHandle = BAD_HANDLE; + } +} + +const char *MyDriver::GetIdentifier() +{ + return "mysql"; +} + +Handle_t MyDriver::GetHandle() +{ + if (m_MyHandle == BAD_HANDLE) + { + m_MyHandle = dbi->CreateHandle(DBHandle_Driver, this, myself->GetIdentity()); + } + + return m_MyHandle; +} + +IdentityToken_t *MyDriver::GetIdentity() +{ + return myself->GetIdentity(); +} + +const char *MyDriver::GetProductName() +{ + return "MySQL"; +} + +MYSQL *Connect(const DatabaseInfo *info, char *error, size_t maxlength) +{ + MYSQL *mysql = mysql_init(NULL); + + if (info->maxTimeout > 0) + { + mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&(info->maxTimeout)); + } + + if (!mysql_real_connect(mysql, + info->host, + info->user, + info->pass, + info->database, + info->port, + NULL, + M_CLIENT_MULTI_RESULTS)) + { + /* :TODO: expose UTIL_Format from smutil! */ + snprintf(error, maxlength, "[%d]: %s", mysql_errno(mysql), mysql_error(mysql)); + mysql_close(mysql); + return NULL; + } + + return mysql; +} + +bool CompareField(const char *str1, const char *str2) +{ + if ((str1 == NULL && str2 != NULL) + || (str1 != NULL && str2 == NULL)) + { + return false; + } + + if (str1 == NULL && str2 == NULL) + { + return true; + } + + return (strcmp(str1, str2) == NULL); +} + +IDatabase *MyDriver::Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength) +{ + if (persistent) + { + /* Try to find a matching persistent connection */ + List<MyDatabase *>::iterator iter; + for (iter=m_PermDbs.begin(); + iter!=m_PermDbs.end(); + iter++) + { + MyDatabase *db = (*iter); + const DatabaseInfo &other = db->GetInfo(); + if (CompareField(info->host, other.host) + && CompareField(info->user, other.user) + && CompareField(info->pass, other.pass) + && CompareField(info->database, other.database) + && (info->port == other.port)) + { + db->IncRefCount(); + return db; + } + } + } + + MYSQL *mysql = ::Connect(info, error, maxlength); + if (!mysql) + { + return NULL; + } + + MyDatabase *db = new MyDatabase(mysql, info, persistent); + + if (persistent) + { + m_PermDbs.push_back(db); + } + + return db; +} + +void MyDriver::RemoveFromList(MyDatabase *pdb, bool persistent) +{ + if (persistent) + { + m_PermDbs.remove(pdb); + } +} + +unsigned int strncopy(char *dest, const char *src, size_t count) +{ + if (!count) + { + return 0; + } + + char *start = dest; + while ((*src) && (--count)) + { + *dest++ = *src++; + } + *dest = '\0'; + + return (dest - start); +} diff --git a/extensions/mysql/mysql/MyDriver.h b/extensions/mysql/mysql/MyDriver.h new file mode 100644 index 00000000..6a41cf24 --- /dev/null +++ b/extensions/mysql/mysql/MyDriver.h @@ -0,0 +1,45 @@ +#ifndef _INCLUDE_SM_MYSQL_DRIVER_H_ +#define _INCLUDE_SM_MYSQL_DRIVER_H_ + +#include <IDBDriver.h> +#include <sm_platform.h> +#if defined PLATFORM_WINDOWS +#include <winsock.h> +#endif + +#include <mysql.h> + +#include <sh_string.h> +#include <sh_list.h> + +using namespace SourceMod; +using namespace SourceHook; + +#define M_CLIENT_MULTI_RESULTS ((1) << 17) /* Enable/disable multi-results */ + +class MyDatabase; + +class MyDriver : public IDBDriver +{ +public: + MyDriver(); +public: //IDBDriver + IDatabase *Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength); + const char *GetIdentifier(); + const char *GetProductName(); + Handle_t GetHandle(); + IdentityToken_t *GetIdentity(); +public: + void Shutdown(); + void RemoveFromList(MyDatabase *pdb, bool persistent); +private: + Handle_t m_MyHandle; + List<MyDatabase *> m_TempDbs; + List<MyDatabase *> m_PermDbs; +}; + +extern MyDriver g_MyDriver; + +unsigned int strncopy(char *dest, const char *src, size_t count); + +#endif //_INCLUDE_SM_MYSQL_DRIVER_H_ diff --git a/extensions/mysql/mysql/MyQuery.cpp b/extensions/mysql/mysql/MyQuery.cpp new file mode 100644 index 00000000..bf42682d --- /dev/null +++ b/extensions/mysql/mysql/MyQuery.cpp @@ -0,0 +1,7 @@ +#include "MyQuery.h" + +MyQuery::MyQuery(MyDatabase *db, MYSQL_RES *res) +: m_pParent(db) +{ + +} diff --git a/extensions/mysql/mysql/MyQuery.h b/extensions/mysql/mysql/MyQuery.h new file mode 100644 index 00000000..b3aa45e1 --- /dev/null +++ b/extensions/mysql/mysql/MyQuery.h @@ -0,0 +1,26 @@ +#ifndef _INCLUDE_SM_MYSQL_QUERY_H_ +#define _INCLUDE_SM_MYSQL_QUERY_H_ + +#include "MyDriver.h" +#include "MyDatabase.h" + +class MyResultSet : + public IResultSet, + public IResultRow +{ +public: + +}; + +class MyQuery : public IQuery +{ +public: + MyQuery(MyDatabase *db, MYSQL_RES *res); +public: + IResultSet *GetResults(); + void Destroy(); +private: + MyDatabase *m_pParent; +}; + +#endif //_INCLUDE_SM_MYSQL_QUERY_H_ diff --git a/extensions/mysql/mysql/MyStatement.cpp b/extensions/mysql/mysql/MyStatement.cpp new file mode 100644 index 00000000..b1038abd --- /dev/null +++ b/extensions/mysql/mysql/MyStatement.cpp @@ -0,0 +1,257 @@ +#include "MyStatement.h" +#include "MyBoundResults.h" + +MyStatement::MyStatement(MyDatabase *db, MYSQL_STMT *stmt) +: m_mysql(db->m_mysql), m_pParent(db), m_stmt(stmt), m_rs(NULL), m_Results(false) +{ + m_Params = (unsigned int)mysql_stmt_param_count(m_stmt); + + if (m_Params) + { + m_pushinfo = (ParamBind *)malloc(sizeof(ParamBind) * m_Params); + memset(m_pushinfo, 0, sizeof(ParamBind) * m_Params); + m_bind = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * m_Params); + memset(m_bind, 0, sizeof(MYSQL_BIND) * m_Params); + } else { + m_pushinfo = NULL; + m_bind = NULL; + } + + m_pParent->IncRefCount(); + + m_pRes = mysql_stmt_result_metadata(stmt); + m_Results = false; +} + +MyStatement::~MyStatement() +{ + /* Free result set structures */ + delete m_rs; + + /* Free old blobs */ + for (unsigned int i=0; i<m_Params; i++) + { + free(m_pushinfo[i].blob); + } + + /* Free our allocated arrays */ + free(m_pushinfo); + free(m_bind); + + /* Close our mysql handles */ + if (m_pRes) + { + mysql_free_result(m_pRes); + } + mysql_stmt_close(m_stmt); + + /* Tell the parent database that we're done referencing it */ + m_pParent->Close(); +} + +void MyStatement::Destroy() +{ + delete this; +} + +bool MyStatement::FetchMoreResults() +{ + /* Multiple result sets are not supported by statements, + * thank god. + */ + return false; +} + +void *MyStatement::CopyBlob(unsigned int param, const void *blobptr, size_t length) +{ + void *copy_ptr = NULL; + + if (m_pushinfo[param].blob != NULL) + { + if (m_pushinfo[param].length < length) + { + free(m_pushinfo[param].blob); + } else { + copy_ptr = m_pushinfo[param].blob; + } + } + + if (copy_ptr == NULL) + { + copy_ptr = malloc(length); + m_pushinfo[param].blob = copy_ptr; + m_pushinfo[param].length = length; + } + + memcpy(copy_ptr, blobptr, length); + + return copy_ptr; +} + +bool MyStatement::BindParamInt(unsigned int param, int num, bool signd) +{ + if (param >= m_Params) + { + return false; + } + + m_pushinfo[param].data.ival = num; + m_bind[param].buffer_type = MYSQL_TYPE_LONG; + m_bind[param].buffer = &(m_pushinfo[param].data.ival); + m_bind[param].is_unsigned = signd ? 0 : 1; + m_bind[param].length = NULL; + + return true; +} + +bool MyStatement::BindParamFloat(unsigned int param, float f) +{ + if (param >= m_Params) + { + return false; + } + + m_pushinfo[param].data.fval = f; + m_bind[param].buffer_type = MYSQL_TYPE_FLOAT; + m_bind[param].buffer = &(m_pushinfo[param].data.fval); + m_bind[param].length = NULL; + + return true; +} + +bool MyStatement::BindParamString(unsigned int param, const char *text, bool copy) +{ + if (param >= m_Params) + { + return false; + } + + const void *final_ptr; + size_t len; + + if (copy) + { + len = strlen(text); + final_ptr = CopyBlob(param, text, len+1); + } else { + len = strlen(text); + final_ptr = text; + } + + m_bind[param].buffer_type = MYSQL_TYPE_STRING; + m_bind[param].buffer = (void *)final_ptr; + m_bind[param].buffer_length = (unsigned long)len; + m_bind[param].length = &(m_bind[param].buffer_length); + + return true; +} + +bool MyStatement::BindParamBlob(unsigned int param, const void *data, size_t length, bool copy) +{ + if (param >= m_Params) + { + return false; + } + + const void *final_ptr; + + if (copy) + { + final_ptr = CopyBlob(param, data, length); + } else { + final_ptr = data; + } + + m_bind[param].buffer_type = MYSQL_TYPE_BLOB; + m_bind[param].buffer = (void *)final_ptr; + m_bind[param].buffer_length = (unsigned long)length; + m_bind[param].length = &(m_bind[param].buffer_length); + + return true; +} + +bool MyStatement::BindParamNull(unsigned int param) +{ + if (param >= m_Params) + { + return false; + } + + m_bind[param].buffer_type = MYSQL_TYPE_NULL; + + return true; +} + +bool MyStatement::Execute() +{ + /* Clear any past result first! */ + m_Results = false; + + /* Bind the parameters */ + if (m_Params) + { + if (mysql_stmt_bind_param(m_stmt, m_bind) != 0) + { + return false; + } + } + + if (mysql_stmt_execute(m_stmt) != 0) + { + return false; + } + + /* Skip away if we don't have data */ + if (!m_pRes) + { + return true; + } + + /* If we don't have a result manager, create one. */ + if (!m_rs) + { + m_rs = new MyBoundResults(m_stmt, m_pRes); + } + + /* Tell the result set to update its bind info, + * and initialize itself if necessary. + */ + if (!(m_Results = m_rs->Initialize())) + { + return false; + } + + /* Try precaching the results. */ + m_Results = (mysql_stmt_store_result(m_stmt) == 0); + + /* Update now that the data is known. */ + m_rs->Update(); + + /* Return indicator */ + return m_Results; +} + +const char *MyStatement::GetError(int *errCode/* =NULL */) +{ + if (errCode) + { + *errCode = mysql_stmt_errno(m_stmt); + } + + return mysql_stmt_error(m_stmt); +} + +unsigned int MyStatement::GetAffectedRows() +{ + return (unsigned int)mysql_stmt_affected_rows(m_stmt); +} + +unsigned int MyStatement::GetInsertID() +{ + return (unsigned int)mysql_stmt_insert_id(m_stmt); +} + +IResultSet *MyStatement::GetResultSet() +{ + return (m_Results ? m_rs : NULL); +} diff --git a/extensions/mysql/mysql/MyStatement.h b/extensions/mysql/mysql/MyStatement.h new file mode 100644 index 00000000..8d2f9f98 --- /dev/null +++ b/extensions/mysql/mysql/MyStatement.h @@ -0,0 +1,51 @@ +#ifndef _INCLUDE_SM_MYSQL_STATEMENT_H_ +#define _INCLUDE_SM_MYSQL_STATEMENT_H_ + +#include "MyDatabase.h" +#include "MyBoundResults.h" + +struct ParamBind +{ + union + { + float fval; + int ival; + } data; + void *blob; + size_t length; +}; + +class MyStatement : public IPreparedQuery +{ +public: + MyStatement(MyDatabase *db, MYSQL_STMT *stmt); + ~MyStatement(); +public: //IQuery + IResultSet *GetResultSet(); + bool FetchMoreResults(); + void Destroy(); +public: //IPreparedQuery + bool BindParamInt(unsigned int param, int num, bool signd=true); + bool BindParamFloat(unsigned int param, float f); + bool BindParamNull(unsigned int param); + bool BindParamString(unsigned int param, const char *text, bool copy); + bool BindParamBlob(unsigned int param, const void *data, size_t length, bool copy); + bool Execute(); + const char *GetError(int *errCode=NULL); + unsigned int GetAffectedRows(); + unsigned int GetInsertID(); +private: + void *CopyBlob(unsigned int param, const void *blobptr, size_t length); +private: + MYSQL *m_mysql; + MYSQL_STMT *m_stmt; + MYSQL_BIND *m_bind; + MYSQL_RES *m_pRes; + MyDatabase *m_pParent; + ParamBind *m_pushinfo; + unsigned int m_Params; + MyBoundResults *m_rs; + bool m_Results; +}; + +#endif //_INCLUDE_SM_MYSQL_STATEMENT_H_ diff --git a/extensions/mysql/sdk/smsdk_config.h b/extensions/mysql/sdk/smsdk_config.h new file mode 100644 index 00000000..f52dfaf6 --- /dev/null +++ b/extensions/mysql/sdk/smsdk_config.h @@ -0,0 +1,54 @@ +/** + * vim: set ts=4 : + * =============================================================== + * SourceMod, Copyright (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 public/licenses/LICENSE.txt. As of this notice, derivative + * works must be licensed under the GNU General Public License (version 2 or + * greater). A copy of the GPL is included under public/licenses/GPL.txt. + * + * To view the latest information, see: http://www.sourcemod.net/license.php + * + * Version: $Id: smsdk_config.h 763 2007-05-09 05:20:03Z damagedsoul $ + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ + +/** + * @file smsdk_config.h + * @brief Contains macros for configuring basic extension information. + */ + +/* Basic information exposed publicly */ +#define SMEXT_CONF_NAME "MySQL-DBI" +#define SMEXT_CONF_DESCRIPTION "MySQL driver implementation for DBI" +#define SMEXT_CONF_VERSION "1.0.0.0" +#define SMEXT_CONF_AUTHOR "AlliedModders" +#define SMEXT_CONF_URL "http://www.sourcemod.net/" +#define SMEXT_CONF_LOGTAG "MYSQL" +#define SMEXT_CONF_LICENSE "GPL" +#define SMEXT_CONF_DATESTRING __DATE__ + +/** + * @brief Exposes plugin's main interface. + */ +#define SMEXT_LINK(name) SDKExtension *g_pExtensionIface = name; + +/** + * @brief Sets whether or not this plugin required Metamod. + * NOTE: Uncomment to enable, comment to disable. + */ +//#define SMEXT_CONF_METAMOD + +/** Enable interfaces you want to use here by uncommenting lines */ +//#define SMEXT_ENABLE_FORWARDSYS +//#define SMEXT_ENABLE_HANDLESYS +//#define SMEXT_ENABLE_PLAYERHELPERS +#define SMEXT_ENABLE_DBMANAGER + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ diff --git a/extensions/mysql/sdk/smsdk_ext.cpp b/extensions/mysql/sdk/smsdk_ext.cpp new file mode 100644 index 00000000..9d6c8cca --- /dev/null +++ b/extensions/mysql/sdk/smsdk_ext.cpp @@ -0,0 +1,361 @@ +/** + * vim: set ts=4 : + * =============================================================== + * SourceMod, Copyright (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 public/licenses/LICENSE.txt. As of this notice, derivative + * works must be licensed under the GNU General Public License (version 2 or + * greater). A copy of the GPL is included under public/licenses/GPL.txt. + * + * To view the latest information, see: http://www.sourcemod.net/license.php + * + * Version: $Id: smsdk_ext.cpp 763 2007-05-09 05:20:03Z damagedsoul $ + */ + +#include <stdio.h> +#include <malloc.h> +#include "smsdk_ext.h" + +/** + * @file smsdk_ext.cpp + * @brief Contains wrappers for making Extensions easier to write. + */ + +IExtension *myself = NULL; /**< Ourself */ +IShareSys *g_pShareSys = NULL; /**< Share system */ +IShareSys *sharesys = NULL; /**< Share system */ +ISourceMod *g_pSM = NULL; /**< SourceMod helpers */ +ISourceMod *smutils = NULL; /**< SourceMod helpers */ + +#if defined SMEXT_ENABLE_FORWARDSYS +IForwardManager *g_pForwards = NULL; /**< Forward system */ +IForwardManager *forwards = NULL; /**< Forward system */ +#endif +#if defined SMEXT_ENABLE_HANDLESYS +IHandleSys *g_pHandleSys = NULL; /**< Handle system */ +IHandleSys *handlesys = NULL; /**< Handle system */ +#endif +#if defined SMEXT_ENABLE_PLAYERHELPERS +IPlayerHelpers *playerhelpers = NULL; /**< Player helpers */ +#endif //SMEXT_ENABLE_PLAYERHELPERS +#if defined SMEXT_ENABLE_DBMANAGER +IDBManager *dbi = NULL; /**< DB Manager */ +#endif //SMEXT_ENABLE_DBMANAGER + +/** Exports the main interface */ +PLATFORM_EXTERN_C IExtensionInterface *GetSMExtAPI() +{ + return g_pExtensionIface; +} + +SDKExtension::SDKExtension() +{ +#if defined SMEXT_CONF_METAMOD + m_SourceMMLoaded = false; + m_WeAreUnloaded = false; + m_WeGotPauseChange = false; +#endif +} + +bool SDKExtension::OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, size_t maxlength, bool late) +{ + g_pShareSys = sharesys = sys; + myself = me; + +#if defined SMEXT_CONF_METAMOD + m_WeAreUnloaded = true; + + if (!m_SourceMMLoaded) + { + if (error) + { + snprintf(error, maxlength, "Metamod attach failed"); + } + return false; + } +#endif + SM_GET_IFACE(SOURCEMOD, g_pSM); + smutils = g_pSM; +#if defined SMEXT_ENABLE_HANDLESYS + SM_GET_IFACE(HANDLESYSTEM, g_pHandleSys); + handlesys = g_pHandleSys; +#endif +#if defined SMEXT_ENABLE_FORWARDSYS + SM_GET_IFACE(FORWARDMANAGER, g_pForwards); + forwards = g_pForwards; +#endif +#if defined SMEXT_ENABLE_PLAYERHELPERS + SM_GET_IFACE(PLAYERMANAGER, playerhelpers); +#endif +#if defined SMEXT_ENABLE_DBMANAGER + SM_GET_IFACE(DBI, dbi); +#endif + + if (SDK_OnLoad(error, maxlength, late)) + { +#if defined SMEXT_CONF_METAMOD + m_WeAreUnloaded = true; +#endif + return true; + } + + return false; +} + +bool SDKExtension::IsMetamodExtension() +{ +#if defined SMEXT_CONF_METAMOD + return true; +#else + return false; +#endif +} + +void SDKExtension::OnExtensionPauseChange(bool state) +{ +#if defined SMEXT_CONF_METAMOD + m_WeGotPauseChange = true; +#endif + SDK_OnPauseChange(state); +} + +void SDKExtension::OnExtensionsAllLoaded() +{ + SDK_OnAllLoaded(); +} + +void SDKExtension::OnExtensionUnload() +{ +#if defined SMEXT_CONF_METAMOD + m_WeAreUnloaded = true; +#endif + SDK_OnUnload(); +} + +const char *SDKExtension::GetExtensionAuthor() +{ + return SMEXT_CONF_AUTHOR; +} + +const char *SDKExtension::GetExtensionDateString() +{ + return SMEXT_CONF_DATESTRING; +} + +const char *SDKExtension::GetExtensionDescription() +{ + return SMEXT_CONF_DESCRIPTION; +} + +const char *SDKExtension::GetExtensionVerString() +{ + return SMEXT_CONF_VERSION; +} + +const char *SDKExtension::GetExtensionName() +{ + return SMEXT_CONF_NAME; +} + +const char *SDKExtension::GetExtensionTag() +{ + return SMEXT_CONF_LOGTAG; +} + +const char *SDKExtension::GetExtensionURL() +{ + return SMEXT_CONF_URL; +} + +bool SDKExtension::SDK_OnLoad(char *error, size_t maxlength, bool late) +{ + return true; +} + +void SDKExtension::SDK_OnUnload() +{ +} + +void SDKExtension::SDK_OnPauseChange(bool paused) +{ +} + +void SDKExtension::SDK_OnAllLoaded() +{ +} + +#if defined SMEXT_CONF_METAMOD + +PluginId g_PLID = 0; /**< Metamod plugin ID */ +ISmmPlugin *g_PLAPI = NULL; /**< Metamod plugin API */ +SourceHook::ISourceHook *g_SHPtr = NULL; /**< SourceHook pointer */ +ISmmAPI *g_SMAPI = NULL; /**< SourceMM API pointer */ + +IVEngineServer *engine = NULL; /**< IVEngineServer pointer */ +IServerGameDLL *gamedll = NULL; /**< IServerGameDLL pointer */ + +/** Exposes the extension to Metamod */ +SMM_API void *PL_EXPOSURE(const char *name, int *code) +{ + if (name && !strcmp(name, PLAPI_NAME)) + { + if (code) + { + *code = IFACE_OK; + } + return static_cast<void *>(g_pExtensionIface); + } + + if (code) + { + *code = IFACE_FAILED; + } + + return NULL; +} + +bool SDKExtension::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late) +{ + PLUGIN_SAVEVARS(); + + GET_V_IFACE_ANY(serverFactory, gamedll, IServerGameDLL, INTERFACEVERSION_SERVERGAMEDLL); + GET_V_IFACE_CURRENT(engineFactory, engine, IVEngineServer, INTERFACEVERSION_VENGINESERVER); + + m_SourceMMLoaded = true; + + return SDK_OnMetamodLoad(ismm, error, maxlen, late); +} + +bool SDKExtension::Unload(char *error, size_t maxlen) +{ + if (!m_WeAreUnloaded) + { + if (error) + { + snprintf(error, maxlen, "This extension must be unloaded by SourceMod."); + } + return false; + } + + return SDK_OnMetamodUnload(error, maxlen); +} + +bool SDKExtension::Pause(char *error, size_t maxlen) +{ + if (!m_WeGotPauseChange) + { + if (error) + { + snprintf(error, maxlen, "This extension must be paused by SourceMod."); + } + return false; + } + + m_WeGotPauseChange = false; + + return SDK_OnMetamodPauseChange(true, error, maxlen); +} + +bool SDKExtension::Unpause(char *error, size_t maxlen) +{ + if (!m_WeGotPauseChange) + { + if (error) + { + snprintf(error, maxlen, "This extension must be unpaused by SourceMod."); + } + return false; + } + + m_WeGotPauseChange = false; + + return SDK_OnMetamodPauseChange(false, error, maxlen); +} + +const char *SDKExtension::GetAuthor() +{ + return GetExtensionAuthor(); +} + +const char *SDKExtension::GetDate() +{ + return GetExtensionDateString(); +} + +const char *SDKExtension::GetDescription() +{ + return GetExtensionDescription(); +} + +const char *SDKExtension::GetLicense() +{ + return SMEXT_CONF_LICENSE; +} + +const char *SDKExtension::GetLogTag() +{ + return GetExtensionTag(); +} + +const char *SDKExtension::GetName() +{ + return GetExtensionName(); +} + +const char *SDKExtension::GetURL() +{ + return GetExtensionURL(); +} + +const char *SDKExtension::GetVersion() +{ + return GetExtensionVerString(); +} + +bool SDKExtension::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late) +{ + return true; +} + +bool SDKExtension::SDK_OnMetamodUnload(char *error, size_t maxlength) +{ + return true; +} + +bool SDKExtension::SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength) +{ + return true; +} + +#endif + +/* Overload a few things to prevent libstdc++ linking */ +#if defined __linux__ +extern "C" void __cxa_pure_virtual(void) +{ +} + +void *operator new(size_t size) +{ + return malloc(size); +} + +void *operator new[](size_t size) +{ + return malloc(size); +} + +void operator delete(void *ptr) +{ + free(ptr); +} + +void operator delete[](void * ptr) +{ + free(ptr); +} +#endif diff --git a/extensions/mysql/sdk/smsdk_ext.h b/extensions/mysql/sdk/smsdk_ext.h new file mode 100644 index 00000000..d5877cdd --- /dev/null +++ b/extensions/mysql/sdk/smsdk_ext.h @@ -0,0 +1,237 @@ +/** + * vim: set ts=4 : + * =============================================================== + * SourceMod, Copyright (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 public/licenses/LICENSE.txt. As of this notice, derivative + * works must be licensed under the GNU General Public License (version 2 or + * greater). A copy of the GPL is included under public/licenses/GPL.txt. + * + * To view the latest information, see: http://www.sourcemod.net/license.php + * + * Version: $Id: smsdk_ext.h 763 2007-05-09 05:20:03Z damagedsoul $ + */ + +#ifndef _INCLUDE_SOURCEMOD_EXTENSION_BASESDK_H_ +#define _INCLUDE_SOURCEMOD_EXTENSION_BASESDK_H_ + +/** + * @file smsdk_ext.h + * @brief Contains wrappers for making Extensions easier to write. + */ + +#include "smsdk_config.h" +#include <IExtensionSys.h> +#include <IHandleSys.h> +#include <sp_vm_api.h> +#include <sm_platform.h> +#include <ISourceMod.h> +#if defined SMEXT_ENABLE_FORWARDSYS +#include <IForwardSys.h> +#endif //SMEXT_ENABLE_FORWARDSYS +#if defined SMEXT_ENABLE_PLAYERHELPERS +#include <IPlayerHelpers.h> +#endif //SMEXT_ENABLE_PlAYERHELPERS +#if defined SMEXT_ENABLE_DBMANAGER +#include <IDBDriver.h> +#endif //SMEXT_ENABLE_DBMANAGER + +#if defined SMEXT_CONF_METAMOD +#include <ISmmPlugin.h> +#include <eiface.h> +#endif + +using namespace SourceMod; +using namespace SourcePawn; + +class SDKExtension : +#if defined SMEXT_CONF_METAMOD + public ISmmPlugin, +#endif + public IExtensionInterface +{ +public: + /** Constructor */ + SDKExtension(); +public: + /** + * @brief This is called after the initial loading sequence has been processed. + * + * @param error Error message buffer. + * @param maxlength Size of error message buffer. + * @param late Whether or not the module was loaded after map load. + * @return True to succeed loading, false to fail. + */ + virtual bool SDK_OnLoad(char *error, size_t maxlength, bool late); + + /** + * @brief This is called right before the extension is unloaded. + */ + virtual void SDK_OnUnload(); + + /** + * @brief This is called once all known extensions have been loaded. + */ + virtual void SDK_OnAllLoaded(); + + /** + * @brief Called when the pause state is changed. + */ + virtual void SDK_OnPauseChange(bool paused); + +#if defined SMEXT_CONF_METAMOD + /** + * @brief Called when Metamod is attached, before the extension version is called. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @param late Whether or not Metamod considers this a late load. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlength, bool late); + + /** + * @brief Called when Metamod is detaching, after the extension version is called. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodUnload(char *error, size_t maxlength); + + /** + * @brief Called when Metamod's pause state is changing. + * NOTE: By default this is blocked unless sent from SourceMod. + * + * @param paused Pause state being set. + * @param error Error buffer. + * @param maxlength Maximum size of error buffer. + * @return True to succeed, false to fail. + */ + virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength); +#endif + +public: //IExtensionInterface + virtual bool OnExtensionLoad(IExtension *me, IShareSys *sys, char *error, size_t maxlength, bool late); + virtual void OnExtensionUnload(); + virtual void OnExtensionsAllLoaded(); + + /** Returns whether or not this is a Metamod-based extension */ + virtual bool IsMetamodExtension(); + + /** + * @brief Called when the pause state changes. + * + * @param state True if being paused, false if being unpaused. + */ + virtual void OnExtensionPauseChange(bool state); + + /** Returns name */ + virtual const char *GetExtensionName(); + /** Returns URL */ + virtual const char *GetExtensionURL(); + /** Returns log tag */ + virtual const char *GetExtensionTag(); + /** Returns author */ + virtual const char *GetExtensionAuthor(); + /** Returns version string */ + virtual const char *GetExtensionVerString(); + /** Returns description string */ + virtual const char *GetExtensionDescription(); + /** Returns date string */ + virtual const char *GetExtensionDateString(); +#if defined SMEXT_CONF_METAMOD +public: //ISmmPlugin + /** Called when the extension is attached to Metamod. */ + virtual bool Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlength, bool late); + /** Returns the author to MM */ + virtual const char *GetAuthor(); + /** Returns the name to MM */ + virtual const char *GetName(); + /** Returns the description to MM */ + virtual const char *GetDescription(); + /** Returns the URL to MM */ + virtual const char *GetURL(); + /** Returns the license to MM */ + virtual const char *GetLicense(); + /** Returns the version string to MM */ + virtual const char *GetVersion(); + /** Returns the date string to MM */ + virtual const char *GetDate(); + /** Returns the logtag to MM */ + virtual const char *GetLogTag(); + /** Called on unload */ + virtual bool Unload(char *error, size_t maxlength); + /** Called on pause */ + virtual bool Pause(char *error, size_t maxlength); + /** Called on unpause */ + virtual bool Unpause(char *error, size_t maxlength); +private: + bool m_SourceMMLoaded; + bool m_WeAreUnloaded; + bool m_WeGotPauseChange; +#endif +}; + +extern SDKExtension *g_pExtensionIface; +extern IExtension *myself; + +extern IShareSys *g_pShareSys; +extern IShareSys *sharesys; /* Note: Newer name */ +extern ISourceMod *g_pSM; +extern ISourceMod *smutils; /* Note: Newer name */ + +/* Optional interfaces are below */ +#if defined SMEXT_ENABLE_FORWARDSYS +extern IForwardManager *g_pForwards; +extern IForwardManager *forwards; /* Note: Newer name */ +#endif //SMEXT_ENABLE_FORWARDSYS +#if defined SMEXT_ENABLE_HANDLESYS +extern IHandleSys *g_pHandleSys; +extern IHandleSys *handlesys; /* Note: Newer name */ +#endif //SMEXT_ENABLE_HANDLESYS +#if defined SMEXT_ENABLE_PLAYERHELPERS +extern IPlayerHelpers *playerhelpers; +#endif //SMEXT_ENABLE_PLAYERHELPERS +#if defined SMEXT_ENABLE_DBMANAGER +extern IDBManager *dbi; +#endif //SMEXT_ENABLE_DBMANAGER + +#if defined SMEXT_CONF_METAMOD +PLUGIN_GLOBALVARS(); +extern IVEngineServer *engine; +extern IServerGameDLL *gamedll; +#endif + +/** Creates a SourceMod interface macro pair */ +#define SM_MKIFACE(name) SMINTERFACE_##name##_NAME, SMINTERFACE_##name##_VERSION +/** Automates retrieving SourceMod interfaces */ +#define SM_GET_IFACE(prefix, addr) \ + if (!g_pShareSys->RequestInterface(SM_MKIFACE(prefix), myself, (SMInterface **)&addr)) \ + { \ + if (error) \ + { \ + snprintf(error, maxlength, "Could not find interface: %s (version: %d)", SMINTERFACE_##prefix##_NAME, SMINTERFACE_##prefix##_VERSION); \ + return false; \ + } \ + } +/** Automates retrieving SourceMod interfaces when needed outside of SDK_OnLoad() */ +#define SM_GET_LATE_IFACE(prefix, addr) \ + g_pShareSys->RequestInterface(SM_MKIFACE(prefix), myself, (SMInterface **)&addr) +/** Validates a SourceMod interface pointer */ +#define SM_CHECK_IFACE(prefix, addr) \ + if (!addr) \ + { \ + if (error) \ + { \ + snprintf(error, maxlength, "Could not find interface: %s", SMINTERFACE_##prefix##_NAME); \ + return false; \ + } \ + } + +#endif // _INCLUDE_SOURCEMOD_EXTENSION_BASESDK_H_ diff --git a/public/IDBDriver.h b/public/IDBDriver.h index 130ea6d1..88d3a31f 100644 --- a/public/IDBDriver.h +++ b/public/IDBDriver.h @@ -224,6 +224,14 @@ namespace SourceMod */ virtual IResultRow *FetchRow() =0; + /** + * @brief Returns a pointer to the current row. + * + * @return IResultRow pointer to the current row, + * or NULL if the current row is invalid. + */ + virtual IResultRow *CurrentRow() =0; + /** * @brief Rewinds back to the beginning of the row iteration. *