Das ORM-System (BlueG's MySQL Plugin R33+)

Wichtiger Hinweis: Bitte ändert nicht manuell die Schriftfarbe auf schwarz sondern belasst es bei der Standardeinstellung. Somit tragt ihr dazu bei dass euer Text auch bei Verwendung unseren dunklen Forenstils noch lesbar ist!

Tipp: Ihr wollt längere Codeausschnitte oder Logfiles bereitstellen? Benutzt unseren eigenen PasteBin-Dienst Link
  • Das ist ein Tutorial für das ORM Feature des MySQL Plugins ab R33+. ORM steht für objekt-relationales Mapping und was das im Zusammenhang mit dem MySQL Plugin und PAWN zu tun hat, versuche ich in diesem Tutorial zu erklären.


    Dieses Feature, entwickelt von Pain123/Maddinat0r, ist dazu da, das Scripting für Leute zu vereinfachen, die einfach nur tolle Sachen scripten wollen anstatt sich um die SQL-Syntax und andere MySQL-spezifische Sachen sorgen zu machen. Das heißt, dass das ganze MySQL-Zeugs (von Query erstellen bis zur Ergebnisverarbeitung) vom Plugin übernommen wird.


    Das Erste das getan werden muss ist die Erstellung einer ORM-Instanz. Dies geschieht mit id = orm_create(tabellenname[]), wobei mit "tabellenname" bestimmt wird, welche Tabelle das ORM-System, oder besser gesagt diese ORM-Instanz, verwalten soll. Jetzt müssen ein paar globale Variablen an diese ORM-Instanz gebunden werden. Dies passiert mit Hilfe von diesen Funktionen:

    //globale Variablen
    new kills, Float:kd_ratio, name[MAX_PLAYER_NAME+1];


    // ... irgendwo in einer Funktion
    new ORM:id = orm_create("players");
    orm_addvar_int(id, kills, "kills"); // orm_addvar_int(ORM:id, variable, field_name[])
    orm_addvar_float(id, kd_ratio, "ratio"); // orm_addvar_float(ORM:id, Float:variable, field_name[])
    orm_addvar_string(id, name, sizeof(name), "name"); // orm_addvar_string(ORM:id, array[], max_len, field_name[])

    Diese Code-Zeilen fügen 3 Einträge hinzu, welche die zuvor erstellte ORM-Instanz im Auge behalten wird. Die Tabelle die in orm_create eingetragen wurde muss die Feld-Namen enthalten, die zuvor mit orm_addvar_* bestimmt wurden. Und - wie schon zuvor erwähnt - die Variable muss global deklariert worden sein - in anderen Worten sie darf nicht auf dem Stack sein.


    Um noch es noch einmal zusammenzufassen: dieses ORM-System führt für dich Queries aus und verarbeitet anschließend das Ergebnis (weist die Daten des Ergebnisses den Variablen zu).
    Hier ein realistisches Szenario, wo so ein System zum Einsatz kommen könnte. Der Sinn des folgenden Scripts ist ein grundlegendes User-System zu erstellen.

    #include <a_mysql>


    enum E_PLAYER {
    ORM:ORM_ID,
    ID,
    Name[MAX_PLAYER_NAME+1],
    Money,
    Level,
    Float:PosX,
    Float:PosY,
    Float:PosZ,
    };
    new Player[MAX_PLAYERS][E_PLAYER];


    public OnPlayerConnect(playerid)
    {
    GetPlayerName(playerid, Player[playerid][Name], MAX_PLAYER_NAME+1);
    new ORM:ormid = Player[playerid][ORM_ID] = orm_create("players");


    orm_addvar_int(ormid, Player[playerid][ID], "ID");
    orm_addvar_string(ormid, Player[playerid][Name], MAX_PLAYER_NAME+1, "Name");
    orm_addvar_int(ormid, Player[playerid][Money], "Money");
    orm_addvar_int(ormid, Player[playerid][Level], "Level");
    orm_addvar_float(ormid, Player[playerid][PosX], "PosX");
    orm_addvar_float(ormid, Player[playerid][PosY], "PosY");
    orm_addvar_float(ormid, Player[playerid][PosZ], "PosZ");

    orm_setkey(ormid, "Name");
    orm_select(ormid, "OnPlayerDataLoad", "d", playerid);
    return 1;
    }


    forward OnPlayerDataLoad(playerid);
    public OnPlayerDataLoad(playerid)
    {
    switch(orm_errno(Player[playerid][ORM_ID]))
    {
    case ERROR_OK: {
    ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", "Bitte gib dein Passwort ein.", "Login", "Abbruch");
    }
    case ERROR_NO_DATA: {
    ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registrierung", "Bitte gib dein Passwort ein.", "Registrieren", "Abbruch");
    }
    }
    orm_setkey(Player[playerid][ORM_ID], "ID"); // setzt einen neuen Schlüssel für WHERE `ID` = ... in zukünftigen Queries!
    return 1;
    }


    public OnPlayerDisconnect(playerid, reason)
    {
    if(Player[playerid][ID] != 0)
    orm_update(Player[playerid][ORM_ID]);

    orm_destroy(Player[playerid][ORM_ID]);


    for(new E_PLAYER:e; e < E_PLAYER; ++e)
    Player[playerid][e] = 0;
    return 1;
    }

    Das Erstellen einer ORM-Instanz und das Hinzufügen der entsprechenden Variablen sollte hier nun nichts neues darstellen. Doch danach kommt orm_setkey, welches benötigt wird um die WHERE-Bedinung für deine Queries zu generieren.
    Dieser Teil von OnPlayerConnect

    orm_setkey(ormid, "Name");
    orm_select(ormid, "OnPlayerDataLoad", "d", playerid);

    macht folgendes:
    1. setzt das interne Feld, welches für den WHERE-Teil der Query benötigt wird, zu "Name"
    2. generiert diese Query: "SELECT `ID`,`Name`,`Money`,`Level`,`PosX`,`PosY`,`PosZ` FROM `players` WHERE `Name`='%s' LIMIT 1" (%s ist zu ersetzen mit dem Spieler-Namen)
    3. führt diese Query aus (nicht im PAWN-Thread)
    4. nimmt das Ergebnis der Query (die aktuelle Cache) und weist die Daten den entsprechend zuvor registrierten Variablen zu
    5. ruft OnPlayerDataLoad mit playerid as Parameter auf


    Abhängig davon was die Query findet, setzt das ORM-System einen Wert für die Error-Variable. Im Callback, welches von orm_select aufgerufen wird, muss überprüft werden, ob alles in Ordnung ist oder es einen Fehler gab. Der Error-Wert kann mit orm_errno abgefragt werden. Zur Zeit gibt es folgende mögliche Fehler bzw. Error-Werte:
    * ERROR_OK (0)
    * ERROR_NO_DATA (1)


    ERROR_OK, in unserem Beispiel, zeigt, dass es den Spieler-Account gibt und wir weiter mit unserem Login-Fenster fortfahren können. Die Spieler-Variablen (Player[playerid][...]) haben in diesem Moment nun alle richtigen Werte aus der Tabelle zugewiesen bekommen.


    Eine andere Sache hier ist, dass für zukünftige, effizientere Queries ein neues Schlüssel-Feld gesetzt wird:
    orm_setkey(Player[playerid][ORM_ID], "ID");
    Das bedeuted, dass jede zukünftige Query, die von orm_update (in OnPlayerDisconnect zu sehen), orm_select oder orm_delete generiert wird, am Ende wie folgt aussehen wird: "... WHERE `ID`='%d'" (%d ist zu erstzen mit Player[playerid][ID]). Wenn der Spieler disconnected, wird die ORM-Instanz mit orm_destroy(ORM:id) zerstört. Davor können alle ORM-Funktionen außer orm_select und orm_update frei verwendet werden.


    Sollte sich der Spieler registrieren müssen, wird orm_insert verwendet. Diese Funktion schickt eine INSERT-Query ab, erzeugt also einen neuen Spieler-Eintrag in der Tabelle. Die Syntax ist dieselbe wie bei orm_select:

    orm_insert(Player[playerid][ORM_ID], "OnPlayerRegister", "d", playerid);
    // ...
    forward OnPlayerRegister(playerid);
    public OnPlayerRegister(playerid)
    {
    printf("Spieler %s hat sich registriert und hat die ID %d.", Player[playerid][Name], Player[playerid][ID]);
    }

    Wie man sehen kann, wird der "Player[playerid][ID]"-Variable automatisch die neue Datenzeilen-ID aus der Tabelle zugewiesen. Dies passiert immer sobald solch eine ORM-Query abgeschickt wurde. Jedoch muss, bevor orm_insert aufgerufen wird, die richtige Variable als Schlüssel mit orm_setkey markiert sein.


    Okay, soweit sollte klar sein, wie man Daten abfrägt (orm_select) und hinzufügt (orm_insert). Aber wie werden schon existierende Daten in der Tabelle aktualisiert? Dazu muss orm_update verwendet werden. Diese Funktion generiert eine UPDATE-Query mit allen aktuellen Werten der registrierten Variablen.
    Ein Beispiel: Der Spieler "Spieler1" hat die ID 65 (nicht die playerid, die ID in der Tabelle), ist Level 4, hat 54634$ und ist irgendwo in Los Santos. In diesem Fall würde orm_update diese Query generieren:

    SQL
    UPDATE `players` SET `Name`='Spieler1', `Money`='54634', `Level`='4', `PosX`='745.231', `PosY`='-967.1425', `PosZ`='14.2543' WHERE `ID`='65'


    Wir können nun fast alle wichtigen Arten von Queries generieren, nur die DELETE-Query fehlt. Dafür gibt es auch eine Funktion, orm_delete. Wie man sich bereits denken kannst, generiert diese Funktion eine DELETE-Query. In unserem Beispiel würde es folgende Query generieren und absenden:

    SQL
    DELETE FROM `players` WHERE `ID`='65'


    Anders als orm_update hat orm_delete einen extra (optionalen) Parameter namens "clearvars". Wenn dieser auf 'true' gesetzt wird, wird orm_delete nicht nur den Tabellen-Eintrag löschen, sondern auch alle regestrierten Variablen auf 0 setzen (auch die Schlüssel-Variable).



    In diesem Tutorial wurde bis jetzt die ganze Zeit als Beispiel ein User-System genommen. Das heißt aber nicht, dass man sich nur auf solch ein System beschränken soll. Man kann jede Daten managen die man will, zum Beispiel bei einem Haus-System oder Fahrzeug-System. Hier ist ein Beispiel-Script das zeigt, wie man Fahrzeug-Daten mit dem ORM-System laden könnte:

    new SQL = -1;


    enum e_Vehicle
    {
    ORM:ORM_ID,
    VID,
    ID,
    ModelID,
    Color1,
    Plate[32],
    Float:Pos[4],
    };
    new Vehicle[MAX_VEHICLES][e_Vehicle];



    public OnGameModeInit()
    {
    mysql_log();

    SQL = mysql_connect("127.0.0.1", "root", "test", "pass");

    //lade Fahrzeuge
    mysql_tquery(SQL, "SELECT * FROM `vehicles`", "OnVehiclesLoad", "");
    return 1;
    }



    forward OnVehiclesLoad();
    public OnVehiclesLoad()
    {
    for(new r=0; r < cache_num_rows(); ++r) {
    new ORM:ormid = Vehicle[r][ORM_ID] = orm_create("vehicles");


    orm_addvar_int(ormid, Vehicle[r][ID], "ID"); //das ist unser Schlüssel
    orm_setkey(ormid, "ID"); //hier legen wir ihn auch als Schlüssel fest
    orm_addvar_int(ormid, Vehicle[r][ModelID], "ModelID");
    orm_addvar_int(ormid, Vehicle[r][Color1], "Color1");
    orm_addvar_string(ormid, Vehicle[r][Plate], 32, "Plate");
    orm_addvar_float(ormid, Vehicle[r][Pos][0], "PosX");
    orm_addvar_float(ormid, Vehicle[r][Pos][1], "PosY");
    orm_addvar_float(ormid, Vehicle[r][Pos][2], "PosZ");
    orm_addvar_float(ormid, Vehicle[r][Pos][3], "PosA");


    orm_apply_cache(ormid, r);


    Vehicle[r][VID] = CreateVehicle(Vehicle[r][ModelID], Vehicle[r][Pos][0], Vehicle[r][Pos][1], Vehicle[r][Pos][2], Vehicle[r][Pos][3], Vehicle[r][Color1], -1, -1);
    }


    return 1;
    }

    Was macht dieser Code?
    Er sendet eine normale Query (die in OnGameModeInit), welche alle Daten aus der Fahrzeug-Tabelle holt. Anschließend iteriert er durch die Tabellen-Reihen im "OnVehiclesLoad"-Callback. Für jede Tabellen-Reihe wird eine ORM-Instanz erstellt und dazugehörige Variablen registriert. Dann wird eine Funktion benutzt, die bislang nicht besprochen wurde: orm_apply_cache. Diese Funktion nimmt die derzeitige, aktive Cache und schaut ob in der Reihe mit dem Reihen-Index "r" Felder mit den registrierten Feldnamen sind (also "ID", "ModelID", "Color1" usw.). Wenn ja, wird der Wert dieses Feldes genommen und der jeweiligen Variable zugewiesen.


    Ich hoffe dieses Feature und Tutorial wird als nützlich erachtet.


    Danke an AndreT aus dem englischen SA-MP Forum für den Großteil dieses Tutorials und Kritiken/Anregungen zum ORM-System.
    Auch ein Dank an BigETI aus dem offiziellen deutschen SA-MP Forum für Korrekturen, Tipps und Anregungen zum Tutorial.