├── .gitattributes ├── .gitignore ├── README.md ├── example ├── account_bcrypt.pwn └── account_sha256.pwn ├── mysql_prepared.inc ├── pawn.json └── test.pwn /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pwn linguist-language=Pawn 2 | *.inc linguist-language=Pawn 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Package only files 3 | # 4 | 5 | # Compiled Bytecode, precompiled output and assembly 6 | *.amx 7 | *.lst 8 | *.asm 9 | 10 | # Vendor directory for dependencies 11 | dependencies/ 12 | .vscode/ 13 | 14 | # Dependency versions lockfile 15 | pawn.lock 16 | 17 | 18 | # 19 | # Server/gamemode related files 20 | # 21 | 22 | # compiled settings file 23 | # keep `samp.json` file on version control 24 | # but make sure the `rcon_password` field is set externally 25 | # you can use the environment variable `SAMP_RCON_PASSWORD` to do this. 26 | server.cfg 27 | 28 | # Plugins directory 29 | plugins/ 30 | 31 | # binaries 32 | *.exe 33 | *.dll 34 | *.so 35 | announce 36 | samp03svr 37 | samp-npc 38 | 39 | # logs 40 | logs/ 41 | server_log.txt 42 | crashinfo.txt 43 | 44 | # Ban list 45 | samp.ban 46 | 47 | # 48 | # Common files 49 | # 50 | 51 | *.sublime-workspace -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 6 | 7 | 30 | 31 | ## Installation 32 | 33 | Simply install to your project: 34 | 35 | ```bash 36 | sampctl package install PatrickGTR/MySQL-Prepared-Statements 37 | ``` 38 | 39 | Include in your code and begin using the library: 40 | 41 | ```pawn 42 | #include 43 | ``` 44 | 45 | ## Usage 46 | 47 | 53 | #### Functions 54 | ```pawn 55 | MySQL_StatementClose(Statement:statement) 56 | MySQL_PrepareStatement(MySQL:handle, const query[]) 57 | MySQL_Statement_RowsLeft(&Statement:statement) 58 | MySQL_Statement_FetchRow(Statement:statement) 59 | ``` 60 | #### Writing 61 | ```pawn 62 | MySQL_Bind(Statement:statement, param, const str[]) 63 | MySQL_BindInt(Statement:statement, param, value) 64 | MySQL_BindFloat(Statement:statement, param, Float:value) 65 | 66 | MySQL_BindPlayerName(Statement:statement, param, playerid) 67 | MySQL_BindPlayerIp(Statement:statement, param, playerid) 68 | ``` 69 | #### Reading 70 | ```pawn 71 | MySQL_BindResult(Statement:statement, field, const result[], len = sizeof(result)) 72 | MySQL_BindResultInt(Statement:statement, field, &result) 73 | MySQL_BindResultFloat(Statement:statement, field, &Float:result) 74 | ``` 75 | #### Executing 76 | ```pawn 77 | MySQL_ExecuteThreaded(Statement:statement, const callback[] = "", const fmat[] = "", {Float,_}:...) 78 | MySQL_ExecuteParallel(Statement:statement, const callback[] = "", const fmat[] = "", {Float,_}:...) 79 | MySQL_ExecuteThreaded_Inline(Statement:statement, Func:callback<>) 80 | MySQL_ExecuteParallel_Inline(Statement:statement, Func:callback<>) 81 | ``` 82 | 83 | ## Testing 84 | 85 | 89 | 90 | To test, simply run the package: 91 | 92 | ```bash 93 | sampctl package run 94 | ``` 95 | 96 | 97 | ## Examples 98 | 99 | ### Reading Data (Using inline) 100 | ```pawn 101 | new stmt_readloop = MySQL_PrepareStatement(MySQLHandle, "SELECT * FROM spawns"); 102 | 103 | // Run Threaded on statement 104 | inline OnSpawnsLoad() { 105 | new 106 | spawnID, 107 | Float:spawnX, 108 | Float:spawnY, 109 | Float:spawnZ, 110 | Float:spawnA; 111 | 112 | MySQL_BindResultInt(stmt_readloop, 0, spawnID); 113 | MySQL_BindResultFloat(stmt_readloop, 1, spawnX); 114 | MySQL_BindResultFloat(stmt_readloop, 2, spawnY); 115 | MySQL_BindResultFloat(stmt_readloop, 3, spawnZ); 116 | MySQL_BindResultFloat(stmt_readloop, 4, spawnA); 117 | 118 | while(MySQL_Statement_FetchRow(stmt_readloop)) { 119 | printf("%i, %.3f, %.3f, %.3f", spawnID, spawnX, spawnY, spawnZ, spawnA); 120 | } 121 | MySQL_StatementClose(stmt_readloop); 122 | } 123 | MySQL_ExecuteThreaded_Inline(stmt_readloop, using inline OnSpawnsLoad); 124 | ``` 125 | 126 | ### Writing Data 127 | ```pawn 128 | new Statement:stmt_insert = MySQL_PrepareStatement(MySQLHandle, "INSERT INTO accounts(username, password, salt, money, kills, deaths) VALUES (?,?,?,?,?,?)"); 129 | 130 | // Arrow values in questions (first 0, second is 1, etc ...) 131 | MySQL_Bind(stmt_insert, 0 , "patrickgtr"); 132 | MySQL_Bind(stmt_insert, 1 , "patrickgtrpassword"); 133 | MySQL_Bind(stmt_insert, 2 , "pgtrhash"); 134 | MySQL_BindInt(stmt_insert, 3, 100); 135 | MySQL_BindInt(stmt_insert, 4, 200); 136 | MySQL_BindInt(stmt_insert, 5, 300); 137 | 138 | MySQL_ExecuteParallel(stmt_insert); 139 | MySQL_StatementClose(stmt_insert); 140 | ``` 141 | 142 | Full account system example can be found [here](https://github.com/PatrickGTR/MySQL-Prepared-Statements/blob/master/example/account.pwn) 143 | 144 | ## Special Thanks to: 145 | | Username | | 146 | |----------------------|---------------------------| 147 | | Southclaws | sampctl | 148 | | Y_Less / JustMichael | YSI / Constructive feedback | 149 | | Dayvison | Initial idea | 150 | | Slice | Base code of sqlite improved | 151 | | maddinat0r / BlueG | MySQL Plugin | 152 | 153 | -------------------------------------------------------------------------------- /example/account_bcrypt.pwn: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #define BCRYPT_COST 12 6 | #include 7 | 8 | #define YSI_NO_HEAP_MALLOC 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | 19 | new 20 | // player 21 | Player_UniqueID[MAX_PLAYERS], 22 | Player_Kills[MAX_PLAYERS], 23 | Player_Deaths[MAX_PLAYERS], 24 | // connection 25 | MySQL: MySQL_Handle, 26 | // statements 27 | Statement: stmt_checkPlayer, 28 | Statement: stmt_insertPlayer, 29 | Statement: stmt_loadPlayerData, 30 | Statement: stmt_savePlayerData 31 | // end 32 | ; 33 | 34 | main () { 35 | 36 | } 37 | 38 | public OnGameModeInit() { 39 | 40 | // Connect to MySQL 41 | MySQL_Handle = mysql_connect("localhost", "root", "password", "example"); 42 | 43 | if(mysql_errno() != 0) { 44 | printf("ERROR: MySQL could not connect to database!"); 45 | SendRconCommand("exit"); 46 | } 47 | 48 | // Prepare Statement 49 | stmt_checkPlayer = MySQL_PrepareStatement(MySQL_Handle, "SELECT uid, password FROM accounts WHERE username = ?"); 50 | stmt_insertPlayer = MySQL_PrepareStatement(MySQL_Handle, "INSERT INTO accounts (username, password) VALUES (?, ?)"); 51 | stmt_loadPlayerData = MySQL_PrepareStatement(MySQL_Handle, "SELECT kills, deaths FROM accounts WHERE uid = ?"); 52 | stmt_savePlayerData = MySQL_PrepareStatement(MySQL_Handle, "UPDATE accounts SET kills = ?, deaths = ? WHERE uid = ?"); 53 | return 1; 54 | } 55 | 56 | public OnGameModeExit() { 57 | mysql_close(MySQL_Handle); 58 | return 1; 59 | } 60 | 61 | 62 | public OnPlayerConnect(playerid) { 63 | inline const OnDataLoad() { 64 | new 65 | playerHash[65]; 66 | 67 | MySQL_BindResultInt(stmt_checkPlayer, 0, Player_UniqueID[playerid]); 68 | MySQL_BindResult(stmt_checkPlayer, 1, playerHash, sizeof (playerHash)); 69 | 70 | // run the statement with the binded data and fetch 71 | // the rows and store it to the variables assigned 72 | if(MySQL_Statement_FetchRow(stmt_checkPlayer)) { 73 | Account_PromptLogin(playerid, playerHash); 74 | } 75 | else { 76 | Account_PromptRegister(playerid); 77 | } 78 | } 79 | 80 | // insert player name to first ? (question mark) 81 | MySQL_BindPlayerName(stmt_checkPlayer, 0, playerid); 82 | // execute the query. 83 | MySQL_ExecuteParallel_Inline(stmt_checkPlayer, using inline OnDataLoad); 84 | return 1; 85 | } 86 | 87 | public OnPlayerDeath(playerid, killerid, reason) { 88 | // check if the killer is valid. 89 | // this check is to avoid Out of Bounds. 90 | if(killerid != INVALID_PLAYER_ID){ 91 | Player_Kills[killerid] ++; 92 | } 93 | Player_Deaths[playerid] ++; 94 | return 1; 95 | } 96 | 97 | public OnPlayerDisconnect(playerid, reason) { 98 | Account_Save(playerid); 99 | return 1; 100 | } 101 | 102 | // Account_PromptLogin shows the login dialog. 103 | // this function is called under OnPlayerConnect. 104 | // return --> none. 105 | Account_PromptLogin(playerid, const password[]) { 106 | // y_inline fix 107 | // this issue: https://github.com/pawn-lang/YSI-Includes/issues/249 108 | // copy to local 109 | 110 | new 111 | fix_password[64]; 112 | 113 | strcpy(fix_password, password, sizeof(fix_password)); 114 | inline const _response(pid, dialogid, response, listitem, string:inputtext[]) { 115 | #pragma unused pid, dialogid, listitem 116 | 117 | // if user does not respond, kick him. 118 | if (!response) { 119 | Kick(playerid); 120 | return; 121 | } 122 | Validate(playerid, inputtext, fix_password); 123 | } 124 | 125 | new 126 | string[MAX_PLAYER_NAME + 37], 127 | playerName[MAX_PLAYER_NAME]; 128 | 129 | GetPlayerName(playerid, playerName, sizeof(playerName)); 130 | format(string, sizeof(string), "Hello %s. Welcome back to the server!", playerName); 131 | 132 | Dialog_ShowCallback( 133 | playerid, 134 | using inline _response, // Handler 135 | DIALOG_STYLE_PASSWORD, // Style 136 | "Please login...", // Title 137 | string, // Content 138 | "Login", // Button Left 139 | "Leave" // Button Right 140 | ); 141 | } 142 | 143 | // Account_PromptRegister shows the register dialog. 144 | // this function is called under OnPlayerConnect. 145 | Account_PromptRegister(playerid) { 146 | 147 | inline _response(pid, dialogid, response, listitem, string:inputtext[]) { 148 | #pragma unused pid, dialogid, listitem 149 | if (!response) { 150 | Kick(playerid); 151 | return; 152 | } 153 | Create(playerid, inputtext); 154 | } 155 | 156 | new 157 | string[MAX_PLAYER_NAME + 32], 158 | playerName[MAX_PLAYER_NAME]; 159 | 160 | GetPlayerName(playerid, playerName, sizeof(playerName)); 161 | format(string, sizeof(string), "Hello %s. Welcome to the server!", playerName); 162 | 163 | Dialog_ShowCallback( 164 | playerid, 165 | using inline _response, // Handler 166 | DIALOG_STYLE_PASSWORD, // Style 167 | "Please register...", // Title 168 | string, // Content 169 | "Register", // Button left 170 | "Leave" // Button right 171 | ); 172 | } 173 | 174 | // Account_Save saves the player's stats, in this example it only saves kills and deaths. 175 | // NOTE: Although for best practices you should be saving data everytime it changes. 176 | // as this is only an example we'll do it the basic way :) 177 | Account_Save(playerid) { 178 | // insert kills to first ? (question mark) 179 | MySQL_BindInt(stmt_savePlayerData, 0, Player_Kills[playerid]); 180 | // insert deaths to second ? (question mark) 181 | MySQL_BindInt(stmt_savePlayerData, 1, Player_Deaths[playerid]); 182 | // insert unique id to third ? (question mark) 183 | MySQL_BindInt(stmt_savePlayerData, 2, Player_UniqueID[playerid]); 184 | // execute statement 185 | MySQL_ExecuteThreaded(stmt_savePlayerData); 186 | } 187 | 188 | // ---------------------------------------------------------------- 189 | // Local function, can't be accessed outside this file. 190 | // ---------------------------------------------------------------- 191 | 192 | // Validate(playerid, const input[], dbPassword[]) 193 | // compares whether the password from the database 194 | // matches the user input, if it doesn't match we prompt login 195 | // if password match we load the remaining data that needs loading. 196 | static Validate(playerid, const input[], const dbPassword[]) { 197 | 198 | // -- DO NOT REMOVE, IMPORTANT! -- // 199 | // y_inline fix 200 | // this issue: https://github.com/pawn-lang/YSI-Includes/issues/249 201 | // copy to local 202 | new 203 | fix_password[64]; 204 | 205 | strcpy(fix_password, dbPassword, sizeof(fix_password)); 206 | // -- DO NOT REMOVE, IMPORTANT! -- // 207 | 208 | inline const OnPasswordVerify(bool: success) { 209 | if(!success) { 210 | Account_PromptLogin(playerid, fix_password); 211 | SendClientMessage(playerid, -1, "Could not validate password, try again."); 212 | return; 213 | } 214 | 215 | Load(playerid); 216 | } 217 | BCrypt_CheckInline(input, dbPassword, using inline OnPasswordVerify); 218 | } 219 | 220 | // Load(playerid) retrieves the remaining data from the database 221 | // so in this case we load kills and deaths 222 | // and store it to global variable so it can be 223 | // modified and accessed for later purposes 224 | static Load(playerid) { 225 | 226 | inline const OnDataLoad() { 227 | MySQL_BindResultInt(stmt_loadPlayerData, 0, Player_Kills[playerid]); 228 | MySQL_BindResultInt(stmt_loadPlayerData, 1, Player_Deaths[playerid]); 229 | 230 | if(MySQL_Statement_FetchRow(stmt_loadPlayerData)) { 231 | SendClientMessage(playerid, -1, "You have successfully logged in. Welcome back!"); 232 | } 233 | } 234 | 235 | MySQL_BindInt(stmt_loadPlayerData, 0, Player_UniqueID[playerid]); 236 | MySQL_ExecuteParallel_Inline(stmt_loadPlayerData, using inline OnDataLoad); 237 | } 238 | 239 | // Create(playerid, const password[]) is called to initiate account creation process. 240 | // Current validations are... 241 | // checks if password is more than 3 but less than 20. 242 | // checks if password contains all numbers. 243 | // checks if password contains valid characters 244 | static Create(playerid, const password[]) { 245 | 246 | if (strlen(password) <= 5) { 247 | SendClientMessage(playerid, -1, "Invalid length on the password. It should be more than 5 characters" ); 248 | Account_PromptRegister(playerid); 249 | return; 250 | } 251 | if (isnumeric(password)) { 252 | SendClientMessage(playerid, -1, "Your password is invalid. The password should include alphabets."); 253 | Account_PromptRegister(playerid); 254 | return; 255 | } 256 | 257 | inline const OnPasswordHash(string:hash[]) { 258 | InsertToDB(playerid, hash); 259 | } 260 | BCrypt_HashInline(password, BCRYPT_COST, using inline OnPasswordHash); 261 | } 262 | 263 | static InsertToDB(playerid, const password[]) { 264 | inline const OnRegister() { 265 | Player_UniqueID[playerid] = cache_insert_id(); 266 | 267 | SendClientMessage(playerid, -1, "You have just registered to our server! You have been automatically logged in!"); 268 | } 269 | 270 | MySQL_BindPlayerName(stmt_insertPlayer, 0, playerid); 271 | MySQL_Bind(stmt_insertPlayer, 1, password); 272 | MySQL_ExecuteThreaded_Inline(stmt_insertPlayer, using inline OnRegister); 273 | } 274 | -------------------------------------------------------------------------------- /example/account_sha256.pwn: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "../mysql_prepared" 8 | 9 | new 10 | // player 11 | Player_UniqueID[MAX_PLAYERS], 12 | Player_Kills[MAX_PLAYERS], 13 | Player_Deaths[MAX_PLAYERS], 14 | // connection 15 | MySQL: MySQL_Handle, 16 | // statements 17 | Statement: stmt_checkPlayer, 18 | Statement: stmt_insertPlayer, 19 | Statement: stmt_loadPlayerData, 20 | Statement: stmt_savePlayerData 21 | // end 22 | ; 23 | 24 | main () { 25 | 26 | } 27 | 28 | public OnGameModeInit() { 29 | 30 | // Connect to MySQL 31 | MySQL_Handle = mysql_connect("localhost", "root", "", "example"); 32 | 33 | if(mysql_errno() != 0) { 34 | printf("ERROR: MySQL could not connect to database!"); 35 | SendRconCommand("exit"); 36 | } 37 | 38 | // Prepare Statement 39 | stmt_checkPlayer = MySQL_PrepareStatement(MySQL_Handle, "SELECT uid, hash, salt FROM accounts WHERE username = ?"); 40 | stmt_insertPlayer = MySQL_PrepareStatement(MySQL_Handle, "INSERT INTO accounts (username, hash, salt) VALUES (?, ?, ?)"); 41 | stmt_loadPlayerData = MySQL_PrepareStatement(MySQL_Handle, "SELECT kills, deaths FROM accounts WHERE uid = ?"); 42 | stmt_savePlayerData = MySQL_PrepareStatement(MySQL_Handle, "UPDATE accounts SET kills = ?, deaths = ? WHERE uid = ?"); 43 | return 1; 44 | } 45 | 46 | public OnGameModeExit() { 47 | mysql_close(MySQL_Handle); 48 | return 1; 49 | } 50 | 51 | public OnPlayerConnect(playerid) { 52 | 53 | inline OnDataLoad() { 54 | 55 | new 56 | playerHash[64 + 1], 57 | playerSalt[11 + 1]; 58 | 59 | MySQL_BindResultInt(stmt_checkPlayer, 0, Player_UniqueID[playerid]); 60 | MySQL_BindResult(stmt_checkPlayer, 1, playerHash, sizeof (playerHash)); 61 | MySQL_BindResult(stmt_checkPlayer, 2, playerSalt, sizeof (playerSalt)); 62 | 63 | if(MySQL_Statement_FetchRow(stmt_checkPlayer)) { 64 | Account_PromptLogin(playerid, playerHash, playerSalt); 65 | } else { 66 | Account_PromptRegister(playerid); 67 | } 68 | } 69 | // insert player name to first ? (question mark) 70 | MySQL_BindPlayerName(stmt_checkPlayer, 0, playerid); 71 | 72 | // execute the query. 73 | MySQL_ExecuteParallel_Inline(stmt_checkPlayer, using inline OnDataLoad); 74 | return 1; 75 | } 76 | 77 | public OnPlayerDeath(playerid, killerid, reason) { 78 | 79 | // check if the killer is valid. 80 | // this check is to avoid Out of Bounds. 81 | if(killerid != INVALID_PLAYER_ID){ 82 | Player_Kills[killerid] ++; 83 | } 84 | Player_Deaths[playerid] ++; 85 | return 1; 86 | } 87 | 88 | public OnPlayerDisconnect(playerid, reason) { 89 | 90 | Account_Save(playerid); 91 | return 1; 92 | } 93 | 94 | // Account_InsertToDatabase inserts the data to the database 95 | // this function should be only used under Account_Create 96 | // return --> none 97 | stock Account_InsertToDatabase(playerid, const hash[], const salt[]) { 98 | inline RegisterPlayer() { 99 | Player_UniqueID[playerid] = cache_insert_id(); 100 | SendClientMessage(playerid, -1, "You have just registered to our server! You have been automatically logged in!"); 101 | } 102 | MySQL_BindPlayerName(stmt_insertPlayer, 0, playerid); 103 | MySQL_Bind(stmt_insertPlayer, 1, hash); 104 | MySQL_Bind(stmt_insertPlayer, 2, salt); 105 | MySQL_ExecuteThreaded_Inline(stmt_insertPlayer, using inline RegisterPlayer); 106 | } 107 | 108 | // Account_Load loads the remaining data from the database 109 | // this function should be only used under Account_PromptLogin 110 | // return --> none 111 | stock Account_Load(playerid) { 112 | 113 | inline OnDataLoad() { 114 | MySQL_BindResultInt(stmt_loadPlayerData, 0, Player_Kills[playerid]); 115 | MySQL_BindResultInt(stmt_loadPlayerData, 1, Player_Deaths[playerid]); 116 | 117 | if(MySQL_Statement_FetchRow(stmt_loadPlayerData)) { 118 | SendClientMessage(playerid, -1, "You have successfully logged in. Welcome back!"); 119 | } 120 | } 121 | 122 | MySQL_BindInt(stmt_loadPlayerData, 0, Player_UniqueID[playerid]); 123 | MySQL_ExecuteParallel_Inline(stmt_loadPlayerData, using inline OnDataLoad); 124 | } 125 | 126 | 127 | // Account_PromptLogin shows the login dialog. 128 | // this function is called under OnPlayerConnect. 129 | // return --> none. 130 | stock Account_PromptLogin(playerid, const hash[], const salt[]) { 131 | // y_inline fix 132 | // this issue: https://github.com/pawn-lang/YSI-Includes/issues/249 133 | new 134 | fix_salt[11 + 1], 135 | fix_hash[64 + 1]; 136 | 137 | // copy to local 138 | strcpy(fix_salt, salt, sizeof(fix_salt)); 139 | strcpy(fix_hash, hash, sizeof(fix_hash)); 140 | 141 | inline PromptLogin_Response(pid, dialogid, response, listitem, string:inputtext[]) { 142 | #pragma unused pid, dialogid, listitem 143 | 144 | // if user does not respond, kick him. 145 | if (!response) { 146 | Kick(playerid); 147 | return; 148 | } 149 | 150 | new 151 | buf[65]; 152 | 153 | SHA256_PassHash(inputtext, fix_salt, buf, sizeof(buf)); 154 | printf("fix_hash: %s | buf: %s", fix_hash, buf); 155 | 156 | // compare password 157 | // if password from database isn't the same as the user input ==> wrong 158 | // if password is empty ==> wrong 159 | if (strcmp(buf, fix_hash) != 0 && !isnull(inputtext)) { 160 | SendClientMessage(playerid, -1, "Incorrect password"); 161 | Account_PromptLogin(playerid, fix_hash, fix_salt); 162 | return; 163 | } 164 | 165 | Account_Load(playerid); 166 | } 167 | 168 | new 169 | string[MAX_PLAYER_NAME + 37], 170 | playerName[MAX_PLAYER_NAME]; 171 | 172 | GetPlayerName(playerid, playerName, sizeof(playerName)); 173 | format(string, sizeof(string), "Hello %s. Welcome back to the server!", playerName); 174 | 175 | Dialog_ShowCallback(playerid, 176 | using inline PromptLogin_Response, // Handler 177 | DIALOG_STYLE_PASSWORD, // Style 178 | "Please login...", // Title 179 | string, // Content 180 | "Login", // Button Left 181 | "Leave"); // Button Right 182 | } 183 | 184 | // Account_PromptRegister shows the register dialog. 185 | // this function is called under OnPlayerConnect. 186 | // return --> none. 187 | stock Account_PromptRegister(playerid) { 188 | 189 | inline PromptRegister_Response(pid, dialogid, response, listitem, string:inputtext[]) { 190 | #pragma unused pid, dialogid, listitem 191 | if (!response) { 192 | Kick(playerid); 193 | return; 194 | } 195 | Account_Create(playerid, inputtext); 196 | } 197 | 198 | new 199 | string[MAX_PLAYER_NAME + 32], 200 | playerName[MAX_PLAYER_NAME]; 201 | 202 | GetPlayerName(playerid, playerName, sizeof(playerName)); 203 | format(string, sizeof(string), "Hello %s. Welcome to the server!", playerName); 204 | 205 | Dialog_ShowCallback(playerid, 206 | using inline PromptRegister_Response, 207 | DIALOG_STYLE_PASSWORD, // style 208 | "Please register...", // title 209 | string, // content 210 | "Register", // button left 211 | "Leave"); // button right 212 | } 213 | 214 | // Account_Save saves the player's stats, in this example it only saves kills and deaths. 215 | // this function is called under OnPlayerDisconnect. 216 | // return --> none. 217 | stock Account_Save(playerid) { 218 | // insert kills to first ? (question mark) 219 | MySQL_BindInt(stmt_savePlayerData, 0, Player_Kills[playerid]); 220 | // insert deaths to second ? (question mark) 221 | MySQL_BindInt(stmt_savePlayerData, 1, Player_Deaths[playerid]); 222 | // insert unique id to third ? (question mark) 223 | MySQL_BindInt(stmt_savePlayerData, 2, Player_UniqueID[playerid]); 224 | // execute statement 225 | MySQL_ExecuteThreaded(stmt_savePlayerData); 226 | } 227 | 228 | // Account_Create is called to initiate account creation process. 229 | // this function checks if password is more than 3 but less than 20. 230 | // this function checks if password contains all numbers. 231 | // this function checks if password contains valid characters 232 | // if password passes all checks, we create a randomised string for salt 233 | // then we hash the password using SHA256_PassHash -> This is not recommended to use but I am using this as an example only. 234 | // return --> none. 235 | stock Account_Create(playerid, const password[]) { 236 | 237 | if (!(3 <= strlen(password) <= 20)) { 238 | SendClientMessage(playerid, -1, "Invalid length on the password. It should be between 3-20 characters" ); 239 | Account_PromptRegister(playerid); 240 | return; 241 | } 242 | if (isnumeric(password)) { 243 | SendClientMessage(playerid, -1, "Your password is invalid. The password should include alphabets."); 244 | Account_PromptRegister(playerid); 245 | return; 246 | } 247 | 248 | new 249 | bool: ret; 250 | for(new i = 0; password[i] != EOS; ++i) { 251 | switch(password[i]) { 252 | case '0'..'9', 'A'..'Z', 'a'..'z': { 253 | ret = true; 254 | } 255 | default: { 256 | ret = false; 257 | } 258 | } 259 | } 260 | 261 | if(!ret) { 262 | SendClientMessage(playerid, -1, "Your password is invalid. Valid characters are: A-Z, a-z, 0-9."); 263 | Account_PromptRegister(playerid); 264 | return; 265 | } 266 | 267 | new 268 | salt[11 + 1], 269 | hash[64 + 1]; 270 | 271 | for(new i = 0; i < 10; i++) { 272 | salt[i] = random(79) + 47; 273 | } 274 | salt[10] = 0; 275 | SHA256_PassHash(password, salt, hash, sizeof(hash)); 276 | 277 | // insert 278 | Account_InsertToDatabase(playerid, hash, salt); 279 | } 280 | -------------------------------------------------------------------------------- /mysql_prepared.inc: -------------------------------------------------------------------------------- 1 | 2 | #if !defined _INC_y_va 3 | #error You need to "#include " before using this library. 4 | #endif 5 | 6 | #if defined MYSQL_PREPARED_INC 7 | #endinput 8 | #endif 9 | #define MYSQL_PREPARED_INC 10 | 11 | #if !defined MAX_PARAMS 12 | #define MAX_PARAMS (32) 13 | #endif 14 | 15 | #if !defined MAX_FIELD_NAME 16 | #define MAX_FIELD_NAME (66) 17 | #endif 18 | 19 | #if !defined MAX_STATEMENTS 20 | #define MAX_STATEMENTS (16) 21 | #endif 22 | 23 | #if !defined MAX_STATEMENT_SIZE 24 | #define MAX_STATEMENT_SIZE (1024) 25 | #endif 26 | 27 | #if !defined MAX_FIELDS 28 | #define MAX_FIELDS (64) 29 | #endif 30 | 31 | #define Warning(%1) print(!"Warning: " %1) 32 | #define Warningf(%1) printf("Warning: " %1) 33 | 34 | #define Error(%1) print(!"Error: " %1) 35 | #define Errorf(%1) printf("Error: " %1) 36 | 37 | #define Debug(%1) printf("Debug: " %1) 38 | 39 | #if !defined MYSQL_PREPARE_DEBUG 40 | #define MYSQL_PREPARE_DEBUG (false) 41 | #endif 42 | 43 | const 44 | Statement:INVALID_STATEMENT = Statement:-1, 45 | MySQL:INVALID_CONNECTION = MYSQL_INVALID_HANDLE; 46 | 47 | 48 | enum DataType { 49 | TYPE_NONE, 50 | TYPE_NULL, 51 | TYPE_INT, 52 | TYPE_FLOAT, 53 | TYPE_STRING, 54 | TYPE_RAW_STRING, 55 | }; 56 | 57 | static enum QueryType { 58 | TYPE_THREADED, 59 | TYPE_PARALLEL 60 | } 61 | 62 | static enum E_STMT_MYSQL { 63 | // The ready-to-run query. 64 | E_STMT_MYSQL_QUERY[MAX_STATEMENT_SIZE + 1], 65 | // Types of bound return fields 66 | DataType:E_STMT_MYSQL_FIELD_TYPE[MAX_FIELDS], 67 | // Parameter count 68 | E_STMT_MYSQL_PARAM_COUNT, 69 | // The parameter types. 70 | DataType:E_STMT_MYSQL_PARAM_TYPE[MAX_PARAMS], 71 | // Sizes of bound result fields (used only for strings currently) 72 | E_STMT_MYSQL_FIELD_SIZE[MAX_FIELDS], 73 | // Position of parameters in the query string. 74 | E_STMT_MYSQL_PARAM_POS[MAX_PARAMS], 75 | // Addresses of bound result fields 76 | E_STMT_MYSQL_FIELD_ADDRESS[MAX_FIELDS], 77 | E_STMT_MYSQL_ROWS_FETCHED, 78 | // Length of parameters in the query string. 79 | E_STMT_MYSQL_PARAM_LEN[MAX_PARAMS], 80 | // The database it was created for 81 | MySQL:E_STMT_MYSQL_HANDLE, 82 | bool:E_STMT_COUNT_ROW_FIRST 83 | }; 84 | 85 | static stock 86 | gs_szBuffer[8192], 87 | gs_Statements[Statement:MAX_STATEMENTS][E_STMT_MYSQL], 88 | totalRows[Statement:MAX_STATEMENTS] = 0; 89 | 90 | // -- 91 | // Internally used, Deprecated Function Names 92 | // -- 93 | static stock escape_string(szString[], const szEnclosing[] = "'", argSize = sizeof(szString)) { 94 | new 95 | iPos; 96 | 97 | while (-1 != (iPos = strfind(szString, szEnclosing, _, iPos))) { 98 | strins(szString, szEnclosing, iPos, argSize); 99 | iPos += 2; 100 | } 101 | } 102 | 103 | static stock stmt_close(Statement:statement) { 104 | if (statement == INVALID_STATEMENT || !(0 <= _:statement < sizeof(gs_Statements))) { 105 | Errorf("(stmt_close) Invalid Statement (%d).", _:statement); 106 | return false; 107 | } 108 | 109 | #if MYSQL_PREPARE_DEBUG 110 | Debug("(stmt_close:%d) Statament closed.", _:statement); 111 | #endif 112 | 113 | // reset data. 114 | gs_Statements[statement][E_STMT_MYSQL_HANDLE] = INVALID_CONNECTION; 115 | return true; 116 | } 117 | 118 | static stock bool:stmt_execute(Statement:statement, QueryType:type, const callback[] = "", const fmat[] = "", {Float,_}:...) { 119 | if (statement == INVALID_STATEMENT || !(0 <= _:statement < sizeof(gs_Statements))) { 120 | Errorf("(stmt_execute) Invalid Statement (%d).", _:statement); 121 | return false; 122 | } 123 | 124 | if (mysql_errno(gs_Statements[statement][E_STMT_MYSQL_HANDLE]) != 0) { 125 | Errorf("(stmt_execute) Invalid Connection (%d).", _:statement); 126 | return false; 127 | } 128 | 129 | // Make sure all parameters have been set. 130 | for (new i = 0; i < gs_Statements[statement][E_STMT_MYSQL_PARAM_COUNT]; i++) { 131 | if (gs_Statements[statement][E_STMT_MYSQL_PARAM_TYPE][i] == TYPE_NONE) { 132 | Errorf("(stmt_execute) Parameter (%d) not started in statement", i); 133 | return false; 134 | } 135 | } 136 | 137 | // write 138 | strunpack(gs_szBuffer, gs_Statements[statement][E_STMT_MYSQL_QUERY]); 139 | 140 | new ret = 141 | type 142 | ? 143 | mysql_tquery(gs_Statements[statement][E_STMT_MYSQL_HANDLE], gs_szBuffer, callback, fmat, ___(4)) 144 | : 145 | mysql_pquery(gs_Statements[statement][E_STMT_MYSQL_HANDLE], gs_szBuffer, callback, fmat, ___(4)); 146 | 147 | // throw error if query failed. 148 | if(!ret) { 149 | Errorf("(stmt_execute:%d) failed to execute the query", _:statement); 150 | return false; 151 | } 152 | 153 | gs_Statements[statement][E_STMT_MYSQL_ROWS_FETCHED] = 0; 154 | totalRows[statement] = 0; 155 | 156 | #if MYSQL_PREPARE_DEBUG 157 | Debug("(stmt_execute:%d) Statement executed.", _:statement); 158 | Debug("(stmt_execute:%d) :Query executed [%s]", _:statement, gs_szBuffer); 159 | #endif 160 | return bool:ret; 161 | } 162 | 163 | static stock bool:stmt_bind_value(&Statement:statement, param, DataType:type, {Float,_}:...) 164 | { 165 | 166 | new 167 | lengthDifference, 168 | queryLength; 169 | 170 | if (statement == INVALID_STATEMENT || !(0 <= _:statement < sizeof(gs_Statements))) { 171 | Warningf("(stmt_bind_value) Invalid statement (%d).", _:statement); 172 | return false; 173 | } 174 | 175 | if (param >= gs_Statements[statement][E_STMT_MYSQL_PARAM_COUNT]) { 176 | Warningf("(stmt_bind_value) Parameter index larger than number of parameters (%d > %d)", param, gs_Statements[statement][E_STMT_MYSQL_PARAM_COUNT]); 177 | return false; 178 | } 179 | 180 | // Fill gs_szBuffer with the new contents. 181 | gs_szBuffer[0] = EOS; 182 | 183 | switch (type) { 184 | case TYPE_NULL: { 185 | goto default_case; 186 | } 187 | case TYPE_INT: { 188 | new 189 | iArgValue = getarg(3) 190 | ; 191 | 192 | if (iArgValue == cellmin) { 193 | gs_szBuffer = !"-2147483648"; 194 | } else { 195 | format(gs_szBuffer, sizeof(gs_szBuffer), "%d", getarg(3)); 196 | } 197 | } 198 | case TYPE_FLOAT: { 199 | format(gs_szBuffer, sizeof(gs_szBuffer), "%f", getarg(3)); 200 | } 201 | case TYPE_STRING: { 202 | 203 | new ret[128]; 204 | va_getstring(ret, 3); 205 | 206 | strpack(gs_szBuffer, ret, (sizeof(gs_szBuffer) - 3)); 207 | 208 | escape_string(gs_szBuffer, "'", sizeof(gs_szBuffer) - 1); 209 | 210 | strins(gs_szBuffer, !"'", 0); // insert an '(apostrophe) first 211 | strcat(gs_szBuffer, !"'"); // insert an 'apostrophe end 212 | } 213 | case TYPE_RAW_STRING: { 214 | new ret[128]; 215 | getstringarg(ret, 3); 216 | strcat(gs_szBuffer, ret); 217 | } 218 | default: { 219 | default_case: 220 | strcat(gs_szBuffer, "NULL"); 221 | } 222 | } 223 | 224 | queryLength = strlen(gs_szBuffer); 225 | 226 | lengthDifference = queryLength - gs_Statements[statement][E_STMT_MYSQL_PARAM_LEN][param]; 227 | 228 | // Adjust the position of any params after the one being modified. 229 | for (new i = param + 1; i < gs_Statements[statement][E_STMT_MYSQL_PARAM_COUNT]; i++) 230 | gs_Statements[statement][E_STMT_MYSQL_PARAM_POS][i] += lengthDifference; 231 | 232 | // Delete the old parameter from the query. 233 | strdel(gs_Statements[statement][E_STMT_MYSQL_QUERY], gs_Statements[statement][E_STMT_MYSQL_PARAM_POS][param], gs_Statements[statement][E_STMT_MYSQL_PARAM_POS][param] + gs_Statements[statement][E_STMT_MYSQL_PARAM_LEN][param]); 234 | 235 | // Make sure we have enough space. 236 | if ((strlen(gs_Statements[statement][E_STMT_MYSQL_QUERY]) + queryLength) char > MAX_STATEMENT_SIZE) { 237 | Error("(stmt_bind_value) Buffer overflow. Aumente MAX_STATEMENT_SIZE."); 238 | MySQL_StatementClose(statement); 239 | return false; 240 | } 241 | 242 | // Insert the new parameter. 243 | strins(gs_Statements[statement][E_STMT_MYSQL_QUERY], gs_szBuffer, gs_Statements[statement][E_STMT_MYSQL_PARAM_POS][param], MAX_STATEMENT_SIZE); 244 | 245 | #if MYSQL_PREPARE_DEBUG 246 | if (ispacked(gs_szBuffer)) { 247 | strunpack(gs_szBuffer, gs_szBuffer); 248 | } 249 | Debug("(stmt_bind_value:%d) New parameter entered for %d in %d: %s", _:statement, param, gs_Statements[statement][E_STMT_MYSQL_PARAM_POS][param], gs_szBuffer); 250 | #endif 251 | 252 | gs_Statements[statement][E_STMT_MYSQL_PARAM_LEN][param] = queryLength; 253 | gs_Statements[statement][E_STMT_MYSQL_PARAM_TYPE][param] = type; 254 | return true; 255 | } 256 | 257 | static stock stmt_bind_result_field(Statement:statement, field, DataType:type, {Float, _}:...) { 258 | new 259 | address, 260 | argSize, 261 | countArgs 262 | ; 263 | 264 | #emit LOAD.S.pri 8 265 | #emit SHR.C.pri 2 266 | #emit STOR.S.pri countArgs 267 | 268 | if (statement == INVALID_STATEMENT || !(0 <= _:statement < sizeof(gs_Statements))) { 269 | Warningf("(stmt_bind_result_field) Invalid statement passed (%d).", _:statement); 270 | return; 271 | } 272 | 273 | if (field < 0) { 274 | Errorf("(stmt_bind_result_field) Negative field index (%d).", field); 275 | return; 276 | } 277 | 278 | switch (type) { 279 | case 280 | TYPE_STRING, 281 | TYPE_RAW_STRING: { 282 | if (countArgs != 5) { 283 | Error("(stmt_bind_result_field) Invalid number of arguments passed. Strings and arrays require an additional argument containing the string size."); 284 | return; 285 | } 286 | 287 | argSize = getarg(4); 288 | } 289 | 290 | case TYPE_NONE: { 291 | gs_Statements[statement][E_STMT_MYSQL_FIELD_TYPE][field] = TYPE_NONE; 292 | return; 293 | } 294 | 295 | default: { 296 | if (countArgs != 4) { 297 | Error("(stmt_bind_result_field) Invalid number of arguments passed."); 298 | return; 299 | } 300 | 301 | argSize = 1; 302 | } 303 | } 304 | 305 | if (field >= MAX_FIELDS) { 306 | Warningf("(stmt_bind_result_field) Field index larger than max number of fields (%d > %d). Increase DB_MAX_FIELDS.", field, MAX_FIELDS); 307 | return; 308 | } 309 | 310 | {} 311 | 312 | #emit LOAD.S.pri 24 313 | #emit STOR.S.pri address 314 | 315 | gs_Statements[statement][E_STMT_MYSQL_FIELD_TYPE][field] = type; 316 | gs_Statements[statement][E_STMT_MYSQL_FIELD_ADDRESS][field] = address; 317 | gs_Statements[statement][E_STMT_MYSQL_FIELD_SIZE][field] = argSize; 318 | 319 | #if MYSQL_PREPARE_DEBUG 320 | Debug("(stmt_bind_result_field:%d) Bound result field %d (type %d) to variable 0x%04x%04x.", _:statement, field, _:type, address >>> 16, address & 0xFFFF); 321 | #endif 322 | } 323 | 324 | static stock Statement:stmt_prepare(MySQL:handle, const szQuery[]) { 325 | 326 | new 327 | Statement:statement = INVALID_STATEMENT, 328 | paramPos, 329 | i, 330 | queryLength; 331 | 332 | if(mysql_errno(handle) != 0) { 333 | Error("(stmt_prepare) Invalid Connection."); 334 | return INVALID_STATEMENT; 335 | } 336 | 337 | // Pretty useless to prepare empty queries. 338 | if (!(queryLength = strlen(szQuery))) { 339 | Error("(stmt_prepare) Empty query."); 340 | return INVALID_STATEMENT; 341 | } 342 | 343 | if (queryLength char > MAX_STATEMENT_SIZE) { 344 | Error("(stmt_prepare) Query too long. Increase MAX_STATEMENT_SIZE."); 345 | return INVALID_STATEMENT; 346 | } 347 | 348 | // Find an empty slot in gs_Statements. 349 | for (i = 0; i < sizeof(gs_Statements); i++) { 350 | if (gs_Statements[Statement:i][E_STMT_MYSQL_HANDLE] == INVALID_CONNECTION) { 351 | statement = Statement:i; 352 | break; 353 | } 354 | } 355 | 356 | if (statement == INVALID_STATEMENT) { 357 | Error("(stmt_prepare) No slots found free to do statament. Increase MAX_STATEMENTS."); 358 | return INVALID_STATEMENT; 359 | } 360 | 361 | // Make sure no parameters are initialized. 362 | for (i = 0; i < MAX_PARAMS; i++) 363 | gs_Statements[statement][E_STMT_MYSQL_PARAM_TYPE][i] = TYPE_NONE; 364 | 365 | paramPos = -1; 366 | i = 0; 367 | 368 | // Find all parameters 369 | while (-1 != (paramPos = strfind(szQuery, "?", _, ++paramPos))) { 370 | gs_Statements[statement][E_STMT_MYSQL_PARAM_POS][i] = paramPos; 371 | gs_Statements[statement][E_STMT_MYSQL_PARAM_LEN][i] = 1; 372 | 373 | if (++i >= MAX_PARAMS) { 374 | Error("(stmt_prepare) Parameter limit exceeded. Increase MAX_PARAMS."); 375 | return INVALID_STATEMENT; 376 | } 377 | } 378 | 379 | // init values 380 | gs_Statements[statement][E_STMT_MYSQL_HANDLE] = handle; 381 | gs_Statements[statement][E_STMT_MYSQL_PARAM_COUNT] = i; 382 | gs_Statements[statement][E_STMT_MYSQL_QUERY][0] = 0; 383 | gs_Statements[statement][E_STMT_MYSQL_ROWS_FETCHED] = 0; 384 | gs_Statements[statement][E_STMT_COUNT_ROW_FIRST] = true; 385 | 386 | if (ispacked(szQuery)) { 387 | #if MYSQL_PREPARE_DEBUG 388 | strunpack(gs_szBuffer, szQuery); 389 | Debug("(stmt_prepare:%d) Preparing statement with %d parameters: %s", _:statement, i, gs_szBuffer); 390 | #endif 391 | strcat(gs_Statements[statement][E_STMT_MYSQL_QUERY], szQuery, MAX_STATEMENT_SIZE); 392 | } 393 | else { 394 | #if MYSQL_PREPARE_DEBUG 395 | Debug("(stmt_prepare:%d) Preparing statement with %d parameters: %s", _:statement, i, szQuery); 396 | #endif 397 | 398 | strpack(gs_Statements[statement][E_STMT_MYSQL_QUERY], szQuery, MAX_STATEMENT_SIZE); 399 | } 400 | return statement; 401 | } 402 | 403 | static stock stmt_rows_left(&Statement:statement) { 404 | if (statement == INVALID_STATEMENT || !(0 <= _:statement < sizeof(gs_Statements))) { 405 | Errorf("(stmt_rows_left) Invalid statement passed (%d).", _:statement); 406 | return false; 407 | } 408 | 409 | new 410 | rows; 411 | cache_get_row_count(rows); 412 | return (rows - gs_Statements[statement][E_STMT_MYSQL_ROWS_FETCHED]); 413 | } 414 | 415 | static stock bool:stmt_fetch_row(Statement:statement) { 416 | if (statement == INVALID_STATEMENT || !(0 <= _:statement < sizeof(gs_Statements))) { 417 | Errorf("(stmt_fetch_row) Invalid statement passed (%d).", _:statement); 418 | return false; 419 | } 420 | 421 | new 422 | fields, 423 | address, 424 | value, 425 | count, 426 | rows; 427 | 428 | cache_get_field_count(fields); 429 | 430 | if (fields > MAX_FIELDS) { 431 | Warning("(stmt_fetch_row) There are more fields returned than MAX_FIELDS."); 432 | fields = MAX_FIELDS; 433 | } 434 | 435 | // store total rows into 'totalRows' array. 436 | if(totalRows[statement] == 0) { 437 | new 438 | numRows; 439 | cache_get_row_count(numRows); 440 | totalRows[statement] = numRows; 441 | } 442 | 443 | rows = gs_Statements[statement][E_STMT_MYSQL_ROWS_FETCHED]; 444 | 445 | // stop the while() loop if function used in one. 446 | if(rows >= totalRows[statement]) { 447 | #if MYSQL_PREPARE_DEBUG 448 | Debug("(stmt_fetch_row) No rows left."); 449 | #endif 450 | return false; 451 | } 452 | 453 | for (new field = 0; field < fields; field++) { 454 | count ++; 455 | switch (gs_Statements[statement][E_STMT_MYSQL_FIELD_TYPE][field]) { 456 | case TYPE_NONE, 457 | TYPE_NULL: 458 | continue; 459 | 460 | case TYPE_INT, 461 | TYPE_FLOAT: { 462 | cache_get_value_index(rows, field, gs_szBuffer, sizeof(gs_szBuffer) - 1); 463 | address = gs_Statements[statement][E_STMT_MYSQL_FIELD_ADDRESS][field]; 464 | value = 465 | (gs_Statements[statement][E_STMT_MYSQL_FIELD_TYPE][field] != TYPE_FLOAT) 466 | ? 467 | strval(gs_szBuffer) 468 | : 469 | _:floatstr(gs_szBuffer 470 | ); 471 | 472 | #emit LOAD.S.pri value 473 | #emit SREF.S.pri address 474 | } 475 | 476 | case TYPE_STRING, 477 | TYPE_RAW_STRING: { 478 | new 479 | argSize; 480 | 481 | static const 482 | sc_szFormatString[] = "%s"; 483 | 484 | address = gs_Statements[statement][E_STMT_MYSQL_FIELD_ADDRESS][field]; 485 | argSize = gs_Statements[statement][E_STMT_MYSQL_FIELD_SIZE][field]; 486 | 487 | #emit PUSH.S argSize // push address of variable argSize 488 | #emit PUSH.S address // push address of variable address 489 | #emit PUSH.S field // push address of variable field 490 | #emit PUSH.S rows // retrieve first row 491 | #emit PUSH.C 16 // bytes passed to cache_get_value_index 492 | #emit SYSREQ.C cache_get_value_index // call cache_get_value_index 493 | #emit STACK 20 //move stack to 20 494 | 495 | {} 496 | 497 | // format(output[], len, const format[], {Float,_}:...) 498 | #emit PUSH.S address //push address of variable address 499 | #emit PUSH.C sc_szFormatString //(%s into variable) 500 | #emit PUSH.S argSize //push address of variable argSize 501 | #emit PUSH.S address //push address of variable address 502 | #emit PUSH.C 16 // bytes passed to format 503 | #emit SYSREQ.C format //call format 504 | #emit STACK 20 //move stack to 20 505 | } 506 | } 507 | #if MYSQL_PREPARE_DEBUG 508 | Debug("(stmt_fetch_row:%d) Fetched %d fields.", _:statement, count); 509 | #endif 510 | } 511 | gs_Statements[statement][E_STMT_MYSQL_ROWS_FETCHED] ++; 512 | return true; 513 | } 514 | 515 | // -- 516 | // External -- Wrappers 517 | // -- 518 | 519 | // -- 520 | // Functions 521 | // -- 522 | stock MySQL_StatementClose(Statement:statement) { 523 | return stmt_close(statement); 524 | } 525 | 526 | stock Statement:MySQL_PrepareStatement(MySQL:handle, const query[]) { 527 | return stmt_prepare(handle, query); 528 | } 529 | 530 | stock MySQL_Statement_RowsLeft(&Statement:statement) { 531 | return stmt_rows_left(statement); 532 | } 533 | 534 | stock MySQL_Statement_FetchRow(Statement:statement) { 535 | return stmt_fetch_row(statement); 536 | } 537 | 538 | // -- 539 | // Writing 540 | // -- 541 | stock MySQL_Bind(Statement:statement, param, const str[], bool:isRaw = false) { 542 | stmt_bind_value(statement, param, (!isRaw) ? (TYPE_STRING) : (TYPE_RAW_STRING), str); 543 | } 544 | 545 | stock MySQL_BindPlayerName(Statement:statement, param, playerid) { 546 | new playerName[MAX_PLAYER_NAME + 1]; 547 | if (!GetPlayerName(playerid, playerName, sizeof(playerName))) { 548 | stmt_bind_value(statement, param, TYPE_NULL); 549 | } 550 | else { 551 | stmt_bind_value(statement, param, TYPE_STRING, playerName); 552 | } 553 | } 554 | 555 | stock MySQL_BindPlayerIp(Statement:statement, param, playerid) 556 | { 557 | new playerIp[16 + 1]; 558 | if (GetPlayerIp(playerid, playerIp, sizeof(playerIp)) == -1) { 559 | stmt_bind_value(statement, param, TYPE_NULL); 560 | } 561 | else { 562 | stmt_bind_value(statement, param, TYPE_STRING, playerIp); 563 | } 564 | } 565 | 566 | stock MySQL_BindInt(Statement:statement, param, value) { 567 | stmt_bind_value(statement, param, TYPE_INT, value); 568 | } 569 | 570 | stock MySQL_BindFloat(Statement:statement, param, Float:value) { 571 | stmt_bind_value(statement, param, TYPE_FLOAT, value); 572 | } 573 | 574 | // -- 575 | // Reading 576 | // -- 577 | stock MySQL_BindResult(Statement:statement, field, const result[], len = sizeof(result)) { 578 | stmt_bind_result_field(statement, field, TYPE_STRING, result, len); 579 | } 580 | 581 | stock MySQL_BindResultInt(Statement:statement, field, &result) { 582 | stmt_bind_result_field(statement, field, TYPE_INT, result); 583 | } 584 | 585 | stock MySQL_BindResultFloat(Statement:statement, field, &Float:result) { 586 | stmt_bind_result_field(statement, field, TYPE_FLOAT, result); 587 | } 588 | 589 | // -- 590 | // Executing 591 | // -- 592 | 593 | stock bool:MySQL_ExecuteThreaded(Statement:statement, const callback[] = "", const fmat[] = "", {Float,_}:...) { 594 | return stmt_execute(statement, TYPE_THREADED, callback, fmat, ___(3)); 595 | } 596 | 597 | stock bool:MySQL_ExecuteParallel(Statement:statement, const callback[] = "", const fmat[] = "", {Float,_}:...) { 598 | return stmt_execute(statement, TYPE_PARALLEL, callback, fmat, ___(3)); 599 | } 600 | 601 | // -- 602 | // YSI - Inline 603 | // -- 604 | #if defined _INC_y_inline 605 | 606 | #if YSI_VERSION_MAJOR != 5 607 | #error Please update "https://github.com/pawn-lang/YSI-Includes" to version 5.x in order to use the inline version of this library. 608 | #endif 609 | 610 | static stock bool:stmt_execute_inline(Statement:statement, QueryType:type, Func:callback<>) { 611 | 612 | if (statement == INVALID_STATEMENT || !(0 <= _:statement < sizeof(gs_Statements))) { 613 | Errorf("(stmt_execute_inline) Invalid Statement (%d).", _:statement); 614 | return false; 615 | } 616 | 617 | if (mysql_errno(gs_Statements[statement][E_STMT_MYSQL_HANDLE]) != 0) { 618 | Errorf("(stmt_execute_inline) Invalid Connection (%d).", _:statement); 619 | return false; 620 | } 621 | 622 | // Make sure all parameters have been set. 623 | for (new i = 0; i < gs_Statements[statement][E_STMT_MYSQL_PARAM_COUNT]; i++) { 624 | if (gs_Statements[statement][E_STMT_MYSQL_PARAM_TYPE][i] == TYPE_NONE) { 625 | Errorf("(stmt_execute_inline) Parameter (%d) not started in statement", i); 626 | return false; 627 | } 628 | } 629 | 630 | // write 631 | strunpack(gs_szBuffer, gs_Statements[statement][E_STMT_MYSQL_QUERY]); 632 | 633 | new 634 | ret; 635 | 636 | if(_:type == 0) { 637 | ret = mysql_tquery(gs_Statements[statement][E_STMT_MYSQL_HANDLE], gs_szBuffer, "Indirect_FromCallback", "ii", _:callback, true); 638 | } else { 639 | ret = mysql_pquery(gs_Statements[statement][E_STMT_MYSQL_HANDLE], gs_szBuffer, "Indirect_FromCallback", "ii", _:callback, true); 640 | } 641 | 642 | // throw error if query failed. 643 | if(!ret) { 644 | Errorf("(stmt_execute_inline:%d) failed to execute the query", _:statement); 645 | return false; 646 | } else { 647 | // tell the memory to stay 648 | Indirect_Claim(callback); 649 | } 650 | 651 | gs_Statements[statement][E_STMT_MYSQL_ROWS_FETCHED] = 0; 652 | totalRows[statement] = 0; 653 | 654 | #if MYSQL_PREPARE_DEBUG 655 | Debug("(stmt_execute_inline:%d) Statement executed.", _:statement); 656 | Debug("(stmt_execute_inline:%d) Query executed [%s]", _:statement, gs_szBuffer); 657 | #endif 658 | return bool:ret; 659 | } 660 | stock bool:MySQL_ExecuteThreaded_Inline(Statement:statement, Func:callback<>) { 661 | return stmt_execute_inline(statement, TYPE_THREADED, callback); 662 | } 663 | 664 | stock bool:MySQL_ExecuteParallel_Inline(Statement:statement, Func:callback<>) { 665 | return stmt_execute_inline(statement, TYPE_PARALLEL, callback); 666 | } 667 | #else 668 | #error You need to "#include " before using this library. 669 | #endif 670 | 671 | 672 | // -- 673 | // Removing the debugging function, lets keep it local to this include only. 674 | // This is to avoid any conflict with other libraries that has the following 675 | // functions below e.g pawn-errors 676 | // -- 677 | #undef Warning 678 | #undef Warningf 679 | #undef Error 680 | #undef Errorf 681 | #undef Debug 682 | -------------------------------------------------------------------------------- /pawn.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "PatrickGTR", 3 | "repo": "mysql-prepared-stmt", 4 | "entry": "test.pwn", 5 | "output": "test.amx", 6 | "dependencies": [ 7 | "sampctl/samp-stdlib", 8 | "pBlueG/SA-MP-MySQL", 9 | "pawn-lang/YSI-Includes@5.x" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test.pwn: -------------------------------------------------------------------------------- 1 | // generated by "sampctl package generate" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "mysql_prepared" 11 | 12 | new MySQL:MySQLHandle; 13 | 14 | new 15 | Statement:stmt_readone; 16 | 17 | main() { 18 | 19 | MySQLHandle = mysql_connect("localhost", "root", "", "cnr"); 20 | 21 | mysql_log(ALL); 22 | 23 | mysql_pquery(MySQLHandle, "CREATE TABLE IF NOT EXISTS accounts(\ 24 | username VARCHAR(24) NOT NULL, \ 25 | password VARCHAR(65) NOT NULL, \ 26 | salt VARCHAR(11) NOT NULL, \ 27 | money INT(11), \ 28 | kills, INT(11), \ 29 | deaths INT(11))"); 30 | 31 | new Statement: stmt_insert = MySQL_PrepareStatement(MySQLHandle, "INSERT INTO accounts(username, password, salt, money, kills, deaths) VALUES (?,?,?,?,?,?) " ); 32 | 33 | // Arrow values in questions (first 0, second is 1, etc ...) 34 | MySQL_Bind(stmt_insert, 0 , "patrickgtr"); 35 | MySQL_Bind(stmt_insert, 1 , "patrickgtrpassword"); 36 | MySQL_Bind(stmt_insert, 2 , "pgtrhash"); 37 | MySQL_BindInt(stmt_insert, 3, 100); 38 | MySQL_BindInt(stmt_insert, 4, 200); 39 | MySQL_BindInt(stmt_insert, 5, 300); 40 | 41 | MySQL_ExecuteParallel(stmt_insert); 42 | MySQL_StatementClose(stmt_insert); 43 | 44 | stmt_readone = MySQL_PrepareStatement(MySQLHandle, "SELECT username, password, salt, money, kills, deaths FROM accounts where username = ?"); 45 | 46 | 47 | new Statement:stmt_readloop = MySQL_PrepareStatement(MySQLHandle, "SELECT * FROM spawns"); 48 | 49 | // Run Threaded on statement 50 | inline OnSpawnsLoad() { 51 | new 52 | spawnID, 53 | Float:spawnX, 54 | Float:spawnY, 55 | Float:spawnZ, 56 | Float:spawnA; 57 | 58 | MySQL_BindResultInt(stmt_readloop, 0, spawnID); 59 | MySQL_BindResultFloat(stmt_readloop, 1, spawnX); 60 | MySQL_BindResultFloat(stmt_readloop, 2, spawnY); 61 | MySQL_BindResultFloat(stmt_readloop, 3, spawnZ); 62 | MySQL_BindResultFloat(stmt_readloop, 4, spawnA); 63 | 64 | while(MySQL_Statement_FetchRow(stmt_readloop)) { 65 | printf("%i, %.3f, %.3f, %.3f", spawnID, spawnX, spawnY, spawnZ, spawnA); 66 | } 67 | MySQL_StatementClose(stmt_readloop); 68 | } 69 | MySQL_ExecuteThreaded_Inline(stmt_readloop, using inline OnSpawnsLoad); 70 | 71 | SetTimerEx("Emulate_OnPlayerConnect", 2000, false, "i", 0); 72 | } 73 | 74 | forward Emulate_OnPlayerConnect(playerid); 75 | public Emulate_OnPlayerConnect(playerid) { 76 | printf("OPC, playerid: %i", playerid); 77 | MySQL_Bind(stmt_readone, 0, "patrickgtr"); 78 | MySQL_ExecuteThreaded(stmt_readone, "OnPlayerLoad", "isf", playerid, "Hello World;", 192.168); 79 | return 1; 80 | } 81 | 82 | forward OnPlayerLoad(playerid,const fmat[], Float:pos); 83 | public OnPlayerLoad(playerid, const fmat[], Float:pos) 84 | { 85 | printf("OnPlayerLoad, playerid: %i", playerid); 86 | printf("OnPlayerLoad, fmat: %s", fmat); 87 | printf("OnPlayerLoad, pos: %.4f", pos); 88 | 89 | new 90 | playerUsername[MAX_PLAYER_NAME], 91 | playerPassword[65], 92 | playerSalt[11], 93 | playerMoney, 94 | playerKills, 95 | playerDeaths; 96 | 97 | // retrieve 98 | MySQL_BindResult(stmt_readone, 0, playerUsername, sizeof(playerUsername)); 99 | MySQL_BindResult(stmt_readone, 1, playerPassword, sizeof(playerPassword)); 100 | MySQL_BindResult(stmt_readone, 2, playerSalt, sizeof(playerSalt)); 101 | MySQL_BindResultInt(stmt_readone, 3, playerMoney); 102 | MySQL_BindResultInt(stmt_readone, 4, playerKills); 103 | MySQL_BindResultInt(stmt_readone, 5, playerDeaths); 104 | 105 | if(MySQL_Statement_FetchRow(stmt_readone)) { 106 | printf("username %s", playerUsername); 107 | printf("password %s", playerPassword); 108 | printf("salt %s", playerSalt); 109 | printf("money %i", playerMoney); 110 | printf("kills %i", playerKills); 111 | printf("deaths %i", playerDeaths); 112 | } 113 | MySQL_StatementClose(stmt_readone); 114 | } --------------------------------------------------------------------------------