Guide oxmysql : MySQL.query.await, single, scalar, insert

Comment utiliser oxmysql dans FiveM Lua : MySQL.query.await, single, scalar, insert, update, prepare, paramètres sécurisés ?, l'import MySQL.lua, éviter les boucles et les transactions.

Vous avez besoin de lire ou d'écrire dans la base de données à partir d'un script serveur, et les exemples en ligne mélangent les anciens appels MySQL.Async, les callbacks et exports.oxmysql dans différents styles. Cet article montre les fonctions MySQL actuelles, laquelle utiliser pour quel résultat, et comment garder les requêtes sûres et bon marché.

Charger l'objet MySQL

Pour utiliser le global MySQL, importez le wrapper Lua d'oxmysql dans le manifeste de la ressource, avant vos propres scripts serveur :

lua
-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'

server_scripts {
    '@oxmysql/lib/MySQL.lua',
    'server/main.lua',
}

dependency 'oxmysql'

Sans cette ligne, MySQL est nil et vous obtenez attempt to index a nil value (global 'MySQL'). Le fichier fonctionne uniquement sur le serveur. Si oxmysql lui-même ne peut pas se connecter, consultez erreurs de connexion oxmysql.

Les fonctions et ce qu'elles retournent

Chaque fonction a une forme .await qui attend le résultat et le retourne. Elle doit s'exécuter dans un thread ou un gestionnaire d'événement, ce qui est déjà le cas pour la plupart de votre code.

Fonction Retourne Utilisez-le pour
MySQL.query.await Un tableau de lignes (vide si aucune) SELECT avec plusieurs lignes
MySQL.single.await Une ligne, ou nil SELECT pour un seul enregistrement
MySQL.scalar.await Une valeur, ou nil COUNT, SUM, une colonne
MySQL.insert.await Le nouvel ID auto-incrémenté INSERT
MySQL.update.await Nombre de lignes affectées UPDATE, DELETE
lua
-- all rows
local rows = MySQL.query.await('SELECT identifier, name FROM users WHERE job = ?', { 'police' })
for _, row in ipairs(rows) do
    print(row.identifier, row.name)
end

-- one row
local user = MySQL.single.await('SELECT * FROM users WHERE identifier = ?', { identifier })
if not user then return end

-- one value
local count = MySQL.scalar.await('SELECT COUNT(*) FROM owned_vehicles WHERE owner = ?', { identifier })

-- insert
local id = MySQL.insert.await('INSERT INTO my_logs (identifier, action) VALUES (?, ?)', { identifier, 'login' })

-- update or delete
local changed = MySQL.update.await('UPDATE users SET job = ? WHERE identifier = ?', { 'unemployed', identifier })

Vérifiez les résultats. Une requête single ou scalar qui ne trouve rien donne nil, et l'utiliser sans vérification est à la source de nombreuses erreurs attempt to index a nil value.

Paramètres avec ?

Toujours passer les valeurs via les espaces réservés ? et le deuxième argument, un tableau dans le même ordre :

lua
-- safe
MySQL.query.await('SELECT * FROM users WHERE name = ?', { name })

-- dangerous: SQL injection
MySQL.query.await('SELECT * FROM users WHERE name = "' .. name .. '"')

Avec la version non sécurisée, un nom tel que x" OR 1=1 -- réécrit votre requête. Avec les espaces réservés, oxmysql envoie les valeurs séparément, de sorte qu'elles ne peuvent jamais devenir du SQL. Cela s'applique à chaque valeur qui provient d'un joueur, d'un argument d'événement ou d'une commande.

Attention : les espaces réservés fonctionnent uniquement pour les valeurs, pas pour les noms de table ou de colonne. Si un nom de colonne provient de l'extérieur, vérifiez-le par rapport à une liste fixe de noms autorisés au lieu de le coller.

Style callback

Les mêmes fonctions acceptent un callback si vous ne voulez pas attendre. C'est utile en dehors d'un thread :

lua
MySQL.query('SELECT * FROM users WHERE job = ?', { 'police' }, function(rows)
    print(#rows)
end)

.await est plus lisible et c'est acceptable dans les événements. Utilisez le style callback quand vous voulez que le code qui suit continue de s'exécuter sans attendre la base de données.

MySQL.prepare

MySQL.prepare exécute une instruction préparée, que la base de données peut réutiliser quand vous exécutez la même requête encore et encore. Il utilise les mêmes paramètres ? :

lua
local row = MySQL.prepare.await('SELECT * FROM users WHERE identifier = ?', { identifier })

Ce qu'il retourne dépend du résultat : une seule ligne revient comme une table, et une requête avec une colonne et une ligne peut revenir comme une valeur. Pour le code où la forme du résultat a de l'importance, single et scalar sont plus prévisibles, donc utilisez prepare quand vous avez une requête chaude qui s'exécute souvent.

Ne pas faire de requête dans une boucle

Chaque requête est un aller-retour à la base de données. Une boucle qui exécute une requête par élément devient lentement lente, et elle bloque le thread pendant qu'elle attend :

lua
-- one query per player
for _, id in ipairs(ids) do
    local row = MySQL.single.await('SELECT * FROM users WHERE id = ?', { id })
end

Récupérez tout dans une seule requête, puis bouclez sur le résultat en Lua :

lua
local rows = MySQL.query.await('SELECT * FROM users WHERE job = ?', { 'police' })

Pour de nombreuses insertions, créez une seule instruction avec plusieurs lignes, ou exécutez-les ensemble dans une transaction. Évitez également de faire une requête à chaque tick ou à chaque frame : lisez une valeur une seule fois et gardez-la dans une table Lua. Si une requête est lente, ajoutez un index sur la colonne par laquelle vous recherchez. Les requêtes lentes apparaissent comme avertissements de scintillement du thread serveur.

Transactions

Quand plusieurs écritures doivent tous réussir ou tous échouer, comme prendre de l'argent d'une ligne et le donner à une autre, utilisez une transaction :

lua
local ok = MySQL.transaction.await({
    { query = 'UPDATE bank SET balance = balance - ? WHERE identifier = ?', values = { 100, fromId } },
    { query = 'UPDATE bank SET balance = balance + ? WHERE identifier = ?', values = { 100, toId } },
})

if not ok then
    print('transfer failed, nothing was changed')
end

Si une déclaration échoue, la base de données annule tout. Elle retourne true quand tout s'est bien passé.

Les tables doivent exister d'abord

Une requête contre une table qui n'existe pas donne Table 'x' doesn't exist. Créez la table avec un fichier SQL quand vous installez le script, ou au démarrage de la ressource avec MySQL.query.await('CREATE TABLE IF NOT EXISTS …'). Plus dans la table SQL n'existe pas.

Liste de contrôle

Symptôme Correction
MySQL est nil Ajoutez '@oxmysql/lib/MySQL.lua' à server_scripts, avant vos fichiers
Le résultat est nil et le script génère une erreur single et scalar retournent nil quand rien n'est trouvé : vérifiez-le
Les valeurs proviennent de joueurs Utilisez ? et une table de paramètres, jamais de concaténation
Le serveur scintille sur une commande Aucune requête dans une boucle : récupérez une fois, bouclez en Lua
Deux écritures doivent aller ensemble MySQL.transaction.await
La même requête s'exécute constamment MySQL.prepare, un index, ou mettez en cache la valeur en Lua

Réponses rapides

Comment utiliser MySQL.query.await dans mon script ?

Ajoutez server_script '@oxmysql/lib/MySQL.lua' avant vos scripts serveur dans fxmanifest.lua, puis appelez MySQL.query.await('SELECT * FROM users WHERE id = ?', { id }) à l'intérieur d'un thread ou d'un gestionnaire d'événement.

Comment empêcher les injections SQL avec oxmysql ?

Ne construisez jamais la requête avec une concaténation de chaîne. Mettez des espaces réservés ? dans la requête et passez les valeurs dans une table, de sorte que oxmysql les envoie séparément.

Quelle est la différence entre query, single et scalar ?

query retourne toutes les lignes, single retourne la première ligne ou nil, et scalar retourne la première colonne de la première ligne, comme un décompte.

Des scripts sans ce problème

Shop CreatorCréez un magasin en moins d’une minute — propriétaires, employés, coffres et braquages inclus.Voir le script →Item Creator V2Créez des items utilisables avec animations, props, effets et plus — sans écrire une ligne de code.Voir le script →Crypto MiningAchetez un entrepôt, montez vos rigs pièce par pièce et minez sur un marché qui bouge.Voir le script →

À lire aussi