├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── Examples ├── database.lua ├── multi_parameters.lua ├── multi_results.lua ├── prepared_query.lua ├── query.lua └── transactions.lua ├── GmodLUA ├── CMakeLists.txt └── GarrysMod │ └── Lua │ ├── Interface.h │ ├── LuaBase.h │ ├── SourceCompat.h │ ├── Types.h │ └── UserData.h ├── IntegrationTest ├── README.md └── lua │ ├── autorun │ └── server │ │ └── init.lua │ └── mysqloo │ ├── init.lua │ ├── setup.lua │ ├── testframework.lua │ └── tests │ ├── basic_mysql_test.lua │ ├── database_tests.lua │ ├── prepared_query_tests.lua │ ├── query_tests.lua │ └── transaction_test.lua ├── LICENSE ├── MySQL ├── include │ ├── errmsg.h │ ├── ma_io.h │ ├── ma_list.h │ ├── ma_pvio.h │ ├── ma_tls.h │ ├── mariadb_com.h │ ├── mariadb_ctype.h │ ├── mariadb_dyncol.h │ ├── mariadb_rpl.h │ ├── mariadb_stmt.h │ ├── mariadb_version.h │ ├── mysql.h │ ├── mysql │ │ ├── client_plugin.h │ │ ├── plugin_auth.h │ │ └── plugin_auth_common.h │ └── mysqld_error.h ├── lib │ ├── linux │ │ ├── libcrypto.a │ │ ├── libmariadbclient.a │ │ └── libssl.a │ └── windows │ │ └── mariadbclient.lib └── lib64 │ ├── linux │ ├── libcrypto.a │ ├── libmariadbclient.a │ └── libssl.a │ └── windows │ └── mariadbclient.lib ├── README.md ├── lua ├── connectionpool.lua ├── mysqloolib.lua └── tmysql4.lua ├── minorversion.txt └── src ├── BlockingQueue.h ├── lua ├── GMModule.cpp ├── LuaDatabase.cpp ├── LuaDatabase.h ├── LuaIQuery.cpp ├── LuaIQuery.h ├── LuaObject.cpp ├── LuaObject.h ├── LuaPreparedQuery.cpp ├── LuaPreparedQuery.h ├── LuaQuery.cpp ├── LuaQuery.h ├── LuaTransaction.cpp └── LuaTransaction.h └── mysql ├── Database.cpp ├── Database.h ├── IQuery.cpp ├── IQuery.h ├── MySQLHeader.h ├── MySQLOOException.h ├── PingQuery.cpp ├── PingQuery.h ├── PreparedQuery.cpp ├── PreparedQuery.h ├── Query.cpp ├── Query.h ├── ResultData.cpp ├── ResultData.h ├── StatementHandle.h ├── Transaction.cpp └── Transaction.h /.gitignore: -------------------------------------------------------------------------------- 1 | /solutions 2 | /out/windows/gmsv_mysqloo_win32.iobj 3 | /out/windows/gmsv_mysqloo_win32.ipdb 4 | /out/windows/gmsv_mysqloo_win32.pdb 5 | /premake4 6 | /premake4.exe 7 | /premake5 8 | /premake5.exe 9 | /out 10 | .vscode 11 | *.zip 12 | cmake-build-debug 13 | .idea 14 | .vs 15 | .cache -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(mysqloo) 3 | add_subdirectory(GmodLUA) 4 | 5 | file(GLOB_RECURSE MYSQLOO_SRC "src/*.h" "src/*.cpp") 6 | set(SOURCE_FILES ${MYSQLOO_SRC}) 7 | set(CMAKE_BUILD_TYPE RelWithDebInfo) 8 | set (CMAKE_CXX_STANDARD 14) 9 | 10 | add_library(mysqloo SHARED ${SOURCE_FILES}) 11 | target_link_libraries(mysqloo gmod-module-base) 12 | 13 | target_include_directories(mysqloo PRIVATE MySQL/include) 14 | 15 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 16 | if (WIN32) 17 | find_library(MARIADB_CLIENT_LIB mariadbclient HINTS "${PROJECT_SOURCE_DIR}/MySQL/lib64/windows") 18 | else () 19 | find_library(MARIADB_CLIENT_LIB mariadbclient HINTS "${PROJECT_SOURCE_DIR}/MySQL/lib64/linux") 20 | find_library(CRYPTO_LIB crypto HINTS "${PROJECT_SOURCE_DIR}/MySQL/lib64/linux") 21 | find_library(SSL_LIB ssl HINTS "${PROJECT_SOURCE_DIR}/MySQL/lib64/linux") 22 | endif () 23 | elseif (CMAKE_SIZEOF_VOID_P EQUAL 4) 24 | if (WIN32) 25 | find_library(MARIADB_CLIENT_LIB mariadbclient HINTS "${PROJECT_SOURCE_DIR}/MySQL/lib/windows") 26 | else () 27 | find_library(MARIADB_CLIENT_LIB mariadbclient HINTS "${PROJECT_SOURCE_DIR}/MySQL/lib/linux") 28 | find_library(CRYPTO_LIB crypto HINTS "${PROJECT_SOURCE_DIR}/MySQL/lib/linux") 29 | find_library(SSL_LIB ssl HINTS "${PROJECT_SOURCE_DIR}/MySQL/lib/linux") 30 | endif () 31 | endif () 32 | 33 | if (WIN32) 34 | target_link_libraries(mysqloo ${MARIADB_CLIENT_LIB} crypt32 ws2_32 shlwapi bcrypt secur32) 35 | else () 36 | find_package(Threads REQUIRED) 37 | target_link_libraries(mysqloo ${MARIADB_CLIENT_LIB} ${SSL_LIB} ${CRYPTO_LIB} Threads::Threads ${CMAKE_DL_LIBS}) 38 | target_link_libraries(mysqloo -static-libstdc++) 39 | endif () 40 | 41 | set_gmod_suffix_prefix(mysqloo) -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-RelDebug", 5 | "generator": "Ninja", 6 | "configurationType": "RelWithDebInfo", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "", 12 | "ctestCommandArgs": "" 13 | }, 14 | { 15 | "name": "x86-RelDebug", 16 | "generator": "Ninja", 17 | "configurationType": "RelWithDebInfo", 18 | "buildRoot": "${projectDir}\\out\\build\\${name}", 19 | "installRoot": "${projectDir}\\out\\install\\${name}", 20 | "cmakeCommandArgs": "", 21 | "buildCommandArgs": "", 22 | "ctestCommandArgs": "", 23 | "inheritEnvironments": [ "msvc_x86" ], 24 | "variables": [] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Examples/database.lua: -------------------------------------------------------------------------------- 1 | --By Drakehawke 2 | require( "mysqloo" ) 3 | 4 | db = mysqloo.connect( "123.456.789.0", "drake", "abc123", "database_name", 3306 ) 5 | 6 | function db:onConnected() 7 | 8 | print( "Database has connected!" ) 9 | 10 | local q = self:query( "SELECT 5+5" ) 11 | function q:onSuccess( data ) 12 | 13 | print( "Query successful!" ) 14 | PrintTable( data ) 15 | 16 | end 17 | 18 | function q:onError( err, sql ) 19 | 20 | print( "Query errored!" ) 21 | print( "Query:", sql ) 22 | print( "Error:", err ) 23 | 24 | end 25 | 26 | q:start() 27 | 28 | end 29 | 30 | function db:onConnectionFailed( err ) 31 | 32 | print( "Connection to database failed!" ) 33 | print( "Error:", err ) 34 | 35 | end 36 | 37 | db:connect() -------------------------------------------------------------------------------- /Examples/multi_parameters.lua: -------------------------------------------------------------------------------- 1 | include("database.lua") -- check database.lua for an example of how to create database instances 2 | 3 | local preparedQuery = db:prepare("INSERT INTO users (`steamid`, `nick`, `rank`, `playtime`, `sex`) VALUES(?, ?, ?, ?, ?)") 4 | function preparedQuery:onSuccess(data) 5 | print("Rows inserted successfully!") 6 | end 7 | 8 | function preparedQuery:onError(err) 9 | print("An error occured while executing the query: " .. err) 10 | end 11 | 12 | preparedQuery:setString(1, "STEAM_0:0:123456") 13 | preparedQuery:setString(2, "''``--lel") -- you don't need to escape the string if you use setString 14 | preparedQuery:setNull(3) 15 | preparedQuery:setNumber(4, 100) 16 | preparedQuery:setBoolean(5, true) 17 | preparedQuery:start() 18 | 19 | -- you can now reuse prepared queries 20 | preparedQuery:setString(1, "STEAM_0:0:654321") 21 | preparedQuery:setString(2, "HufflePuffle") 22 | preparedQuery:setString(3, "owner") 23 | preparedQuery:setNumber(4, 100000) 24 | preparedQuery:setBoolean(5, false) 25 | preparedQuery:start() -------------------------------------------------------------------------------- /Examples/multi_results.lua: -------------------------------------------------------------------------------- 1 | include("database.lua") -- check database.lua for an example of how to create database instances 2 | 3 | local query = db:query("SELECT 1, 2, 3; SELECT 4, 5, 6; SELECT 7, 8, 9") -- In mysqloo 9 a query can be started before the database is connected 4 | function query:onSuccess(data) 5 | while(self:hasMoreResults()) do -- should be true three times 6 | row = query:getData()[1] 7 | for k,v in pairs(row) do 8 | print(v) -- should print 1, 2, 3, 4, 5, 6, 7, 8, 9 in any order 9 | end 10 | self:getNextResults() 11 | end 12 | end 13 | 14 | function query:onError(err) 15 | print("An error occured while executing the query: " .. err) 16 | end 17 | 18 | query:start() -------------------------------------------------------------------------------- /Examples/prepared_query.lua: -------------------------------------------------------------------------------- 1 | include("database.lua") -- check database.lua for an example of how to create database instances 2 | 3 | local preparedQuery = db:prepare("INSERT INTO users (`steamid`, `nick`, `rank`, `playtime`, `sex`) VALUES(?, ?, ?, ?, ?)") 4 | function preparedQuery:onSuccess(data) 5 | print("Rows inserted successfully!") 6 | end 7 | 8 | function preparedQuery:onError(err) 9 | print("An error occured while executing the query: " .. err) 10 | end 11 | 12 | preparedQuery:setString(1, "STEAM_0:0:123456") 13 | preparedQuery:setString(2, "''``--lel") -- you don't need to escape the string if you use setString 14 | preparedQuery:setNull(3) 15 | preparedQuery:setNumber(4, 100) 16 | preparedQuery:setBoolean(5, true) 17 | preparedQuery:start() -------------------------------------------------------------------------------- /Examples/query.lua: -------------------------------------------------------------------------------- 1 | include("database.lua") -- check database.lua for an example of how to create database instances 2 | 3 | local query = db:query("SELECT 1, 2, 3") -- In mysqloo 9 a query can be started before the database is connected 4 | function query:onSuccess(data) 5 | local row = data[1] 6 | for k,v in pairs(row) do 7 | print(v) -- should print 1, 2, 3 8 | end 9 | end 10 | 11 | function query:onError(err) 12 | print("An error occured while executing the query: " .. err) 13 | end 14 | 15 | query:start() -------------------------------------------------------------------------------- /Examples/transactions.lua: -------------------------------------------------------------------------------- 1 | include("database.lua") -- check database.lua for an example of how to create database instances 2 | 3 | -- The transaction makes sure that either all of the queries are executed successfully, or none of them have any effect 4 | -- If you want to read more about the concept of transactions, you can do it here: https://en.wikipedia.org/wiki/ACID 5 | local transaction = db:createTransaction() 6 | 7 | local query1 = db:query("UPDATE users SET cash = cash - 123 WHERE userid = 5") 8 | local query2 = db:query("UPDATE users SET bank = bank + 123 WHERE userid = 5") 9 | 10 | transaction:addQuery(query1) -- This also works with PreparedQueries 11 | transaction:addQuery(query2) 12 | 13 | -- Executed if and only if all of the queries in the transaction were executed successfully 14 | function transaction:onSuccess() 15 | local allQueries = transaction:getQueries() 16 | local query1Duplicate = allQueries[1] -- You can either use this function or save a reference to the query 17 | print("Transaction completed successfully") 18 | print("Affected rows: " .. query1:affectedRows()) 19 | end 20 | 21 | -- Executed if any of the queries in the transaction fail 22 | function transaction:onError(err) 23 | print("Transaction failed: " .. err) 24 | end 25 | 26 | transaction:start() -------------------------------------------------------------------------------- /GmodLUA/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCES 2 | GarrysMod/Lua/Interface.h 3 | GarrysMod/Lua/LuaBase.h 4 | GarrysMod/Lua/SourceCompat.h 5 | GarrysMod/Lua/Types.h 6 | GarrysMod/Lua/UserData.h) 7 | 8 | add_library(gmod-module-base INTERFACE) 9 | target_include_directories(gmod-module-base INTERFACE ./) 10 | 11 | function(set_gmod_suffix_prefix library) 12 | SET_TARGET_PROPERTIES(${library} PROPERTIES PREFIX "gmsv_") 13 | 14 | if(APPLE) 15 | if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") 16 | SET_TARGET_PROPERTIES(${library} PROPERTIES SUFFIX "_osx.dll") 17 | else() 18 | SET_TARGET_PROPERTIES(${library} PROPERTIES SUFFIX "_osx64.dll") 19 | endif() 20 | elseif(UNIX) 21 | if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") 22 | SET_TARGET_PROPERTIES(${library} PROPERTIES SUFFIX "_linux.dll") 23 | else() 24 | SET_TARGET_PROPERTIES(${library} PROPERTIES SUFFIX "_linux64.dll") 25 | endif() 26 | elseif(WIN32) 27 | if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") 28 | SET_TARGET_PROPERTIES(${library} PROPERTIES SUFFIX "_win32.dll") 29 | else() 30 | SET_TARGET_PROPERTIES(${library} PROPERTIES SUFFIX "_win64.dll") 31 | endif() 32 | endif() 33 | endfunction() -------------------------------------------------------------------------------- /GmodLUA/GarrysMod/Lua/Interface.h: -------------------------------------------------------------------------------- 1 | #ifndef GARRYSMOD_LUA_INTERFACE_H 2 | #define GARRYSMOD_LUA_INTERFACE_H 3 | 4 | #include "LuaBase.h" 5 | 6 | struct lua_State { 7 | #if defined( _WIN32 ) && !defined( _M_X64 ) 8 | // Win32 9 | unsigned char _ignore_this_common_lua_header_[48 + 22]; 10 | #elif defined( _WIN32 ) && defined( _M_X64 ) 11 | // Win64 12 | unsigned char _ignore_this_common_lua_header_[92 + 22]; 13 | #elif defined( __linux__ ) && !defined( __x86_64__ ) 14 | // Linux32 15 | unsigned char _ignore_this_common_lua_header_[48 + 22]; 16 | #elif defined( __linux__ ) && defined( __x86_64__ ) 17 | // Linux64 18 | unsigned char _ignore_this_common_lua_header_[92 + 22]; 19 | #elif defined ( __APPLE__ ) && !defined( __x86_64__ ) 20 | // macOS32 21 | unsigned char _ignore_this_common_lua_header_[48 + 22]; 22 | #elif defined ( __APPLE__ ) && defined( __x86_64__ ) 23 | // macOS64 24 | unsigned char _ignore_this_common_lua_header_[92 + 22]; 25 | #else 26 | #error agh 27 | #endif 28 | 29 | GarrysMod::Lua::ILuaBase *luabase; 30 | }; 31 | 32 | #ifndef GMOD 33 | #ifdef _WIN32 34 | #define DLL_EXPORT extern "C" __declspec( dllexport ) 35 | #else 36 | #define DLL_EXPORT extern "C" __attribute__((visibility("default"))) 37 | #endif 38 | 39 | #ifdef GMOD_ALLOW_DEPRECATED 40 | // Stop using this and use LUA_FUNCTION! 41 | #define LUA ( state->luabase ) 42 | 43 | #define GMOD_MODULE_OPEN() DLL_EXPORT int gmod13_open( lua_State* state ) 44 | #define GMOD_MODULE_CLOSE() DLL_EXPORT int gmod13_close( lua_State* state ) 45 | #else 46 | #define GMOD_MODULE_OPEN() \ 47 | int gmod13_open__Imp( GarrysMod::Lua::ILuaBase* LUA ); \ 48 | DLL_EXPORT int gmod13_open( lua_State* L ) \ 49 | { \ 50 | return gmod13_open__Imp( L->luabase ); \ 51 | } \ 52 | int gmod13_open__Imp( GarrysMod::Lua::ILuaBase* LUA ) 53 | 54 | #define GMOD_MODULE_CLOSE() \ 55 | int gmod13_close__Imp( GarrysMod::Lua::ILuaBase* LUA ); \ 56 | DLL_EXPORT int gmod13_close( lua_State* L ) \ 57 | { \ 58 | return gmod13_close__Imp( L->luabase ); \ 59 | } \ 60 | int gmod13_close__Imp( GarrysMod::Lua::ILuaBase* LUA ) 61 | 62 | #define LUA_FUNCTION(FUNC) \ 63 | static int FUNC##__Imp( GarrysMod::Lua::ILuaBase* LUA ); \ 64 | static int FUNC( lua_State* L ) \ 65 | { \ 66 | GarrysMod::Lua::ILuaBase* LUA = L->luabase; \ 67 | LUA->SetState(L); \ 68 | return FUNC##__Imp( LUA ); \ 69 | } \ 70 | static int FUNC##__Imp( GarrysMod::Lua::ILuaBase* LUA ) 71 | #endif 72 | #endif 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /GmodLUA/GarrysMod/Lua/SourceCompat.h: -------------------------------------------------------------------------------- 1 | #ifndef GARRYSMOD_LUA_SOURCECOMPAT_H 2 | #define GARRYSMOD_LUA_SOURCECOMPAT_H 3 | 4 | #ifdef GMOD_USE_SOURCESDK 5 | #include "mathlib/vector.h" 6 | #else 7 | struct Vector 8 | { 9 | Vector() 10 | : x( 0.f ) 11 | , y( 0.f ) 12 | , z( 0.f ) 13 | {} 14 | 15 | Vector( const Vector& src ) 16 | : x( src.x ) 17 | , y( src.y ) 18 | , z( src.z ) 19 | {} 20 | 21 | Vector& operator=( const Vector& src ) 22 | { 23 | x = src.x; 24 | y = src.y; 25 | z = src.z; 26 | return *this; 27 | } 28 | 29 | float x, y, z; 30 | }; 31 | 32 | using QAngle = Vector; 33 | #endif 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /GmodLUA/GarrysMod/Lua/Types.h: -------------------------------------------------------------------------------- 1 | #ifndef GARRYSMOD_LUA_TYPES_H 2 | #define GARRYSMOD_LUA_TYPES_H 3 | 4 | namespace GarrysMod 5 | { 6 | namespace Lua 7 | { 8 | namespace Type 9 | { 10 | enum 11 | { 12 | #ifdef GMOD_ALLOW_DEPRECATED 13 | // Deprecated: Use `None` instead of `Invalid` 14 | Invalid = -1, 15 | #endif 16 | 17 | // Default Lua Types 18 | None = -1, 19 | Nil, 20 | Bool, 21 | LightUserData, 22 | Number, 23 | String, 24 | Table, 25 | Function, 26 | UserData, 27 | Thread, 28 | 29 | // GMod Types 30 | Entity, 31 | Vector, 32 | Angle, 33 | PhysObj, 34 | Save, 35 | Restore, 36 | DamageInfo, 37 | EffectData, 38 | MoveData, 39 | RecipientFilter, 40 | UserCmd, 41 | ScriptedVehicle, 42 | Material, 43 | Panel, 44 | Particle, 45 | ParticleEmitter, 46 | Texture, 47 | UserMsg, 48 | ConVar, 49 | IMesh, 50 | Matrix, 51 | Sound, 52 | PixelVisHandle, 53 | DLight, 54 | Video, 55 | File, 56 | Locomotion, 57 | Path, 58 | NavArea, 59 | SoundHandle, 60 | NavLadder, 61 | ParticleSystem, 62 | ProjectedTexture, 63 | PhysCollide, 64 | SurfaceInfo, 65 | 66 | Type_Count 67 | }; 68 | 69 | #if ( defined( GMOD ) || defined( GMOD_ALLOW_DEPRECATED ) ) 70 | // You should use ILuaBase::GetTypeName instead of directly accessing this array 71 | static const char* Name[] = 72 | { 73 | "nil", 74 | "bool", 75 | "lightuserdata", 76 | "number", 77 | "string", 78 | "table", 79 | "function", 80 | "userdata", 81 | "thread", 82 | "entity", 83 | "vector", 84 | "angle", 85 | "physobj", 86 | "save", 87 | "restore", 88 | "damageinfo", 89 | "effectdata", 90 | "movedata", 91 | "recipientfilter", 92 | "usercmd", 93 | "vehicle", 94 | "material", 95 | "panel", 96 | "particle", 97 | "particleemitter", 98 | "texture", 99 | "usermsg", 100 | "convar", 101 | "mesh", 102 | "matrix", 103 | "sound", 104 | "pixelvishandle", 105 | "dlight", 106 | "video", 107 | "file", 108 | "locomotion", 109 | "path", 110 | "navarea", 111 | "soundhandle", 112 | "navladder", 113 | "particlesystem", 114 | "projectedtexture", 115 | "physcollide", 116 | "surfaceinfo", 117 | nullptr 118 | }; 119 | #endif 120 | } 121 | } 122 | } 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /GmodLUA/GarrysMod/Lua/UserData.h: -------------------------------------------------------------------------------- 1 | #ifndef GARRYSMOD_LUA_USERDATA_H 2 | #define GARRYSMOD_LUA_USERDATA_H 3 | 4 | #ifdef GMOD_ALLOW_DEPRECATED 5 | namespace GarrysMod 6 | { 7 | namespace Lua 8 | { 9 | struct UserData 10 | { 11 | void* data; 12 | unsigned char type; 13 | }; 14 | } 15 | } 16 | #endif 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /IntegrationTest/README.md: -------------------------------------------------------------------------------- 1 | # MySQLOO Lua Integration Tests 2 | 3 | This folder contains integration tests for MySQLOO. 4 | 5 | ## Running 6 | 7 | - Place this folder into the server's addons folder. 8 | - Adjust the database settings in lua/autorun/server/init.lua 9 | - ensure that the database used is **empty** as it will be filled with test data 10 | - run `mysqloo_start_tests` in the server console 11 | 12 | Each of the tests outputs its result to the console and at the end there is an overview of all tests printed. -------------------------------------------------------------------------------- /IntegrationTest/lua/autorun/server/init.lua: -------------------------------------------------------------------------------- 1 | DatabaseSettings = { 2 | Host = "localhost", 3 | Port = 3306, 4 | Username = "root", 5 | Password = "", 6 | Database = "test" 7 | } 8 | 9 | print("Loading MySQLOO Testing Framework") 10 | include("mysqloo/init.lua") -------------------------------------------------------------------------------- /IntegrationTest/lua/mysqloo/init.lua: -------------------------------------------------------------------------------- 1 | include("testframework.lua") 2 | include("setup.lua") 3 | 4 | 5 | local files = file.Find("mysqloo/tests/*.lua", "LUA") 6 | for _,f in pairs(files) do 7 | include("tests/" .. f) 8 | end -------------------------------------------------------------------------------- /IntegrationTest/lua/mysqloo/setup.lua: -------------------------------------------------------------------------------- 1 | require("mysqloo") 2 | 3 | function TestFramework:ConnectToDatabase() 4 | local db = mysqloo.connect(DatabaseSettings.Host, DatabaseSettings.Username, DatabaseSettings.Password, DatabaseSettings.Database, DatabaseSettings.Port) 5 | db:connect() 6 | db:wait() 7 | return db 8 | end 9 | 10 | function TestFramework:RunQuery(db, queryStr) 11 | local query = db:query(queryStr) 12 | query:start() 13 | function query:onError(err) 14 | error(err) 15 | end 16 | query:wait() 17 | return query:getData() 18 | end -------------------------------------------------------------------------------- /IntegrationTest/lua/mysqloo/testframework.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | TestFramework = TestFramework or {} 4 | TestFramework.RegisteredTests = {} 5 | 6 | local TestMT = {} 7 | TestMT.__index = TestMT 8 | 9 | function TestFramework:RegisterTest(name, f) 10 | local tbl = setmetatable({}, {__index = TestMT}) 11 | tbl.TestFunction = f 12 | tbl.Name = name 13 | table.insert(TestFramework.RegisteredTests, tbl) 14 | print("Registered test ", name) 15 | end 16 | 17 | function TestFramework:RunNextTest() 18 | TestFramework.CurrentIndex = (TestFramework.CurrentIndex or 0) + 1 19 | TestFramework.TestTimeout = CurTime() + 3 20 | local test = TestFramework.RegisteredTests[TestFramework.CurrentIndex] 21 | TestFramework.CurrentTest = test 22 | if (!test) then 23 | TestFramework:OnCompleted() 24 | else 25 | test:Run() 26 | end 27 | end 28 | 29 | function TestFramework:CheckTimeout() 30 | if (!TestFramework.CurrentTest) then return end 31 | if (CurTime() > TestFramework.TestTimeout) then 32 | TestFramework.CurrentTest:Fail("TIMEOUT") 33 | end 34 | end 35 | 36 | hook.Add("Think", "TestFrameworkTimeoutCheck", function() 37 | TestFramework:CheckTimeout() 38 | end) 39 | 40 | function TestFramework:ReportResult(success) 41 | TestFramework.TestCount = (TestFramework.TestCount or 0) + 1 42 | if (success) then 43 | TestFramework.SuccessCount = (TestFramework.SuccessCount or 0) + 1 44 | else 45 | TestFramework.FailureCount = (TestFramework.FailureCount or 0) + 1 46 | end 47 | end 48 | 49 | function TestFramework:OnCompleted() 50 | print("[MySQLOO] Tests completed") 51 | MsgC(Color(255, 255, 255), "Completed: ", Color(30, 230, 30), TestFramework.SuccessCount, Color(255, 255, 255), " Failures: ", Color(230, 30, 30), TestFramework.FailureCount, "\n") 52 | 53 | for j = 0, 3 do 54 | timer.Simple(j * 0.5, function() 55 | for i = 1, 100 do 56 | collectgarbage("collect") 57 | end 58 | end) 59 | end 60 | timer.Simple(2, function() 61 | for i = 1, 100 do 62 | collectgarbage("collect") 63 | end 64 | local diffBefore = TestFramework.AllocationCount - TestFramework.DeallocationCount 65 | local diffAfter = mysqloo.allocationCount() - mysqloo.deallocationCount() 66 | if (diffAfter > diffBefore) then 67 | MsgC(Color(255, 255, 255), "Found potential memory leak with ", diffAfter - diffBefore, " new allocations that were not freed\n") 68 | else 69 | MsgC(Color(255, 255, 255), "All allocated objects were freed\n") 70 | end 71 | 72 | 73 | diffBefore = TestFramework.ReferenceCreatedCount - TestFramework.ReferenceFreedCount 74 | diffAfter = mysqloo.referenceCreatedCount() - mysqloo.referenceFreedCount() 75 | if (diffAfter > diffBefore) then 76 | MsgC(Color(255, 255, 255), "Found potential memory leak with ", diffAfter - diffBefore, " new references created that were not freed\n") 77 | else 78 | MsgC(Color(255, 255, 255), "All created references were freed\n") 79 | end 80 | 81 | 82 | MsgC(Color(255, 255, 255), "Lua Heap Before: ", TestFramework.LuaMemory, " After: ", collectgarbage("count"), "\n") 83 | end) 84 | end 85 | 86 | function TestFramework:Start() 87 | for i = 1, 5 do 88 | collectgarbage("collect") 89 | end 90 | TestFramework.CurrentIndex = 0 91 | TestFramework.SuccessCount = 0 92 | TestFramework.FailureCount = 0 93 | TestFramework.ReferenceCreatedCount = mysqloo.referenceCreatedCount() 94 | TestFramework.ReferenceFreedCount = mysqloo.referenceFreedCount() 95 | TestFramework.AllocationCount = mysqloo.allocationCount() 96 | TestFramework.DeallocationCount = mysqloo.deallocationCount() 97 | TestFramework.LuaMemory = collectgarbage("count") 98 | TestFramework:RunNextTest() 99 | end 100 | 101 | function TestMT:Fail(reason) 102 | if (self.Completed) then return end 103 | self.Completed = true 104 | MsgC(Color(230, 30, 30), "FAILED\n") 105 | MsgC(Color(230, 30, 30), "Error: ", reason, "\n") 106 | TestFramework:ReportResult(false) 107 | TestFramework:RunNextTest() 108 | end 109 | 110 | function TestMT:Complete() 111 | if (self.Completed) then return end 112 | self.Completed = true 113 | MsgC(Color(30, 230, 30), "PASSED\n") 114 | TestFramework:ReportResult(true) 115 | TestFramework:RunNextTest() 116 | end 117 | 118 | function TestMT:Run() 119 | MsgC("Test: ", self.Name, " ") 120 | self.Completed = false 121 | local status, err = pcall(function() 122 | self.TestFunction(self) 123 | end) 124 | if (!status) then 125 | self:Fail(err) 126 | end 127 | end 128 | 129 | function TestMT:shouldBeNil(a) 130 | if (a != nil) then 131 | self:Fail(tostring(a) .. " was expected to be nil, but was not nil") 132 | error("Assertion failed") 133 | end 134 | end 135 | 136 | function TestMT:shouldBeGreaterThan(a, num) 137 | if (num >= a) then 138 | self:Fail(tostring(a) .. " was expected to be greater than " .. tostring(num)) 139 | error("Assertion failed") 140 | end 141 | end 142 | 143 | function TestMT:shouldNotBeNil(a) 144 | if (a == nil) then 145 | self:Fail(tostring(a) .. " was expected to not be nil, but was nil") 146 | error("Assertion failed") 147 | end 148 | end 149 | 150 | function TestMT:shouldNotBeEqual(a, b) 151 | if (a == b) then 152 | self:Fail(tostring(a) .. " was equal to " .. tostring(b)) 153 | error("Assertion failed") 154 | end 155 | end 156 | 157 | function TestMT:shouldBeEqual(a, b) 158 | if (a != b) then 159 | self:Fail(tostring(a) .. " was not equal to " .. tostring(b)) 160 | error("Assertion failed") 161 | end 162 | end 163 | 164 | function TestMT:shouldHaveLength(tbl, exactLength) 165 | if (#tbl != exactLength) then 166 | self:Fail("Length of " .. tostring(tbl) .. " was not equal to " .. exactLength) 167 | error("Assertion failed") 168 | end 169 | end 170 | 171 | concommand.Add("mysqloo_start_tests", function(ply) 172 | if (IsValid(ply)) then return end 173 | print("Starting MySQLOO Tests") 174 | if (#player.GetBots() == 0) then 175 | RunConsoleCommand("bot") 176 | end 177 | timer.Simple(0.1, function() 178 | TestFramework:Start() 179 | end) 180 | end) -------------------------------------------------------------------------------- /IntegrationTest/lua/mysqloo/tests/basic_mysql_test.lua: -------------------------------------------------------------------------------- 1 | TestFramework:RegisterTest("[Basic] selecting 1 should return 1", function(test) 2 | local db = TestFramework:ConnectToDatabase() 3 | local query = db:query("SELECT 3 as test") 4 | function query:onSuccess(data) 5 | test:shouldHaveLength(data, 1) 6 | test:shouldBeEqual(data[1]["test"], 3) 7 | test:Complete() 8 | end 9 | function query:onError(err) 10 | test:Fail(err) 11 | end 12 | query:start() 13 | query:wait() 14 | end) -------------------------------------------------------------------------------- /IntegrationTest/lua/mysqloo/tests/database_tests.lua: -------------------------------------------------------------------------------- 1 | TestFramework:RegisterTest("[Database] should return info correctly", function(test) 2 | local db = TestFramework:ConnectToDatabase() 3 | local serverInfo = db:serverInfo() 4 | local hostInfo = db:hostInfo() 5 | local serverVersion = db:serverVersion() 6 | test:shouldBeGreaterThan(#serverInfo, 0) 7 | test:shouldBeGreaterThan(#hostInfo, 0) 8 | test:shouldBeGreaterThan(serverVersion, 0) 9 | test:Complete() 10 | end) 11 | 12 | TestFramework:RegisterTest("[Database] queue size should return correct size", function(test) 13 | local db = TestFramework:ConnectToDatabase() 14 | test:shouldBeEqual(db:queueSize(), 0) 15 | local query1 = db:query("SELECT SLEEP(0.5)") 16 | local query2 = db:query("SELECT SLEEP(0.5)") 17 | local query3 = db:query("SELECT SLEEP(0.5)") 18 | function query1:onSuccess() 19 | test:shouldBeEqual(db:queueSize(), 1) //1 because the next was already started at this point 20 | end 21 | function query2:onSuccess() 22 | test:shouldBeEqual(db:queueSize(), 0) //0 because the next was already started at this point 23 | test:Complete() 24 | end 25 | function query3:onSuccess() 26 | test:shouldBeEqual(db:queueSize(), 0) 27 | test:Complete() 28 | end 29 | query1:start() 30 | query2:start() 31 | query3:start() 32 | test:shouldBeGreaterThan(db:queueSize(), 1) 33 | end) 34 | 35 | TestFramework:RegisterTest("[Database] should abort all queries correctly", function(test) 36 | local db = TestFramework:ConnectToDatabase() 37 | local query1 = db:query("SELECT SLEEP(0.5)") 38 | local query2 = db:query("SELECT SLEEP(0.5)") 39 | local query3 = db:query("SELECT SLEEP(0.5)") 40 | local abortedCount = 0 41 | local f = function(q) 42 | abortedCount = abortedCount + 1 43 | if (abortedCount == 2) then 44 | test:Complete() 45 | end 46 | end 47 | query1.onAborted = f 48 | query2.onAborted = f 49 | query3.onAborted = f 50 | query1:start() 51 | query2:start() 52 | query3:start() 53 | local amountAborted = db:abortAllQueries() 54 | test:shouldBeGreaterThan(amountAborted, 1) //The one already processing might not be aborted 55 | end) 56 | 57 | TestFramework:RegisterTest("[Database] should escape a string correctly", function(test) 58 | local db = TestFramework:ConnectToDatabase() 59 | local escapedStr = db:escape("t'a") 60 | test:shouldBeEqual(escapedStr, "t\\'a") 61 | test:Complete() 62 | end) 63 | 64 | TestFramework:RegisterTest("[Database] should return correct status", function(test) 65 | local db = mysqloo.connect(DatabaseSettings.Host, DatabaseSettings.Username, DatabaseSettings.Password, DatabaseSettings.Database, DatabaseSettings.Port) 66 | test:shouldBeEqual(db:status(), mysqloo.DATABASE_NOT_CONNECTED) 67 | db:connect() 68 | function db:onConnected() 69 | test:shouldBeEqual(db:status(), mysqloo.DATABASE_CONNECTED) 70 | 71 | db:disconnect(true) 72 | test:shouldBeEqual(db:status(), mysqloo.DATABASE_NOT_CONNECTED) 73 | test:Complete() 74 | end 75 | end) 76 | 77 | TestFramework:RegisterTest("[Database] should call onConnected callback correctly", function(test) 78 | local db = mysqloo.connect(DatabaseSettings.Host, DatabaseSettings.Username, DatabaseSettings.Password, DatabaseSettings.Database, DatabaseSettings.Port) 79 | function db:onConnected() 80 | test:Complete() 81 | end 82 | db:connect() 83 | end) 84 | 85 | TestFramework:RegisterTest("[Database] should call onConnectionFailed callback correctly", function(test) 86 | local db = mysqloo.connect(DatabaseSettings.Host, DatabaseSettings.Username, "incorrect_password", DatabaseSettings.Database, DatabaseSettings.Port) 87 | function db:onConnectionFailed(err) 88 | test:shouldBeGreaterThan(#err, 0) 89 | test:Complete() 90 | end 91 | db:connect() 92 | end) 93 | 94 | TestFramework:RegisterTest("[Database] should ping correctly", function(test) 95 | local db = TestFramework:ConnectToDatabase() 96 | test:shouldBeEqual(db:ping(), true) 97 | test:Complete() 98 | end) 99 | 100 | TestFramework:RegisterTest("[Database] allow setting only valid character set", function(test) 101 | local db = TestFramework:ConnectToDatabase() 102 | test:shouldBeEqual(db:setCharacterSet("utf8"), true) 103 | test:shouldBeEqual(db:setCharacterSet("ascii"), true) 104 | test:shouldBeEqual(db:setCharacterSet("invalid_name"), false) 105 | test:Complete() 106 | end) 107 | 108 | TestFramework:RegisterTest("[Database] wait for queries when disconnecting", function(test) 109 | local db = TestFramework:ConnectToDatabase() 110 | local qu = db:query("SELECT SLEEP(1)") 111 | local wasCalled = false 112 | function qu:onSuccess() 113 | wasCalled = true 114 | end 115 | qu:start() 116 | db:disconnect(true) 117 | test:shouldBeEqual(wasCalled, true) 118 | test:Complete() 119 | end) -------------------------------------------------------------------------------- /IntegrationTest/lua/mysqloo/tests/prepared_query_tests.lua: -------------------------------------------------------------------------------- 1 | TestFramework:RegisterTest("[Prepared Query] have correct set... functions", function(test) 2 | local db = TestFramework:ConnectToDatabase() 3 | local qu = db:prepare("SELECT ? as a, ? as b, ? as c, ? as d, ? as e") 4 | qu:setNumber(1, 5.5) 5 | qu:setString(2, "test") 6 | qu:setBoolean(3, true) 7 | qu:setBoolean(4, false) 8 | qu:setString(5, "b") 9 | qu:setNull(5) 10 | qu:start() 11 | qu:wait() 12 | local data = qu:getData() 13 | test:shouldHaveLength(data, 1) 14 | test:shouldBeEqual(data[1].a, 5.5) 15 | test:shouldBeEqual(data[1].b, "test") 16 | test:shouldBeEqual(data[1].c, 1) 17 | test:shouldBeEqual(data[1].d, 0) 18 | test:shouldBeNil(data[1].e) 19 | test:Complete() 20 | end) 21 | 22 | TestFramework:RegisterTest("[Prepared Query] allow batching parameters correctly", function(test) 23 | local db = TestFramework:ConnectToDatabase() 24 | local qu = db:prepare("SELECT ? as a") 25 | qu:setNumber(1, 1) 26 | qu:putNewParameters() 27 | qu:setNumber(1, 2) 28 | qu:putNewParameters() 29 | qu:setNumber(1, 3) 30 | qu:start() 31 | qu:wait() 32 | test:shouldBeEqual(qu:hasMoreResults(), true) 33 | test:shouldBeEqual(qu:getData()[1].a, 1) 34 | qu:getNextResults() 35 | test:shouldBeEqual(qu:hasMoreResults(), true) 36 | test:shouldBeEqual(qu:getData()[1].a, 2) 37 | qu:getNextResults() 38 | test:shouldBeEqual(qu:hasMoreResults(), true) 39 | test:shouldBeEqual(qu:getData()[1].a, 3) 40 | qu:getNextResults() 41 | test:shouldBeEqual(qu:hasMoreResults(), false) 42 | test:Complete() 43 | end) 44 | 45 | TestFramework:RegisterTest("[Prepared Query] clear parameters correctly", function(test) 46 | local db = TestFramework:ConnectToDatabase() 47 | local qu = db:prepare("SELECT ? as a, ? as b") 48 | qu:setNumber(1, 1) 49 | qu:setString(2, "test") 50 | qu:clearParameters() 51 | qu:start() 52 | qu:wait() 53 | local data = qu:getData() 54 | test:shouldHaveLength(data, 1) 55 | test:shouldBeNil(data[1].a) 56 | test:shouldBeNil(data[1].b) 57 | test:Complete() 58 | end) 59 | 60 | TestFramework:RegisterTest("[Prepared Query] last insert should return correct values", function(test) 61 | local db = TestFramework:ConnectToDatabase() 62 | TestFramework:RunQuery(db, [[DROP TABLE IF EXISTS last_insert_test]]) 63 | TestFramework:RunQuery(db, [[CREATE TABLE last_insert_test(id INT AUTO_INCREMENT PRIMARY KEY)]]) 64 | 65 | local qu = db:prepare("INSERT INTO last_insert_test VALUES()") 66 | function qu:onSuccess() 67 | test:shouldBeEqual(qu:lastInsert(), 1) 68 | end 69 | qu:start() 70 | local qu2 = db:prepare("INSERT INTO last_insert_test VALUES()") 71 | function qu2:onSuccess() 72 | test:shouldBeEqual(qu2:lastInsert(), 2) 73 | end 74 | qu2:start() 75 | local qu3 = db:prepare("INSERT INTO last_insert_test VALUES()") 76 | function qu3:onSuccess() 77 | test:shouldBeEqual(qu3:lastInsert(), 3) 78 | function qu3:onSuccess() 79 | test:shouldBeEqual(qu3:lastInsert(), 4) 80 | qu3.onSuccess = nil 81 | qu3:start() 82 | qu3:wait() 83 | test:shouldBeEqual(qu3:lastInsert(), 5) 84 | test:Complete() 85 | end 86 | qu3:start() 87 | end 88 | qu3:start() 89 | end) 90 | 91 | TestFramework:RegisterTest("[Prepared Query] affected rows should return correct values", function(test) 92 | local db = TestFramework:ConnectToDatabase() 93 | TestFramework:RunQuery(db, [[DROP TABLE IF EXISTS affected_rows_test]]) 94 | TestFramework:RunQuery(db, [[CREATE TABLE affected_rows_test(id INT AUTO_INCREMENT PRIMARY KEY)]]) 95 | TestFramework:RunQuery(db, "INSERT INTO affected_rows_test VALUES()") 96 | TestFramework:RunQuery(db, "INSERT INTO affected_rows_test VALUES()") 97 | TestFramework:RunQuery(db, "INSERT INTO affected_rows_test VALUES()") 98 | TestFramework:RunQuery(db, "INSERT INTO affected_rows_test VALUES()") 99 | local qu = db:prepare("DELETE FROM affected_rows_test WHERE id = ?") 100 | qu:setNumber(1, 4) 101 | qu:start() 102 | qu:wait() 103 | test:shouldBeEqual(qu:affectedRows(), 1) 104 | qu:start() 105 | local qu2 = db:prepare("DELETE FROM affected_rows_test") 106 | function qu2:onSuccess() 107 | test:shouldBeEqual(qu2:affectedRows(), 3) 108 | function qu2:onSuccess() 109 | test:shouldBeEqual(qu2:affectedRows(), 0) 110 | test:Complete() 111 | end 112 | qu2:start() 113 | end 114 | qu2:start() 115 | end) 116 | 117 | TestFramework:RegisterTest("[Prepared Query] isRunning should return the correct value", function(test) 118 | local db = TestFramework:ConnectToDatabase() 119 | local qu = db:prepare("SELECT SLEEP(0.1)") 120 | test:shouldBeEqual(qu:isRunning(), false) 121 | function qu:onSuccess() 122 | test:shouldBeEqual(qu:isRunning(), true) 123 | timer.Simple(0.1, function() 124 | test:shouldBeEqual(qu:isRunning(), false) 125 | test:Complete() 126 | end) 127 | end 128 | qu:start() 129 | test:shouldBeEqual(qu:isRunning(), true) 130 | end) 131 | 132 | TestFramework:RegisterTest("[Prepared Query] should return correct data", function(test) 133 | local db = TestFramework:ConnectToDatabase() 134 | TestFramework:RunQuery(db, [[DROP TABLE IF EXISTS data_test]]) 135 | TestFramework:RunQuery(db, [[CREATE TABLE data_test(id INT PRIMARY KEY, str VARCHAR(10), big BIGINT, bin BLOB, num DOUBLE, bool BIT)]]) 136 | TestFramework:RunQuery(db, [[INSERT INTO data_test VALUES(1, '2', 8589934588, X'470047', 3.3, TRUE)]]) 137 | TestFramework:RunQuery(db, [[INSERT INTO data_test VALUES(2, null, -8589930588, X'00AB', 10.1, FALSE)]]) 138 | 139 | local qu = db:prepare("SELECT * FROM data_test") 140 | function qu:onSuccess(data) 141 | test:shouldBeEqual(data, qu:getData()) //Check that it is cached correctly 142 | test:shouldBeEqual(#data, 2) 143 | local row1 = data[1] 144 | test:shouldBeEqual(row1.id, 1) 145 | test:shouldBeEqual(row1.str, "2") 146 | test:shouldBeEqual(row1.big, 8589934588) 147 | test:shouldBeEqual(row1.bin, string.char(0x47,0x00,0x47)) 148 | test:shouldBeEqual(row1.num, 3.3) 149 | test:shouldBeEqual(row1.bool, 1) 150 | local row2 = data[2] 151 | test:shouldBeEqual(row2.id, 2) 152 | test:shouldBeNil(row2.str) 153 | test:shouldBeEqual(row2.big, -8589930588) 154 | test:shouldBeEqual(row2.bin, string.char(0x00,0xAB)) 155 | test:shouldBeEqual(row2.num, 10.1) 156 | test:shouldBeEqual(row2.bool, 0) 157 | test:Complete() 158 | end 159 | function qu:onError(err) 160 | print(err) 161 | end 162 | qu:start() 163 | end) 164 | 165 | TestFramework:RegisterTest("[Prepared Query] should return correct data if numeric is enabled", function(test) 166 | local db = TestFramework:ConnectToDatabase() 167 | local qu = db:prepare("SELECT 1, 2, 4") 168 | qu:setOption(mysqloo.OPTION_NUMERIC_FIELDS) 169 | function qu:onSuccess(data) 170 | test:shouldBeEqual(#data, 1) 171 | local row = data[1] 172 | test:shouldBeEqual(row[1], 1) 173 | test:shouldBeEqual(row[2], 2) 174 | test:shouldBeEqual(row[3], 4) 175 | test:shouldBeNil(row[4]) 176 | test:Complete() 177 | end 178 | qu:start() 179 | end) 180 | 181 | TestFramework:RegisterTest("[Prepared Query] should return correct error", function(test) 182 | local db = TestFramework:ConnectToDatabase() 183 | local qu = db:prepare("SEsdg") 184 | function qu:onError(err, sql) 185 | test:shouldBeEqual(qu:error(), err) 186 | test:shouldBeGreaterThan(#qu:error(), 0) 187 | test:shouldBeEqual(sql, "SEsdg") 188 | test:Complete() 189 | end 190 | qu:start() 191 | end) 192 | 193 | TestFramework:RegisterTest("[Prepared Query] should return correct error", function(test) 194 | local db = TestFramework:ConnectToDatabase() 195 | local qu = db:prepare("SEsdg") 196 | function qu:onError(err) 197 | test:shouldBeEqual(qu:error(), err) 198 | test:shouldBeGreaterThan(#qu:error(), 0) 199 | timer.Simple(0.1, function() 200 | test:shouldBeEqual(qu:error(), err) 201 | test:Complete() 202 | end) 203 | end 204 | qu:start() 205 | end) 206 | 207 | TestFramework:RegisterTest("[Prepared Query] should return correct error if waiting", function(test) 208 | local db = TestFramework:ConnectToDatabase() 209 | local qu = db:prepare("SEsdg") 210 | qu:start() 211 | qu:wait() 212 | test:shouldBeGreaterThan(#qu:error(), 0) 213 | test:Complete() 214 | end) 215 | 216 | TestFramework:RegisterTest("[Prepared Query] prevent multiple statements if disabled", function(test) 217 | local db = mysqloo.connect(DatabaseSettings.Host, DatabaseSettings.Username, DatabaseSettings.Password, DatabaseSettings.Database, DatabaseSettings.Port) 218 | db:setMultiStatements(false) 219 | db:connect() 220 | db:wait() 221 | local qu = db:prepare("SELECT 1; SELECT 2;") 222 | function qu:onError() 223 | test:Complete() 224 | end 225 | function qu:onSuccess() 226 | test:Fail("Query should have failed but did not") 227 | end 228 | qu:start() 229 | qu:wait() 230 | end) 231 | 232 | TestFramework:RegisterTest("[Prepared Query] prevent multiple statements even if enabled", function(test) 233 | local db = TestFramework:ConnectToDatabase() 234 | local qu = db:prepare("SELECT 1 as a; SELECT 2 as b;") 235 | function qu:onError() 236 | test:Complete() 237 | end 238 | qu:start() 239 | end) 240 | 241 | TestFramework:RegisterTest("[Prepared Query] call onData correctly", function(test) 242 | local db = TestFramework:ConnectToDatabase() 243 | local qu = db:prepare("SELECT ? as a UNION ALL SELECT ?") 244 | qu:setNumber(1, 1) 245 | qu:setNumber(2, 2) 246 | local callCount = 0 247 | local sum = 0 248 | function qu:onSuccess(data) //onData is called before onSuccess 249 | test:shouldHaveLength(data, 2) 250 | test:shouldBeEqual(callCount, 2) 251 | test:shouldBeEqual(sum, 3) 252 | test:Complete() 253 | end 254 | function qu:onData(row) 255 | callCount = callCount + 1 256 | sum = sum + row.a 257 | end 258 | qu:start() 259 | end) 260 | 261 | TestFramework:RegisterTest("[Prepared Query] abort query correctly", function(test) 262 | local db = TestFramework:ConnectToDatabase() 263 | local qu = db:prepare("SELECT SLEEP(1)") //This should block for a bit 264 | qu:start() 265 | local qu2 = db:prepare("SELECT 1") 266 | qu2:start() 267 | function qu2:onAborted() 268 | test:Complete() 269 | end 270 | test:shouldBeEqual(qu2:abort(), true) 271 | test:shouldBeEqual(qu:abort(), false) 272 | end) 273 | 274 | TestFramework:RegisterTest("[Prepared Query] Work with stored procedure correctly", function(test) 275 | local db = TestFramework:ConnectToDatabase() 276 | //db:setMultiStatements(false) 277 | TestFramework:RunQuery(db, "DROP PROCEDURE IF EXISTS test_procedure") 278 | TestFramework:RunQuery(db, [[ 279 | CREATE PROCEDURE test_procedure (IN param INT) 280 | BEGIN 281 | SELECT param as a; 282 | SELECT 999 as b; 283 | END 284 | ]]) 285 | local qu = db:prepare("CALL test_procedure(?)") 286 | qu:setNumber(1, 5) 287 | qu:start() 288 | qu:wait() 289 | test:shouldBeEqual(qu:hasMoreResults(), true) 290 | local first = qu:getData() 291 | test:shouldBeEqual(first[1].a, 5) 292 | qu:getNextResults() 293 | test:shouldBeEqual(qu:hasMoreResults(), true) 294 | local second = qu:getData() 295 | test:shouldBeEqual(second[1].b, 999) 296 | qu:getNextResults() 297 | test:shouldBeEqual(qu:hasMoreResults(), true) //For some reason, stored procedures add extra result sets 298 | qu:getNextResults() 299 | test:shouldBeEqual(qu:hasMoreResults(), false) 300 | test:Complete() 301 | end) -------------------------------------------------------------------------------- /IntegrationTest/lua/mysqloo/tests/query_tests.lua: -------------------------------------------------------------------------------- 1 | TestFramework:RegisterTest("[Query] last insert should return correct values", function(test) 2 | local db = TestFramework:ConnectToDatabase() 3 | TestFramework:RunQuery(db, [[DROP TABLE IF EXISTS last_insert_test]]) 4 | TestFramework:RunQuery(db, [[CREATE TABLE last_insert_test(id INT AUTO_INCREMENT PRIMARY KEY)]]) 5 | 6 | local qu = db:query("INSERT INTO last_insert_test VALUES()") 7 | function qu:onSuccess() 8 | test:shouldBeEqual(qu:lastInsert(), 1) 9 | end 10 | qu:start() 11 | local qu2 = db:query("INSERT INTO last_insert_test VALUES()") 12 | function qu2:onSuccess() 13 | test:shouldBeEqual(qu2:lastInsert(), 2) 14 | end 15 | qu2:start() 16 | local qu3 = db:query("INSERT INTO last_insert_test VALUES()") 17 | function qu3:onSuccess() 18 | test:shouldBeEqual(qu3:lastInsert(), 3) 19 | function qu3:onSuccess() 20 | test:shouldBeEqual(qu3:lastInsert(), 4) 21 | qu3.onSuccess = nil 22 | qu3:start() 23 | qu3:wait() 24 | test:shouldBeEqual(qu3:lastInsert(), 5) 25 | test:Complete() 26 | end 27 | qu3:start() 28 | end 29 | qu3:start() 30 | end) 31 | 32 | TestFramework:RegisterTest("[Query] affected rows should return correct values", function(test) 33 | local db = TestFramework:ConnectToDatabase() 34 | TestFramework:RunQuery(db, [[DROP TABLE IF EXISTS affected_rows_test]]) 35 | TestFramework:RunQuery(db, [[CREATE TABLE affected_rows_test(id INT AUTO_INCREMENT PRIMARY KEY)]]) 36 | TestFramework:RunQuery(db, "INSERT INTO affected_rows_test VALUES()") 37 | TestFramework:RunQuery(db, "INSERT INTO affected_rows_test VALUES()") 38 | TestFramework:RunQuery(db, "INSERT INTO affected_rows_test VALUES()") 39 | TestFramework:RunQuery(db, "INSERT INTO affected_rows_test VALUES()") 40 | local qu = db:query("DELETE FROM affected_rows_test WHERE id = 4") 41 | qu:start() 42 | qu:wait() 43 | test:shouldBeEqual(qu:affectedRows(), 1) 44 | qu:start() 45 | local qu2 = db:query("DELETE FROM affected_rows_test") 46 | function qu2:onSuccess() 47 | test:shouldBeEqual(qu2:affectedRows(), 3) 48 | function qu2:onSuccess() 49 | test:shouldBeEqual(qu2:affectedRows(), 0) 50 | test:Complete() 51 | end 52 | qu2:start() 53 | end 54 | qu2:start() 55 | end) 56 | 57 | TestFramework:RegisterTest("[Query] isRunning should return the correct value", function(test) 58 | local db = TestFramework:ConnectToDatabase() 59 | local qu = db:query("SELECT SLEEP(0.1)") 60 | test:shouldBeEqual(qu:isRunning(), false) 61 | function qu:onSuccess() 62 | test:shouldBeEqual(qu:isRunning(), true) 63 | timer.Simple(0.1, function() 64 | test:shouldBeEqual(qu:isRunning(), false) 65 | test:Complete() 66 | end) 67 | end 68 | qu:start() 69 | test:shouldBeEqual(qu:isRunning(), true) 70 | end) 71 | 72 | TestFramework:RegisterTest("[Query] should return correct data", function(test) 73 | local db = TestFramework:ConnectToDatabase() 74 | TestFramework:RunQuery(db, [[DROP TABLE IF EXISTS data_test]]) 75 | TestFramework:RunQuery(db, [[CREATE TABLE data_test(id INT PRIMARY KEY, str VARCHAR(10), big BIGINT, bin BLOB, num DOUBLE, bool BIT)]]) 76 | TestFramework:RunQuery(db, [[INSERT INTO data_test VALUES(1, '2', 8589934588, X'470047', 3.3, TRUE)]]) 77 | TestFramework:RunQuery(db, [[INSERT INTO data_test VALUES(2, null, -8589930588, X'00AB', 10.1, FALSE)]]) 78 | 79 | local qu = db:query("SELECT * FROM data_test") 80 | function qu:onSuccess(data) 81 | test:shouldBeEqual(data, qu:getData()) //Check that it is cached correctly 82 | test:shouldBeEqual(#data, 2) 83 | local row1 = data[1] 84 | test:shouldBeEqual(row1.id, 1) 85 | test:shouldBeEqual(row1.str, "2") 86 | test:shouldBeEqual(row1.big, 8589934588) 87 | test:shouldBeEqual(row1.bin, string.char(0x47,0x00,0x47)) 88 | test:shouldBeEqual(row1.num, 3.3) 89 | test:shouldBeEqual(row1.bool, 1) 90 | local row2 = data[2] 91 | test:shouldBeEqual(row2.id, 2) 92 | test:shouldBeNil(row2.str) 93 | test:shouldBeEqual(row2.big, -8589930588) 94 | test:shouldBeEqual(row2.bin, string.char(0x00,0xAB)) 95 | test:shouldBeEqual(row2.num, 10.1) 96 | test:shouldBeEqual(row2.bool, 0) 97 | test:Complete() 98 | end 99 | function qu:onError(err) 100 | print(err) 101 | end 102 | qu:start() 103 | end) 104 | 105 | TestFramework:RegisterTest("[Query] should return correct data if numeric is enabled", function(test) 106 | local db = TestFramework:ConnectToDatabase() 107 | local qu = db:query("SELECT 1, 2, 4") 108 | qu:setOption(mysqloo.OPTION_NUMERIC_FIELDS) 109 | function qu:onSuccess(data) 110 | test:shouldBeEqual(#data, 1) 111 | local row = data[1] 112 | test:shouldBeEqual(row[1], 1) 113 | test:shouldBeEqual(row[2], 2) 114 | test:shouldBeEqual(row[3], 4) 115 | test:shouldBeNil(row[4]) 116 | test:Complete() 117 | end 118 | qu:start() 119 | end) 120 | 121 | TestFramework:RegisterTest("[Query] should return correct error", function(test) 122 | local db = TestFramework:ConnectToDatabase() 123 | local qu = db:query("SEsdg") 124 | function qu:onError(err, sql) 125 | test:shouldBeEqual(qu:error(), err) 126 | test:shouldBeEqual(sql, "SEsdg") 127 | test:shouldBeGreaterThan(#qu:error(), 0) 128 | test:Complete() 129 | end 130 | qu:start() 131 | end) 132 | 133 | TestFramework:RegisterTest("[Query] should return correct error", function(test) 134 | local db = TestFramework:ConnectToDatabase() 135 | local qu = db:query("SEsdg") 136 | function qu:onError(err) 137 | test:shouldBeEqual(qu:error(), err) 138 | test:shouldBeGreaterThan(#qu:error(), 0) 139 | timer.Simple(0.1, function() 140 | test:shouldBeEqual(qu:error(), err) 141 | test:Complete() 142 | end) 143 | end 144 | qu:start() 145 | end) 146 | 147 | TestFramework:RegisterTest("[Query] should return correct error if waiting", function(test) 148 | local db = TestFramework:ConnectToDatabase() 149 | local qu = db:query("SEsdg") 150 | qu:start() 151 | qu:wait() 152 | test:shouldBeGreaterThan(#qu:error(), 0) 153 | test:Complete() 154 | end) 155 | 156 | TestFramework:RegisterTest("[Query] prevent multiple statements if disabled", function(test) 157 | local db = mysqloo.connect(DatabaseSettings.Host, DatabaseSettings.Username, DatabaseSettings.Password, DatabaseSettings.Database, DatabaseSettings.Port) 158 | db:setMultiStatements(false) 159 | db:connect() 160 | db:wait() 161 | local qu = db:query("SELECT 1; SELECT 2;") 162 | function qu:onError() 163 | test:Complete() 164 | end 165 | function qu:onSuccess() 166 | test:Fail("Query should have failed but did not") 167 | end 168 | qu:start() 169 | qu:wait() 170 | end) 171 | 172 | TestFramework:RegisterTest("[Query] work correctly with multi statements and multiple results", function(test) 173 | local db = TestFramework:ConnectToDatabase() 174 | local qu = db:query("SELECT 1 as a; SELECT 2 as b;") 175 | qu:start() 176 | qu:wait() 177 | local data = qu:getData() 178 | test:shouldBeEqual(#data, 1) 179 | test:shouldBeEqual(data[1].a, 1) 180 | test:shouldBeEqual(qu:hasMoreResults(), true) 181 | 182 | qu:getNextResults() 183 | local newData = qu:getData() 184 | test:shouldNotBeEqual(newData, data) 185 | test:shouldBeEqual(#newData, 1) 186 | test:shouldBeEqual(newData[1].b, 2) 187 | test:shouldBeEqual(qu:hasMoreResults(), true) 188 | qu:getNextResults() 189 | test:shouldBeEqual(qu:hasMoreResults(), false) 190 | test:Complete() 191 | end) 192 | 193 | TestFramework:RegisterTest("[Query] work correctly with multi statements and multiple results with affectedRows/lastInserts", function(test) 194 | local db = TestFramework:ConnectToDatabase() 195 | TestFramework:RunQuery(db, [[DROP TABLE IF EXISTS last_insert_test]]) 196 | TestFramework:RunQuery(db, [[CREATE TABLE last_insert_test(id INT AUTO_INCREMENT PRIMARY KEY)]]) 197 | local qu = db:query("INSERT INTO last_insert_test VALUES(); INSERT INTO last_insert_test VALUES(); INSERT INTO last_insert_test VALUES()") 198 | qu:start() 199 | qu:wait() 200 | test:shouldBeEqual(qu:lastInsert(), 1) 201 | test:shouldBeEqual(qu:hasMoreResults(), true) 202 | qu:getNextResults() 203 | test:shouldBeEqual(qu:lastInsert(), 2) 204 | test:shouldBeEqual(qu:hasMoreResults(), true) 205 | qu:getNextResults() 206 | test:shouldBeEqual(qu:lastInsert(), 3) 207 | test:shouldBeEqual(qu:hasMoreResults(), true) 208 | qu:getNextResults() 209 | test:shouldBeEqual(qu:hasMoreResults(), false) 210 | 211 | local qu2 = db:query("DELETE FROM last_insert_test WHERE id = 1; DELETE FROM last_insert_test") 212 | qu2:start() 213 | qu2:wait() 214 | test:shouldBeEqual(qu2:affectedRows(), 1) 215 | test:shouldBeEqual(qu2:hasMoreResults(), true) 216 | qu2:getNextResults() 217 | test:shouldBeEqual(qu2:affectedRows(), 2) 218 | test:shouldBeEqual(qu2:hasMoreResults(), true) 219 | qu2:getNextResults() 220 | test:shouldBeEqual(qu2:hasMoreResults(), false) 221 | 222 | test:Complete() 223 | end) 224 | 225 | TestFramework:RegisterTest("[Query] call onData correctly", function(test) 226 | local db = TestFramework:ConnectToDatabase() 227 | local qu = db:query("SELECT 1 as a UNION ALL SELECT 2") 228 | local callCount = 0 229 | local sum = 0 230 | function qu:onSuccess(data) //onData is called before onSuccess 231 | test:shouldHaveLength(data, 2) 232 | test:shouldBeEqual(callCount, 2) 233 | test:shouldBeEqual(sum, 3) 234 | test:Complete() 235 | end 236 | function qu:onData(row) 237 | callCount = callCount + 1 238 | sum = sum + row.a 239 | end 240 | qu:start() 241 | end) 242 | 243 | TestFramework:RegisterTest("[Query] abort query correctly", function(test) 244 | local db = TestFramework:ConnectToDatabase() 245 | local qu = db:query("SELECT SLEEP(1)") //This should block for a bit 246 | qu:start() 247 | local qu2 = db:query("SELECT 1") 248 | qu2:start() 249 | function qu2:onAborted() 250 | test:Complete() 251 | end 252 | test:shouldBeEqual(qu2:abort(), true) 253 | test:shouldBeEqual(qu:abort(), false) 254 | end) 255 | 256 | TestFramework:RegisterTest("[Query] not crash if waiting on query of a failed database", function(test) 257 | local db = mysqloo.connect("127.0.0.1", "root", "test", "test", 33406) 258 | db:connect() 259 | local qu = db:query("SELECT 1") 260 | qu:start() 261 | function qu:onError() 262 | test:Complete() 263 | end 264 | qu:wait() 265 | end) -------------------------------------------------------------------------------- /IntegrationTest/lua/mysqloo/tests/transaction_test.lua: -------------------------------------------------------------------------------- 1 | TestFramework:RegisterTest("[Transaction] should return added queries correctly", function(test) 2 | local db = TestFramework:ConnectToDatabase() 3 | local q1 = db:query("SELECT 1") 4 | local q2 = db:prepare("SELECT ?") 5 | q2:setNumber(2, 2) 6 | local q3 = db:query("SELECT 3") 7 | local transaction = db:createTransaction() 8 | test:shouldHaveLength(transaction:getQueries(), 0) 9 | transaction:addQuery(q1) 10 | transaction:addQuery(q2) 11 | transaction:addQuery(q3) 12 | local queries = transaction:getQueries() 13 | test:shouldHaveLength(transaction:getQueries(), 3) 14 | test:shouldBeEqual(queries[1], q1) 15 | test:shouldBeEqual(queries[2], q2) 16 | test:shouldBeEqual(queries[3], q3) 17 | test:Complete() 18 | end) 19 | 20 | TestFramework:RegisterTest("[Transaction] run transaction with same query correctly", function(test) 21 | local db = TestFramework:ConnectToDatabase() 22 | local transaction = db:createTransaction() 23 | local qu = db:prepare("SELECT ? as a") 24 | qu:setNumber(1, 1) 25 | transaction:addQuery(qu) 26 | qu:setNumber(1, 3) 27 | transaction:addQuery(qu) 28 | function transaction:onSuccess(data) 29 | test:shouldHaveLength(data, 2) 30 | test:shouldBeEqual(data[1][1].a, 1) 31 | test:shouldBeEqual(data[2][1].a, 3) 32 | test:Complete() 33 | end 34 | transaction:start() 35 | transaction:wait() 36 | end) 37 | 38 | TestFramework:RegisterTest("[Transaction] rollback failure correctly", function(test) 39 | local db = TestFramework:ConnectToDatabase() 40 | TestFramework:RunQuery(db, [[DROP TABLE IF EXISTS transaction_test]]) 41 | TestFramework:RunQuery(db, [[CREATE TABLE transaction_test(id INT AUTO_INCREMENT PRIMARY KEY)]]) 42 | local transaction = db:createTransaction() 43 | local qu = db:query("INSERT INTO transaction_test VALUES()") 44 | local qu2 = db:query("gfdgdg") 45 | transaction:addQuery(qu) 46 | transaction:addQuery(qu2) 47 | function transaction:onError() 48 | local qu3 = db:query("SELECT * FROM transaction_test") 49 | qu3:start() 50 | qu3:wait() 51 | test:shouldHaveLength(qu3, 0) 52 | test:Complete() 53 | end 54 | transaction:start() 55 | end) -------------------------------------------------------------------------------- /MySQL/include/errmsg.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB 2 | 2012-2016 SkySQL AB, MariaDB Corporation AB 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not, write to the Free 16 | Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 17 | MA 02111-1301, USA */ 18 | 19 | /* Error messages for mysql clients */ 20 | /* error messages for the demon is in share/language/errmsg.sys */ 21 | #ifndef _errmsg_h_ 22 | #define _errmsg_h_ 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | void init_client_errs(void); 28 | extern const char *client_errors[]; /* Error messages */ 29 | extern const char *mariadb_client_errors[]; /* Error messages */ 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | 35 | 36 | #define CR_MIN_ERROR 2000 /* For easier client code */ 37 | #define CR_MAX_ERROR 2999 38 | #define CER_MIN_ERROR 5000 39 | #define CER_MAX_ERROR 5999 40 | #define CER(X) mariadb_client_errors[(X)-CER_MIN_ERROR] 41 | #define ER(X) client_errors[(X)-CR_MIN_ERROR] 42 | #define CLIENT_ERRMAP 2 /* Errormap used by ma_error() */ 43 | 44 | #define CR_UNKNOWN_ERROR 2000 45 | #define CR_SOCKET_CREATE_ERROR 2001 46 | #define CR_CONNECTION_ERROR 2002 47 | #define CR_CONN_HOST_ERROR 2003 /* never sent to a client, message only */ 48 | #define CR_IPSOCK_ERROR 2004 49 | #define CR_UNKNOWN_HOST 2005 50 | #define CR_SERVER_GONE_ERROR 2006 /* disappeared _between_ queries */ 51 | #define CR_VERSION_ERROR 2007 52 | #define CR_OUT_OF_MEMORY 2008 53 | #define CR_WRONG_HOST_INFO 2009 54 | #define CR_LOCALHOST_CONNECTION 2010 55 | #define CR_TCP_CONNECTION 2011 56 | #define CR_SERVER_HANDSHAKE_ERR 2012 57 | #define CR_SERVER_LOST 2013 /* disappeared _during_ a query */ 58 | #define CR_COMMANDS_OUT_OF_SYNC 2014 59 | #define CR_NAMEDPIPE_CONNECTION 2015 60 | #define CR_NAMEDPIPEWAIT_ERROR 2016 61 | #define CR_NAMEDPIPEOPEN_ERROR 2017 62 | #define CR_NAMEDPIPESETSTATE_ERROR 2018 63 | #define CR_CANT_READ_CHARSET 2019 64 | #define CR_NET_PACKET_TOO_LARGE 2020 65 | #define CR_SSL_CONNECTION_ERROR 2026 66 | #define CR_MALFORMED_PACKET 2027 67 | #define CR_NO_PREPARE_STMT 2030 68 | #define CR_PARAMS_NOT_BOUND 2031 69 | #define CR_INVALID_PARAMETER_NO 2034 70 | #define CR_INVALID_BUFFER_USE 2035 71 | #define CR_UNSUPPORTED_PARAM_TYPE 2036 72 | 73 | #define CR_SHARED_MEMORY_CONNECTION 2037 74 | #define CR_SHARED_MEMORY_CONNECT_ERROR 2038 75 | 76 | #define CR_CONN_UNKNOWN_PROTOCOL 2047 77 | #define CR_SECURE_AUTH 2049 78 | #define CR_NO_DATA 2051 79 | #define CR_NO_STMT_METADATA 2052 80 | #define CR_NOT_IMPLEMENTED 2054 81 | #define CR_SERVER_LOST_EXTENDED 2055 /* never sent to a client, message only */ 82 | #define CR_STMT_CLOSED 2056 83 | #define CR_NEW_STMT_METADATA 2057 84 | #define CR_ALREADY_CONNECTED 2058 85 | #define CR_AUTH_PLUGIN_CANNOT_LOAD 2059 86 | #define CR_DUPLICATE_CONNECTION_ATTR 2060 87 | #define CR_AUTH_PLUGIN_ERR 2061 88 | /* Always last, if you add new error codes please update the 89 | value for CR_MYSQL_LAST_ERROR */ 90 | #define CR_MYSQL_LAST_ERROR CR_AUTH_PLUGIN_ERR 91 | 92 | /* 93 | * MariaDB Connector/C errors: 94 | */ 95 | #define CR_EVENT_CREATE_FAILED 5000 96 | #define CR_BIND_ADDR_FAILED 5001 97 | #define CR_ASYNC_NOT_SUPPORTED 5002 98 | #define CR_FUNCTION_NOT_SUPPORTED 5003 99 | #define CR_FILE_NOT_FOUND 5004 100 | #define CR_FILE_READ 5005 101 | #define CR_BULK_WITHOUT_PARAMETERS 5006 102 | #define CR_INVALID_STMT 5007 103 | #define CR_VERSION_MISMATCH 5008 104 | /* Always last, if you add new error codes please update the 105 | value for CR_MARIADB_LAST_ERROR */ 106 | #define CR_MARIADB_LAST_ERROR CR_VERSION_MISMATCH 107 | #endif 108 | -------------------------------------------------------------------------------- /MySQL/include/ma_io.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2015 MariaDB Corporation AB 2 | 3 | This library is free software; you can redistribute it and/or 4 | modify it under the terms of the GNU Library General Public 5 | License as published by the Free Software Foundation; either 6 | version 2 of the License, or (at your option) any later version. 7 | 8 | This library is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | Library General Public License for more details. 12 | 13 | You should have received a copy of the GNU Library General Public 14 | License along with this library; if not, write to the Free 15 | Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | MA 02111-1301, USA */ 17 | 18 | #ifndef _ma_io_h_ 19 | #define _ma_io_h_ 20 | 21 | 22 | #ifdef HAVE_REMOTEIO 23 | #include 24 | #endif 25 | 26 | enum enum_file_type { 27 | MA_FILE_NONE=0, 28 | MA_FILE_LOCAL=1, 29 | MA_FILE_REMOTE=2 30 | }; 31 | 32 | typedef struct 33 | { 34 | enum enum_file_type type; 35 | void *ptr; 36 | } MA_FILE; 37 | 38 | #ifdef HAVE_REMOTEIO 39 | struct st_rio_methods { 40 | MA_FILE *(*mopen)(const char *url, const char *mode); 41 | int (*mclose)(MA_FILE *ptr); 42 | int (*mfeof)(MA_FILE *file); 43 | size_t (*mread)(void *ptr, size_t size, size_t nmemb, MA_FILE *file); 44 | char * (*mgets)(char *ptr, size_t size, MA_FILE *file); 45 | }; 46 | #endif 47 | 48 | /* function prototypes */ 49 | MA_FILE *ma_open(const char *location, const char *mode, MYSQL *mysql); 50 | int ma_close(MA_FILE *file); 51 | int ma_feof(MA_FILE *file); 52 | size_t ma_read(void *ptr, size_t size, size_t nmemb, MA_FILE *file); 53 | char *ma_gets(char *ptr, size_t size, MA_FILE *file); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /MySQL/include/ma_list.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB 2 | 3 | This library is free software; you can redistribute it and/or 4 | modify it under the terms of the GNU Library General Public 5 | License as published by the Free Software Foundation; either 6 | version 2 of the License, or (at your option) any later version. 7 | 8 | This library is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | Library General Public License for more details. 12 | 13 | You should have received a copy of the GNU Library General Public 14 | License along with this library; if not, write to the Free 15 | Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | MA 02111-1301, USA */ 17 | 18 | #ifndef _list_h_ 19 | #define _list_h_ 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | typedef struct st_list { 26 | struct st_list *prev,*next; 27 | void *data; 28 | } LIST; 29 | 30 | typedef int (*list_walk_action)(void *,void *); 31 | 32 | extern LIST *list_add(LIST *root,LIST *element); 33 | extern LIST *list_delete(LIST *root,LIST *element); 34 | extern LIST *list_cons(void *data,LIST *root); 35 | extern LIST *list_reverse(LIST *root); 36 | extern void list_free(LIST *root,unsigned int free_data); 37 | extern unsigned int list_length(LIST *list); 38 | extern int list_walk(LIST *list,list_walk_action action,char * argument); 39 | 40 | #define list_rest(a) ((a)->next) 41 | #define list_push(a,b) (a)=list_cons((b),(a)) 42 | #define list_pop(A) do {LIST *old=(A); (A)=list_delete(old,old) ; ma_free((char *) old,MYF(MY_FAE)); } while(0) 43 | 44 | #ifdef __cplusplus 45 | } 46 | #endif 47 | #endif 48 | -------------------------------------------------------------------------------- /MySQL/include/ma_pvio.h: -------------------------------------------------------------------------------- 1 | #ifndef _ma_pvio_h_ 2 | #define _ma_pvio_h_ 3 | #define cio_defined 4 | 5 | #ifdef HAVE_TLS 6 | #include 7 | #else 8 | #define MARIADB_TLS void 9 | #endif 10 | 11 | /* CONC-492: Allow to buuld plugins outside of MariaDB Connector/C 12 | source tree wnen ma_global.h was not included. */ 13 | #if !defined(_global_h) && !defined(MY_GLOBAL_INCLUDED) 14 | typedef unsigned char uchar; 15 | #endif 16 | 17 | #define PVIO_SET_ERROR if (pvio->set_error) \ 18 | pvio->set_error 19 | 20 | #define PVIO_READ_AHEAD_CACHE_SIZE 16384 21 | #define PVIO_READ_AHEAD_CACHE_MIN_SIZE 2048 22 | #define PVIO_EINTR_TRIES 2 23 | 24 | struct st_ma_pvio_methods; 25 | typedef struct st_ma_pvio_methods PVIO_METHODS; 26 | 27 | #define IS_PVIO_ASYNC(a) \ 28 | ((a)->mysql && (a)->mysql->options.extension && (a)->mysql->options.extension->async_context) 29 | 30 | #define IS_PVIO_ASYNC_ACTIVE(a) \ 31 | (IS_PVIO_ASYNC(a)&& (a)->mysql->options.extension->async_context->active) 32 | 33 | #define IS_MYSQL_ASYNC(a) \ 34 | ((a)->options.extension && (a)->options.extension->async_context) 35 | 36 | #define IS_MYSQL_ASYNC_ACTIVE(a) \ 37 | (IS_MYSQL_ASYNC(a)&& (a)->options.extension->async_context->active) 38 | 39 | enum enum_pvio_timeout { 40 | PVIO_CONNECT_TIMEOUT= 0, 41 | PVIO_READ_TIMEOUT, 42 | PVIO_WRITE_TIMEOUT 43 | }; 44 | 45 | enum enum_pvio_io_event 46 | { 47 | VIO_IO_EVENT_READ, 48 | VIO_IO_EVENT_WRITE, 49 | VIO_IO_EVENT_CONNECT 50 | }; 51 | 52 | enum enum_pvio_type { 53 | PVIO_TYPE_UNIXSOCKET= 0, 54 | PVIO_TYPE_SOCKET, 55 | PVIO_TYPE_NAMEDPIPE, 56 | PVIO_TYPE_SHAREDMEM, 57 | }; 58 | 59 | enum enum_pvio_operation { 60 | PVIO_READ= 0, 61 | PVIO_WRITE=1 62 | }; 63 | 64 | #define SHM_DEFAULT_NAME "MYSQL" 65 | 66 | struct st_pvio_callback; 67 | 68 | typedef struct st_pvio_callback { 69 | void (*callback)(MYSQL *mysql, uchar *buffer, size_t size); 70 | struct st_pvio_callback *next; 71 | } PVIO_CALLBACK; 72 | 73 | struct st_ma_pvio { 74 | void *data; 75 | /* read ahead cache */ 76 | uchar *cache; 77 | uchar *cache_pos; 78 | size_t cache_size; 79 | enum enum_pvio_type type; 80 | int timeout[3]; 81 | int ssl_type; /* todo: change to enum (ssl plugins) */ 82 | MARIADB_TLS *ctls; 83 | MYSQL *mysql; 84 | PVIO_METHODS *methods; 85 | void (*set_error)(MYSQL *mysql, unsigned int error_nr, const char *sqlstate, const char *format, ...); 86 | void (*callback)(MARIADB_PVIO *pvio, my_bool is_read, const uchar *buffer, size_t length); 87 | }; 88 | 89 | typedef struct st_ma_pvio_cinfo 90 | { 91 | const char *host; 92 | const char *unix_socket; 93 | int port; 94 | enum enum_pvio_type type; 95 | MYSQL *mysql; 96 | } MA_PVIO_CINFO; 97 | 98 | struct st_ma_pvio_methods 99 | { 100 | my_bool (*set_timeout)(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout); 101 | int (*get_timeout)(MARIADB_PVIO *pvio, enum enum_pvio_timeout type); 102 | ssize_t (*read)(MARIADB_PVIO *pvio, uchar *buffer, size_t length); 103 | ssize_t (*async_read)(MARIADB_PVIO *pvio, uchar *buffer, size_t length); 104 | ssize_t (*write)(MARIADB_PVIO *pvio, const uchar *buffer, size_t length); 105 | ssize_t (*async_write)(MARIADB_PVIO *pvio, const uchar *buffer, size_t length); 106 | int (*wait_io_or_timeout)(MARIADB_PVIO *pvio, my_bool is_read, int timeout); 107 | int (*blocking)(MARIADB_PVIO *pvio, my_bool value, my_bool *old_value); 108 | my_bool (*connect)(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo); 109 | my_bool (*close)(MARIADB_PVIO *pvio); 110 | int (*fast_send)(MARIADB_PVIO *pvio); 111 | int (*keepalive)(MARIADB_PVIO *pvio); 112 | my_bool (*get_handle)(MARIADB_PVIO *pvio, void *handle); 113 | my_bool (*is_blocking)(MARIADB_PVIO *pvio); 114 | my_bool (*is_alive)(MARIADB_PVIO *pvio); 115 | my_bool (*has_data)(MARIADB_PVIO *pvio, ssize_t *data_len); 116 | int(*shutdown)(MARIADB_PVIO *pvio); 117 | }; 118 | 119 | /* Function prototypes */ 120 | MARIADB_PVIO *ma_pvio_init(MA_PVIO_CINFO *cinfo); 121 | void ma_pvio_close(MARIADB_PVIO *pvio); 122 | ssize_t ma_pvio_cache_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length); 123 | ssize_t ma_pvio_read(MARIADB_PVIO *pvio, uchar *buffer, size_t length); 124 | ssize_t ma_pvio_write(MARIADB_PVIO *pvio, const uchar *buffer, size_t length); 125 | int ma_pvio_get_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type); 126 | my_bool ma_pvio_set_timeout(MARIADB_PVIO *pvio, enum enum_pvio_timeout type, int timeout); 127 | int ma_pvio_fast_send(MARIADB_PVIO *pvio); 128 | int ma_pvio_keepalive(MARIADB_PVIO *pvio); 129 | my_socket ma_pvio_get_socket(MARIADB_PVIO *pvio); 130 | my_bool ma_pvio_is_blocking(MARIADB_PVIO *pvio); 131 | my_bool ma_pvio_blocking(MARIADB_PVIO *pvio, my_bool block, my_bool *previous_mode); 132 | my_bool ma_pvio_is_blocking(MARIADB_PVIO *pvio); 133 | int ma_pvio_wait_io_or_timeout(MARIADB_PVIO *pvio, my_bool is_read, int timeout); 134 | my_bool ma_pvio_connect(MARIADB_PVIO *pvio, MA_PVIO_CINFO *cinfo); 135 | my_bool ma_pvio_is_alive(MARIADB_PVIO *pvio); 136 | my_bool ma_pvio_get_handle(MARIADB_PVIO *pvio, void *handle); 137 | my_bool ma_pvio_has_data(MARIADB_PVIO *pvio, ssize_t *length); 138 | 139 | #endif /* _ma_pvio_h_ */ 140 | -------------------------------------------------------------------------------- /MySQL/include/ma_tls.h: -------------------------------------------------------------------------------- 1 | #ifndef _ma_tls_h_ 2 | #define _ma_tls_h_ 3 | 4 | enum enum_pvio_tls_type { 5 | SSL_TYPE_DEFAULT=0, 6 | #ifdef _WIN32 7 | SSL_TYPE_SCHANNEL, 8 | #endif 9 | SSL_TYPE_OPENSSL, 10 | SSL_TYPE_GNUTLS 11 | }; 12 | 13 | #define PROTOCOL_SSLV3 0 14 | #define PROTOCOL_TLS_1_0 1 15 | #define PROTOCOL_TLS_1_1 2 16 | #define PROTOCOL_TLS_1_2 3 17 | #define PROTOCOL_TLS_1_3 4 18 | #define PROTOCOL_UNKNOWN 5 19 | #define PROTOCOL_MAX PROTOCOL_TLS_1_3 20 | 21 | #define TLS_VERSION_LENGTH 64 22 | extern char tls_library_version[TLS_VERSION_LENGTH]; 23 | 24 | typedef struct st_ma_pvio_tls { 25 | void *data; 26 | MARIADB_PVIO *pvio; 27 | void *ssl; 28 | } MARIADB_TLS; 29 | 30 | /* Function prototypes */ 31 | 32 | /* ma_tls_start 33 | initializes the ssl library 34 | Parameter: 35 | errmsg pointer to error message buffer 36 | errmsg_len length of error message buffer 37 | Returns: 38 | 0 success 39 | 1 if an error occurred 40 | Notes: 41 | On success the global variable ma_tls_initialized will be set to 1 42 | */ 43 | int ma_tls_start(char *errmsg, size_t errmsg_len); 44 | 45 | /* ma_tls_end 46 | unloads/deinitializes ssl library and unsets global variable 47 | ma_tls_initialized 48 | */ 49 | void ma_tls_end(void); 50 | 51 | /* ma_tls_init 52 | creates a new SSL structure for a SSL connection and loads 53 | client certificates 54 | 55 | Parameters: 56 | MYSQL a mysql structure 57 | Returns: 58 | void * a pointer to internal SSL structure 59 | */ 60 | void * ma_tls_init(MYSQL *mysql); 61 | 62 | /* ma_tls_connect 63 | performs SSL handshake 64 | Parameters: 65 | MARIADB_TLS MariaDB SSL container 66 | Returns: 67 | 0 success 68 | 1 error 69 | */ 70 | my_bool ma_tls_connect(MARIADB_TLS *ctls); 71 | 72 | /* ma_tls_read 73 | reads up to length bytes from socket 74 | Parameters: 75 | ctls MariaDB SSL container 76 | buffer read buffer 77 | length buffer length 78 | Returns: 79 | 0-n bytes read 80 | -1 if an error occurred 81 | */ 82 | ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length); 83 | 84 | /* ma_tls_write 85 | write buffer to socket 86 | Parameters: 87 | ctls MariaDB SSL container 88 | buffer write buffer 89 | length buffer length 90 | Returns: 91 | 0-n bytes written 92 | -1 if an error occurred 93 | */ 94 | ssize_t ma_tls_write(MARIADB_TLS *ctls, const uchar* buffer, size_t length); 95 | 96 | /* ma_tls_close 97 | closes SSL connection and frees SSL structure which was previously 98 | created by ma_tls_init call 99 | Parameters: 100 | MARIADB_TLS MariaDB SSL container 101 | Returns: 102 | 0 success 103 | 1 error 104 | */ 105 | my_bool ma_tls_close(MARIADB_TLS *ctls); 106 | 107 | /* ma_tls_verify_server_cert 108 | validation check of server certificate 109 | Parameter: 110 | MARIADB_TLS MariaDB SSL container 111 | Returns: 112 | ß success 113 | 1 error 114 | */ 115 | int ma_tls_verify_server_cert(MARIADB_TLS *ctls); 116 | 117 | /* ma_tls_get_cipher 118 | returns cipher for current ssl connection 119 | Parameter: 120 | MARIADB_TLS MariaDB SSL container 121 | Returns: 122 | cipher in use or 123 | NULL on error 124 | */ 125 | const char *ma_tls_get_cipher(MARIADB_TLS *ssl); 126 | 127 | /* ma_tls_get_finger_print 128 | returns SHA1 finger print of server certificate 129 | Parameter: 130 | MARIADB_TLS MariaDB SSL container 131 | fp buffer for fingerprint 132 | fp_len buffer length 133 | Returns: 134 | actual size of finger print 135 | */ 136 | unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int fp_len); 137 | 138 | /* ma_tls_get_protocol_version 139 | returns protocol version number in use 140 | Parameter: 141 | MARIADB_TLS MariaDB SSL container 142 | Returns: 143 | protocol number 144 | */ 145 | int ma_tls_get_protocol_version(MARIADB_TLS *ctls); 146 | const char *ma_pvio_tls_get_protocol_version(MARIADB_TLS *ctls); 147 | int ma_pvio_tls_get_protocol_version_id(MARIADB_TLS *ctls); 148 | 149 | /* Function prototypes */ 150 | MARIADB_TLS *ma_pvio_tls_init(MYSQL *mysql); 151 | my_bool ma_pvio_tls_connect(MARIADB_TLS *ctls); 152 | ssize_t ma_pvio_tls_read(MARIADB_TLS *ctls, const uchar *buffer, size_t length); 153 | ssize_t ma_pvio_tls_write(MARIADB_TLS *ctls, const uchar *buffer, size_t length); 154 | my_bool ma_pvio_tls_close(MARIADB_TLS *ctls); 155 | int ma_pvio_tls_verify_server_cert(MARIADB_TLS *ctls); 156 | const char *ma_pvio_tls_cipher(MARIADB_TLS *ctls); 157 | my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_list); 158 | my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio); 159 | void ma_pvio_tls_end(); 160 | 161 | #endif /* _ma_tls_h_ */ 162 | -------------------------------------------------------------------------------- /MySQL/include/mariadb_ctype.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB 2 | 3 | This library is free software; you can redistribute it and/or 4 | modify it under the terms of the GNU Library General Public 5 | License as published by the Free Software Foundation; either 6 | version 2 of the License, or (at your option) any later version. 7 | 8 | This library is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | Library General Public License for more details. 12 | 13 | You should have received a copy of the GNU Library General Public 14 | License along with this library; if not, write to the Free 15 | Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | MA 02111-1301, USA */ 17 | 18 | /* 19 | A better implementation of the UNIX ctype(3) library. 20 | Notes: my_global.h should be included before ctype.h 21 | */ 22 | 23 | #ifndef _mariadb_ctype_h 24 | #define _mariadb_ctype_h 25 | 26 | #include 27 | 28 | #ifdef __cplusplus 29 | extern "C" { 30 | #endif 31 | 32 | #define CHARSET_DIR "charsets/" 33 | #define MY_CS_NAME_SIZE 32 34 | 35 | #define MADB_DEFAULT_CHARSET_NAME "latin1" 36 | #define MADB_DEFAULT_COLLATION_NAME "latin1_swedish_ci" 37 | #define MADB_AUTODETECT_CHARSET_NAME "auto" 38 | 39 | /* we use the mysqlnd implementation */ 40 | typedef struct ma_charset_info_st 41 | { 42 | unsigned int nr; /* so far only 1 byte for charset */ 43 | unsigned int state; 44 | const char *csname; 45 | const char *name; 46 | const char *dir; 47 | unsigned int codepage; 48 | const char *encoding; 49 | unsigned int char_minlen; 50 | unsigned int char_maxlen; 51 | unsigned int (*mb_charlen)(unsigned int c); 52 | unsigned int (*mb_valid)(const char *start, const char *end); 53 | } MARIADB_CHARSET_INFO; 54 | 55 | extern const MARIADB_CHARSET_INFO mariadb_compiled_charsets[]; 56 | extern MARIADB_CHARSET_INFO *ma_default_charset_info; 57 | extern MARIADB_CHARSET_INFO *ma_charset_bin; 58 | extern MARIADB_CHARSET_INFO *ma_charset_latin1; 59 | extern MARIADB_CHARSET_INFO *ma_charset_utf8_general_ci; 60 | extern MARIADB_CHARSET_INFO *ma_charset_utf16le_general_ci; 61 | 62 | MARIADB_CHARSET_INFO *find_compiled_charset(unsigned int cs_number); 63 | MARIADB_CHARSET_INFO *find_compiled_charset_by_name(const char *name); 64 | 65 | size_t mysql_cset_escape_quotes(const MARIADB_CHARSET_INFO *cset, char *newstr, const char *escapestr, size_t escapestr_len); 66 | size_t mysql_cset_escape_slashes(const MARIADB_CHARSET_INFO *cset, char *newstr, const char *escapestr, size_t escapestr_len); 67 | const char* madb_get_os_character_set(void); 68 | #ifdef _WIN32 69 | int madb_get_windows_cp(const char *charset); 70 | #endif 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /MySQL/include/mariadb_dyncol.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2011, Monty Program Ab 2 | Copyright (c) 2011, Oleksandr Byelkin 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY ``AS IS'' AND ANY 16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 19 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 22 | USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 25 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | SUCH DAMAGE. 27 | */ 28 | 29 | #ifndef ma_dyncol_h 30 | #define ma_dyncol_h 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | #ifndef LIBMARIADB 37 | #include 38 | #include 39 | #endif 40 | #include 41 | 42 | #ifndef longlong_defined 43 | #if defined(HAVE_LONG_LONG) && SIZEOF_LONG != 8 44 | typedef unsigned long long int ulonglong; /* ulong or unsigned long long */ 45 | typedef long long int longlong; 46 | #else 47 | typedef unsigned long ulonglong; /* ulong or unsigned long long */ 48 | typedef long longlong; 49 | #endif 50 | #define longlong_defined 51 | #endif 52 | 53 | 54 | #ifndef _my_sys_h 55 | typedef struct st_dynamic_string 56 | { 57 | char *str; 58 | size_t length,max_length,alloc_increment; 59 | } DYNAMIC_STRING; 60 | #endif 61 | 62 | struct st_mysql_lex_string 63 | { 64 | char *str; 65 | size_t length; 66 | }; 67 | typedef struct st_mysql_lex_string MYSQL_LEX_STRING; 68 | typedef struct st_mysql_lex_string LEX_STRING; 69 | /* 70 | Limits of implementation 71 | */ 72 | #define MAX_TOTAL_NAME_LENGTH 65535 73 | #define MAX_NAME_LENGTH (MAX_TOTAL_NAME_LENGTH/4) 74 | 75 | /* NO and OK is the same used just to show semantics */ 76 | #define ER_DYNCOL_NO ER_DYNCOL_OK 77 | 78 | enum enum_dyncol_func_result 79 | { 80 | ER_DYNCOL_OK= 0, 81 | ER_DYNCOL_YES= 1, /* For functions returning 0/1 */ 82 | ER_DYNCOL_FORMAT= -1, /* Wrong format of the encoded string */ 83 | ER_DYNCOL_LIMIT= -2, /* Some limit reached */ 84 | ER_DYNCOL_RESOURCE= -3, /* Out of resourses */ 85 | ER_DYNCOL_DATA= -4, /* Incorrect input data */ 86 | ER_DYNCOL_UNKNOWN_CHARSET= -5, /* Unknown character set */ 87 | ER_DYNCOL_TRUNCATED= 2 /* OK, but data was truncated */ 88 | }; 89 | 90 | typedef DYNAMIC_STRING DYNAMIC_COLUMN; 91 | 92 | enum enum_dynamic_column_type 93 | { 94 | DYN_COL_NULL= 0, 95 | DYN_COL_INT, 96 | DYN_COL_UINT, 97 | DYN_COL_DOUBLE, 98 | DYN_COL_STRING, 99 | DYN_COL_DECIMAL, 100 | DYN_COL_DATETIME, 101 | DYN_COL_DATE, 102 | DYN_COL_TIME, 103 | DYN_COL_DYNCOL 104 | }; 105 | 106 | typedef enum enum_dynamic_column_type DYNAMIC_COLUMN_TYPE; 107 | 108 | struct st_dynamic_column_value 109 | { 110 | DYNAMIC_COLUMN_TYPE type; 111 | union 112 | { 113 | long long long_value; 114 | unsigned long long ulong_value; 115 | double double_value; 116 | struct { 117 | MYSQL_LEX_STRING value; 118 | MARIADB_CHARSET_INFO *charset; 119 | } string; 120 | #ifndef LIBMARIADB 121 | struct { 122 | decimal_digit_t buffer[DECIMAL_BUFF_LENGTH]; 123 | decimal_t value; 124 | } decimal; 125 | #endif 126 | MYSQL_TIME time_value; 127 | } x; 128 | }; 129 | 130 | typedef struct st_dynamic_column_value DYNAMIC_COLUMN_VALUE; 131 | 132 | #ifdef MADYNCOL_DEPRECATED 133 | enum enum_dyncol_func_result 134 | dynamic_column_create(DYNAMIC_COLUMN *str, 135 | uint column_nr, DYNAMIC_COLUMN_VALUE *value); 136 | 137 | enum enum_dyncol_func_result 138 | dynamic_column_create_many(DYNAMIC_COLUMN *str, 139 | uint column_count, 140 | uint *column_numbers, 141 | DYNAMIC_COLUMN_VALUE *values); 142 | enum enum_dyncol_func_result 143 | dynamic_column_update(DYNAMIC_COLUMN *org, uint column_nr, 144 | DYNAMIC_COLUMN_VALUE *value); 145 | enum enum_dyncol_func_result 146 | dynamic_column_update_many(DYNAMIC_COLUMN *str, 147 | uint add_column_count, 148 | uint *column_numbers, 149 | DYNAMIC_COLUMN_VALUE *values); 150 | 151 | enum enum_dyncol_func_result 152 | dynamic_column_exists(DYNAMIC_COLUMN *org, uint column_nr); 153 | 154 | enum enum_dyncol_func_result 155 | dynamic_column_list(DYNAMIC_COLUMN *org, DYNAMIC_ARRAY *array_of_uint); 156 | 157 | enum enum_dyncol_func_result 158 | dynamic_column_get(DYNAMIC_COLUMN *org, uint column_nr, 159 | DYNAMIC_COLUMN_VALUE *store_it_here); 160 | #endif 161 | 162 | /* new functions */ 163 | enum enum_dyncol_func_result 164 | mariadb_dyncol_create_many_num(DYNAMIC_COLUMN *str, 165 | uint column_count, 166 | uint *column_numbers, 167 | DYNAMIC_COLUMN_VALUE *values, 168 | my_bool new_string); 169 | enum enum_dyncol_func_result 170 | mariadb_dyncol_create_many_named(DYNAMIC_COLUMN *str, 171 | uint column_count, 172 | MYSQL_LEX_STRING *column_keys, 173 | DYNAMIC_COLUMN_VALUE *values, 174 | my_bool new_string); 175 | 176 | 177 | enum enum_dyncol_func_result 178 | mariadb_dyncol_update_many_num(DYNAMIC_COLUMN *str, 179 | uint add_column_count, 180 | uint *column_keys, 181 | DYNAMIC_COLUMN_VALUE *values); 182 | enum enum_dyncol_func_result 183 | mariadb_dyncol_update_many_named(DYNAMIC_COLUMN *str, 184 | uint add_column_count, 185 | MYSQL_LEX_STRING *column_keys, 186 | DYNAMIC_COLUMN_VALUE *values); 187 | 188 | 189 | enum enum_dyncol_func_result 190 | mariadb_dyncol_exists_num(DYNAMIC_COLUMN *org, uint column_nr); 191 | enum enum_dyncol_func_result 192 | mariadb_dyncol_exists_named(DYNAMIC_COLUMN *str, MYSQL_LEX_STRING *name); 193 | 194 | /* List of not NULL columns */ 195 | enum enum_dyncol_func_result 196 | mariadb_dyncol_list_num(DYNAMIC_COLUMN *str, uint *count, uint **nums); 197 | enum enum_dyncol_func_result 198 | mariadb_dyncol_list_named(DYNAMIC_COLUMN *str, uint *count, 199 | MYSQL_LEX_STRING **names); 200 | 201 | /* 202 | if the column do not exists it is NULL 203 | */ 204 | enum enum_dyncol_func_result 205 | mariadb_dyncol_get_num(DYNAMIC_COLUMN *org, uint column_nr, 206 | DYNAMIC_COLUMN_VALUE *store_it_here); 207 | enum enum_dyncol_func_result 208 | mariadb_dyncol_get_named(DYNAMIC_COLUMN *str, MYSQL_LEX_STRING *name, 209 | DYNAMIC_COLUMN_VALUE *store_it_here); 210 | 211 | my_bool mariadb_dyncol_has_names(DYNAMIC_COLUMN *str); 212 | 213 | enum enum_dyncol_func_result 214 | mariadb_dyncol_check(DYNAMIC_COLUMN *str); 215 | 216 | enum enum_dyncol_func_result 217 | mariadb_dyncol_json(DYNAMIC_COLUMN *str, DYNAMIC_STRING *json); 218 | 219 | void mariadb_dyncol_free(DYNAMIC_COLUMN *str); 220 | 221 | #define mariadb_dyncol_init(A) memset((A), 0, sizeof(DYNAMIC_COLUMN)) 222 | #define dynamic_column_initialize(A) mariadb_dyncol_init((A)) 223 | #define dynamic_column_column_free(A) mariadb_dyncol_free((A)) 224 | 225 | /* conversion of values to 3 base types */ 226 | enum enum_dyncol_func_result 227 | mariadb_dyncol_val_str(DYNAMIC_STRING *str, DYNAMIC_COLUMN_VALUE *val, 228 | MARIADB_CHARSET_INFO *cs, char quote); 229 | enum enum_dyncol_func_result 230 | mariadb_dyncol_val_long(longlong *ll, DYNAMIC_COLUMN_VALUE *val); 231 | enum enum_dyncol_func_result 232 | mariadb_dyncol_val_double(double *dbl, DYNAMIC_COLUMN_VALUE *val); 233 | 234 | enum enum_dyncol_func_result 235 | mariadb_dyncol_unpack(DYNAMIC_COLUMN *str, 236 | uint *count, 237 | MYSQL_LEX_STRING **names, DYNAMIC_COLUMN_VALUE **vals); 238 | 239 | int mariadb_dyncol_column_cmp_named(const MYSQL_LEX_STRING *s1, 240 | const MYSQL_LEX_STRING *s2); 241 | 242 | enum enum_dyncol_func_result 243 | mariadb_dyncol_column_count(DYNAMIC_COLUMN *str, uint *column_count); 244 | 245 | #define mariadb_dyncol_value_init(V) \ 246 | do {\ 247 | (V)->type= DYN_COL_NULL;\ 248 | } while(0) 249 | 250 | /* 251 | Prepare value for using as decimal 252 | */ 253 | void mariadb_dyncol_prepare_decimal(DYNAMIC_COLUMN_VALUE *value); 254 | 255 | 256 | #ifdef __cplusplus 257 | } 258 | #endif 259 | #endif 260 | -------------------------------------------------------------------------------- /MySQL/include/mariadb_rpl.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2018 MariaDB Corporation AB 2 | 3 | This library is free software; you can redistribute it and/or 4 | modify it under the terms of the GNU Library General Public 5 | License as published by the Free Software Foundation; either 6 | version 2 of the License, or (at your option) any later version. 7 | 8 | This library is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | Library General Public License for more details. 12 | 13 | You should have received a copy of the GNU Library General Public 14 | License along with this library; if not, write to the Free 15 | Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | MA 02111-1301, USA */ 17 | #ifndef _mariadb_rpl_h_ 18 | #define _mariadb_rpl_h_ 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | #include 25 | 26 | #define MARIADB_RPL_VERSION 0x0001 27 | #define MARIADB_RPL_REQUIRED_VERSION 0x0001 28 | 29 | /* Protocol flags */ 30 | #define MARIADB_RPL_BINLOG_DUMP_NON_BLOCK 1 31 | #define MARIADB_RPL_BINLOG_SEND_ANNOTATE_ROWS 2 32 | #define MARIADB_RPL_IGNORE_HEARTBEAT (1 << 17) 33 | 34 | #define EVENT_HEADER_OFS 20 35 | 36 | #define FL_GROUP_COMMIT_ID 2 37 | #define FL_STMT_END 1 38 | 39 | #define LOG_EVENT_ARTIFICIAL_F 0x20 40 | 41 | 42 | /* Options */ 43 | enum mariadb_rpl_option { 44 | MARIADB_RPL_FILENAME, /* Filename and length */ 45 | MARIADB_RPL_START, /* Start position */ 46 | MARIADB_RPL_SERVER_ID, /* Server ID */ 47 | MARIADB_RPL_FLAGS, /* Protocol flags */ 48 | MARIADB_RPL_GTID_CALLBACK, /* GTID callback function */ 49 | MARIADB_RPL_GTID_DATA, /* GTID data */ 50 | MARIADB_RPL_BUFFER 51 | }; 52 | 53 | /* Event types: From MariaDB Server sql/log_event.h */ 54 | enum mariadb_rpl_event { 55 | UNKNOWN_EVENT= 0, 56 | START_EVENT_V3= 1, 57 | QUERY_EVENT= 2, 58 | STOP_EVENT= 3, 59 | ROTATE_EVENT= 4, 60 | INTVAR_EVENT= 5, 61 | LOAD_EVENT= 6, 62 | SLAVE_EVENT= 7, 63 | CREATE_FILE_EVENT= 8, 64 | APPEND_BLOCK_EVENT= 9, 65 | EXEC_LOAD_EVENT= 10, 66 | DELETE_FILE_EVENT= 11, 67 | NEW_LOAD_EVENT= 12, 68 | RAND_EVENT= 13, 69 | USER_VAR_EVENT= 14, 70 | FORMAT_DESCRIPTION_EVENT= 15, 71 | XID_EVENT= 16, 72 | BEGIN_LOAD_QUERY_EVENT= 17, 73 | EXECUTE_LOAD_QUERY_EVENT= 18, 74 | TABLE_MAP_EVENT = 19, 75 | 76 | PRE_GA_WRITE_ROWS_EVENT = 20, /* deprecated */ 77 | PRE_GA_UPDATE_ROWS_EVENT = 21, /* deprecated */ 78 | PRE_GA_DELETE_ROWS_EVENT = 22, /* deprecated */ 79 | 80 | WRITE_ROWS_EVENT_V1 = 23, 81 | UPDATE_ROWS_EVENT_V1 = 24, 82 | DELETE_ROWS_EVENT_V1 = 25, 83 | INCIDENT_EVENT= 26, 84 | HEARTBEAT_LOG_EVENT= 27, 85 | IGNORABLE_LOG_EVENT= 28, 86 | ROWS_QUERY_LOG_EVENT= 29, 87 | WRITE_ROWS_EVENT = 30, 88 | UPDATE_ROWS_EVENT = 31, 89 | DELETE_ROWS_EVENT = 32, 90 | GTID_LOG_EVENT= 33, 91 | ANONYMOUS_GTID_LOG_EVENT= 34, 92 | PREVIOUS_GTIDS_LOG_EVENT= 35, 93 | TRANSACTION_CONTEXT_EVENT= 36, 94 | VIEW_CHANGE_EVENT= 37, 95 | XA_PREPARE_LOG_EVENT= 38, 96 | 97 | /* 98 | Add new events here - right above this comment! 99 | Existing events (except ENUM_END_EVENT) should never change their numbers 100 | */ 101 | 102 | /* New MySQL/Sun events are to be added right above this comment */ 103 | MYSQL_EVENTS_END, 104 | 105 | MARIA_EVENTS_BEGIN= 160, 106 | ANNOTATE_ROWS_EVENT= 160, 107 | BINLOG_CHECKPOINT_EVENT= 161, 108 | GTID_EVENT= 162, 109 | GTID_LIST_EVENT= 163, 110 | START_ENCRYPTION_EVENT= 164, 111 | QUERY_COMPRESSED_EVENT = 165, 112 | WRITE_ROWS_COMPRESSED_EVENT_V1 = 166, 113 | UPDATE_ROWS_COMPRESSED_EVENT_V1 = 167, 114 | DELETE_ROWS_COMPRESSED_EVENT_V1 = 168, 115 | WRITE_ROWS_COMPRESSED_EVENT = 169, 116 | UPDATE_ROWS_COMPRESSED_EVENT = 170, 117 | DELETE_ROWS_COMPRESSED_EVENT = 171, 118 | 119 | /* Add new MariaDB events here - right above this comment! */ 120 | 121 | ENUM_END_EVENT /* end marker */ 122 | }; 123 | 124 | typedef struct { 125 | char *str; 126 | size_t length; 127 | } MARIADB_STRING; 128 | 129 | enum mariadb_row_event_type { 130 | WRITE_ROWS= 0, 131 | UPDATE_ROWS= 1, 132 | DELETE_ROWS= 2 133 | }; 134 | 135 | /* Global transaction id */ 136 | typedef struct st_mariadb_gtid { 137 | unsigned int domain_id; 138 | unsigned int server_id; 139 | unsigned long long sequence_nr; 140 | } MARIADB_GTID; 141 | 142 | /* Generic replication handle */ 143 | typedef struct st_mariadb_rpl { 144 | unsigned int version; 145 | MYSQL *mysql; 146 | char *filename; 147 | uint32_t filename_length; 148 | unsigned char *buffer; 149 | unsigned long buffer_size; 150 | uint32_t server_id; 151 | unsigned long start_position; 152 | uint32_t flags; 153 | uint8_t fd_header_len; /* header len from last format description event */ 154 | uint8_t use_checksum; 155 | } MARIADB_RPL; 156 | 157 | /* Event header */ 158 | struct st_mariadb_rpl_rotate_event { 159 | unsigned long long position; 160 | MARIADB_STRING filename; 161 | }; 162 | 163 | struct st_mariadb_rpl_query_event { 164 | uint32_t thread_id; 165 | uint32_t seconds; 166 | MARIADB_STRING database; 167 | uint32_t errornr; 168 | MARIADB_STRING status; 169 | MARIADB_STRING statement; 170 | }; 171 | 172 | struct st_mariadb_rpl_gtid_list_event { 173 | uint32_t gtid_cnt; 174 | MARIADB_GTID *gtid; 175 | }; 176 | 177 | struct st_mariadb_rpl_format_description_event 178 | { 179 | uint16_t format; 180 | char *server_version; 181 | uint32_t timestamp; 182 | uint8_t header_len; 183 | }; 184 | 185 | struct st_mariadb_rpl_checkpoint_event { 186 | MARIADB_STRING filename; 187 | }; 188 | 189 | struct st_mariadb_rpl_xid_event { 190 | uint64_t transaction_nr; 191 | }; 192 | 193 | struct st_mariadb_rpl_gtid_event { 194 | uint64_t sequence_nr; 195 | uint32_t domain_id; 196 | uint8_t flags; 197 | uint64_t commit_id; 198 | }; 199 | 200 | struct st_mariadb_rpl_annotate_rows_event { 201 | MARIADB_STRING statement; 202 | }; 203 | 204 | struct st_mariadb_rpl_table_map_event { 205 | unsigned long long table_id; 206 | MARIADB_STRING database; 207 | MARIADB_STRING table; 208 | unsigned int column_count; 209 | MARIADB_STRING column_types; 210 | MARIADB_STRING metadata; 211 | char *null_indicator; 212 | }; 213 | 214 | struct st_mariadb_rpl_rand_event { 215 | unsigned long long first_seed; 216 | unsigned long long second_seed; 217 | }; 218 | 219 | struct st_mariadb_rpl_encryption_event { 220 | char scheme; 221 | unsigned int key_version; 222 | char *nonce; 223 | }; 224 | 225 | struct st_mariadb_rpl_intvar_event { 226 | char type; 227 | unsigned long long value; 228 | }; 229 | 230 | struct st_mariadb_rpl_uservar_event { 231 | MARIADB_STRING name; 232 | uint8_t is_null; 233 | uint8_t type; 234 | uint32_t charset_nr; 235 | MARIADB_STRING value; 236 | uint8_t flags; 237 | }; 238 | 239 | struct st_mariadb_rpl_rows_event { 240 | enum mariadb_row_event_type type; 241 | uint64_t table_id; 242 | uint16_t flags; 243 | uint32_t column_count; 244 | char *column_bitmap; 245 | char *column_update_bitmap; 246 | size_t row_data_size; 247 | void *row_data; 248 | }; 249 | 250 | struct st_mariadb_rpl_heartbeat_event { 251 | uint32_t timestamp; 252 | uint32_t next_position; 253 | uint8_t type; 254 | uint16_t flags; 255 | }; 256 | 257 | typedef struct st_mariadb_rpl_event 258 | { 259 | /* common header */ 260 | MA_MEM_ROOT memroot; 261 | unsigned int checksum; 262 | char ok; 263 | enum mariadb_rpl_event event_type; 264 | unsigned int timestamp; 265 | unsigned int server_id; 266 | unsigned int event_length; 267 | unsigned int next_event_pos; 268 | unsigned short flags; 269 | /****************/ 270 | union { 271 | struct st_mariadb_rpl_rotate_event rotate; 272 | struct st_mariadb_rpl_query_event query; 273 | struct st_mariadb_rpl_format_description_event format_description; 274 | struct st_mariadb_rpl_gtid_list_event gtid_list; 275 | struct st_mariadb_rpl_checkpoint_event checkpoint; 276 | struct st_mariadb_rpl_xid_event xid; 277 | struct st_mariadb_rpl_gtid_event gtid; 278 | struct st_mariadb_rpl_annotate_rows_event annotate_rows; 279 | struct st_mariadb_rpl_table_map_event table_map; 280 | struct st_mariadb_rpl_rand_event rand; 281 | struct st_mariadb_rpl_encryption_event encryption; 282 | struct st_mariadb_rpl_intvar_event intvar; 283 | struct st_mariadb_rpl_uservar_event uservar; 284 | struct st_mariadb_rpl_rows_event rows; 285 | struct st_mariadb_rpl_heartbeat_event heartbeat; 286 | } event; 287 | } MARIADB_RPL_EVENT; 288 | 289 | #define mariadb_rpl_init(a) mariadb_rpl_init_ex((a), MARIADB_RPL_VERSION) 290 | 291 | /* Function prototypes */ 292 | MARIADB_RPL * STDCALL mariadb_rpl_init_ex(MYSQL *mysql, unsigned int version); 293 | 294 | int mariadb_rpl_optionsv(MARIADB_RPL *rpl, enum mariadb_rpl_option, ...); 295 | int mariadb_rpl_get_optionsv(MARIADB_RPL *rpl, enum mariadb_rpl_option, ...); 296 | 297 | int STDCALL mariadb_rpl_open(MARIADB_RPL *rpl); 298 | void STDCALL mariadb_rpl_close(MARIADB_RPL *rpl); 299 | MARIADB_RPL_EVENT * STDCALL mariadb_rpl_fetch(MARIADB_RPL *rpl, MARIADB_RPL_EVENT *event); 300 | void STDCALL mariadb_free_rpl_event(MARIADB_RPL_EVENT *event); 301 | 302 | #ifdef __cplusplus 303 | } 304 | #endif 305 | #endif 306 | -------------------------------------------------------------------------------- /MySQL/include/mariadb_version.h: -------------------------------------------------------------------------------- 1 | /* Copyright Abandoned 1996, 1999, 2001 MySQL AB 2 | This file is public domain and comes with NO WARRANTY of any kind */ 3 | 4 | /* Version numbers for protocol & mysqld */ 5 | 6 | #ifndef _mariadb_version_h_ 7 | #define _mariadb_version_h_ 8 | 9 | #ifdef _CUSTOMCONFIG_ 10 | #include 11 | #else 12 | #define PROTOCOL_VERSION 10 13 | #define MARIADB_CLIENT_VERSION_STR "10.5.5" 14 | #define MARIADB_BASE_VERSION "mariadb-10.5" 15 | #define MARIADB_VERSION_ID 100505 16 | #define MARIADB_PORT 3306 17 | #define MARIADB_UNIX_ADDR "/tmp/mysql.sock" 18 | #ifndef MYSQL_UNIX_ADDR 19 | #define MYSQL_UNIX_ADDR MARIADB_UNIX_ADDR 20 | #endif 21 | #ifndef MYSQL_PORT 22 | #define MYSQL_PORT MARIADB_PORT 23 | #endif 24 | 25 | #define MYSQL_CONFIG_NAME "my" 26 | #define MYSQL_VERSION_ID 100505 27 | #define MYSQL_SERVER_VERSION "10.5.5-MariaDB" 28 | 29 | #define MARIADB_PACKAGE_VERSION "3.1.12" 30 | #define MARIADB_PACKAGE_VERSION_ID 30112 31 | #define MARIADB_SYSTEM_TYPE "Windows" 32 | #define MARIADB_MACHINE_TYPE "AMD64" 33 | #define MARIADB_PLUGINDIR "C:/Program Files/mariadb-connector-c/lib/mariadb/plugin" 34 | 35 | /* mysqld compile time options */ 36 | #ifndef MYSQL_CHARSET 37 | #define MYSQL_CHARSET "" 38 | #endif 39 | #endif 40 | 41 | /* Source information */ 42 | #define CC_SOURCE_REVISION "7d304d26c787a3f0430624db977b615aba56e4bb" 43 | 44 | #endif /* _mariadb_version_h_ */ 45 | -------------------------------------------------------------------------------- /MySQL/include/mysql/client_plugin.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2010 - 2012 Sergei Golubchik and Monty Program Ab 2 | 2014 MariaDB Corporation AB 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not see 16 | or write to the Free Software Foundation, Inc., 17 | 51 Franklin St., Fifth Floor, Boston, MA 02110, USA */ 18 | 19 | /** 20 | @file 21 | 22 | MySQL Client Plugin API 23 | 24 | This file defines the API for plugins that work on the client side 25 | */ 26 | #ifndef MYSQL_CLIENT_PLUGIN_INCLUDED 27 | #define MYSQL_CLIENT_PLUGIN_INCLUDED 28 | 29 | #ifndef MYSQL_ABI_CHECK 30 | #include 31 | #include 32 | #endif 33 | 34 | 35 | #ifndef PLUGINDIR 36 | #define PLUGINDIR "lib/plugin" 37 | #endif 38 | 39 | #define plugin_declarations_sym "_mysql_client_plugin_declaration_" 40 | 41 | /* known plugin types */ 42 | #define MYSQL_CLIENT_PLUGIN_RESERVED 0 43 | #define MYSQL_CLIENT_PLUGIN_RESERVED2 1 44 | #define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2 /* authentication */ 45 | 46 | #define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100 47 | #define MYSQL_CLIENT_MAX_PLUGINS 3 48 | 49 | /* Connector/C specific plugin types */ 50 | #define MARIADB_CLIENT_REMOTEIO_PLUGIN 100 /* communication IO */ 51 | #define MARIADB_CLIENT_PVIO_PLUGIN 101 52 | #define MARIADB_CLIENT_TRACE_PLUGIN 102 53 | #define MARIADB_CLIENT_CONNECTION_PLUGIN 103 54 | 55 | #define MARIADB_CLIENT_REMOTEIO_PLUGIN_INTERFACE_VERSION 0x0100 56 | #define MARIADB_CLIENT_PVIO_PLUGIN_INTERFACE_VERSION 0x0100 57 | #define MARIADB_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION 0x0100 58 | #define MARIADB_CLIENT_CONNECTION_PLUGIN_INTERFACE_VERSION 0x0100 59 | 60 | #define MARIADB_CLIENT_MAX_PLUGINS 4 61 | 62 | #define mysql_declare_client_plugin(X) \ 63 | struct st_mysql_client_plugin_ ## X \ 64 | _mysql_client_plugin_declaration_ = { \ 65 | MYSQL_CLIENT_ ## X ## _PLUGIN, \ 66 | MYSQL_CLIENT_ ## X ## _PLUGIN_INTERFACE_VERSION, 67 | #define mysql_end_client_plugin } 68 | 69 | /* generic plugin header structure */ 70 | #ifndef MYSQL_CLIENT_PLUGIN_HEADER 71 | #define MYSQL_CLIENT_PLUGIN_HEADER \ 72 | int type; \ 73 | unsigned int interface_version; \ 74 | const char *name; \ 75 | const char *author; \ 76 | const char *desc; \ 77 | unsigned int version[3]; \ 78 | const char *license; \ 79 | void *mysql_api; \ 80 | int (*init)(char *, size_t, int, va_list); \ 81 | int (*deinit)(void); \ 82 | int (*options)(const char *option, const void *); 83 | struct st_mysql_client_plugin 84 | { 85 | MYSQL_CLIENT_PLUGIN_HEADER 86 | }; 87 | #endif 88 | 89 | struct st_mysql; 90 | 91 | /********* connection handler plugin specific declarations **********/ 92 | 93 | typedef struct st_ma_connection_plugin 94 | { 95 | MYSQL_CLIENT_PLUGIN_HEADER 96 | /* functions */ 97 | MYSQL *(*connect)(MYSQL *mysql, const char *host, 98 | const char *user, const char *passwd, 99 | const char *db, unsigned int port, 100 | const char *unix_socket, unsigned long clientflag); 101 | void (*close)(MYSQL *mysql); 102 | int (*set_optionsv)(MYSQL *mysql, unsigned int option, ...); 103 | int (*set_connection)(MYSQL *mysql,enum enum_server_command command, 104 | const char *arg, 105 | size_t length, my_bool skipp_check, void *opt_arg); 106 | my_bool (*reconnect)(MYSQL *mysql); 107 | int (*reset)(MYSQL *mysql); 108 | } MARIADB_CONNECTION_PLUGIN; 109 | 110 | #define MARIADB_DB_DRIVER(a) ((a)->ext_db) 111 | 112 | /******************* Communication IO plugin *****************/ 113 | #include 114 | 115 | typedef struct st_mariadb_client_plugin_PVIO 116 | { 117 | MYSQL_CLIENT_PLUGIN_HEADER 118 | struct st_ma_pvio_methods *methods; 119 | } MARIADB_PVIO_PLUGIN; 120 | 121 | /******** authentication plugin specific declarations *********/ 122 | #include 123 | 124 | struct st_mysql_client_plugin_AUTHENTICATION 125 | { 126 | MYSQL_CLIENT_PLUGIN_HEADER 127 | int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql); 128 | }; 129 | 130 | /******** trace plugin *******/ 131 | struct st_mysql_client_plugin_TRACE 132 | { 133 | MYSQL_CLIENT_PLUGIN_HEADER 134 | }; 135 | 136 | /** 137 | type of the mysql_authentication_dialog_ask function 138 | 139 | @param mysql mysql 140 | @param type type of the input 141 | 1 - ordinary string input 142 | 2 - password string 143 | @param prompt prompt 144 | @param buf a buffer to store the use input 145 | @param buf_len the length of the buffer 146 | 147 | @retval a pointer to the user input string. 148 | It may be equal to 'buf' or to 'mysql->password'. 149 | In all other cases it is assumed to be an allocated 150 | string, and the "dialog" plugin will free() it. 151 | */ 152 | typedef char *(*mysql_authentication_dialog_ask_t)(struct st_mysql *mysql, 153 | int type, const char *prompt, char *buf, int buf_len); 154 | 155 | /********************** remote IO plugin **********************/ 156 | #ifdef HAVE_REMOTEIO 157 | #include 158 | 159 | /* Remote IO plugin */ 160 | typedef struct st_mysql_client_plugin_REMOTEIO 161 | { 162 | MYSQL_CLIENT_PLUGIN_HEADER 163 | struct st_rio_methods *methods; 164 | } MARIADB_REMOTEIO_PLUGIN; 165 | #endif 166 | 167 | /******** using plugins ************/ 168 | 169 | /** 170 | loads a plugin and initializes it 171 | 172 | @param mysql MYSQL structure. only MYSQL_PLUGIN_DIR option value is used, 173 | and last_errno/last_error, for error reporting 174 | @param name a name of the plugin to load 175 | @param type type of plugin that should be loaded, -1 to disable type check 176 | @param argc number of arguments to pass to the plugin initialization 177 | function 178 | @param ... arguments for the plugin initialization function 179 | 180 | @retval 181 | a pointer to the loaded plugin, or NULL in case of a failure 182 | */ 183 | struct st_mysql_client_plugin * 184 | mysql_load_plugin(struct st_mysql *mysql, const char *name, int type, 185 | int argc, ...); 186 | 187 | /** 188 | loads a plugin and initializes it, taking va_list as an argument 189 | 190 | This is the same as mysql_load_plugin, but take va_list instead of 191 | a list of arguments. 192 | 193 | @param mysql MYSQL structure. only MYSQL_PLUGIN_DIR option value is used, 194 | and last_errno/last_error, for error reporting 195 | @param name a name of the plugin to load 196 | @param type type of plugin that should be loaded, -1 to disable type check 197 | @param argc number of arguments to pass to the plugin initialization 198 | function 199 | @param args arguments for the plugin initialization function 200 | 201 | @retval 202 | a pointer to the loaded plugin, or NULL in case of a failure 203 | */ 204 | struct st_mysql_client_plugin * STDCALL 205 | mysql_load_plugin_v(struct st_mysql *mysql, const char *name, int type, 206 | int argc, va_list args); 207 | 208 | /** 209 | finds an already loaded plugin by name, or loads it, if necessary 210 | 211 | @param mysql MYSQL structure. only MYSQL_PLUGIN_DIR option value is used, 212 | and last_errno/last_error, for error reporting 213 | @param name a name of the plugin to load 214 | @param type type of plugin that should be loaded 215 | 216 | @retval 217 | a pointer to the plugin, or NULL in case of a failure 218 | */ 219 | struct st_mysql_client_plugin * STDCALL 220 | mysql_client_find_plugin(struct st_mysql *mysql, const char *name, int type); 221 | 222 | /** 223 | adds a plugin structure to the list of loaded plugins 224 | 225 | This is useful if an application has the necessary functionality 226 | (for example, a special load data handler) statically linked into 227 | the application binary. It can use this function to register the plugin 228 | directly, avoiding the need to factor it out into a shared object. 229 | 230 | @param mysql MYSQL structure. It is only used for error reporting 231 | @param plugin an st_mysql_client_plugin structure to register 232 | 233 | @retval 234 | a pointer to the plugin, or NULL in case of a failure 235 | */ 236 | struct st_mysql_client_plugin * STDCALL 237 | mysql_client_register_plugin(struct st_mysql *mysql, 238 | struct st_mysql_client_plugin *plugin); 239 | 240 | extern struct st_mysql_client_plugin *mysql_client_builtins[]; 241 | 242 | #endif 243 | 244 | 245 | -------------------------------------------------------------------------------- /MySQL/include/mysql/plugin_auth.h: -------------------------------------------------------------------------------- 1 | #ifndef MYSQL_PLUGIN_AUTH_COMMON_INCLUDED 2 | /* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Library General Public 6 | License as published by the Free Software Foundation; either 7 | version 2 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Library General Public License for more details. 13 | 14 | You should have received a copy of the GNU Library General Public 15 | License along with this library; if not, write to the Free 16 | Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 17 | MA 02111-1301, USA */ 18 | 19 | /** 20 | @file 21 | 22 | This file defines constants and data structures that are the same for 23 | both client- and server-side authentication plugins. 24 | */ 25 | #define MYSQL_PLUGIN_AUTH_COMMON_INCLUDED 26 | 27 | /** the max allowed length for a user name */ 28 | #define MYSQL_USERNAME_LENGTH 512 29 | 30 | /** 31 | return values of the plugin authenticate_user() method. 32 | */ 33 | 34 | /** 35 | Authentication failed. Additionally, all other CR_xxx values 36 | (libmariadb error code) can be used too. 37 | 38 | The client plugin may set the error code and the error message directly 39 | in the MYSQL structure and return CR_ERROR. If a CR_xxx specific error 40 | code was returned, an error message in the MYSQL structure will be 41 | overwritten. If CR_ERROR is returned without setting the error in MYSQL, 42 | CR_UNKNOWN_ERROR will be user. 43 | */ 44 | #define CR_ERROR 0 45 | /** 46 | Authentication (client part) was successful. It does not mean that the 47 | authentication as a whole was successful, usually it only means 48 | that the client was able to send the user name and the password to the 49 | server. If CR_OK is returned, the libmariadb reads the next packet expecting 50 | it to be one of OK, ERROR, or CHANGE_PLUGIN packets. 51 | */ 52 | #define CR_OK -1 53 | /** 54 | Authentication was successful. 55 | It means that the client has done its part successfully and also that 56 | a plugin has read the last packet (one of OK, ERROR, CHANGE_PLUGIN). 57 | In this case, libmariadb will not read a packet from the server, 58 | but it will use the data at mysql->net.read_pos. 59 | 60 | A plugin may return this value if the number of roundtrips in the 61 | authentication protocol is not known in advance, and the client plugin 62 | needs to read one packet more to determine if the authentication is finished 63 | or not. 64 | */ 65 | #define CR_OK_HANDSHAKE_COMPLETE -2 66 | 67 | typedef struct st_plugin_vio_info 68 | { 69 | enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET, 70 | MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY } protocol; 71 | int socket; /**< it's set, if the protocol is SOCKET or TCP */ 72 | #ifdef _WIN32 73 | HANDLE handle; /**< it's set, if the protocol is PIPE or MEMORY */ 74 | #endif 75 | } MYSQL_PLUGIN_VIO_INFO; 76 | 77 | /** 78 | Provides plugin access to communication channel 79 | */ 80 | typedef struct st_plugin_vio 81 | { 82 | /** 83 | Plugin provides a pointer reference and this function sets it to the 84 | contents of any incoming packet. Returns the packet length, or -1 if 85 | the plugin should terminate. 86 | */ 87 | int (*read_packet)(struct st_plugin_vio *vio, 88 | unsigned char **buf); 89 | 90 | /** 91 | Plugin provides a buffer with data and the length and this 92 | function sends it as a packet. Returns 0 on success, 1 on failure. 93 | */ 94 | int (*write_packet)(struct st_plugin_vio *vio, 95 | const unsigned char *packet, 96 | int packet_len); 97 | 98 | /** 99 | Fills in a st_plugin_vio_info structure, providing the information 100 | about the connection. 101 | */ 102 | void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info); 103 | 104 | } MYSQL_PLUGIN_VIO; 105 | 106 | #endif 107 | 108 | -------------------------------------------------------------------------------- /MySQL/include/mysql/plugin_auth_common.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2010 Sergei Golubchik and Monty Program Ab 2 | 3 | This library is free software; you can redistribute it and/or 4 | modify it under the terms of the GNU Library General Public 5 | License as published by the Free Software Foundation; either 6 | version 2 of the License, or (at your option) any later version. 7 | 8 | This library is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | Library General Public License for more details. 12 | 13 | You should have received a copy of the GNU Library General Public 14 | License along with this library; if not, write to the Free 15 | Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 16 | MA 02111-1301, USA */ 17 | 18 | 19 | #ifndef MYSQL_PLUGIN_AUTH_COMMON_INCLUDED 20 | /** 21 | @file 22 | 23 | This file defines constants and data structures that are the same for 24 | both client- and server-side authentication plugins. 25 | */ 26 | #define MYSQL_PLUGIN_AUTH_COMMON_INCLUDED 27 | 28 | /** the max allowed length for a user name */ 29 | #define MYSQL_USERNAME_LENGTH 512 30 | 31 | /** 32 | return values of the plugin authenticate_user() method. 33 | */ 34 | 35 | /** 36 | Authentication failed. Additionally, all other CR_xxx values 37 | (libmariadb error code) can be used too. 38 | 39 | The client plugin may set the error code and the error message directly 40 | in the MYSQL structure and return CR_ERROR. If a CR_xxx specific error 41 | code was returned, an error message in the MYSQL structure will be 42 | overwritten. If CR_ERROR is returned without setting the error in MYSQL, 43 | CR_UNKNOWN_ERROR will be user. 44 | */ 45 | #define CR_ERROR 0 46 | /** 47 | Authentication (client part) was successful. It does not mean that the 48 | authentication as a whole was successful, usually it only means 49 | that the client was able to send the user name and the password to the 50 | server. If CR_OK is returned, the libmariadb reads the next packet expecting 51 | it to be one of OK, ERROR, or CHANGE_PLUGIN packets. 52 | */ 53 | #define CR_OK -1 54 | /** 55 | Authentication was successful. 56 | It means that the client has done its part successfully and also that 57 | a plugin has read the last packet (one of OK, ERROR, CHANGE_PLUGIN). 58 | In this case, libmariadb will not read a packet from the server, 59 | but it will use the data at mysql->net.read_pos. 60 | 61 | A plugin may return this value if the number of roundtrips in the 62 | authentication protocol is not known in advance, and the client plugin 63 | needs to read one packet more to determine if the authentication is finished 64 | or not. 65 | */ 66 | #define CR_OK_HANDSHAKE_COMPLETE -2 67 | 68 | typedef struct st_plugin_vio_info 69 | { 70 | enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET, 71 | MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY } protocol; 72 | #ifndef _WIN32 73 | int socket; /**< it's set, if the protocol is SOCKET or TCP */ 74 | #else 75 | SOCKET socket; /**< it's set, if the protocol is SOCKET or TCP */ 76 | HANDLE handle; /**< it's set, if the protocol is PIPE or MEMORY */ 77 | #endif 78 | } MYSQL_PLUGIN_VIO_INFO; 79 | 80 | /** 81 | Provides plugin access to communication channel 82 | */ 83 | typedef struct st_plugin_vio 84 | { 85 | /** 86 | Plugin provides a pointer reference and this function sets it to the 87 | contents of any incoming packet. Returns the packet length, or -1 if 88 | the plugin should terminate. 89 | */ 90 | int (*read_packet)(struct st_plugin_vio *vio, 91 | unsigned char **buf); 92 | 93 | /** 94 | Plugin provides a buffer with data and the length and this 95 | function sends it as a packet. Returns 0 on success, 1 on failure. 96 | */ 97 | int (*write_packet)(struct st_plugin_vio *vio, 98 | const unsigned char *packet, 99 | int packet_len); 100 | 101 | /** 102 | Fills in a st_plugin_vio_info structure, providing the information 103 | about the connection. 104 | */ 105 | void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info); 106 | 107 | } MYSQL_PLUGIN_VIO; 108 | 109 | #endif 110 | 111 | -------------------------------------------------------------------------------- /MySQL/lib/linux/libcrypto.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FredyH/MySQLOO/6d947968bef9028858539903eafe07c8ad181a4c/MySQL/lib/linux/libcrypto.a -------------------------------------------------------------------------------- /MySQL/lib/linux/libmariadbclient.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FredyH/MySQLOO/6d947968bef9028858539903eafe07c8ad181a4c/MySQL/lib/linux/libmariadbclient.a -------------------------------------------------------------------------------- /MySQL/lib/linux/libssl.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FredyH/MySQLOO/6d947968bef9028858539903eafe07c8ad181a4c/MySQL/lib/linux/libssl.a -------------------------------------------------------------------------------- /MySQL/lib/windows/mariadbclient.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FredyH/MySQLOO/6d947968bef9028858539903eafe07c8ad181a4c/MySQL/lib/windows/mariadbclient.lib -------------------------------------------------------------------------------- /MySQL/lib64/linux/libcrypto.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FredyH/MySQLOO/6d947968bef9028858539903eafe07c8ad181a4c/MySQL/lib64/linux/libcrypto.a -------------------------------------------------------------------------------- /MySQL/lib64/linux/libmariadbclient.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FredyH/MySQLOO/6d947968bef9028858539903eafe07c8ad181a4c/MySQL/lib64/linux/libmariadbclient.a -------------------------------------------------------------------------------- /MySQL/lib64/linux/libssl.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FredyH/MySQLOO/6d947968bef9028858539903eafe07c8ad181a4c/MySQL/lib64/linux/libssl.a -------------------------------------------------------------------------------- /MySQL/lib64/windows/mariadbclient.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FredyH/MySQLOO/6d947968bef9028858539903eafe07c8ad181a4c/MySQL/lib64/windows/mariadbclient.lib -------------------------------------------------------------------------------- /lua/connectionpool.lua: -------------------------------------------------------------------------------- 1 | //A simple connection pool for mysqloo9 2 | //Use mysqloo.CreateConnectionPool(connectionCount, host, username, password [, database, port, socket]) to create a connection pool 3 | //That automatically balances queries between several connections and acts like a regular database instance 4 | 5 | require("mysqloo") 6 | if (mysqloo.VERSION != "9") then 7 | error("Using outdated mysqloo version") 8 | end 9 | 10 | local pool = {} 11 | local poolMT = {__index = pool} 12 | 13 | 14 | function mysqloo.CreateConnectionPool(conCount, ...) 15 | if (conCount < 1) then 16 | error("Has to contain at least one connection") 17 | end 18 | local newPool = setmetatable({}, poolMT) 19 | newPool._Connections = {} 20 | local function failCallback(db, err) 21 | print("Failed to connect all db pool connections:") 22 | print(err) 23 | end 24 | for i = 1, conCount do 25 | local db = mysqloo.connect(...) 26 | db.onConnectionFailed = failCallback 27 | db:connect() 28 | table.insert(newPool._Connections, db) 29 | end 30 | return newPool 31 | end 32 | 33 | function pool:queueSize() 34 | local count = 0 35 | for k,v in pairs(self._Connections) do 36 | count = count + v:queueSize() 37 | end 38 | return count 39 | end 40 | 41 | function pool:abortAllQueries() 42 | for k,v in pairs(self._Connections) do 43 | v:abortAllQueries() 44 | end 45 | end 46 | 47 | function pool:getLeastOccupiedDB() 48 | local lowest = nil 49 | local lowestCount = 0 50 | for k, db in pairs(self._Connections) do 51 | local queueSize = db:queueSize() 52 | if (!lowest || queueSize < lowestCount) then 53 | lowest = db 54 | lowestCount = queueSize 55 | end 56 | end 57 | if (!lowest) then 58 | error("failed to find database in the pool") 59 | end 60 | return lowest 61 | end 62 | 63 | local overrideFunctions = {"escape", "query", "prepare", "createTransaction", "status", "serverVersion", "hostInfo", "ping"} 64 | 65 | for _, name in pairs(overrideFunctions) do 66 | pool[name] = function(pool, ...) 67 | local db = pool:getLeastOccupiedDB() 68 | return db[name](db, ...) 69 | end 70 | end -------------------------------------------------------------------------------- /lua/mysqloolib.lua: -------------------------------------------------------------------------------- 1 | --[==[ 2 | This library aims to provide an easier and less verbose way to use mysqloo 3 | Function overview: 4 | 5 | mysqloo.ConvertDatabase(database) 6 | Returns: the modified database 7 | Modifies an existing database to make use of the extended functionality of this library 8 | 9 | mysqloo.CreateDatabase(host, username, password [, database, port, socket]) 10 | Returns: the newly created database instance 11 | Does the same as mysqloo.connect() but adds convenient functions provided by this library 12 | 13 | Query callbacks are of this structure: 14 | function callback([additionalArgs], query, status, dataOrError) end 15 | additionalArgs are any additional arguments that are passed after the callback in RunQuery and similar 16 | query is the query object that represents the started query 17 | status is true if the query executed successfully, false otherwise 18 | dataOrError is either the results returned by query:getData() if the query executed successfully 19 | or and error message if an error occured (use status to know which one) 20 | Note: dataOrError is nil for transaction if the transaction finished successfully 21 | 22 | Database:RunQuery(queryStr, [callback [, additionalArgs]]) 23 | Parameters: 24 | queryStr: the query to run 25 | callback: the callback function that is called when the query is done 26 | additionalArgs: any args that will be passed to the callback function on success (see callback structure) 27 | Returns: the query that has been created and started 28 | Description: Creates and runs a mysqloo query using the specified queryStr and runs the provided 29 | callback function when the query finished. If no callback function is provided then an error message is printed if the query errors 30 | Example: database:RunQuery("SELECT 1", function(query, status, data) 31 | PrintTable(data) 32 | end) 33 | 34 | Database:PrepareQuery(queryStr, parameterValues, [callback [, additionalArgs]) 35 | Parameters: 36 | queryStr: the query string to run with ? representing parameters to be passed in parameterValues 37 | parameterValues: a table containing values that are supposed to replace the ? in the prepared query 38 | additionalArgs: see Database:RunQuery() 39 | Returns: the prepared query that has been created and started 40 | Description: Creates and runs a mysqloo prepared query using the specified queryStr and parameters and runs the provided 41 | callback function when the query finished. If no callback function is provided then an error message is printed if the query errors 42 | Example: database:PrepareQuery("SELECT ?, ?", {1, "a"}, function(query, status, data) 43 | PrintTable(data) 44 | end) 45 | 46 | Database:CreateTransaction() 47 | Parameters: none 48 | Returns: a transaction object 49 | 50 | Transaction:Query(queryStr) 51 | Parameters: 52 | queryStr: the query to run 53 | Returns: the query that has been added to the transaction 54 | Description: Same as Database:RunQuery() but doesn't take a callback and instead of starting the query 55 | adds the query to the transaction 56 | 57 | Transaction:Prepare(queryStr, parameterValues) 58 | Parameters: 59 | queryStr: the query string to run with ? representing parameters to be passed in parameterValues 60 | parameterValues: a table containing values that are supposed to replace the ? in the prepared query 61 | Returns: the prepared query that has been added to the transaction 62 | Description: Same as Database:PrepareQuery() but doesn't take a callback and instead of starting the query 63 | adds the query to the transaction 64 | 65 | Transaction:Start(callback [, additionalArgs]) 66 | Parameters: 67 | callback: The callback that is called when the transaction is finished 68 | additionalArgs: see Database:RunQuery() 69 | Returns: nothing 70 | Description: starts the transaction and calls the callback when done 71 | If the transaction finishes successfully all queries that belong to it have been 72 | executed successfully. If the transaction fails none of the queries will have had effect 73 | Check https://en.wikipedia.org/wiki/ACID for more information 74 | 75 | Transaction example: 76 | local transaction = Database:CreateTransaction() 77 | transaction:Query("SELECT 1") 78 | transaction:Prepare("INSERT INTO `some_tbl` (`some_field`) VALUES(?)", {1}) 79 | transaction:Query("SELECT * FROM `some_tbl` WHERE `id` = LAST_INSERT_ID()") 80 | transaction:Start(function(transaction, status, err) 81 | if (!status) then error(err) end 82 | PrintTable(transaction:getQueries()[1]:getData()) 83 | PrintTable(transaction:getQueries()[3]:getData()) 84 | end) 85 | ]==] 86 | 87 | require("mysqloo") 88 | if (mysqloo.VERSION != "9" || !mysqloo.MINOR_VERSION || tonumber(mysqloo.MINOR_VERSION) < 1) then 89 | MsgC(Color(255, 0, 0), "You are using an outdated mysqloo version\n") 90 | MsgC(Color(255, 0, 0), "Download the latest mysqloo9 from here\n") 91 | MsgC(Color(86, 156, 214), "https://github.com/FredyH/MySQLOO/releases") 92 | return 93 | end 94 | 95 | local db = {} 96 | local baseMeta = FindMetaTable("MySQLOO Database") or {} -- this ensures backwards compatibility to <=9.6 97 | local dbMetatable = {__index = function(tbl, key) 98 | return (db[key] or baseMeta[key]) 99 | end} 100 | 101 | --This converts an already existing database instance to be able to make use 102 | --of the easier functionality provided by mysqloo.CreateDatabase 103 | function mysqloo.ConvertDatabase(database) 104 | return setmetatable(database, dbMetatable) 105 | end 106 | 107 | --The same as mysqloo.connect() but adds easier functionality 108 | function mysqloo.CreateDatabase(...) 109 | local db = mysqloo.connect(...) 110 | db:connect() 111 | return mysqloo.ConvertDatabase(db) 112 | end 113 | 114 | local function addQueryFunctions(query, func, ...) 115 | local oldtrace = debug.traceback() 116 | local args = {...} 117 | table.insert(args, query) 118 | function query.onAborted(qu) 119 | table.insert(args, false) 120 | table.insert(args, "aborted") 121 | if (func) then 122 | func(unpack(args)) 123 | end 124 | end 125 | 126 | function query.onError(qu, err) 127 | table.insert(args, false) 128 | table.insert(args, err) 129 | if (func) then 130 | func(unpack(args)) 131 | else 132 | ErrorNoHalt(err .. "\n" .. oldtrace .. "\n") 133 | end 134 | end 135 | 136 | function query.onSuccess(qu, data) 137 | table.insert(args, true) 138 | table.insert(args, data) 139 | if (func) then 140 | func(unpack(args)) 141 | end 142 | end 143 | end 144 | 145 | function db:RunQuery(str, callback, ...) 146 | local query = self:query(str) 147 | addQueryFunctions(query, callback, ...) 148 | query:start() 149 | return query 150 | end 151 | 152 | local function setPreparedQueryArguments(query, values) 153 | query:clearParameters() 154 | 155 | if (type(values) != "table") then 156 | values = { values } 157 | end 158 | local typeFunctions = { 159 | ["string"] = function(query, index, value) query:setString(index, value) end, 160 | ["number"] = function(query, index, value) query:setNumber(index, value) end, 161 | ["boolean"] = function(query, index, value) query:setBoolean(index, value) end, 162 | } 163 | --This has to be pairs instead of ipairs 164 | --because nil is allowed as value 165 | for k, v in pairs(values) do 166 | local varType = type(v) 167 | if (typeFunctions[varType]) then 168 | typeFunctions[varType](query, k, v) 169 | else 170 | query:setString(k, tostring(v)) 171 | end 172 | end 173 | end 174 | 175 | function db:PrepareQuery(str, values, callback, ...) 176 | self.CachedStatements = self.CachedStatements or {} 177 | local preparedQuery = self.CachedStatements[str] or self:prepare(str) 178 | self.CachedStatements[str] = preparedQuery 179 | addQueryFunctions(preparedQuery, callback, ...) 180 | setPreparedQueryArguments(preparedQuery, values) 181 | preparedQuery:start() 182 | return preparedQuery 183 | end 184 | 185 | local transaction = {} 186 | local baseTransactionMeta = FindMetaTable("MySQLOO Transaction") or {} -- this ensures backwards compatibility to <=9.6 187 | local transactionMT = {__index = function(tbl, key) 188 | return (transaction[key] or baseTransactionMeta[key]) 189 | end} 190 | 191 | function transaction:Prepare(str, values) 192 | --TODO: Cache queries 193 | local preparedQuery = self._db:prepare(str) 194 | setPreparedQueryArguments(preparedQuery, values) 195 | self:addQuery(preparedQuery) 196 | return preparedQuery 197 | end 198 | function transaction:Query(str) 199 | local query = self._db:query(str) 200 | self:addQuery(query) 201 | return query 202 | end 203 | 204 | function transaction:Start(callback, ...) 205 | local args = {...} 206 | table.insert(args, self) 207 | function self:onSuccess() 208 | table.insert(args, true) 209 | if (callback) then 210 | callback(unpack(args)) 211 | end 212 | end 213 | function self:onError(err) 214 | err = err or "aborted" 215 | table.insert(args, false) 216 | table.insert(args, err) 217 | if (callback) then 218 | callback(unpack(args)) 219 | else 220 | ErrorNoHalt(err) 221 | end 222 | end 223 | self.onAborted = self.onError 224 | self:start() 225 | end 226 | 227 | function db:CreateTransaction() 228 | local transaction = self:createTransaction() 229 | transaction._db = self 230 | setmetatable(transaction, transactionMT) 231 | return transaction 232 | end 233 | -------------------------------------------------------------------------------- /lua/tmysql4.lua: -------------------------------------------------------------------------------- 1 | //Put this into your server's lua/includes/modules/ folder to replace tmysql4 functionality with mysqloo 2 | //This is only a temporary solution and the best would be to change to mysqloo completely 3 | //A few incompatibilities: 4 | //Mysql Bit fields are returned as numbers instead of a single char 5 | //Mysql Bigint fields are returned as numbers 6 | //This might pose a problem if you have a bigint field for steamid64 7 | //Always make sure to cast that field to a string in the SELECT clause of your query 8 | //Example: SELECT CAST(steamid64 as 'CHAR') as steamid64 FROM ... 9 | 10 | require("mysqloo") 11 | if (mysqloo.VERSION != "9") then 12 | error("using outdated mysqloo version") 13 | end 14 | tmysql = tmysql or {} 15 | tmysql.Connections = tmysql.Connections or {} 16 | local database = {} 17 | local baseMeta = FindMetaTable("MySQLOO Database") or {} -- this ensures backwards compatibility to <=9.6 18 | local databaseMT = {__index = function(tbl, key) 19 | return database[key] or baseMeta[key] 20 | end} 21 | 22 | function database:Escape(...) 23 | if (self.Disconnected) then error("database already disconnected") end 24 | return self:escape(...) 25 | end 26 | 27 | function database:Connect() 28 | self:connect() 29 | self:wait() //this is dumb 30 | //Unfortunately mysqloo only passes the error message to a callback 31 | //because waiting for the db to connect is really dumb 32 | //so there is no way to retrieve the actual error message here 33 | if (self:status() != mysqloo.DATABASE_CONNECTED) then 34 | return false, "[TMYSQL Wrapper]: Failed to connect to database" 35 | end 36 | table.insert(tmysql.Connections, self) 37 | return true 38 | end 39 | 40 | function database:Query(str, callback, ...) 41 | if (self.Disconnected) then error("database already disconnected") end 42 | local additionalArgs = {...} 43 | local qu = self:query(str) 44 | if (!callback) then 45 | qu:start() 46 | return 47 | end 48 | qu.onSuccess = function(qu, result) 49 | local results = { 50 | { 51 | status = true, 52 | error = nil, 53 | affected = qu:affectedRows(), 54 | lastid = qu:lastInsert(), 55 | data = result 56 | } 57 | } 58 | while(qu:hasMoreResults()) do 59 | result = qu:getNextResults() 60 | table.insert(results, { 61 | status = true, 62 | error = nil, 63 | affected = qu:affectedRows(), 64 | lastid = qu:lastInsert(), 65 | data = result 66 | }) 67 | end 68 | table.insert(additionalArgs, results) 69 | callback(unpack(additionalArgs)) 70 | end 71 | qu.onAborted = function(qu) 72 | local data = { 73 | status = false, 74 | error = "Query aborted" 75 | } 76 | table.insert(additionalArgs, {data}) 77 | callback(unpack(additionalArgs)) 78 | end 79 | qu.onError = function(qu, err) 80 | local data = { 81 | status = false, 82 | error = err 83 | } 84 | table.insert(additionalArgs, {data}) 85 | callback(unpack(additionalArgs)) 86 | end 87 | qu:start() 88 | end 89 | 90 | function database:SetCharacterSet(name) 91 | return self:setCharacterSet(name) 92 | end 93 | 94 | function database:Disconnect() 95 | if (self.Disconnected) then error("database already disconnected") end 96 | self:abortAllQueries() 97 | table.RemoveByValue(tmysql.Connections, self) 98 | self.Disconnected = true 99 | self:disconnect() 100 | end 101 | 102 | function database:Option(option, value) 103 | if (self.Disconnected) then error("database already disconnected") end 104 | if (option == bit.lshift(1, 16)) then 105 | self:setMultiStatements(tobool(value)) 106 | else 107 | print("[TMYSQL Wrapper]: Unsupported tmysql option") 108 | end 109 | end 110 | 111 | function tmysql.GetTable() 112 | return tmysql.Connections 113 | end 114 | 115 | //Clientflags are ignored, multistatements are always enabled by default 116 | function tmysql.initialize(host, user, password, database, port, unixSocketPath, clientFlags) 117 | local db = mysqloo.connect(host, user, password, database, port, unixSocketPath) 118 | setmetatable(db, databaseMT) 119 | local status, err = db:Connect() 120 | if (!status) then 121 | return nil, err 122 | end 123 | return db, err 124 | end 125 | 126 | function tmysql.Create(host, user, password, database, port, unixSocketPath, clientFlags) 127 | local db = mysqloo.connect(host, user, password, database, port, unixSocketPath) 128 | setmetatable(db, databaseMT) 129 | return db 130 | end 131 | 132 | tmysql.Connect = tmysql.initialize 133 | -------------------------------------------------------------------------------- /minorversion.txt: -------------------------------------------------------------------------------- 1 | 7 -------------------------------------------------------------------------------- /src/BlockingQueue.h: -------------------------------------------------------------------------------- 1 | #ifndef BLOCKING_QUEUE_ 2 | #define BLOCKING_QUEUE_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | class BlockingQueue { 11 | public: 12 | void put(T elem) { 13 | { 14 | std::lock_guard lock(mutex); 15 | backingQueue.push_back(elem); 16 | } 17 | waitObj.notify_all(); 18 | } 19 | 20 | bool empty() { 21 | return size() == 0; 22 | } 23 | 24 | bool swapToFrontIf(std::function func) { 25 | std::lock_guard lock(mutex); 26 | auto pos = std::find_if(backingQueue.begin(), backingQueue.end(), func); 27 | if (pos != backingQueue.begin() && pos != backingQueue.end()) { 28 | std::iter_swap(pos, backingQueue.begin()); 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | bool removeIf(std::function func) { 35 | std::lock_guard lock(mutex); 36 | auto it = std::remove_if(backingQueue.begin(), backingQueue.end(), func); 37 | bool removed = it != backingQueue.end(); 38 | backingQueue.erase(it, backingQueue.end()); 39 | return removed; 40 | } 41 | 42 | void remove(T elem) { 43 | std::lock_guard lock(mutex); 44 | backingQueue.erase(std::remove(backingQueue.begin(), backingQueue.end(), elem), backingQueue.end()); 45 | } 46 | 47 | size_t size() { 48 | std::lock_guard lock(mutex); 49 | return backingQueue.size(); 50 | } 51 | 52 | T take() { 53 | std::unique_lock lock(mutex); 54 | waitObj.wait(lock, [this] { return this->size() > 0; }); 55 | auto front = backingQueue.front(); 56 | backingQueue.pop_front(); 57 | return front; 58 | } 59 | 60 | std::deque clear() { 61 | std::lock_guard lock(mutex); 62 | std::deque returnQueue = backingQueue; 63 | backingQueue.clear(); 64 | return returnQueue; 65 | } 66 | 67 | private: 68 | std::deque backingQueue{}; 69 | std::recursive_mutex mutex{}; 70 | std::condition_variable_any waitObj{}; 71 | }; 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/lua/GMModule.cpp: -------------------------------------------------------------------------------- 1 | #include "GarrysMod/Lua/Interface.h" 2 | #include "../mysql/Database.h" 3 | #include 4 | #include 5 | #include "LuaObject.h" 6 | #include "LuaDatabase.h" 7 | #include "LuaTransaction.h" 8 | #include "LuaQuery.h" 9 | #include "LuaPreparedQuery.h" 10 | 11 | #define MYSQLOO_VERSION "9" 12 | #define MYSQLOO_MINOR_VERSION "7" 13 | 14 | // Variable to hold the reference to the version check ConVar object 15 | static int versionCheckConVar = 0; 16 | 17 | GMOD_MODULE_CLOSE() { 18 | // Free the version check ConVar object reference 19 | if (versionCheckConVar != 0) { 20 | LuaReferenceFree(LUA, versionCheckConVar); 21 | versionCheckConVar = 0; 22 | } 23 | mysql_thread_end(); 24 | mysql_library_end(); 25 | 26 | return 0; 27 | } 28 | 29 | /* Returns the amount of currently allocated objects. 30 | */ 31 | LUA_FUNCTION(objectCount) { 32 | LUA->PushNumber((double) (LuaObject::allocationCount - LuaObject::deallocationCount)); 33 | return 1; 34 | } 35 | 36 | static void runInTimer(GarrysMod::Lua::ILuaBase *LUA, double delay, GarrysMod::Lua::CFunc func) { 37 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 38 | LUA->GetField(-1, "timer"); 39 | //In case someone removes the timer library 40 | if (LUA->IsType(-1, GarrysMod::Lua::Type::Nil)) { 41 | LUA->Pop(2); 42 | return; 43 | } 44 | LUA->GetField(-1, "Simple"); 45 | LUA->PushNumber(delay); 46 | LUA->PushCFunction(func); 47 | LUA->Call(2, 0); 48 | LUA->Pop(2); 49 | } 50 | 51 | static void printMessage(GarrysMod::Lua::ILuaBase *LUA, const char *str, int r, int g, int b) { 52 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 53 | LUA->GetField(-1, "Color"); 54 | LUA->PushNumber(r); 55 | LUA->PushNumber(g); 56 | LUA->PushNumber(b); 57 | LUA->Call(3, 1); 58 | int ref = LuaReferenceCreate(LUA); 59 | LUA->GetField(-1, "MsgC"); 60 | LUA->ReferencePush(ref); 61 | LUA->PushString(str); 62 | LUA->Call(2, 0); 63 | LUA->Pop(); 64 | LuaReferenceFree(LUA, ref); 65 | } 66 | 67 | static int printOutdatedVersion(lua_State *state) { 68 | GarrysMod::Lua::ILuaBase *LUA = state->luabase; 69 | LUA->SetState(state); 70 | printMessage(LUA, "Your server is using an outdated mysqloo9 version\n", 255, 0, 0); 71 | printMessage(LUA, "Download the latest version from here:\n", 255, 0, 0); 72 | printMessage(LUA, "https://github.com/FredyH/MySQLOO/releases\n", 86, 156, 214); 73 | runInTimer(LUA, 3600, printOutdatedVersion); 74 | return 0; 75 | } 76 | 77 | static int fetchSuccessful(lua_State *state) { 78 | GarrysMod::Lua::ILuaBase *LUA = state->luabase; 79 | LUA->SetState(state); 80 | std::string version = LUA->GetString(1); 81 | //version.size() < 3 so that the 404 response gets ignored 82 | if (version != MYSQLOO_MINOR_VERSION && version.size() <= 3) { 83 | printOutdatedVersion(state); 84 | } else { 85 | printMessage(LUA, "Your server is using the latest mysqloo9 version\n", 0, 255, 0); 86 | } 87 | return 0; 88 | } 89 | 90 | static int fetchFailed(lua_State *state) { 91 | GarrysMod::Lua::ILuaBase *LUA = state->luabase; 92 | LUA->SetState(state); 93 | printMessage(LUA, "Failed to retrieve latest version of mysqloo9\n", 255, 0, 0); 94 | return 0; 95 | } 96 | 97 | static int doVersionCheck(lua_State *state) { 98 | GarrysMod::Lua::ILuaBase *LUA = state->luabase; 99 | LUA->SetState(state); 100 | 101 | // Check if the reference to the ConVar object is set 102 | if (versionCheckConVar != 0) { 103 | // Retrieve the value of the ConVar 104 | LUA->ReferencePush(versionCheckConVar); // Push the ConVar object 105 | LUA->GetField(-1, "GetInt"); // Push the name of the function 106 | LUA->ReferencePush(versionCheckConVar); // Push the ConVar object as the first self argument 107 | LUA->Call(1, 1); // Call with 1 argument and 1 return 108 | int versionCheckEnabled = (int) LUA->GetNumber(-1); // Retrieve the returned value 109 | 110 | // Check if the version check convar is set to 1 111 | if (versionCheckEnabled == 1) { 112 | // Execute the HTTP request 113 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 114 | LUA->GetField(-1, "http"); 115 | LUA->GetField(-1, "Fetch"); 116 | LUA->PushString("https://raw.githubusercontent.com/FredyH/MySQLOO/master/minorversion.txt"); 117 | LUA->PushCFunction(fetchSuccessful); 118 | LUA->PushCFunction(fetchFailed); 119 | LUA->PCall(3, 0, 0); 120 | LUA->Pop(2); 121 | } 122 | } 123 | 124 | return 0; 125 | } 126 | 127 | LUA_FUNCTION(allocationCount) { 128 | LUA->PushNumber(LuaObject::allocationCount); 129 | return 1; 130 | } 131 | 132 | LUA_FUNCTION(deallocationCount) { 133 | LUA->PushNumber(LuaObject::deallocationCount); 134 | return 1; 135 | } 136 | 137 | LUA_FUNCTION(referenceCreatedCount) { 138 | LUA->PushNumber((double) LuaObject::referenceCreatedCount); 139 | return 1; 140 | } 141 | 142 | LUA_FUNCTION(referenceFreedCount) { 143 | LUA->PushNumber((double) LuaObject::referenceFreedCount); 144 | return 1; 145 | } 146 | 147 | LUA_FUNCTION(mysqlooThink) { 148 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 149 | LUA->GetField(-1, "mysqloo"); 150 | if (LUA->IsType(-1, GarrysMod::Lua::Type::Nil)) { 151 | LUA->Pop(2); //nil, Global 152 | return 0; 153 | } 154 | LuaDatabase::runAllThinkHooks(LUA); 155 | LUA->Pop(2); //nil, Global 156 | return 0; 157 | } 158 | 159 | GMOD_MODULE_OPEN() { 160 | if (mysql_library_init(0, nullptr, nullptr)) { 161 | LUA->ThrowError("Could not initialize mysql library."); 162 | } 163 | 164 | //Creating MetaTables 165 | LuaObject::createUserDataMetaTable(LUA); 166 | LuaDatabase::createMetaTable(LUA); 167 | LuaQuery::createMetaTable(LUA); 168 | LuaPreparedQuery::createMetaTable(LUA); 169 | LuaTransaction::createMetaTable(LUA); 170 | 171 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 172 | LUA->GetField(-1, "hook"); 173 | LUA->GetField(-1, "Add"); 174 | LUA->PushString("Think"); 175 | LUA->PushString("__MySQLOOThinkHook"); 176 | LUA->PushCFunction(mysqlooThink); 177 | LUA->Call(3, 0); 178 | LUA->Pop(); 179 | LUA->Pop(); 180 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 181 | LUA->CreateTable(); //mysqloo 182 | 183 | LUA->PushString(MYSQLOO_VERSION); 184 | LUA->SetField(-2, "VERSION"); 185 | LUA->PushString(MYSQLOO_MINOR_VERSION); 186 | LUA->SetField(-2, "MINOR_VERSION"); 187 | 188 | LUA->PushNumber(DATABASE_CONNECTED); 189 | LUA->SetField(-2, "DATABASE_CONNECTED"); 190 | LUA->PushNumber(DATABASE_CONNECTING); 191 | LUA->SetField(-2, "DATABASE_CONNECTING"); 192 | LUA->PushNumber(DATABASE_NOT_CONNECTED); 193 | LUA->SetField(-2, "DATABASE_NOT_CONNECTED"); 194 | LUA->PushNumber(DATABASE_CONNECTION_FAILED); 195 | LUA->SetField(-2, "DATABASE_CONNECTION_FAILED"); 196 | 197 | LUA->PushNumber(QUERY_NOT_RUNNING); 198 | LUA->SetField(-2, "QUERY_NOT_RUNNING"); 199 | LUA->PushNumber(QUERY_RUNNING); 200 | LUA->SetField(-2, "QUERY_RUNNING"); 201 | LUA->PushNumber(QUERY_COMPLETE); 202 | LUA->SetField(-2, "QUERY_COMPLETE"); 203 | LUA->PushNumber(QUERY_ABORTED); 204 | LUA->SetField(-2, "QUERY_ABORTED"); 205 | LUA->PushNumber(QUERY_WAITING); 206 | LUA->SetField(-2, "QUERY_WAITING"); 207 | 208 | LUA->PushNumber(OPTION_NUMERIC_FIELDS); 209 | LUA->SetField(-2, "OPTION_NUMERIC_FIELDS"); 210 | LUA->PushNumber(OPTION_INTERPRET_DATA); 211 | LUA->SetField(-2, "OPTION_INTERPRET_DATA"); //Not used anymore 212 | LUA->PushNumber(OPTION_NAMED_FIELDS); 213 | LUA->SetField(-2, "OPTION_NAMED_FIELDS"); //Not used anymore 214 | LUA->PushNumber(OPTION_CACHE); 215 | LUA->SetField(-2, "OPTION_CACHE"); //Not used anymore 216 | 217 | LUA->PushCFunction(LuaDatabase::create); 218 | LUA->SetField(-2, "connect"); 219 | 220 | //Debug/testing functions 221 | LUA->PushCFunction(objectCount); 222 | LUA->SetField(-2, "objectCount"); 223 | LUA->PushCFunction(allocationCount); 224 | LUA->SetField(-2, "allocationCount"); 225 | LUA->PushCFunction(deallocationCount); 226 | LUA->SetField(-2, "deallocationCount"); 227 | LUA->PushCFunction(referenceFreedCount); 228 | LUA->SetField(-2, "referenceFreedCount"); 229 | LUA->PushCFunction(referenceCreatedCount); 230 | LUA->SetField(-2, "referenceCreatedCount"); 231 | 232 | 233 | LuaDatabase::createWeakTable(LUA); 234 | 235 | LUA->SetField(-2, "mysqloo"); 236 | LUA->Pop(); 237 | 238 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); // Push the global table 239 | // Create the version check ConVar 240 | LUA->GetField(-1, "CreateConVar"); 241 | LUA->PushString("sv_mysqloo_versioncheck"); // Name 242 | LUA->PushString("1"); // Default value 243 | LUA->PushNumber(128); // FCVAR flags 244 | LUA->PushString("Enable or disable the MySQLOO update checker."); // Help text 245 | LUA->PushNumber(0); // Min value 246 | LUA->PushNumber(1); // Max value 247 | LUA->Call(6, 1); // Call with 6 arguments and 1 result 248 | versionCheckConVar = LuaReferenceCreate(LUA); // Store the created ConVar object as a global variable 249 | LUA->Pop(); // Pop the global table 250 | 251 | runInTimer(LUA, 5, doVersionCheck); 252 | 253 | return 1; 254 | } 255 | -------------------------------------------------------------------------------- /src/lua/LuaDatabase.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MYSQLOO_LUADATABASE_H 3 | #define MYSQLOO_LUADATABASE_H 4 | 5 | 6 | #include "../mysql/Database.h" 7 | 8 | #include 9 | #include "LuaObject.h" 10 | 11 | class LuaDatabase : public LuaObject { 12 | public: 13 | static void createMetaTable(ILuaBase *LUA); 14 | 15 | static int create(lua_State *L); 16 | 17 | void think(ILuaBase *LUA); 18 | 19 | int m_tableReference = 0; 20 | bool m_hasOnDisconnected = false; 21 | std::shared_ptr m_database; 22 | bool m_dbCallbackRan = false; 23 | 24 | void onDestroyedByLua(ILuaBase *LUA) override; 25 | 26 | explicit LuaDatabase(std::shared_ptr database) : LuaObject("Database"), 27 | m_database(std::move(database)) { 28 | } 29 | 30 | static void createWeakTable(ILuaBase *LUA); 31 | static void runAllThinkHooks(ILuaBase *LUA); 32 | }; 33 | 34 | 35 | #endif //MYSQLOO_LUADATABASE_H 36 | -------------------------------------------------------------------------------- /src/lua/LuaIQuery.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "LuaIQuery.h" 3 | #include "LuaQuery.h" 4 | #include "LuaTransaction.h" 5 | #include "LuaDatabase.h" 6 | 7 | 8 | MYSQLOO_LUA_FUNCTION(start) { 9 | auto query = LuaIQuery::getLuaObject(LUA); 10 | auto queryData = query->buildQueryData(LUA, 1, true); 11 | query->m_query->start(queryData); 12 | return 0; 13 | } 14 | 15 | MYSQLOO_LUA_FUNCTION(error) { 16 | auto query = LuaIQuery::getLuaObject(LUA); 17 | LUA->PushString(query->m_query->error().c_str()); 18 | return 1; 19 | } 20 | 21 | MYSQLOO_LUA_FUNCTION(wait) { 22 | bool shouldSwap = false; 23 | if (LUA->IsType(2, GarrysMod::Lua::Type::Bool)) { 24 | shouldSwap = LUA->GetBool(2); 25 | } 26 | auto query = LuaIQuery::getLuaObject(LUA); 27 | query->m_query->wait(shouldSwap); 28 | if (query->m_databaseReference != 0) { 29 | LUA->ReferencePush(query->m_databaseReference); 30 | auto database = LuaObject::getLuaObject(LUA, -1); 31 | database->think(LUA); 32 | LUA->Pop(); 33 | } 34 | 35 | return 0; 36 | } 37 | 38 | MYSQLOO_LUA_FUNCTION(setOption) { 39 | auto query = LuaIQuery::getLuaObject(LUA); 40 | LUA->CheckType(2, GarrysMod::Lua::Type::Number); 41 | bool set = true; 42 | int option = (int) LUA->GetNumber(2); 43 | 44 | if (LUA->Top() >= 3) { 45 | LUA->CheckType(3, GarrysMod::Lua::Type::Bool); 46 | set = LUA->GetBool(3); 47 | } 48 | query->m_query->setOption(option, set); 49 | return 0; 50 | } 51 | 52 | MYSQLOO_LUA_FUNCTION(isRunning) { 53 | auto query = LuaIQuery::getLuaObject(LUA); 54 | LUA->PushBool(query->m_query->isRunning()); 55 | return 1; 56 | } 57 | 58 | MYSQLOO_LUA_FUNCTION(abort) { 59 | auto query = LuaIQuery::getLuaObject(LUA); 60 | auto abortedData = query->m_query->abort(); 61 | for (auto &data: abortedData) { 62 | LuaIQuery::runAbortedCallback(LUA, data); 63 | LuaIQuery::finishQueryData(LUA, query->m_query, data); 64 | } 65 | LUA->PushBool(!abortedData.empty()); 66 | return 1; 67 | } 68 | 69 | void LuaIQuery::runAbortedCallback(ILuaBase *LUA, const std::shared_ptr &data) { 70 | if (data->m_tableReference == 0) return; 71 | 72 | if (!LuaIQuery::pushCallbackReference(LUA, data->m_abortReference, data->m_tableReference, 73 | "onAborted", data->isFirstData())) { 74 | return; 75 | } 76 | LUA->ReferencePush(data->m_tableReference); 77 | LuaObject::pcallWithErrorReporter(LUA, 1); 78 | } 79 | 80 | void LuaIQuery::runErrorCallback(ILuaBase *LUA, const std::shared_ptr &iQuery, const std::shared_ptr &data) { 81 | if (data->m_tableReference == 0) return; 82 | 83 | if (!LuaIQuery::pushCallbackReference(LUA, data->m_errorReference, data->m_tableReference, 84 | "onError", data->isFirstData())) { 85 | return; 86 | } 87 | LUA->ReferencePush(data->m_tableReference); 88 | auto error = data->getError(); 89 | LUA->PushString(error.c_str()); 90 | LUA->PushString(iQuery->getSQLString().c_str()); 91 | LuaObject::pcallWithErrorReporter(LUA, 3); 92 | } 93 | 94 | void LuaIQuery::addMetaTableFunctions(ILuaBase *LUA) { 95 | LuaObject::addMetaTableFunctions(LUA); 96 | 97 | LUA->PushCFunction(start); 98 | LUA->SetField(-2, "start"); 99 | LUA->PushCFunction(error); 100 | LUA->SetField(-2, "error"); 101 | LUA->PushCFunction(wait); 102 | LUA->SetField(-2, "wait"); 103 | LUA->PushCFunction(setOption); 104 | LUA->SetField(-2, "setOption"); 105 | LUA->PushCFunction(isRunning); 106 | LUA->SetField(-2, "isRunning"); 107 | LUA->PushCFunction(abort); 108 | LUA->SetField(-2, "abort"); 109 | } 110 | 111 | void LuaIQuery::referenceCallbacks(ILuaBase *LUA, int stackPosition, IQueryData &data) { 112 | LUA->Push(stackPosition); 113 | data.m_tableReference = LuaReferenceCreate(LUA); 114 | 115 | if (data.m_successReference == 0) { 116 | data.m_successReference = getFunctionReference(LUA, stackPosition, "onSuccess"); 117 | } 118 | 119 | if (data.m_abortReference == 0) { 120 | data.m_abortReference = getFunctionReference(LUA, stackPosition, "onAborted"); 121 | } 122 | 123 | if (data.m_onDataReference == 0) { 124 | data.m_onDataReference = getFunctionReference(LUA, stackPosition, "onData"); 125 | } 126 | 127 | if (data.m_errorReference == 0) { 128 | data.m_errorReference = getFunctionReference(LUA, stackPosition, "onError"); 129 | } 130 | } 131 | 132 | void LuaIQuery::finishQueryData(GarrysMod::Lua::ILuaBase *LUA, const std::shared_ptr &query, const std::shared_ptr &data) { 133 | query->finishQueryData(data); 134 | if (data->m_tableReference) { 135 | LuaReferenceFree(LUA, data->m_tableReference); 136 | } 137 | if (data->m_onDataReference) { 138 | LuaReferenceFree(LUA, data->m_onDataReference); 139 | } 140 | if (data->m_errorReference) { 141 | LuaReferenceFree(LUA, data->m_errorReference); 142 | } 143 | if (data->m_abortReference) { 144 | LuaReferenceFree(LUA, data->m_abortReference); 145 | } 146 | if (data->m_successReference) { 147 | LuaReferenceFree(LUA, data->m_successReference); 148 | } 149 | data->m_onDataReference = 0; 150 | data->m_errorReference = 0; 151 | data->m_abortReference = 0; 152 | data->m_successReference = 0; 153 | data->m_tableReference = 0; 154 | } 155 | 156 | void LuaIQuery::runCallback(ILuaBase *LUA, const std::shared_ptr &iQuery, const std::shared_ptr &data) { 157 | iQuery->setCallbackData(data); 158 | 159 | auto status = data->getResultStatus(); 160 | switch (status) { 161 | case QUERY_NONE: 162 | break; //Should not happen 163 | case QUERY_ERROR: 164 | runErrorCallback(LUA, iQuery, data); 165 | break; 166 | case QUERY_SUCCESS: 167 | if (auto query = std::dynamic_pointer_cast(iQuery)) { 168 | LuaQuery::runSuccessCallback(LUA, query, std::dynamic_pointer_cast(data)); 169 | } else if (auto transaction = std::dynamic_pointer_cast(iQuery)) { 170 | LuaTransaction::runSuccessCallback(LUA, transaction, std::dynamic_pointer_cast(data)); 171 | } 172 | break; 173 | } 174 | 175 | LuaIQuery::finishQueryData(LUA, iQuery, data); 176 | } 177 | 178 | void LuaIQuery::onDestroyedByLua(ILuaBase *LUA) { 179 | if (m_databaseReference != 0) { 180 | LuaReferenceFree(LUA, m_databaseReference); 181 | m_databaseReference = 0; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/lua/LuaIQuery.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MYSQLOO_LUAIQUERY_H 3 | #define MYSQLOO_LUAIQUERY_H 4 | 5 | #include "LuaObject.h" 6 | #include "../mysql/IQuery.h" 7 | #include "../mysql/Query.h" 8 | 9 | #include 10 | 11 | class LuaIQuery : public LuaObject { 12 | public: 13 | static void addMetaTableFunctions(ILuaBase *lua); 14 | 15 | std::shared_ptr m_query; 16 | //Used to ensure that the database stays alive while the query exists. 17 | //This needs to be the case because we can do qu:start() on the query even if the database 18 | //has already gone out of scope in lua. Also, the callbacks need to be run using the Database's think hook 19 | //even after the database is out of scope in lua code. 20 | int m_databaseReference = 0; 21 | 22 | //The table is at the top 23 | virtual std::shared_ptr buildQueryData(ILuaBase *LUA, int stackPosition, bool shouldRef) = 0; 24 | 25 | static void referenceCallbacks(ILuaBase *LUA, int stackPosition, IQueryData &data); 26 | 27 | static void runAbortedCallback(ILuaBase *LUA, const std::shared_ptr &data); 28 | 29 | static void runErrorCallback(ILuaBase *LUA, const std::shared_ptr &iQuery, const std::shared_ptr &data); 30 | 31 | static void runCallback(ILuaBase *LUA, const std::shared_ptr &query, const std::shared_ptr &data); 32 | 33 | static void finishQueryData(ILuaBase *LUA, const std::shared_ptr &query, const std::shared_ptr &data); 34 | 35 | void onDestroyedByLua(ILuaBase *LUA) override; 36 | 37 | explicit LuaIQuery(std::shared_ptr query, std::string className, int databaseRef) : LuaObject( 38 | std::move(className)), m_query(std::move(query)), m_databaseReference(databaseRef) {} 39 | }; 40 | 41 | #endif //MYSQLOO_LUAIQUERY_H 42 | -------------------------------------------------------------------------------- /src/lua/LuaObject.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "LuaObject.h" 3 | #include "LuaDatabase.h" 4 | #include 5 | 6 | int LuaObject::TYPE_USERDATA = 0; 7 | int LuaObject::TYPE_DATABASE = 0; 8 | int LuaObject::TYPE_QUERY = 0; 9 | int LuaObject::TYPE_TRANSACTION = 0; 10 | int LuaObject::TYPE_PREPARED_QUERY = 0; 11 | 12 | std::atomic_long LuaObject::allocationCount= { 0 }; 13 | std::atomic_long LuaObject::deallocationCount = { 0 }; 14 | 15 | LUA_FUNCTION(luaObjectGc) { 16 | auto luaObject = LUA->GetUserType(1, LuaObject::TYPE_USERDATA); 17 | luaObject->onDestroyedByLua(LUA); 18 | 19 | delete luaObject; 20 | 21 | //After this function this object will be deleted. 22 | //For the Database this might cause the database thread to join 23 | //But the database can only be destroyed if no queries for it exist, i.e. joining 24 | //should always work instantly, unless the server is changing maps, in which case we want it to wait. 25 | return 0; 26 | } 27 | 28 | void LuaObject::createUserDataMetaTable(GarrysMod::Lua::ILuaBase *LUA) { 29 | TYPE_USERDATA = LUA->CreateMetaTable("MySQLOO UserData"); 30 | LUA->PushCFunction(luaObjectGc); 31 | LUA->SetField(-2, "__gc"); 32 | LUA->Pop(); 33 | } 34 | 35 | std::string LuaObject::toString() { 36 | std::stringstream ss; 37 | ss << m_className << " " << this; 38 | return ss.str(); 39 | } 40 | 41 | LUA_FUNCTION(luaObjectToString) { 42 | LUA->GetUserType(1, LuaObject::TYPE_USERDATA); 43 | auto luaObject = LuaObject::getLuaObject(LUA); 44 | auto str = luaObject->toString(); 45 | LUA->PushString(str.c_str()); 46 | return 1; 47 | } 48 | 49 | void LuaObject::addMetaTableFunctions(GarrysMod::Lua::ILuaBase *LUA) { 50 | LUA->Push(-1); 51 | LUA->SetField(-2, "__index"); 52 | 53 | LUA->PushCFunction(luaObjectToString); 54 | LUA->SetField(-2, "__tostring"); 55 | } 56 | 57 | LUA_FUNCTION(errorReporter) { 58 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 59 | LUA->GetField(-1, "debug"); 60 | LUA->GetField(-1, "traceback"); 61 | 62 | if (LUA->IsType(-1, GarrysMod::Lua::Type::Function)) { 63 | LUA->Push(1); 64 | LUA->Call(1, 1); //The resulting stack trace will be returned 65 | } else { 66 | LUA->PushString("MySQLOO Error"); 67 | } 68 | return 1; 69 | } 70 | 71 | /** 72 | * Similar to LUA->PCall but also uses an error reporter and prints the 73 | * error to the console using ErrorNoHalt (if it exists). 74 | * Consumes the function and all nargs arguments on the stack, does not return any values. 75 | */ 76 | void LuaObject::pcallWithErrorReporter(ILuaBase *LUA, int nargs) { 77 | LUA->PushCFunction(errorReporter); 78 | int errorHandlerIndex = LUA->Top() - nargs - 1; 79 | LUA->Insert(errorHandlerIndex); 80 | int pcallResult = LUA->PCall(nargs, 0, errorHandlerIndex); 81 | if (pcallResult == 2) { //LUA_ERRRUN, we now have a stack trace on the stack 82 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 83 | LUA->GetField(-1, "ErrorNoHalt"); 84 | if (LUA->IsType(-1, GarrysMod::Lua::Type::Function)) { 85 | LUA->Push(-3); //Stack trace 86 | LUA->Call(1, 0); 87 | LUA->Pop(2); //Stack trace, global 88 | } else { 89 | LUA->Pop(3); //Stack trace, global, nil 90 | } 91 | } 92 | LUA->Pop(); //Error reporter 93 | } 94 | 95 | bool LuaObject::pushCallbackReference(ILuaBase *LUA, int functionReference, int tableReference, 96 | const std::string &callbackName, bool allowCallback) { 97 | //Push function reference 98 | if (functionReference != 0) { 99 | LUA->ReferencePush(functionReference); 100 | return true; 101 | } else if (allowCallback && tableReference != 0) { 102 | LUA->ReferencePush(tableReference); 103 | LUA->GetField(-1, callbackName.c_str()); 104 | LUA->Remove(LUA->Top() - 2); 105 | if (LUA->IsType(-1, GarrysMod::Lua::Type::Function)) { 106 | return true; 107 | } else { 108 | LUA->Pop(1); //The field that is either nil or some other weird thing 109 | return false; 110 | } 111 | } else { 112 | return false; 113 | } 114 | } 115 | 116 | 117 | int LuaObject::getFunctionReference(ILuaBase *LUA, int stackPosition, const char* fieldName) { 118 | LUA->GetField(stackPosition, fieldName); 119 | int reference = 0; 120 | if (LUA->IsType(-1, GarrysMod::Lua::Type::Function)) { 121 | reference = LuaReferenceCreate(LUA); 122 | } else { 123 | LUA->Pop(); 124 | } 125 | return reference; 126 | } 127 | 128 | uint64_t LuaObject::referenceCreatedCount = 0; 129 | uint64_t LuaObject::referenceFreedCount = 0; 130 | 131 | int LuaReferenceCreate(GarrysMod::Lua::ILuaBase *LUA) { 132 | LuaObject::referenceCreatedCount++; 133 | return LUA->ReferenceCreate(); 134 | } 135 | 136 | void LuaReferenceFree(GarrysMod::Lua::ILuaBase *LUA, int ref) { 137 | LuaObject::referenceFreedCount++; 138 | LUA->ReferenceFree(ref); 139 | } -------------------------------------------------------------------------------- /src/lua/LuaObject.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef MYSQLOO_LUAOBJECT_H 4 | #define MYSQLOO_LUAOBJECT_H 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "GarrysMod/Lua/Interface.h" 12 | #include "../mysql/MySQLOOException.h" 13 | #include "GarrysMod/Lua/LuaBase.h" 14 | 15 | 16 | #include 17 | 18 | class LuaDatabase; 19 | 20 | using namespace GarrysMod::Lua; 21 | 22 | class LuaObject { 23 | public: 24 | explicit LuaObject(std::string className) : m_className(std::move(className)) { 25 | allocationCount++; 26 | } 27 | 28 | virtual ~LuaObject() { 29 | deallocationCount++; 30 | } 31 | 32 | std::string toString(); 33 | 34 | virtual void onDestroyedByLua(ILuaBase *LUA) {}; 35 | 36 | static int TYPE_USERDATA; 37 | static int TYPE_DATABASE; 38 | static int TYPE_QUERY; 39 | static int TYPE_PREPARED_QUERY; 40 | static int TYPE_TRANSACTION; 41 | 42 | static void addMetaTableFunctions(ILuaBase *LUA); 43 | 44 | static void createUserDataMetaTable(ILuaBase *lua); 45 | 46 | //Lua functions 47 | 48 | static void pcallWithErrorReporter(ILuaBase *LUA, int nargs); 49 | 50 | static bool pushCallbackReference(ILuaBase *LUA, int functionReference, int tableReference, 51 | const std::string &callbackName, bool allowCallback); 52 | 53 | template 54 | static T *getLuaObject(ILuaBase *LUA, int stackPos = 1) { 55 | LUA->CheckType(stackPos, GarrysMod::Lua::Type::Table); 56 | LUA->GetField(stackPos, "__CppObject"); 57 | 58 | auto *luaObject = LUA->GetUserType(-1, TYPE_USERDATA); 59 | if (luaObject == nullptr) { 60 | LUA->ThrowError("[MySQLOO] Expected MySQLOO table"); 61 | } 62 | T *returnValue = dynamic_cast(luaObject); 63 | if (returnValue == nullptr) { 64 | LUA->ThrowError("[MySQLOO] Invalid CPP Object"); 65 | } 66 | LUA->Pop(); //__CppObject 67 | return returnValue; 68 | } 69 | 70 | static int getFunctionReference(ILuaBase *LUA, int stackPosition, const char *fieldName); 71 | static std::atomic_long allocationCount; 72 | static std::atomic_long deallocationCount; 73 | static uint64_t referenceCreatedCount; 74 | static uint64_t referenceFreedCount; 75 | protected: 76 | std::string m_className; 77 | }; 78 | 79 | int LuaReferenceCreate(GarrysMod::Lua::ILuaBase *LUA); 80 | 81 | void LuaReferenceFree(GarrysMod::Lua::ILuaBase *LUA, int ref); 82 | 83 | 84 | #define MYSQLOO_LUA_FUNCTION(FUNC) \ 85 | static int FUNC##__Imp( GarrysMod::Lua::ILuaBase* LUA ); \ 86 | static int FUNC( lua_State* L ) \ 87 | { \ 88 | GarrysMod::Lua::ILuaBase* LUA = L->luabase; \ 89 | LUA->SetState(L); \ 90 | try { \ 91 | return FUNC##__Imp( LUA ); \ 92 | } catch (const MySQLOOException& error) { \ 93 | LUA->ThrowError(error.message.c_str()); \ 94 | return 0; \ 95 | } \ 96 | } \ 97 | static int FUNC##__Imp( GarrysMod::Lua::ILuaBase* LUA ) 98 | 99 | #define LUA_CLASS_FUNCTION(CLASS, FUNC) \ 100 | static int FUNC##__Imp( GarrysMod::Lua::ILuaBase* LUA ); \ 101 | int CLASS::FUNC( lua_State* L ) \ 102 | { \ 103 | GarrysMod::Lua::ILuaBase* LUA = L->luabase; \ 104 | LUA->SetState(L); \ 105 | try { \ 106 | return FUNC##__Imp( LUA ); \ 107 | } catch (const MySQLOOException& error) { \ 108 | LUA->ThrowError(error.message.c_str()); \ 109 | return 0; \ 110 | } \ 111 | } \ 112 | static int FUNC##__Imp( GarrysMod::Lua::ILuaBase* LUA ) 113 | 114 | 115 | #endif //MYSQLOO_LUAOBJECT_H 116 | -------------------------------------------------------------------------------- /src/lua/LuaPreparedQuery.cpp: -------------------------------------------------------------------------------- 1 | #include "LuaPreparedQuery.h" 2 | 3 | MYSQLOO_LUA_FUNCTION(setNumber) { 4 | auto luaQuery = LuaObject::getLuaObject(LUA); 5 | auto query = (PreparedQuery *) luaQuery->m_query.get(); 6 | LUA->CheckType(2, GarrysMod::Lua::Type::Number); 7 | LUA->CheckType(3, GarrysMod::Lua::Type::Number); 8 | double index = LUA->GetNumber(2); 9 | auto uIndex = (unsigned int) index; 10 | double value = LUA->GetNumber(3); 11 | 12 | query->setNumber(uIndex, value); 13 | return 0; 14 | } 15 | 16 | MYSQLOO_LUA_FUNCTION(setString) { 17 | auto luaQuery = LuaObject::getLuaObject(LUA); 18 | auto query = (PreparedQuery *) luaQuery->m_query.get(); 19 | LUA->CheckType(2, GarrysMod::Lua::Type::Number); 20 | LUA->CheckType(3, GarrysMod::Lua::Type::String); 21 | double index = LUA->GetNumber(2); 22 | unsigned int length = 0; 23 | const char *string = LUA->GetString(3, &length); 24 | auto uIndex = (unsigned int) index; 25 | query->setString(uIndex, std::string(string, length)); 26 | return 0; 27 | } 28 | 29 | MYSQLOO_LUA_FUNCTION(setBoolean) { 30 | auto luaQuery = LuaObject::getLuaObject(LUA); 31 | auto query = (PreparedQuery *) luaQuery->m_query.get(); 32 | LUA->CheckType(2, GarrysMod::Lua::Type::Number); 33 | LUA->CheckType(3, GarrysMod::Lua::Type::Bool); 34 | double index = LUA->GetNumber(2); 35 | auto uIndex = (unsigned int) index; 36 | bool value = LUA->GetBool(3); 37 | query->setBoolean(uIndex, value); 38 | return 0; 39 | } 40 | 41 | MYSQLOO_LUA_FUNCTION(setNull) { 42 | auto luaQuery = LuaObject::getLuaObject(LUA); 43 | auto query = (PreparedQuery *) luaQuery->m_query.get(); 44 | LUA->CheckType(2, GarrysMod::Lua::Type::Number); 45 | double index = LUA->GetNumber(2); 46 | auto uIndex = (unsigned int) index; 47 | query->setNull(uIndex); 48 | return 0; 49 | } 50 | 51 | MYSQLOO_LUA_FUNCTION(putNewParameters) { 52 | auto luaQuery = LuaObject::getLuaObject(LUA); 53 | auto query = (PreparedQuery *) luaQuery->m_query.get(); 54 | query->putNewParameters(); 55 | return 0; 56 | } 57 | 58 | MYSQLOO_LUA_FUNCTION(clearParameters) { 59 | auto luaQuery = LuaObject::getLuaObject(LUA); 60 | auto query = (PreparedQuery *) luaQuery->m_query.get(); 61 | query->clearParameters(); 62 | return 0; 63 | } 64 | 65 | void LuaPreparedQuery::createMetaTable(ILuaBase *LUA) { 66 | LuaIQuery::TYPE_PREPARED_QUERY = LUA->CreateMetaTable("MySQLOO Prepared Query"); 67 | LuaQuery::addMetaTableFunctions(LUA); 68 | 69 | LUA->PushCFunction(setNumber); 70 | LUA->SetField(-2, "setNumber"); 71 | LUA->PushCFunction(setString); 72 | LUA->SetField(-2, "setString"); 73 | LUA->PushCFunction(setBoolean); 74 | LUA->SetField(-2, "setBoolean"); 75 | LUA->PushCFunction(setNull); 76 | LUA->SetField(-2, "setNull"); 77 | LUA->PushCFunction(putNewParameters); 78 | LUA->SetField(-2, "putNewParameters"); 79 | LUA->PushCFunction(clearParameters); 80 | LUA->SetField(-2, "clearParameters"); 81 | 82 | LUA->Pop(); //Metatable 83 | } 84 | 85 | std::shared_ptr LuaPreparedQuery::buildQueryData(ILuaBase* LUA, int stackPosition, bool shouldRef) { 86 | auto query = (PreparedQuery*) m_query.get(); 87 | auto data = query->buildQueryData(); 88 | if (shouldRef) { 89 | LuaIQuery::referenceCallbacks(LUA, stackPosition, *data); 90 | } 91 | return data; 92 | } 93 | -------------------------------------------------------------------------------- /src/lua/LuaPreparedQuery.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MYSQLOO_LUAPREPAREDQUERY_H 3 | #define MYSQLOO_LUAPREPAREDQUERY_H 4 | 5 | 6 | #include "LuaQuery.h" 7 | #include "../mysql/PreparedQuery.h" 8 | 9 | class LuaPreparedQuery : public LuaQuery { 10 | public: 11 | std::shared_ptr buildQueryData(ILuaBase *LUA, int stackPosition, bool shouldRef) override; 12 | 13 | static void createMetaTable(ILuaBase *LUA); 14 | 15 | explicit LuaPreparedQuery(const std::shared_ptr &query, int databaseRef) : LuaQuery( 16 | std::dynamic_pointer_cast(query), "MySQLOO Prepared Query", databaseRef) { 17 | 18 | } 19 | }; 20 | 21 | 22 | #endif //MYSQLOO_LUAPREPAREDQUERY_H 23 | -------------------------------------------------------------------------------- /src/lua/LuaQuery.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "LuaQuery.h" 3 | 4 | //Function that converts the data stored in a mysql field into a lua type 5 | //Expects the row table to be at the top of the stack at the start of this function 6 | //Adds a column to the row table 7 | static void dataToLua(Query &query, 8 | GarrysMod::Lua::ILuaBase *LUA, unsigned int column, 9 | std::string &columnValue, const char *columnName, int columnType, bool isNull) { 10 | if (query.hasOption(OPTION_NUMERIC_FIELDS)) { 11 | LUA->PushNumber(column); 12 | } 13 | if (isNull) { 14 | LUA->PushNil(); 15 | } else { 16 | switch (columnType) { 17 | case MYSQL_TYPE_FLOAT: 18 | case MYSQL_TYPE_DOUBLE: 19 | case MYSQL_TYPE_LONGLONG: 20 | case MYSQL_TYPE_LONG: 21 | case MYSQL_TYPE_INT24: 22 | case MYSQL_TYPE_TINY: 23 | case MYSQL_TYPE_SHORT: 24 | LUA->PushNumber(atof(columnValue.c_str())); 25 | break; 26 | case MYSQL_TYPE_BIT: 27 | LUA->PushNumber(static_cast(columnValue[0])); 28 | break; 29 | case MYSQL_TYPE_NULL: 30 | LUA->PushNil(); 31 | break; 32 | default: 33 | LUA->PushString(columnValue.c_str(), (unsigned int) columnValue.length()); 34 | break; 35 | } 36 | } 37 | if (query.hasOption(OPTION_NUMERIC_FIELDS)) { 38 | LUA->SetTable(-3); 39 | } else { 40 | LUA->SetField(-2, columnName); 41 | } 42 | } 43 | 44 | //Stores the data associated with the current result set of the query 45 | //Only called once per result set (and then cached) 46 | int LuaQuery::createDataReference(GarrysMod::Lua::ILuaBase *LUA, Query &query, QueryData &data) { 47 | if (query.m_dataReference != 0) 48 | return query.m_dataReference; 49 | LUA->CreateTable(); 50 | int dataStackPosition = LUA->Top(); 51 | if (query.hasCallbackData() && data.hasMoreResults()) { 52 | ResultData ¤tData = data.getResult(); 53 | for (unsigned int i = 0; i < currentData.getRows().size(); i++) { 54 | ResultDataRow &row = currentData.getRows()[i]; 55 | LUA->CreateTable(); 56 | int rowStackPosition = LUA->Top(); 57 | for (unsigned int j = 0; j < row.getValues().size(); j++) { 58 | dataToLua(query, LUA, j + 1, row.getValues()[j], currentData.getColumns()[j].c_str(), 59 | currentData.getColumnTypes()[j], row.isFieldNull(j)); 60 | } 61 | LUA->Push(dataStackPosition); 62 | LUA->PushNumber(i + 1); 63 | LUA->Push(rowStackPosition); 64 | LUA->SetTable(-3); 65 | LUA->Pop(2); //data + row 66 | } 67 | } 68 | query.m_dataReference = LuaReferenceCreate(LUA); 69 | return query.m_dataReference; 70 | } 71 | 72 | static void runOnDataCallbacks( 73 | ILuaBase *LUA, 74 | const std::shared_ptr &query, 75 | const std::shared_ptr &data, 76 | int dataReference 77 | ) { 78 | if (!LuaIQuery::pushCallbackReference(LUA, data->m_onDataReference, data->m_tableReference, 79 | "onData", data->isFirstData())) { 80 | return; 81 | } 82 | int callbackPosition = LUA->Top(); 83 | int index = 1; 84 | LUA->ReferencePush(dataReference); 85 | while (true) { 86 | LUA->PushNumber(index++); 87 | LUA->RawGet(-2); 88 | if (LUA->GetType(-1) == GarrysMod::Lua::Type::Nil) { 89 | LUA->Pop(); //Nil 90 | break; 91 | } 92 | int rowPosition = LUA->Top(); 93 | LUA->Push(callbackPosition); 94 | LUA->ReferencePush(data->m_tableReference); 95 | LUA->Push(rowPosition); 96 | LuaObject::pcallWithErrorReporter(LUA, 2); 97 | 98 | LUA->Pop(); //Row 99 | } 100 | 101 | LUA->Pop(2); //Callback, data 102 | } 103 | 104 | 105 | void LuaQuery::runSuccessCallback(ILuaBase *LUA, const std::shared_ptr& query, const std::shared_ptr &data) { 106 | //Need to clear old data, if it exists 107 | freeDataReference(LUA, *query); 108 | int dataReference = LuaQuery::createDataReference(LUA, *query, *data); 109 | runOnDataCallbacks(LUA, query, data, dataReference); 110 | 111 | if (!LuaIQuery::pushCallbackReference(LUA, data->m_successReference, data->m_tableReference, 112 | "onSuccess", data->isFirstData())) { 113 | return; 114 | } 115 | LUA->ReferencePush(data->m_tableReference); 116 | LUA->ReferencePush(dataReference); 117 | LuaObject::pcallWithErrorReporter(LUA, 2); 118 | freeDataReference(LUA, *query); //Only cache data for duration of callback 119 | } 120 | 121 | MYSQLOO_LUA_FUNCTION(affectedRows) { 122 | auto luaQuery = LuaQuery::getLuaObject(LUA); 123 | auto query = (Query *) luaQuery->m_query.get(); 124 | LUA->PushNumber((double) query->affectedRows()); 125 | return 1; 126 | } 127 | 128 | MYSQLOO_LUA_FUNCTION(lastInsert) { 129 | auto luaQuery = LuaQuery::getLuaObject(LUA); 130 | auto query = (Query *) luaQuery->m_query.get(); 131 | LUA->PushNumber((double) query->lastInsert()); 132 | return 1; 133 | } 134 | 135 | MYSQLOO_LUA_FUNCTION(getData) { 136 | auto luaQuery = LuaQuery::getLuaObject(LUA); 137 | auto query = std::dynamic_pointer_cast(luaQuery->m_query); 138 | if (!query->hasCallbackData() || query->callbackQueryData->getResultStatus() == QUERY_ERROR) { 139 | LUA->PushNil(); 140 | } else { 141 | int ref = LuaQuery::createDataReference(LUA, *query, (QueryData &) *(query->callbackQueryData)); 142 | LUA->ReferencePush(ref); 143 | } 144 | return 1; 145 | } 146 | 147 | MYSQLOO_LUA_FUNCTION(hasMoreResults) { 148 | auto luaQuery = LuaQuery::getLuaObject(LUA); 149 | auto query = (Query *) luaQuery->m_query.get(); 150 | LUA->PushBool(query->hasMoreResults()); 151 | return 1; 152 | } 153 | 154 | LUA_FUNCTION(getNextResults) { 155 | auto luaQuery = LuaQuery::getLuaObject(LUA); 156 | auto query = std::dynamic_pointer_cast(luaQuery->m_query); 157 | LuaQuery::freeDataReference(LUA, *query); 158 | query->getNextResults(); 159 | return 0; 160 | } 161 | 162 | void LuaQuery::addMetaTableFunctions(ILuaBase *LUA) { 163 | LuaIQuery::addMetaTableFunctions(LUA); 164 | 165 | LUA->PushCFunction(affectedRows); 166 | LUA->SetField(-2, "affectedRows"); 167 | LUA->PushCFunction(lastInsert); 168 | LUA->SetField(-2, "lastInsert"); 169 | LUA->PushCFunction(getData); 170 | LUA->SetField(-2, "getData"); 171 | LUA->PushCFunction(hasMoreResults); 172 | LUA->SetField(-2, "hasMoreResults"); 173 | LUA->PushCFunction(getNextResults); 174 | LUA->SetField(-2, "getNextResults"); 175 | } 176 | 177 | void LuaQuery::createMetaTable(ILuaBase *LUA) { 178 | LuaIQuery::TYPE_QUERY = LUA->CreateMetaTable("MySQLOO Query"); 179 | LuaQuery::addMetaTableFunctions(LUA); 180 | LUA->Pop(); //Metatable 181 | } 182 | 183 | std::shared_ptr LuaQuery::buildQueryData(ILuaBase *LUA, int stackPosition, bool shouldRef) { 184 | auto query = std::dynamic_pointer_cast(this->m_query); 185 | auto data = query->buildQueryData(); 186 | data->setStatus(QUERY_COMPLETE); 187 | if (shouldRef) { 188 | LuaIQuery::referenceCallbacks(LUA, stackPosition, *data); 189 | } 190 | return data; 191 | } 192 | 193 | void LuaQuery::onDestroyedByLua(ILuaBase *LUA) { 194 | LuaIQuery::onDestroyedByLua(LUA); 195 | freeDataReference(LUA, *std::dynamic_pointer_cast(m_query)); 196 | } 197 | 198 | void LuaQuery::freeDataReference(ILuaBase *LUA, Query &query) { 199 | if (query.m_dataReference != 0) { 200 | LuaReferenceFree(LUA, query.m_dataReference); 201 | query.m_dataReference = 0; 202 | } 203 | } -------------------------------------------------------------------------------- /src/lua/LuaQuery.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MYSQLOO_LUAQUERY_H 3 | #define MYSQLOO_LUAQUERY_H 4 | 5 | 6 | #include "LuaIQuery.h" 7 | 8 | class LuaQuery : public LuaIQuery { 9 | public: 10 | static int createDataReference(ILuaBase *LUA, Query &query, QueryData &data); 11 | 12 | static void freeDataReference(ILuaBase *LUA, Query &query); 13 | 14 | static void addMetaTableFunctions(ILuaBase *LUA); 15 | 16 | static void createMetaTable(ILuaBase *LUA); 17 | 18 | static void runSuccessCallback(ILuaBase *LUA, const std::shared_ptr& query, const std::shared_ptr &data); 19 | 20 | std::shared_ptr buildQueryData(ILuaBase *LUA, int stackPosition, bool shouldRef) override; 21 | 22 | void onDestroyedByLua(ILuaBase *LUA) override; 23 | 24 | protected: 25 | explicit LuaQuery(const std::shared_ptr &query, const std::string &className, int databaseRef) : LuaIQuery( 26 | std::dynamic_pointer_cast(query), className, databaseRef) { 27 | } 28 | 29 | public: 30 | explicit LuaQuery(const std::shared_ptr &query, int databaseRef) : LuaQuery(query, "MySQLOO Query", 31 | databaseRef) { 32 | 33 | } 34 | }; 35 | 36 | #endif //MYSQLOO_LUAQUERY_H 37 | -------------------------------------------------------------------------------- /src/lua/LuaTransaction.cpp: -------------------------------------------------------------------------------- 1 | #include "LuaTransaction.h" 2 | #include "LuaQuery.h" 3 | 4 | MYSQLOO_LUA_FUNCTION(addQuery) { 5 | auto luaTransaction = LuaObject::getLuaObject(LUA); 6 | 7 | auto addedLuaQuery = LuaQuery::getLuaObject(LUA, 2); 8 | LUA->Push(1); 9 | LUA->GetField(-1, "__queries"); 10 | if (LUA->IsType(-1, GarrysMod::Lua::Type::Nil)) { 11 | LUA->Pop(); 12 | LUA->CreateTable(); 13 | LUA->SetField(-2, "__queries"); 14 | LUA->GetField(-1, "__queries"); 15 | } 16 | int tblIndex = LUA->Top(); 17 | LUA->PushSpecial(GarrysMod::Lua::SPECIAL_GLOB); 18 | LUA->GetField(-1, "table"); 19 | LUA->GetField(-1, "insert"); 20 | LUA->Push(tblIndex); 21 | LUA->Push(2); 22 | LUA->Call(2, 0); 23 | LUA->Pop(4); 24 | 25 | auto queryData = std::dynamic_pointer_cast(addedLuaQuery->buildQueryData(LUA, 2, false)); 26 | 27 | luaTransaction->m_addedQueryData.push_back(queryData); 28 | return 0; 29 | } 30 | 31 | MYSQLOO_LUA_FUNCTION(getQueries) { 32 | LUA->GetField(1, "__queries"); 33 | if (LUA->IsType(-1, GarrysMod::Lua::Type::Nil)) { 34 | LUA->Pop(); 35 | LUA->CreateTable(); 36 | } 37 | return 1; 38 | } 39 | 40 | MYSQLOO_LUA_FUNCTION(clearQueries) { 41 | auto luaTransaction = LuaObject::getLuaObject(LUA); 42 | 43 | LUA->Push(1); 44 | LUA->CreateTable(); 45 | LUA->SetField(-2, "__queries"); 46 | LUA->Pop(); 47 | 48 | luaTransaction->m_addedQueryData.clear(); 49 | 50 | return 0; 51 | } 52 | 53 | void LuaTransaction::createMetaTable(ILuaBase *LUA) { 54 | LuaObject::TYPE_TRANSACTION = LUA->CreateMetaTable("MySQLOO Transaction"); 55 | 56 | LuaIQuery::addMetaTableFunctions(LUA); 57 | LUA->PushCFunction(addQuery); 58 | LUA->SetField(-2, "addQuery"); 59 | LUA->PushCFunction(getQueries); 60 | LUA->SetField(-2, "getQueries"); 61 | LUA->PushCFunction(clearQueries); 62 | LUA->SetField(-2, "clearQueries"); 63 | LUA->Pop(); 64 | } 65 | 66 | std::shared_ptr LuaTransaction::buildQueryData(ILuaBase *LUA, int stackPosition, bool shouldRef) { 67 | LUA->GetField(stackPosition, "__queries"); 68 | std::deque, std::shared_ptr>> queries; 69 | if (LUA->GetType(-1) != GarrysMod::Lua::Type::Nil) { 70 | for (size_t i = 0; i < this->m_addedQueryData.size(); i++) { 71 | auto &queryData = this->m_addedQueryData[i]; 72 | LUA->PushNumber((double) (i + 1)); 73 | LUA->RawGet(-2); 74 | if (!LUA->IsType(-1, GarrysMod::Lua::Type::Table)) { 75 | LUA->Pop(); //Nil or whatever else is on the stack 76 | break; 77 | } 78 | auto luaQuery = LuaQuery::getLuaObject(LUA, -1); 79 | auto query = std::dynamic_pointer_cast(luaQuery->m_query); 80 | query->addQueryData(queryData); 81 | queries.emplace_back(query, queryData); 82 | LUA->Pop(); //Query 83 | } 84 | } 85 | LUA->Pop(); //Queries table 86 | 87 | auto data = Transaction::buildQueryData(queries); 88 | if (shouldRef) { 89 | LuaIQuery::referenceCallbacks(LUA, stackPosition, *data); 90 | } 91 | return data; 92 | } 93 | 94 | void LuaTransaction::runSuccessCallback(ILuaBase *LUA, const std::shared_ptr &transaction, 95 | const std::shared_ptr &data) { 96 | auto transactionData = std::dynamic_pointer_cast(data); 97 | if (transactionData->m_tableReference == 0) return; 98 | transactionData->setStatus(QUERY_COMPLETE); 99 | LUA->CreateTable(); 100 | int index = 0; 101 | for (auto &pair: transactionData->m_queries) { 102 | LUA->PushNumber((double) (++index)); 103 | auto query = pair.first; 104 | //So we get the current data rather than caching it, if the same query is added multiple times. 105 | LuaQuery::freeDataReference(LUA, *query); 106 | auto queryData = std::dynamic_pointer_cast(pair.second); 107 | query->setCallbackData(pair.second); 108 | int ref = LuaQuery::createDataReference(LUA, *query, *queryData); 109 | LUA->ReferencePush(ref); 110 | LUA->SetTable(-3); 111 | //The last data reference can stay cached in the query and will be freed once the query is gc'ed 112 | } 113 | if (!LuaIQuery::pushCallbackReference(LUA, data->m_successReference, data->m_tableReference, 114 | "onSuccess", data->isFirstData())) { 115 | LUA->Pop(); //Table of results 116 | return; 117 | } 118 | LUA->ReferencePush(transactionData->m_tableReference); 119 | LUA->Push(-3); //Table of results 120 | LuaObject::pcallWithErrorReporter(LUA, 2); 121 | 122 | LUA->Pop(); //Table of results 123 | 124 | for (auto &pair: transactionData->m_queries) { 125 | LuaIQuery::finishQueryData(LUA, pair.first, pair.second); 126 | //We should only cache the data for the duration of the callback 127 | LuaQuery::freeDataReference(LUA, *pair.first); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/lua/LuaTransaction.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MYSQLOO_LUATRANSACTION_H 3 | #define MYSQLOO_LUATRANSACTION_H 4 | 5 | #include "LuaIQuery.h" 6 | #include "../mysql/Transaction.h" 7 | 8 | class LuaTransaction : public LuaIQuery { 9 | public: 10 | std::deque> m_addedQueryData = {}; 11 | 12 | std::shared_ptr buildQueryData(ILuaBase *LUA, int stackPosition, bool shouldRef) override; 13 | 14 | static void createMetaTable(ILuaBase *LUA); 15 | 16 | static void runSuccessCallback(ILuaBase *LUA, const std::shared_ptr &transaction, 17 | const std::shared_ptr &data); 18 | 19 | explicit LuaTransaction(const std::shared_ptr &transaction, int databaseRef) : LuaIQuery( 20 | std::static_pointer_cast(transaction), "MySQLOO Transaction", databaseRef 21 | ) { 22 | 23 | } 24 | }; 25 | 26 | 27 | #endif //MYSQLOO_LUATRANSACTION_H 28 | -------------------------------------------------------------------------------- /src/mysql/Database.h: -------------------------------------------------------------------------------- 1 | #ifndef DATABASE_ 2 | #define DATABASE_ 3 | 4 | #include "MySQLHeader.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "StatementHandle.h" 13 | #include 14 | #include 15 | #include "GarrysMod/Lua/Interface.h" 16 | #include "../BlockingQueue.h" 17 | #include "Query.h" 18 | #include "PreparedQuery.h" 19 | #include "IQuery.h" 20 | #include "PingQuery.h" 21 | #include "Transaction.h" 22 | 23 | struct SSLSettings { 24 | std::string key; 25 | std::string cert; 26 | std::string ca; 27 | std::string capath; 28 | std::string cipher; 29 | 30 | void applySSLSettings(MYSQL *m_sql) const; 31 | }; 32 | 33 | enum DatabaseStatus { 34 | DATABASE_CONNECTED = 0, 35 | DATABASE_CONNECTING = 1, 36 | DATABASE_NOT_CONNECTED = 2, 37 | DATABASE_CONNECTION_FAILED = 3 38 | }; 39 | 40 | 41 | class Database : public std::enable_shared_from_this { 42 | friend class IQuery; 43 | 44 | public: 45 | static std::shared_ptr 46 | createDatabase(const std::string &host, const std::string &username, const std::string &pw, 47 | const std::string &database, unsigned int port, 48 | const std::string &unixSocket); 49 | 50 | ~Database(); 51 | 52 | std::shared_ptr cacheStatement(MYSQL_STMT *stmt); 53 | 54 | void freeStatement(const std::shared_ptr &handle); 55 | 56 | void enqueueQuery(const std::shared_ptr &query, const std::shared_ptr &data); 57 | 58 | void setShouldAutoReconnect(bool autoReconnect); 59 | 60 | bool shouldCachePreparedStatements() { 61 | return cachePreparedStatements; 62 | } 63 | 64 | std::shared_ptr query(const std::string &query); 65 | 66 | std::shared_ptr prepare(const std::string &query); 67 | 68 | std::shared_ptr transaction(); 69 | 70 | bool ping(); 71 | 72 | std::string escape(const std::string &str); 73 | 74 | bool setCharacterSet(const std::string &characterSet); 75 | 76 | void connect(); 77 | 78 | void wait(); 79 | 80 | std::deque, std::shared_ptr>> abortAllQueries(); 81 | 82 | DatabaseStatus status() const; 83 | 84 | unsigned int serverVersion(); 85 | 86 | std::string serverInfo(); 87 | 88 | std::string hostInfo(); 89 | 90 | size_t queueSize(); 91 | 92 | void setMultiStatements(bool multiStatement); 93 | 94 | void setCachePreparedStatements(bool cachePreparedStatements); 95 | 96 | void disconnect(bool wait); 97 | 98 | void setConnectTimeout(unsigned int timeout); 99 | 100 | void setReadTimeout(unsigned int timeout); 101 | 102 | void setWriteTimeout(unsigned int timeout); 103 | 104 | void setSSLSettings(const SSLSettings &settings); 105 | 106 | bool isConnectionDone() { return m_connectionDone; } 107 | 108 | bool connectionSuccessful() { return m_success; } 109 | 110 | bool attemptReconnect(); 111 | 112 | std::string connectionError() { return m_connection_err; } 113 | 114 | std::deque, std::shared_ptr>> takeFinishedQueries() { 115 | return finishedQueries.clear(); 116 | } 117 | 118 | bool wasDisconnected(); 119 | private: 120 | Database(std::string host, std::string username, std::string pw, std::string database, unsigned int port, 121 | std::string unixSocket); 122 | 123 | void shutdown(); 124 | 125 | void freeCachedStatements(); 126 | 127 | void freeUnusedStatements(); 128 | 129 | void run(); 130 | 131 | void runQuery(const std::shared_ptr &query, const std::shared_ptr &data, bool retry); 132 | 133 | void connectRun(); 134 | 135 | void abortWaitingQuery(); 136 | 137 | void 138 | failWaitingQuery(const std::shared_ptr &query, const std::shared_ptr &data, std::string reason); 139 | 140 | void waitForQuery(const std::shared_ptr &query, const std::shared_ptr &data); 141 | 142 | void applyTimeoutSettings(); 143 | 144 | BlockingQueue, std::shared_ptr>> finishedQueries{}; 145 | BlockingQueue, std::shared_ptr>> queryQueue{}; 146 | std::unordered_set> cachedStatements{}; 147 | std::unordered_set freedStatements{}; 148 | MYSQL *m_sql = nullptr; 149 | std::thread m_thread; 150 | std::mutex m_connectMutex; //Mutex used during connection 151 | std::mutex m_queryMutex; //Mutex that is locked while query thread operates on m_sql object 152 | std::mutex m_statementMutex; //Mutex that protects cached prepared statements 153 | std::mutex m_queryWaitMutex; //Mutex that prevents deadlocks when calling :wait() 154 | std::condition_variable m_connectWakeupVariable; 155 | unsigned int m_serverVersion = 0; 156 | std::string m_serverInfo; 157 | std::string m_hostInfo; 158 | std::string m_connection_err; 159 | bool shouldAutoReconnect = true; 160 | bool useMultiStatements = true; 161 | bool startedConnecting = false; 162 | bool m_canWait = false; 163 | std::pair, std::shared_ptr> m_waitingQuery = {nullptr, nullptr}; 164 | std::atomic m_success{true}; 165 | std::atomic disconnected { false }; 166 | std::atomic m_connectionDone{false}; 167 | std::atomic cachePreparedStatements{true}; 168 | std::condition_variable m_queryWakeupVariable{}; 169 | std::condition_variable m_queryWaitWakeupVariable{}; 170 | std::string database; 171 | std::string host; 172 | std::string username; 173 | std::string pw; 174 | std::string socket; 175 | unsigned int port; 176 | SSLSettings customSSLSettings{}; 177 | unsigned int readTimeout = 0; 178 | unsigned int writeTimeout = 0; 179 | unsigned int connectTimeout = 0; 180 | std::atomic m_status{DATABASE_NOT_CONNECTED}; 181 | }; 182 | 183 | #endif -------------------------------------------------------------------------------- /src/mysql/IQuery.cpp: -------------------------------------------------------------------------------- 1 | #include "IQuery.h" 2 | 3 | #include 4 | #include "Database.h" 5 | #include "MySQLOOException.h" 6 | #include "../lua/LuaObject.h" 7 | 8 | //Important: 9 | //Calling any query functions that rely on data from the query thread 10 | //before the callback is called can result in race conditions. 11 | //Always check for QUERY_COMPLETE!!! 12 | 13 | IQuery::IQuery(std::shared_ptr database) : m_database(std::move(database)) { 14 | m_options = OPTION_NAMED_FIELDS | OPTION_INTERPRET_DATA | OPTION_CACHE; 15 | LuaObject::allocationCount++; 16 | } 17 | 18 | IQuery::~IQuery() { 19 | LuaObject::allocationCount--; 20 | } 21 | 22 | QueryResultStatus IQuery::getResultStatus() const { 23 | if (!hasCallbackData()) { 24 | return QUERY_NONE; 25 | } 26 | return callbackQueryData->getResultStatus(); 27 | } 28 | 29 | //Wrapper for c api calls 30 | //Just throws an exception if anything goes wrong for ease of use 31 | 32 | //Returns if the query has been queued with the database instance 33 | bool IQuery::isRunning() { 34 | return !runningQueryData.empty(); 35 | } 36 | 37 | //Blocks the current Thread until the query has finished processing 38 | //Possibly dangerous (deadlock when database goes down while waiting) 39 | //If the second argument is set to true, the query is going to be swapped to the front of the query queue 40 | void IQuery::wait(bool shouldSwap) { 41 | if (!isRunning()) { 42 | throw MySQLOOException("Query not started"); 43 | } 44 | std::shared_ptr lastInsertedQuery = runningQueryData.back(); 45 | //Changing the order of the query might have unwanted side effects, so this is disabled by default 46 | auto database = m_database; 47 | if (shouldSwap) { 48 | database->queryQueue.swapToFrontIf( 49 | [&](std::pair, std::shared_ptr> const &p) { 50 | return p.second.get() == lastInsertedQuery.get(); 51 | }); 52 | } 53 | database->waitForQuery(this->shared_from_this(), lastInsertedQuery); 54 | } 55 | 56 | //Returns the error message produced by the mysql query or "" if there is none 57 | std::string IQuery::error() const { 58 | auto currentQueryData = callbackQueryData; 59 | if (!currentQueryData) { 60 | return ""; 61 | } 62 | return currentQueryData->getError(); 63 | } 64 | 65 | //Attempts to abort the query, returns true if it was able to stop at least one query in time, false otherwise 66 | std::vector> IQuery::abort() { 67 | auto database = m_database; 68 | if (!database) return {}; 69 | //This is copied so that I can remove entries from that vector in onQueryDataFinished 70 | auto runningQueries = runningQueryData; 71 | std::vector> abortedQueries; 72 | for (auto &data: runningQueries) { 73 | //It doesn't really matter if any of them are in a transaction since in that case they 74 | //aren't in the query queue 75 | bool wasRemoved = database->queryQueue.removeIf( 76 | [&](std::pair, std::shared_ptr> const &p) { 77 | return p.second == data; 78 | }); 79 | if (wasRemoved) { 80 | data->setStatus(QUERY_ABORTED); 81 | data->setFinished(true); 82 | abortedQueries.push_back(data); 83 | } 84 | } 85 | return abortedQueries; 86 | } 87 | 88 | //Sets several query options 89 | void IQuery::setOption(int option, bool enabled) { 90 | if (option != OPTION_NUMERIC_FIELDS && 91 | option != OPTION_NAMED_FIELDS && 92 | option != OPTION_INTERPRET_DATA && 93 | option != OPTION_CACHE) { 94 | throw MySQLOOException("Invalid Option"); 95 | } 96 | 97 | if (enabled) { 98 | m_options |= option; 99 | } else { 100 | m_options &= ~option; 101 | } 102 | } 103 | 104 | //Wrapper for c api calls 105 | //Just throws an exception if anything goes wrong for ease of use 106 | 107 | void IQuery::mysqlQuery(MYSQL *sql, std::string &query) { 108 | int result = mysql_real_query(sql, query.c_str(), (unsigned long) query.length()); 109 | if (result != 0) { 110 | const char *errorMessage = mysql_error(sql); 111 | unsigned int errorCode = mysql_errno(sql); 112 | throw MySQLException(errorCode, errorMessage); 113 | } 114 | } 115 | 116 | MYSQL_RES *IQuery::mysqlStoreResults(MYSQL *sql) { 117 | MYSQL_RES *result = mysql_store_result(sql); 118 | if (result == nullptr) { 119 | unsigned int errorCode = mysql_errno(sql); 120 | if (errorCode != 0) { 121 | const char *errorMessage = mysql_error(sql); 122 | throw MySQLException(errorCode, errorMessage); 123 | } 124 | } 125 | return result; 126 | } 127 | 128 | bool IQuery::mysqlNextResult(MYSQL *sql) { 129 | int result = mysql_next_result(sql); 130 | if (result == 0) return true; 131 | if (result == -1) return false; 132 | unsigned int errorCode = mysql_errno(sql); 133 | if (errorCode != 0) { 134 | const char *errorMessage = mysql_error(sql); 135 | throw MySQLException(errorCode, errorMessage); 136 | } 137 | return false; 138 | } 139 | 140 | //Queues the query into the queue of the database instance associated with it 141 | void IQuery::start(const std::shared_ptr &queryData) { 142 | auto db = m_database; 143 | addQueryData(queryData); 144 | db->enqueueQuery(shared_from_this(), queryData); 145 | hasBeenStarted = true; 146 | } 147 | 148 | 149 | void IQuery::addQueryData(const std::shared_ptr &data) { 150 | if (!hasBeenStarted) { 151 | data->m_wasFirstData = true; 152 | } 153 | runningQueryData.push_back(data); 154 | } 155 | 156 | void IQuery::finishQueryData(const std::shared_ptr &data) { 157 | if (runningQueryData.empty()) return; 158 | auto front = runningQueryData.front(); 159 | if (front == data) { 160 | //Fast path, O(1) 161 | runningQueryData.pop_front(); 162 | } else { 163 | //Slow path, O(n), should only happen in very rare circumstances. 164 | runningQueryData.erase(std::remove(runningQueryData.begin(), runningQueryData.end(), data), 165 | runningQueryData.end()); 166 | } 167 | } -------------------------------------------------------------------------------- /src/mysql/IQuery.h: -------------------------------------------------------------------------------- 1 | #ifndef IQUERY_ 2 | #define IQUERY_ 3 | 4 | #include "MySQLHeader.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class Database; 14 | 15 | enum QueryStatus { 16 | QUERY_NOT_RUNNING = 0, 17 | QUERY_RUNNING = 1, 18 | QUERY_COMPLETE = 3, //Query is complete right before callback is run 19 | QUERY_ABORTED = 4, 20 | QUERY_WAITING = 5, 21 | }; 22 | enum QueryResultStatus { 23 | QUERY_NONE = 0, 24 | QUERY_ERROR, 25 | QUERY_SUCCESS 26 | }; 27 | enum { 28 | OPTION_NUMERIC_FIELDS = 1, 29 | OPTION_NAMED_FIELDS = 2, 30 | OPTION_INTERPRET_DATA = 4, 31 | OPTION_CACHE = 8, 32 | }; 33 | 34 | class IQueryData; 35 | 36 | class MySQLException : public std::runtime_error { 37 | public: 38 | MySQLException(unsigned int errorCode, const char *message) : runtime_error(message) { 39 | this->m_errorCode = errorCode; 40 | } 41 | 42 | unsigned int getErrorCode() const { return m_errorCode; } 43 | 44 | private: 45 | unsigned int m_errorCode = 0; 46 | }; 47 | 48 | class IQuery : public std::enable_shared_from_this { 49 | friend class Database; 50 | 51 | public: 52 | explicit IQuery(std::shared_ptr database); 53 | 54 | virtual ~IQuery(); 55 | 56 | void setCallbackData(std::shared_ptr data) { 57 | callbackQueryData = std::move(data); 58 | } 59 | 60 | void start(const std::shared_ptr &queryData); 61 | 62 | bool isRunning(); 63 | 64 | void setOption(int option, bool enabled); 65 | 66 | bool hasOption(int option) const { 67 | return m_options & option; 68 | } 69 | 70 | void addQueryData(const std::shared_ptr &data); 71 | 72 | void finishQueryData(const std::shared_ptr &data); 73 | 74 | std::string error() const; 75 | 76 | std::vector> abort(); 77 | 78 | virtual std::string getSQLString() = 0; 79 | 80 | void wait(bool shouldSwap); 81 | 82 | bool hasCallbackData() const { 83 | return callbackQueryData != nullptr; 84 | } 85 | QueryResultStatus getResultStatus() const; 86 | std::shared_ptr callbackQueryData; 87 | protected: 88 | 89 | virtual void executeStatement(Database &database, MYSQL *m_sql, const std::shared_ptr& data) = 0; 90 | 91 | //Wrapper functions for c api that throw exceptions 92 | static void mysqlQuery(MYSQL *sql, std::string &query); 93 | 94 | static MYSQL_RES *mysqlStoreResults(MYSQL *sql); 95 | 96 | static bool mysqlNextResult(MYSQL *sql); 97 | 98 | //fields 99 | std::shared_ptr m_database{}; 100 | int m_options = 0; 101 | std::deque> runningQueryData; 102 | bool hasBeenStarted = false; 103 | }; 104 | 105 | class IQueryData { 106 | friend class IQuery; 107 | 108 | public: 109 | virtual ~IQueryData() = default; 110 | 111 | std::string getError() { 112 | return m_errorText; 113 | } 114 | 115 | void setError(std::string err) { 116 | m_errorText = std::move(err); 117 | } 118 | 119 | bool isFinished() { 120 | return finished; 121 | } 122 | 123 | void setFinished(bool isFinished) { 124 | finished = isFinished; 125 | } 126 | 127 | QueryStatus getStatus() { 128 | return m_status; 129 | } 130 | 131 | void setStatus(QueryStatus status) { 132 | this->m_status = status; 133 | } 134 | 135 | QueryResultStatus getResultStatus() { 136 | return m_resultStatus; 137 | } 138 | 139 | void setResultStatus(QueryResultStatus status) { 140 | m_resultStatus = status; 141 | } 142 | 143 | int getErrorReference() const { 144 | return m_errorReference; 145 | } 146 | 147 | int getSuccessReference() const { 148 | return m_successReference; 149 | } 150 | 151 | int getOnDataReference() const { 152 | return m_onDataReference; 153 | } 154 | 155 | int getAbortReference() const { 156 | return m_abortReference; 157 | } 158 | 159 | bool isFirstData() const { 160 | return m_wasFirstData; 161 | } 162 | int m_successReference = 0; 163 | int m_errorReference = 0; 164 | int m_abortReference = 0; 165 | int m_onDataReference = 0; 166 | int m_tableReference = 0; 167 | protected: 168 | std::string m_errorText; 169 | std::atomic finished{false}; 170 | std::atomic m_status{QUERY_NOT_RUNNING}; 171 | std::atomic m_resultStatus{QUERY_NONE}; 172 | bool m_wasFirstData = false; 173 | }; 174 | 175 | #endif -------------------------------------------------------------------------------- /src/mysql/MySQLHeader.h: -------------------------------------------------------------------------------- 1 | #ifndef _MySQL_H 2 | #define _MySQL_H 3 | #include 4 | //Source: http://stackoverflow.com/a/25510879 5 | //Pretty much an action that is always going to execute when the 6 | //scope it was declared in is left (so exceptions/returns/break/etc.) 7 | template 8 | struct FinalAction { 9 | explicit FinalAction(F f) : clean_{ f } {} 10 | ~FinalAction() { clean_(); } 11 | F clean_; 12 | }; 13 | template 14 | FinalAction finally(F f) { 15 | return FinalAction(f); 16 | } 17 | #ifdef WIN32 18 | #define WIN32_LEAN_AND_MEAN 19 | #define _WINSOCKAPI_ 20 | #include 21 | #include 22 | #endif 23 | #include 24 | 25 | #endif -------------------------------------------------------------------------------- /src/mysql/MySQLOOException.h: -------------------------------------------------------------------------------- 1 | #ifndef MYSQLOO_MYSQLERROR_H 2 | #define MYSQLOO_MYSQLERROR_H 3 | 4 | #include 5 | 6 | //When called from the outside 7 | class MySQLOOException : std::exception { 8 | public: 9 | explicit MySQLOOException(std::string error) : std::exception(), message(std::move(error)) { 10 | } 11 | 12 | std::string message{}; 13 | }; 14 | 15 | #endif //MYSQLOO_MYSQLERROR_H 16 | -------------------------------------------------------------------------------- /src/mysql/PingQuery.cpp: -------------------------------------------------------------------------------- 1 | #include "PingQuery.h" 2 | 3 | #ifdef LINUX 4 | #include 5 | #endif 6 | 7 | #include "Database.h" 8 | 9 | //Dummy class just used with the Database::ping function 10 | PingQuery::PingQuery(const std::shared_ptr &dbase) : Query(dbase, "") { 11 | 12 | } 13 | 14 | PingQuery::~PingQuery() = default; 15 | 16 | /* Executes the ping query 17 | */ 18 | void PingQuery::executeStatement(Database &database, MYSQL *connection, const std::shared_ptr &data) { 19 | this->pingSuccess = mysql_ping(connection) == 0 || database.attemptReconnect(); 20 | } -------------------------------------------------------------------------------- /src/mysql/PingQuery.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef PINGQUERY_ 3 | #define PINGQUERY_ 4 | 5 | #include 6 | #include "Query.h" 7 | #include "MySQLHeader.h" 8 | #include 9 | #include 10 | 11 | 12 | class PingQuery : public Query { 13 | friend class Database; 14 | 15 | public: 16 | ~PingQuery() override; 17 | 18 | protected: 19 | explicit PingQuery(const std::shared_ptr &dbase); 20 | 21 | std::string getSQLString() override { return ""; }; 22 | 23 | void executeStatement(Database &database, MYSQL *m_sql, const std::shared_ptr &data) override; 24 | 25 | bool pingSuccess = false; 26 | }; 27 | 28 | #endif -------------------------------------------------------------------------------- /src/mysql/PreparedQuery.h: -------------------------------------------------------------------------------- 1 | #ifndef PREPAREDQUERY_ 2 | #define PREPAREDQUERY_ 3 | 4 | #include 5 | #include "Query.h" 6 | #include "MySQLHeader.h" 7 | #include "StatementHandle.h" 8 | #include 9 | 10 | class PreparedQueryField { 11 | friend class PreparedQuery; 12 | 13 | public: 14 | PreparedQueryField(unsigned int index, int type) : m_index(index), m_type(type) {} 15 | 16 | PreparedQueryField() : m_index(1), m_type(0) {} 17 | 18 | virtual ~PreparedQueryField() = default; 19 | 20 | private: 21 | unsigned int m_index; 22 | int m_type; 23 | }; 24 | 25 | template 26 | class TypedQueryField : public PreparedQueryField { 27 | friend class PreparedQuery; 28 | 29 | public: 30 | TypedQueryField(unsigned int index, int type, const T &data) 31 | : PreparedQueryField(index, type), m_data(data) {}; 32 | 33 | ~TypedQueryField() override = default; 34 | 35 | private: 36 | T m_data; 37 | }; 38 | 39 | class PreparedQueryData : public QueryData { 40 | friend class PreparedQuery; 41 | 42 | protected: 43 | std::deque>> m_parameters; 44 | bool firstAttempt = true; 45 | 46 | PreparedQueryData() = default; 47 | }; 48 | 49 | class PreparedQuery : public Query { 50 | friend class Database; 51 | 52 | public: 53 | ~PreparedQuery() override; 54 | 55 | void executeStatement(Database &database, MYSQL *connection, const std::shared_ptr &data) override; 56 | 57 | void clearParameters(); 58 | 59 | void setNumber(unsigned int index, double value); 60 | 61 | void setString(unsigned int index, const std::string &value); 62 | 63 | void setBoolean(unsigned int index, bool value); 64 | 65 | void setNull(unsigned int index); 66 | 67 | void putNewParameters(); 68 | 69 | std::shared_ptr buildQueryData() override; 70 | 71 | static std::shared_ptr create(const std::shared_ptr &dbase, std::string query); 72 | 73 | private: 74 | PreparedQuery(const std::shared_ptr &dbase, std::string query); 75 | 76 | std::deque>> m_parameters{}; 77 | 78 | static MYSQL_STMT *mysqlStmtInit(MYSQL *sql); 79 | 80 | static void 81 | generateMysqlBinds(MYSQL_BIND *binds, std::unordered_map> &map, 82 | unsigned int parameterCount); 83 | 84 | static void mysqlStmtBindParameter(MYSQL_STMT *sql, MYSQL_BIND *bind); 85 | 86 | static void mysqlStmtPrepare(MYSQL_STMT *sql, const char *str); 87 | 88 | static void mysqlStmtExecute(MYSQL_STMT *sql); 89 | 90 | static void mysqlStmtStoreResult(MYSQL_STMT *sql); 91 | 92 | static bool mysqlStmtNextResult(MYSQL_STMT *sql); 93 | 94 | std::shared_ptr cachedStatement{nullptr}; 95 | }; 96 | 97 | #endif -------------------------------------------------------------------------------- /src/mysql/Query.cpp: -------------------------------------------------------------------------------- 1 | #include "Query.h" 2 | #include "MySQLOOException.h" 3 | #include 4 | #include 5 | #include 6 | #ifdef LINUX 7 | #include 8 | #endif 9 | 10 | Query::Query(const std::shared_ptr& dbase, std::string query) : IQuery(dbase), m_query(std::move(query)) { 11 | } 12 | 13 | Query::~Query() = default; 14 | 15 | //Executes the raw query 16 | void Query::executeStatement(Database &database, MYSQL* connection, const std::shared_ptr& data) { 17 | auto queryData = std::dynamic_pointer_cast(data); 18 | Query::mysqlQuery(connection, this->m_query); 19 | //Stores all result sets 20 | //MySQL result sets shouldn't be accessed from different threads! 21 | do { 22 | MYSQL_RES * results = Query::mysqlStoreResults(connection); 23 | auto resultFree = finally([&] { mysql_free_result(results); }); 24 | if (results != nullptr) { 25 | queryData->m_results.emplace_back(results); 26 | } else { 27 | queryData->m_results.emplace_back(); 28 | } 29 | queryData->m_insertIds.push_back(mysql_insert_id(connection)); 30 | queryData->m_affectedRows.push_back(mysql_affected_rows(connection)); 31 | } while (Query::mysqlNextResult(connection)); 32 | } 33 | 34 | 35 | //Returns true if a query has at least one additional ResultSet left 36 | bool Query::hasMoreResults() { 37 | if (!hasCallbackData()) { 38 | throw MySQLOOException("Query not completed yet"); 39 | } 40 | auto data = std::dynamic_pointer_cast(this->callbackQueryData); 41 | return data->hasMoreResults(); 42 | } 43 | 44 | //Unreferences the current result set and uses the next result set 45 | void Query::getNextResults() { 46 | if (!hasCallbackData()) { 47 | throw MySQLOOException("Query not completed yet"); 48 | } 49 | auto data = std::dynamic_pointer_cast(this->callbackQueryData); 50 | if (!data->getNextResults()) { 51 | throw MySQLOOException("Query doesn't have any more results"); 52 | } 53 | } 54 | 55 | //Returns the last insert id produced by INSERT INTO statements (or 0 if there is none) 56 | my_ulonglong Query::lastInsert() { 57 | if (!hasCallbackData()) { 58 | return 0; 59 | } 60 | auto data = std::dynamic_pointer_cast(this->callbackQueryData); 61 | //Calling lastInsert() after query was executed but before the callback is run can cause race conditions 62 | return data->getLastInsertID(); 63 | } 64 | 65 | //Returns the last affected rows produced by INSERT/DELETE/UPDATE (0 for none, -1 for errors) 66 | //For a SELECT statement this returns the amount of rows returned 67 | my_ulonglong Query::affectedRows() { 68 | if (!hasCallbackData()) { 69 | return 0; 70 | } 71 | auto data = std::dynamic_pointer_cast(this->callbackQueryData); 72 | //Calling affectedRows() after query was executed but before the callback is run can cause race conditions 73 | return data->getAffectedRows(); 74 | } 75 | 76 | std::shared_ptr Query::buildQueryData() { 77 | return std::shared_ptr(new QueryData()); 78 | } 79 | 80 | std::shared_ptr Query::create(const std::shared_ptr &dbase, const std::string& query) { 81 | return std::shared_ptr(new Query(dbase, query)); 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/mysql/Query.h: -------------------------------------------------------------------------------- 1 | #ifndef QUERY_ 2 | #define QUERY_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "IQuery.h" 9 | #include "ResultData.h" 10 | 11 | class QueryData; 12 | 13 | class Query : public IQuery { 14 | friend class Database; 15 | 16 | friend class Transaction; 17 | 18 | public: 19 | ~Query() override; 20 | 21 | void executeStatement(Database &database, MYSQL *m_sql, const std::shared_ptr &data) override; 22 | 23 | my_ulonglong lastInsert(); 24 | 25 | my_ulonglong affectedRows(); 26 | 27 | bool hasMoreResults(); 28 | 29 | void getNextResults(); 30 | 31 | virtual std::shared_ptr buildQueryData(); 32 | 33 | int m_dataReference = 0; 34 | 35 | std::string getSQLString() override { return m_query; }; 36 | 37 | static std::shared_ptr create(const std::shared_ptr &dbase, const std::string &query); 38 | 39 | protected: 40 | Query(const std::shared_ptr &dbase, std::string query); 41 | 42 | std::string m_query; 43 | }; 44 | 45 | class QueryData : public IQueryData { 46 | friend class Query; 47 | 48 | public: 49 | my_ulonglong getLastInsertID() { 50 | return (m_insertIds.empty()) ? 0 : m_insertIds.front(); 51 | } 52 | 53 | my_ulonglong getAffectedRows() { 54 | return (m_affectedRows.empty()) ? 0 : m_affectedRows.front(); 55 | } 56 | 57 | bool hasMoreResults() { 58 | return !m_insertIds.empty() && !m_affectedRows.empty() && !m_results.empty(); 59 | } 60 | 61 | bool getNextResults() { 62 | if (!hasMoreResults()) return false; 63 | m_results.pop_front(); 64 | m_insertIds.pop_front(); 65 | m_affectedRows.pop_front(); 66 | return true; 67 | } 68 | 69 | ResultData &getResult() { 70 | return m_results.front(); 71 | } 72 | 73 | std::deque getResults() { 74 | return m_results; 75 | } 76 | 77 | protected: 78 | std::deque m_affectedRows; 79 | std::deque m_insertIds; 80 | std::deque m_results; 81 | 82 | QueryData() = default; 83 | }; 84 | 85 | #endif -------------------------------------------------------------------------------- /src/mysql/ResultData.cpp: -------------------------------------------------------------------------------- 1 | #include "ResultData.h" 2 | #include "IQuery.h" 3 | #include "Database.h" 4 | #include 5 | 6 | ResultData::ResultData(unsigned int columnCount, unsigned int rows) { 7 | this->columnCount = columnCount; 8 | this->columns.resize(columnCount); 9 | this->columnTypes.resize(columnCount); 10 | this->rows.reserve(rows); 11 | } 12 | 13 | ResultData::ResultData() : ResultData((unsigned int) 0, (unsigned int) 0) {} //Avoids conflict with pointers 14 | 15 | //Stores all of the rows of a result set 16 | //This is used so the result set can be free'd and doesn't have to be used in 17 | //another thread (which is not safe) 18 | ResultData::ResultData(MYSQL_RES* result) : ResultData((unsigned int)mysql_num_fields(result), (unsigned int)mysql_num_rows(result)) { 19 | if (columnCount == 0) return; 20 | for (unsigned int i = 0; i < columnCount; i++) { 21 | MYSQL_FIELD *field = mysql_fetch_field_direct(result, i); 22 | columnTypes[i] = field->type; 23 | columns[i] = field->name; 24 | } 25 | MYSQL_ROW currentRow; 26 | //This shouldn't error since mysql_store_results stores ALL rows already 27 | while ((currentRow = mysql_fetch_row(result)) != nullptr) { 28 | unsigned long *lengths = mysql_fetch_lengths(result); 29 | this->rows.emplace_back(lengths, currentRow, columnCount); 30 | } 31 | } 32 | 33 | static bool mysqlStmtFetch(MYSQL_STMT* stmt) { 34 | int result = mysql_stmt_fetch(stmt); 35 | if (result == 0) return true; 36 | if (result == 1) { 37 | const char* errorMessage = mysql_stmt_error(stmt); 38 | unsigned int errorCode = mysql_stmt_errno(stmt); 39 | throw MySQLException(errorCode, errorMessage); 40 | } else { 41 | return false; 42 | } 43 | } 44 | static void mysqlStmtBindResult(MYSQL_STMT* stmt, MYSQL_BIND* bind) { 45 | my_bool result = mysql_stmt_bind_result(stmt, bind); 46 | if (result) { 47 | const char* errorMessage = mysql_stmt_error(stmt); 48 | unsigned int errorCode = mysql_stmt_errno(stmt); 49 | throw MySQLException(errorCode, errorMessage); 50 | } 51 | } 52 | 53 | //Stores all of the rows of a prepared query 54 | //This needs to be done because the query shouldn't be accessed from a different thread 55 | ResultData::ResultData(MYSQL_STMT* result, MYSQL_RES* metaData) : ResultData((unsigned int)mysql_stmt_field_count(result), (unsigned int)mysql_stmt_num_rows(result)) { 56 | if (this->columnCount == 0) return; 57 | MYSQL_FIELD* fields = mysql_fetch_fields(metaData); 58 | std::vector binds(columnCount); 59 | std::vector> buffers; 60 | std::vector lengths(columnCount); 61 | //This is needed because C++ is stupid and std::vector is using bit encoding.... 62 | auto* isFieldNullArr = new my_bool[columnCount]; 63 | auto fieldNullArrFree = finally([&] { 64 | delete[] isFieldNullArr; 65 | }); 66 | for (unsigned int i = 0; i < columnCount; i++) { 67 | columnTypes[i] = fields[i].type; 68 | columns[i] = fields[i].name; 69 | MYSQL_BIND& bind = binds[i]; 70 | bind.buffer_type = MYSQL_TYPE_STRING; 71 | buffers.emplace_back(fields[i].max_length + 2); 72 | bind.buffer = buffers.back().data(); 73 | bind.buffer_length = fields[i].max_length + 1; 74 | bind.length = &lengths[i]; 75 | bind.is_null = &isFieldNullArr[i]; 76 | bind.is_unsigned = 0; 77 | } 78 | mysqlStmtBindResult(result, binds.data()); 79 | while (mysqlStmtFetch(result)) { 80 | this->rows.emplace_back(result, binds.data(), columnCount); 81 | } 82 | } 83 | 84 | ResultData::~ResultData() = default; 85 | 86 | ResultDataRow::ResultDataRow(unsigned int columnCount) { 87 | this->columnCount = columnCount; 88 | this->values.resize(columnCount); 89 | this->nullFields.resize(columnCount); 90 | } 91 | 92 | //Datastructure that stores a row of mysql data 93 | ResultDataRow::ResultDataRow(unsigned long *lengths, MYSQL_ROW row, unsigned int columnCount) : ResultDataRow(columnCount) { 94 | for (unsigned int i = 0; i < columnCount; i++) { 95 | if (row[i]) { 96 | this->values[i] = std::string(row[i], lengths[i]); 97 | } else { 98 | if (lengths[i] == 0) { 99 | this->nullFields[i] = true; 100 | } 101 | this->values[i] = ""; 102 | } 103 | } 104 | } 105 | 106 | //Datastructure that stores a row of mysql data of a prepared query 107 | ResultDataRow::ResultDataRow(MYSQL_STMT* statement, MYSQL_BIND* bind, unsigned int columnCount) : ResultDataRow(columnCount) { 108 | for (unsigned int i = 0; i < columnCount; i++) { 109 | if (!*(bind[i].is_null) && bind[i].buffer) { 110 | this->values[i] = std::string((char*)bind[i].buffer, *bind[i].length); 111 | } else { 112 | if (*(bind[i].is_null)) { 113 | this->nullFields[i] = true; 114 | } 115 | this->values[i] = ""; 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /src/mysql/ResultData.h: -------------------------------------------------------------------------------- 1 | #ifndef RESULTDATA_ 2 | #define RESULTDATA_ 3 | #include 4 | #include 5 | #include 6 | #include "MySQLHeader.h" 7 | 8 | class ResultDataRow { 9 | public: 10 | ResultDataRow(unsigned long *lengths, MYSQL_ROW row, unsigned int columnCount); 11 | ResultDataRow(MYSQL_STMT* statement, MYSQL_BIND* bind, unsigned int columnCount); 12 | std::vector & getValues() { 13 | return values; 14 | } 15 | bool isFieldNull(unsigned int index) { 16 | return nullFields[index]; 17 | } 18 | private: 19 | explicit ResultDataRow(unsigned int columns); 20 | unsigned long long columnCount = 0; 21 | std::vector nullFields; 22 | std::vector values; 23 | }; 24 | 25 | class ResultData { 26 | public: 27 | explicit ResultData(MYSQL_RES* result); 28 | ResultData(MYSQL_STMT* result, MYSQL_RES* metaData); 29 | ResultData(); 30 | ~ResultData(); 31 | std::vector & getColumns() { return columns; } 32 | std::vector & getRows() { return rows; } 33 | std::vector & getColumnTypes() { return columnTypes; } 34 | private: 35 | ResultData(unsigned int columns, unsigned int rows); 36 | unsigned int columnCount = 0; 37 | std::vector columns; 38 | std::vector columnTypes; 39 | std::vector rows; 40 | }; 41 | 42 | #endif -------------------------------------------------------------------------------- /src/mysql/StatementHandle.h: -------------------------------------------------------------------------------- 1 | #ifndef MYSQLOO_STATEMENTHANDLE_H 2 | #define MYSQLOO_STATEMENTHANDLE_H 3 | 4 | #include "mysql.h" 5 | 6 | class StatementHandle { 7 | public: 8 | StatementHandle(MYSQL_STMT *stmt, bool valid); 9 | 10 | MYSQL_STMT *stmt = nullptr; 11 | 12 | bool isValid() const { return stmt != nullptr && valid; }; 13 | 14 | void invalidate() { valid = false; } 15 | 16 | private: 17 | bool valid; 18 | }; 19 | 20 | #endif //MYSQLOO_STATEMENTHANDLE_H 21 | -------------------------------------------------------------------------------- /src/mysql/Transaction.cpp: -------------------------------------------------------------------------------- 1 | #include "Transaction.h" 2 | 3 | #include 4 | #include "Database.h" 5 | #include "mysqld_error.h" 6 | 7 | 8 | void Transaction::executeStatement(Database &database, MYSQL *connection, const std::shared_ptr& ptr) { 9 | std::shared_ptr data = std::dynamic_pointer_cast(ptr); 10 | data->setStatus(QUERY_RUNNING); 11 | try { 12 | for (auto &query: data->m_queries) { 13 | //Errors are cleared in case this is retrying after losing connection 14 | query.second->setStatus(QUERY_RUNNING); 15 | query.second->setResultStatus(QUERY_NONE); 16 | query.second->setError(""); 17 | } 18 | 19 | mysqlAutocommit(connection, false); 20 | 21 | for (auto &query: data->m_queries) { 22 | try { 23 | query.first->executeStatement(database, connection, query.second); 24 | } catch (const MySQLException &error) { 25 | query.second->setError(error.what()); 26 | query.second->setResultStatus(QUERY_ERROR); 27 | throw error; 28 | } 29 | } 30 | 31 | mysqlCommit(connection); 32 | data->setResultStatus(QUERY_SUCCESS); 33 | //If this fails the connection was lost but the transaction was already executed fully 34 | //We do not want to throw an error here so the result is ignored. 35 | mysql_autocommit(connection, true); 36 | applyChildResultStatus(data); 37 | } catch (const MySQLException &error) { 38 | data->setResultStatus(QUERY_ERROR); 39 | mysql_rollback(connection); 40 | //If this fails it probably means that the connection was lost 41 | //In that case autocommit is turned back on anyway (once the connection is reestablished) 42 | mysql_autocommit(connection, true); 43 | //In case of reconnect this might get called twice, but this should not affect anything 44 | applyChildResultStatus(data); 45 | throw error; 46 | } 47 | } 48 | 49 | void Transaction::applyChildResultStatus(const std::shared_ptr& data) { 50 | for (auto &pair: data->m_queries) { 51 | pair.second->setResultStatus(data->getResultStatus()); 52 | pair.second->setStatus(QUERY_COMPLETE); 53 | } 54 | } 55 | 56 | void Transaction::mysqlAutocommit(MYSQL *sql, bool auto_mode) { 57 | my_bool result = mysql_autocommit(sql, (my_bool) auto_mode); 58 | if (result) { 59 | const char *errorMessage = mysql_error(sql); 60 | unsigned int errorCode = mysql_errno(sql); 61 | throw MySQLException(errorCode, errorMessage); 62 | } 63 | } 64 | 65 | void Transaction::mysqlCommit(MYSQL *sql) { 66 | if (mysql_commit(sql)) { 67 | const char *errorMessage = mysql_error(sql); 68 | unsigned int errorCode = mysql_errno(sql); 69 | throw MySQLException(errorCode, errorMessage); 70 | } 71 | } 72 | 73 | 74 | std::shared_ptr 75 | Transaction::buildQueryData(const std::deque, std::shared_ptr>> &queries) { 76 | //At this point the transaction is guaranteed to have a referenced table 77 | //since this is always called shortly after transaction:start() 78 | return std::shared_ptr(new TransactionData(queries)); 79 | } 80 | 81 | std::shared_ptr Transaction::create(const std::shared_ptr &database) { 82 | return std::shared_ptr(new Transaction(database)); 83 | } -------------------------------------------------------------------------------- /src/mysql/Transaction.h: -------------------------------------------------------------------------------- 1 | #ifndef TRANSACTION_ 2 | #define TRANSACTION_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "IQuery.h" 10 | #include "Query.h" 11 | 12 | 13 | class TransactionData : public IQueryData { 14 | friend class Transaction; 15 | 16 | public: 17 | std::deque, std::shared_ptr>> m_queries; 18 | protected: 19 | explicit TransactionData(std::deque, std::shared_ptr>> queries) : 20 | m_queries(std::move(queries)) { 21 | }; 22 | }; 23 | 24 | class Transaction : public IQuery { 25 | friend class Database; 26 | 27 | public: 28 | static std::shared_ptr 29 | buildQueryData(const std::deque, std::shared_ptr>> &queries); 30 | 31 | static std::shared_ptr create(const std::shared_ptr &database); 32 | 33 | std::string getSQLString() override { return ""; }; 34 | 35 | protected: 36 | void executeStatement(Database &database, MYSQL *connection, const std::shared_ptr &data) override; 37 | 38 | explicit Transaction(const std::shared_ptr &database) : IQuery(database) { 39 | 40 | } 41 | 42 | private: 43 | static void applyChildResultStatus(const std::shared_ptr &data); 44 | 45 | static void mysqlAutocommit(MYSQL *sql, bool auto_mode); 46 | 47 | static void mysqlCommit(MYSQL *sql); 48 | }; 49 | 50 | #endif 51 | --------------------------------------------------------------------------------