├── .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 | }
--------------------------------------------------------------------------------