rbN.
05/08/2012, 04:13 PM
MySQL tutorial
Inleiding
Hallo jonguns und meiden, vandaag gaan we het houden over MySQL (yaaay). De reden waarom ik het hier over ga houden is omdat veel mensen vele vragen hebben hierover. Niet direct naar MySQL gericht, maar wel bijvoorbeeld vragen die je met MySQL kunt 'beantwoorden' (vb: "Hoe maak je een registratiesysteem?", "Hoe kun je de 5 beste killers zien in een dialog?").
Ook zien de meeste mensen niet in hoe gemakkelijk MySQL eigenlijk is. Dat gaan we in deze tutorial leren. :O
Requirements
Oke, we beginnen met de requirements. Ik ga het hier het meeste over houden over de MySQL plugins, maar eerst de downloads:
MySQL plugin van BlueG (R7) (http://forum.sa-mp.com/showthread.php?t=56564)
XAMPP voor je database (http://www.apachefriends.org/en/xampp.html)
Ik stel je voor, ookal heb je een andere MySQL plugin, om toch de MySQL plugin van BlueG te gebruiken. "Waarom?" zul je waarschijnlijk vragen. Het antwoord is simpel: in de R7 versie MOET je persee threads gebruiken. Dit heeft een heel, heel, HEEL groot voordeel, want als je maar 1 thread gebruikt zou je SA-MP server niet meer reageren totdat er een antwoordt van de database komt. Voor de mensen die is een keer de HTTP functie van SA-MP hebben geprobeert: je zult merken dat je server niet vastloopt wanneer je HTTP gebruikt. Dit is precies hetzelfde met de R7 versie van BlueG. Je server freezed niet meer door MySQL. Hierdoor is de R7 plugin veel beter, ook beter dan alle andere file systems die er zijn (y_ini, DOF2, etc.), want daardoor gaat je server vastlopen (je merkt het niet, maar als je bijvoorbeeld 10 000x op y_ini iets retrieved merk je dat je server niet meer reageert voor een seconde).
XAMPP is gemakkelijk te installeren, dus ik ga daar niet veel over uitleggen. Als je er toch niet uitkomt, reageer hier dan even.
Als je alles correct geinstalleerd hebt kan je door naar de volgende stap.
MySQL configureren (stap 1)
Wanneer XAMPP geinstalleerd is, krijg je als het goed is een klein schermpje te zien waar je een paar keuzes hebt tussen Apache, MySQL, FileZilla, Mercury en Tomcat. Druk start naast Apache en MySQL. Apache is niet een vereiste, maar het is gemakkelijk om zo te beginnen. Als het goed is kan je nu op 'Admin' drukken naast de MySQL text. Die brengt je dan naar een site genaamt 'localhost' of '127.0.0.1'. Druk linksonder op phpMyAdmin. Als het goed is kom je dan nu bij phpMyAdmin terecht. Klik daarna op databases en maak een nieuwe aan. Je kan daarna kolommen toevoegen. Het slimste is om de eerste 3 kolommen UserID, Username en Password te noemen. Bij UserID kies je het type 'int' (want het is een integer). Ook kies je als groep: Primary en specialiteit: auto_increment. Bij Username en Password kan je text kiezen. Sla het op. Je bent nu klaar met het instellen van MySQL! :)
Oh ja, voordat we naar de volgende stap gaan, beveel ik jullie aan om deze stap (http://veerasundar.com/blog/2009/01/how-to-change-the-root-password-for-mysql-in-xampp/) te doen
MySQL configureren (stap 2)
We zijn nu bij het 'configureren' (like a sir) van MySQL voor je gamemode. Het gemakkelijkste is door dit te doen:
#define SQL_SERVER "127.0.0.1" //Dit is de IP van je database. Als je XAMPP gebruikt op je eigen netwerk, gebruik je 127.0.0.1. (Default: 127.0.0.1)
#define SQL_DB "je-database" //Hier vul je je databasenaam in.
#define SQL_USER "root" //Hier vul je je username in. (Default: root)
#define SQL_PASS "je-wachtwoord" //Hier vul je je wachtwoord in van je database.
BAM GECONFIGUREREREERD.
Daarna komt het connecten naar de database. Ik doe dit zelf op deze manier:
//Ergens bovenaan je script. Ik beveel dit aan omdat je script er zo veel georganiseerder uit ziet dan new ...; te gebruiken voor 100 dingen.
enum s_Info
{
MySQL
}
new ServerInfo[s_Info];
//Je OnGameModeInit. We connecten hier naar de database, want bij OnGameModeInit starten wij dus de gamemode
public OnGameModeInit()
{
mysql_debug(1); //Wij schakelen debug aan. Dit is zeer handig om bugs te tracken van je script.
ServerInfo[MySQL] = mysql_connect(SQL_SERVER, SQL_USER, SQL_DB, SQL_PASS); //Connect naar de DB.
if(mysql_ping() == 1) print(" * Connecting to MySQL: Success"); //Als mysql_ping gelijk staat aan 1 betekent dat dat er connectie is.
else print(" x [ERROR] Connecting to MySQL: Failed"); //Zo niet, dan niet :O!
return 1;
}
Als je alles goed hebt geconfigureerd zul je het Success berichtje ontvangen. Zo niet, bekijk dan even je mysql_log naar wat er fout is gegaan.
Joezee, we zijn al heel ver ! :O
We kunnen beginnen!
We zijn nu dus klaar om MySQL te gebruiken voor je gamemode. Door R7 is MySQL VEEL makkelijker geworden voor de meeste mensen. Je hebt eigenlijk niet zo veel meer nodig om bijvoorbeeld op te slaan en te retrieven uit je database. Het gaat heel gemakkelijk door alle threads.
We beginnen wel bij het opslaan van dingen naar je database. Hiervoor gebruiken we dit:
mysql_function_query( connectionHandle, query[], bool:cache, callback[], format[], {Float,_}:... )
Ziet er best lastig uit, of niet? Nou, het is best simpel! Ik leg het je hier even uit:
connectionHandle: De connectie 'ID'. Als je mijn code die ik eerder heb verteld gebruikt, is dit ServerInfo[MySQL].
query[]: Dit is je query, hier komen we zo nog op terug.
bool:cache: Dit vraagt of je cache in wilt schakelen voor de query of niet (true/false). Cache is (bijna) altijd vereist om dingen te laden. Voor saven hoef je dit niet persee te gebruiken.
format[]: De extra informatie die je door wilt geven aan je thread.
{Float,_}:...: De extra informatie die je door wilt geven aan je thread.
Vrij gemakkelijk dus. Het belangrijkste moet toch wel de 'query' zijn, dus laten we daarmee beginnen.
Query
Queries zijn eigenlijk de 'informatie' die je wilt versturen naar de database. Hiermee kan je dus aanduiden of je iets wilt saven, of je iets wilt laden, of je iets wilt verwijderen, enz.
De basis queries zijn eigenlijk vrij gemakkelijk als je nog wat Engels kunt. Dat zijn deze:
//Hiermee maak je een nieuwe row waar je informatie in zet.
INSERT INTO tabel (kolommen) VALUES(informatie)
//Hiermee kan je informatie retrieven. Dit wordt het meeste gebruikt voor het laden van informatie, maar kan ook gebruikt worden voor andere dingen zoals een top 5.
SELECT iets FROM `tabel`
//Hiermee kan je informatie van een row veranderen.
UPDATE `tabel` SET kolom = informatie
Als je een string gebruikt MOET je persee 'text' gebruiken! Als je specifieks iets van een row wilt laden/veranderen moet je WHERE gebruiken. In de voorbeelden hieronder kan je een paar voorbeelden hiervan zien (noshit).
Een voorbeeld van INSERT:
//Wij geven hiermee door dat we een nieuwe row willen voor een ban. Wij geven hiermee de naam van de rulebreaker door, de tijd in UNIX, de admin die hem/haar heeft verbannen, de reden ervoor en de IP van de persoon.
format(megastr, sizeof(megastr), "INSERT INTO `bans` (user, time, admin, reason, IP) VALUES ('%s', %d, '%s', '%s', '%s')", PlayerName(playerid), gettime(), adminname, reason, IP);
Een voorbeeld van SELECT:
//Wij beginnen met het retrieven van de IP van de persoon.
new IP[16];
GetPlayerIp(playerid, IP, 16);
//Daarna gebruiken wij SELECT om te kijken of de persoon banned is. Zoals je kunt zien gebruiken wij WHERE. Wij controleren hiermee of er ergens in de banlijst een IP staat die gelijk is aan de IP van de persoon. De * die naast SELECT staat betekent dat wij alles selecteren van die ene row. We gebruiken ook LIMIT 1, want wij willen alleen maar van 1 IP de informatie hebben.
format(str1,sizeof(str1), "SELECT * FROM `bans` WHERE IP = '%s' LIMIT 1", IP);
Een voorbeeld van UPDATE:
//De player disconnect. Wij willen daarom de stats van de user verzenden naar de database zodat de persoon die dan de volgende dag weer eventjes leuk op je server komt niet weer bij level 1 start. Wij veranderen hier de Level, XP en Money van de persoon. Let even goed op dat wij niet '%d' gebruiken. Wij gebruiken %d ZONDER aanhalingstekens. Let ook goed op dat we %s WEL met aanhalingstekens gebruiken, omdat dat een string is (nee niet zo een string viespeuk). WHERE en LIMIT 1 wordt hier ook weer gebruikt, omdat we het alleen voor 1 persoon willen aanpassen.
format(megastr,sizeof(megastr),"UPDATE `users` SET Level = %d, XP = %d, Money = %d WHERE Username = '%s' LIMIT 1", PlayerInfo[playerid][eLevel], PlayerInfo[playerid][eEXP], PlayerInfo[playerid][eMoney], PlayerName(playerid));
Caching
We zijn nu bij de volgende stap van mysql_function_query: caching. Caching brengt vele voordelen met zich. Een paar van die voordelen zijn dat het sneller gaat, en dat het vele malen overzichtelijker is (om bijvoorbeeld iets te laden). Ik beveel je aan om het standaard uit te doen BEHALVE voor het laden van informatie vanuit de database.
Je hebt voor caching deze functies:
cache_get_data(&num_rows, &num_fields, connectionHandle = 1) //Verkrijg het aantal rows en fields
cache_get_row(row, idx, dest[], connectionHandle = 1) //Verkrijg info in een row
cache_get_field(field_index, dest[], connectionHandle = 1) //Verkrijg field naam
cache_get_field_content(row, const field_name[], dest[], connectionHandle = 1) //Verkrijg field informatie (zelfde als cache_get_row maar dan geef je een naam op in plaats van een ID.
Let wel op dat cache_get_field_content iets trager is dan cache_get_row! Hieronder kun je zien hoeveel milliseconden het verschilt. Het verschil is echter vrij weinig. Je ziet het verschil pas echt als je ongeveer 1 miljoen (!) keer dit gebruikt.
[19:17:01] [DEBUG] cache_get_field_content (1 000 000x) took 329 ms.
[19:17:01] [DEBUG] cache_get_row (1 000 000x) took 136 ms.
[19:17:01] [DEBUG] Difference is 193 ms.
Callback
De volgende stap: Callbacks. Bij mysql_function_query moet je een callback geven omdat het threaded is. De HTTP functie van SA-MP moet dat ook. De callback wordt 'gecalled' wanneer de query voltooid is.
De samenstelling!
Joezee! We weten nu hoe een query werkt. Ik geef je nog wat voorbeelden voor verduidelijking.
Voorbeeld #1:
public OnPlayerConnect(playerid)
{
//We willen de settings laden van de speler. We gebruiken hiervoor SELECT
format(str, sizeof(str), "SELECT * FROM `users` WHERE Username = '%s' LIMIT 1", PlayerName(playerid));
//We verzenden de informatie door naar de database. Zoals je kunt zien is caching ingeschakelt, omdat wij informatie willen laden. Ook kun je zien dat we "i" gebruiken, omdat playerid een integer is. De callback is OnUserLoad. Wanneer de settings geladen zijn kunnen wij OnUserLoad gebruiken.
mysql_function_query(ServerInfo[MySQL], str, true, "OnUserLoad", "i", playerid);
return 1;
}
forward OnUserLoad(playerid);
public OnUserLoad(playerid)
{
//De query is klaar. Laten we de informatie verkrijgen van de speler.
new fields, rows, fetch[50];
cache_get_data(rows, fields);
if(rows) //Speler is geregistreerd, want er zijn rows gevonden (in dit geval, 1 row, want we hebben LIMIT gebruikt).
{
cache_get_field_content(0, "Money", fetch); //cache_get_field_content(row, fieldname, dest, connectionHandle = 1); . De row is 0, want we hebben maar 1 row (LIMIT 1). Je begint altijd bij 0, als je dat nog niet wist :P. De fieldname is "Money". De dest betekent in welke string de informatie moet komen. Dat is dus in dit geval fetch.
PlayerInfo[playerid][Money] = strval(fetch); //We hebben nu de hoeveelheid geld. We gebruiken strval om van de string naar een value te gaan.
}
else //Speler is NIET geregistreerd!
{
}
return 1; //Gebruik return 1; op het einde om de cache te clearen.
}
Veiligheid
We hebben het nu het meeste gehad over snelheid, but what about veiligheid? Ik beveel je aan, nee, ik DWING je om mysql_real_escape_string te gebruiken voor de veiligheid van je gamemode. Gebruik mysql_real_escape_string voor alle strings die je niet zelf in controle hebt (wachtwoorden, roleplay SMSjes, etc.).
Een voorbeeld van een SQL Injection:
new injection[25] = "'; DELETE FROM users WHERE Level < 100;";
format(megastr,sizeof(megastr),"SELECT * FROM `users` WHERE Nametag = '%s'", injection);
//Wat er uit komt:
SELECT * FROM `users` WHERE Nametag = ''; DELETE FROM users WHERE Level < 100;'
Oke, dat moest wel het slechtste voorbeeld zijn dat er bestond, maar je snapt wat er zou kunnen gebeuren. Met mysql_real_escape_string komen er voor de rare tekens die de query kunnen veranderen een \. Hiermee kan MySQL 'zien' dat die statement niet gevolgt hoeft te worden.
Er is nog een ander probleem waar veel mensen zich niet bij omkijken maar die toch nog gevaarlijk kan zijn, en vooral bij servers die niet localhost zijn, of voor servers die heel populair zijn. Dit is het enigste nadeel van threads wat ik heb ontdekt.
Dus: Wat als je MySQL server nou in Amerika is, en jouw SA-MP server zit in Nederland. Wat als iemand nou na een seconde disconnect nadat jij een query start specifiek voor die speler, en dat er een seconde later iemand anders in de server komt met die ID? De ID is hetzelfde, maar de speler niet! Dit kan zorgen voor problemen (oa met tutorials).
Dit kan je gemakkelijk fixen door een 'sync' parameter in al je callbacks met MySQL toe te voegen. Bijvoorbeeld:
enum p_Info
{
pSync
}
new PlayerInfo[MAX_PLAYERS][pSync];
public OnPlayerConnect(playerid)
{
PlayerInfo[playerid][pSync]++;
return 1;
}
public OnPlayerDisconnect(playerid, reason)
{
PlayerInfo[playerid][pSync]++;
return 1;
}
//Stel je voor: De ID van de speler is 5, en de pSync is 0. De query begint. De player leaved en iemand anders connect. De callback wordt gecalled met ID 5 en pSync 0. Dit klopt niet meer, want pSync is nu 2 geworden! Die callback is dus niet voor diezelfde persoon.
//Dit is een simpele manier though. Je kan ook alleen bij OnPlayerConnect de pSync verhogen, en dan ook een IsPlayerConnected check, maar daar zit vrij weinig verschil in.
Tips
Als je je ooit afvraagt waarom er iets fout gaat, kan je het beste dit doen:
1) Bekijk je mysql_log. Die geeft meestal verdere instructies aan.
2) Als je debugging hebt uitgeschakelt of als de mysql_log zegt dat het resultaat in OnQueryError wordt laten zien kan je deze code gebruiken:
public OnQueryError(errorid, error[], callback[], query[], connectionHandle)
{
print("-----------------------------------------");
print("> OnQueryError:");
printf("* Error: %s (%d)", error, errorid);
printf("* Callback: %s", callback);
printf("* Query: %s", query);
print("-----------------------------------------");
return 1;
}
Het slot
Ik dank u zeer voor het lezen (ookal denk ik niet dat IEMAND mijn MySQL tutorial helemaal af gaat lezen) en tot ziens (http://www.ziengs.nl/).
Inleiding
Hallo jonguns und meiden, vandaag gaan we het houden over MySQL (yaaay). De reden waarom ik het hier over ga houden is omdat veel mensen vele vragen hebben hierover. Niet direct naar MySQL gericht, maar wel bijvoorbeeld vragen die je met MySQL kunt 'beantwoorden' (vb: "Hoe maak je een registratiesysteem?", "Hoe kun je de 5 beste killers zien in een dialog?").
Ook zien de meeste mensen niet in hoe gemakkelijk MySQL eigenlijk is. Dat gaan we in deze tutorial leren. :O
Requirements
Oke, we beginnen met de requirements. Ik ga het hier het meeste over houden over de MySQL plugins, maar eerst de downloads:
MySQL plugin van BlueG (R7) (http://forum.sa-mp.com/showthread.php?t=56564)
XAMPP voor je database (http://www.apachefriends.org/en/xampp.html)
Ik stel je voor, ookal heb je een andere MySQL plugin, om toch de MySQL plugin van BlueG te gebruiken. "Waarom?" zul je waarschijnlijk vragen. Het antwoord is simpel: in de R7 versie MOET je persee threads gebruiken. Dit heeft een heel, heel, HEEL groot voordeel, want als je maar 1 thread gebruikt zou je SA-MP server niet meer reageren totdat er een antwoordt van de database komt. Voor de mensen die is een keer de HTTP functie van SA-MP hebben geprobeert: je zult merken dat je server niet vastloopt wanneer je HTTP gebruikt. Dit is precies hetzelfde met de R7 versie van BlueG. Je server freezed niet meer door MySQL. Hierdoor is de R7 plugin veel beter, ook beter dan alle andere file systems die er zijn (y_ini, DOF2, etc.), want daardoor gaat je server vastlopen (je merkt het niet, maar als je bijvoorbeeld 10 000x op y_ini iets retrieved merk je dat je server niet meer reageert voor een seconde).
XAMPP is gemakkelijk te installeren, dus ik ga daar niet veel over uitleggen. Als je er toch niet uitkomt, reageer hier dan even.
Als je alles correct geinstalleerd hebt kan je door naar de volgende stap.
MySQL configureren (stap 1)
Wanneer XAMPP geinstalleerd is, krijg je als het goed is een klein schermpje te zien waar je een paar keuzes hebt tussen Apache, MySQL, FileZilla, Mercury en Tomcat. Druk start naast Apache en MySQL. Apache is niet een vereiste, maar het is gemakkelijk om zo te beginnen. Als het goed is kan je nu op 'Admin' drukken naast de MySQL text. Die brengt je dan naar een site genaamt 'localhost' of '127.0.0.1'. Druk linksonder op phpMyAdmin. Als het goed is kom je dan nu bij phpMyAdmin terecht. Klik daarna op databases en maak een nieuwe aan. Je kan daarna kolommen toevoegen. Het slimste is om de eerste 3 kolommen UserID, Username en Password te noemen. Bij UserID kies je het type 'int' (want het is een integer). Ook kies je als groep: Primary en specialiteit: auto_increment. Bij Username en Password kan je text kiezen. Sla het op. Je bent nu klaar met het instellen van MySQL! :)
Oh ja, voordat we naar de volgende stap gaan, beveel ik jullie aan om deze stap (http://veerasundar.com/blog/2009/01/how-to-change-the-root-password-for-mysql-in-xampp/) te doen
MySQL configureren (stap 2)
We zijn nu bij het 'configureren' (like a sir) van MySQL voor je gamemode. Het gemakkelijkste is door dit te doen:
#define SQL_SERVER "127.0.0.1" //Dit is de IP van je database. Als je XAMPP gebruikt op je eigen netwerk, gebruik je 127.0.0.1. (Default: 127.0.0.1)
#define SQL_DB "je-database" //Hier vul je je databasenaam in.
#define SQL_USER "root" //Hier vul je je username in. (Default: root)
#define SQL_PASS "je-wachtwoord" //Hier vul je je wachtwoord in van je database.
BAM GECONFIGUREREREERD.
Daarna komt het connecten naar de database. Ik doe dit zelf op deze manier:
//Ergens bovenaan je script. Ik beveel dit aan omdat je script er zo veel georganiseerder uit ziet dan new ...; te gebruiken voor 100 dingen.
enum s_Info
{
MySQL
}
new ServerInfo[s_Info];
//Je OnGameModeInit. We connecten hier naar de database, want bij OnGameModeInit starten wij dus de gamemode
public OnGameModeInit()
{
mysql_debug(1); //Wij schakelen debug aan. Dit is zeer handig om bugs te tracken van je script.
ServerInfo[MySQL] = mysql_connect(SQL_SERVER, SQL_USER, SQL_DB, SQL_PASS); //Connect naar de DB.
if(mysql_ping() == 1) print(" * Connecting to MySQL: Success"); //Als mysql_ping gelijk staat aan 1 betekent dat dat er connectie is.
else print(" x [ERROR] Connecting to MySQL: Failed"); //Zo niet, dan niet :O!
return 1;
}
Als je alles goed hebt geconfigureerd zul je het Success berichtje ontvangen. Zo niet, bekijk dan even je mysql_log naar wat er fout is gegaan.
Joezee, we zijn al heel ver ! :O
We kunnen beginnen!
We zijn nu dus klaar om MySQL te gebruiken voor je gamemode. Door R7 is MySQL VEEL makkelijker geworden voor de meeste mensen. Je hebt eigenlijk niet zo veel meer nodig om bijvoorbeeld op te slaan en te retrieven uit je database. Het gaat heel gemakkelijk door alle threads.
We beginnen wel bij het opslaan van dingen naar je database. Hiervoor gebruiken we dit:
mysql_function_query( connectionHandle, query[], bool:cache, callback[], format[], {Float,_}:... )
Ziet er best lastig uit, of niet? Nou, het is best simpel! Ik leg het je hier even uit:
connectionHandle: De connectie 'ID'. Als je mijn code die ik eerder heb verteld gebruikt, is dit ServerInfo[MySQL].
query[]: Dit is je query, hier komen we zo nog op terug.
bool:cache: Dit vraagt of je cache in wilt schakelen voor de query of niet (true/false). Cache is (bijna) altijd vereist om dingen te laden. Voor saven hoef je dit niet persee te gebruiken.
format[]: De extra informatie die je door wilt geven aan je thread.
{Float,_}:...: De extra informatie die je door wilt geven aan je thread.
Vrij gemakkelijk dus. Het belangrijkste moet toch wel de 'query' zijn, dus laten we daarmee beginnen.
Query
Queries zijn eigenlijk de 'informatie' die je wilt versturen naar de database. Hiermee kan je dus aanduiden of je iets wilt saven, of je iets wilt laden, of je iets wilt verwijderen, enz.
De basis queries zijn eigenlijk vrij gemakkelijk als je nog wat Engels kunt. Dat zijn deze:
//Hiermee maak je een nieuwe row waar je informatie in zet.
INSERT INTO tabel (kolommen) VALUES(informatie)
//Hiermee kan je informatie retrieven. Dit wordt het meeste gebruikt voor het laden van informatie, maar kan ook gebruikt worden voor andere dingen zoals een top 5.
SELECT iets FROM `tabel`
//Hiermee kan je informatie van een row veranderen.
UPDATE `tabel` SET kolom = informatie
Als je een string gebruikt MOET je persee 'text' gebruiken! Als je specifieks iets van een row wilt laden/veranderen moet je WHERE gebruiken. In de voorbeelden hieronder kan je een paar voorbeelden hiervan zien (noshit).
Een voorbeeld van INSERT:
//Wij geven hiermee door dat we een nieuwe row willen voor een ban. Wij geven hiermee de naam van de rulebreaker door, de tijd in UNIX, de admin die hem/haar heeft verbannen, de reden ervoor en de IP van de persoon.
format(megastr, sizeof(megastr), "INSERT INTO `bans` (user, time, admin, reason, IP) VALUES ('%s', %d, '%s', '%s', '%s')", PlayerName(playerid), gettime(), adminname, reason, IP);
Een voorbeeld van SELECT:
//Wij beginnen met het retrieven van de IP van de persoon.
new IP[16];
GetPlayerIp(playerid, IP, 16);
//Daarna gebruiken wij SELECT om te kijken of de persoon banned is. Zoals je kunt zien gebruiken wij WHERE. Wij controleren hiermee of er ergens in de banlijst een IP staat die gelijk is aan de IP van de persoon. De * die naast SELECT staat betekent dat wij alles selecteren van die ene row. We gebruiken ook LIMIT 1, want wij willen alleen maar van 1 IP de informatie hebben.
format(str1,sizeof(str1), "SELECT * FROM `bans` WHERE IP = '%s' LIMIT 1", IP);
Een voorbeeld van UPDATE:
//De player disconnect. Wij willen daarom de stats van de user verzenden naar de database zodat de persoon die dan de volgende dag weer eventjes leuk op je server komt niet weer bij level 1 start. Wij veranderen hier de Level, XP en Money van de persoon. Let even goed op dat wij niet '%d' gebruiken. Wij gebruiken %d ZONDER aanhalingstekens. Let ook goed op dat we %s WEL met aanhalingstekens gebruiken, omdat dat een string is (nee niet zo een string viespeuk). WHERE en LIMIT 1 wordt hier ook weer gebruikt, omdat we het alleen voor 1 persoon willen aanpassen.
format(megastr,sizeof(megastr),"UPDATE `users` SET Level = %d, XP = %d, Money = %d WHERE Username = '%s' LIMIT 1", PlayerInfo[playerid][eLevel], PlayerInfo[playerid][eEXP], PlayerInfo[playerid][eMoney], PlayerName(playerid));
Caching
We zijn nu bij de volgende stap van mysql_function_query: caching. Caching brengt vele voordelen met zich. Een paar van die voordelen zijn dat het sneller gaat, en dat het vele malen overzichtelijker is (om bijvoorbeeld iets te laden). Ik beveel je aan om het standaard uit te doen BEHALVE voor het laden van informatie vanuit de database.
Je hebt voor caching deze functies:
cache_get_data(&num_rows, &num_fields, connectionHandle = 1) //Verkrijg het aantal rows en fields
cache_get_row(row, idx, dest[], connectionHandle = 1) //Verkrijg info in een row
cache_get_field(field_index, dest[], connectionHandle = 1) //Verkrijg field naam
cache_get_field_content(row, const field_name[], dest[], connectionHandle = 1) //Verkrijg field informatie (zelfde als cache_get_row maar dan geef je een naam op in plaats van een ID.
Let wel op dat cache_get_field_content iets trager is dan cache_get_row! Hieronder kun je zien hoeveel milliseconden het verschilt. Het verschil is echter vrij weinig. Je ziet het verschil pas echt als je ongeveer 1 miljoen (!) keer dit gebruikt.
[19:17:01] [DEBUG] cache_get_field_content (1 000 000x) took 329 ms.
[19:17:01] [DEBUG] cache_get_row (1 000 000x) took 136 ms.
[19:17:01] [DEBUG] Difference is 193 ms.
Callback
De volgende stap: Callbacks. Bij mysql_function_query moet je een callback geven omdat het threaded is. De HTTP functie van SA-MP moet dat ook. De callback wordt 'gecalled' wanneer de query voltooid is.
De samenstelling!
Joezee! We weten nu hoe een query werkt. Ik geef je nog wat voorbeelden voor verduidelijking.
Voorbeeld #1:
public OnPlayerConnect(playerid)
{
//We willen de settings laden van de speler. We gebruiken hiervoor SELECT
format(str, sizeof(str), "SELECT * FROM `users` WHERE Username = '%s' LIMIT 1", PlayerName(playerid));
//We verzenden de informatie door naar de database. Zoals je kunt zien is caching ingeschakelt, omdat wij informatie willen laden. Ook kun je zien dat we "i" gebruiken, omdat playerid een integer is. De callback is OnUserLoad. Wanneer de settings geladen zijn kunnen wij OnUserLoad gebruiken.
mysql_function_query(ServerInfo[MySQL], str, true, "OnUserLoad", "i", playerid);
return 1;
}
forward OnUserLoad(playerid);
public OnUserLoad(playerid)
{
//De query is klaar. Laten we de informatie verkrijgen van de speler.
new fields, rows, fetch[50];
cache_get_data(rows, fields);
if(rows) //Speler is geregistreerd, want er zijn rows gevonden (in dit geval, 1 row, want we hebben LIMIT gebruikt).
{
cache_get_field_content(0, "Money", fetch); //cache_get_field_content(row, fieldname, dest, connectionHandle = 1); . De row is 0, want we hebben maar 1 row (LIMIT 1). Je begint altijd bij 0, als je dat nog niet wist :P. De fieldname is "Money". De dest betekent in welke string de informatie moet komen. Dat is dus in dit geval fetch.
PlayerInfo[playerid][Money] = strval(fetch); //We hebben nu de hoeveelheid geld. We gebruiken strval om van de string naar een value te gaan.
}
else //Speler is NIET geregistreerd!
{
}
return 1; //Gebruik return 1; op het einde om de cache te clearen.
}
Veiligheid
We hebben het nu het meeste gehad over snelheid, but what about veiligheid? Ik beveel je aan, nee, ik DWING je om mysql_real_escape_string te gebruiken voor de veiligheid van je gamemode. Gebruik mysql_real_escape_string voor alle strings die je niet zelf in controle hebt (wachtwoorden, roleplay SMSjes, etc.).
Een voorbeeld van een SQL Injection:
new injection[25] = "'; DELETE FROM users WHERE Level < 100;";
format(megastr,sizeof(megastr),"SELECT * FROM `users` WHERE Nametag = '%s'", injection);
//Wat er uit komt:
SELECT * FROM `users` WHERE Nametag = ''; DELETE FROM users WHERE Level < 100;'
Oke, dat moest wel het slechtste voorbeeld zijn dat er bestond, maar je snapt wat er zou kunnen gebeuren. Met mysql_real_escape_string komen er voor de rare tekens die de query kunnen veranderen een \. Hiermee kan MySQL 'zien' dat die statement niet gevolgt hoeft te worden.
Er is nog een ander probleem waar veel mensen zich niet bij omkijken maar die toch nog gevaarlijk kan zijn, en vooral bij servers die niet localhost zijn, of voor servers die heel populair zijn. Dit is het enigste nadeel van threads wat ik heb ontdekt.
Dus: Wat als je MySQL server nou in Amerika is, en jouw SA-MP server zit in Nederland. Wat als iemand nou na een seconde disconnect nadat jij een query start specifiek voor die speler, en dat er een seconde later iemand anders in de server komt met die ID? De ID is hetzelfde, maar de speler niet! Dit kan zorgen voor problemen (oa met tutorials).
Dit kan je gemakkelijk fixen door een 'sync' parameter in al je callbacks met MySQL toe te voegen. Bijvoorbeeld:
enum p_Info
{
pSync
}
new PlayerInfo[MAX_PLAYERS][pSync];
public OnPlayerConnect(playerid)
{
PlayerInfo[playerid][pSync]++;
return 1;
}
public OnPlayerDisconnect(playerid, reason)
{
PlayerInfo[playerid][pSync]++;
return 1;
}
//Stel je voor: De ID van de speler is 5, en de pSync is 0. De query begint. De player leaved en iemand anders connect. De callback wordt gecalled met ID 5 en pSync 0. Dit klopt niet meer, want pSync is nu 2 geworden! Die callback is dus niet voor diezelfde persoon.
//Dit is een simpele manier though. Je kan ook alleen bij OnPlayerConnect de pSync verhogen, en dan ook een IsPlayerConnected check, maar daar zit vrij weinig verschil in.
Tips
Als je je ooit afvraagt waarom er iets fout gaat, kan je het beste dit doen:
1) Bekijk je mysql_log. Die geeft meestal verdere instructies aan.
2) Als je debugging hebt uitgeschakelt of als de mysql_log zegt dat het resultaat in OnQueryError wordt laten zien kan je deze code gebruiken:
public OnQueryError(errorid, error[], callback[], query[], connectionHandle)
{
print("-----------------------------------------");
print("> OnQueryError:");
printf("* Error: %s (%d)", error, errorid);
printf("* Callback: %s", callback);
printf("* Query: %s", query);
print("-----------------------------------------");
return 1;
}
Het slot
Ik dank u zeer voor het lezen (ookal denk ik niet dat IEMAND mijn MySQL tutorial helemaal af gaat lezen) en tot ziens (http://www.ziengs.nl/).