Skip to Content
ScriptingDatabase
Report an error
Open in ChatGPTOpen in Claude

Initialize oxmysql before calling MySQL

The Lua MySQL wrapper comes from @oxmysql/lib/MySQL.lua. Declaring dependency 'oxmysql' establishes resource dependency order but does not import that wrapper into your script. Load it first, keep database code server-side and bind values as parameters. oxmysql targets MariaDB/MySQL, not PostgreSQL.

This standalone exercise writes only to a disposable docs_db_probe table. It demonstrates SQL commit/rollback, not a framework inventory, money transfer or authorization system. Use an isolated development database and back up existing data before applying any schema change.

1. Create the table and resource

Follow database connection setup first. Import this schema.sql into the development database selected by your connection string:

CREATE TABLE docs_db_probe ( id INT NOT NULL PRIMARY KEY, message VARCHAR(100) NOT NULL ) ENGINE=InnoDB;

Create resources/[local]/docs_db_probe/fxmanifest.lua:

fx_version 'cerulean' game 'gta5' dependency 'oxmysql' server_scripts { '@oxmysql/lib/MySQL.lua', 'server.lua' }

The table uses InnoDB so its writes participate in transactions. The resource has no client or shared script. Do not put connection strings into files downloaded to players.

2. Add a console-only transaction probe

Save this as server.lua in that resource:

local busy = false RegisterCommand('docsdb', function(playerSource, args) if playerSource ~= 0 then return end local mode = args[1] if mode ~= 'reset' and mode ~= 'commit' and mode ~= 'rollback' and mode ~= 'read' then print('[docsdb] Use: docsdb reset|commit|rollback|read') return end if busy then print('[docsdb] A probe is already running.') return end busy = true local ok = pcall(function() if mode == 'reset' then MySQL.update.await('DELETE FROM docs_db_probe') elseif mode == 'commit' or mode == 'rollback' then local secondId = mode == 'rollback' and 1 or 2 local success = MySQL.transaction.await({ { query = 'INSERT INTO docs_db_probe (id, message) VALUES (?, ?)', values = { 1, 'first statement' } }, { query = 'INSERT INTO docs_db_probe (id, message) VALUES (?, ?)', values = { secondId, 'second statement' } } }) print(('[docsdb] committed=%s'):format(tostring(success))) end local count = MySQL.scalar.await('SELECT COUNT(*) FROM docs_db_probe') print(('[docsdb] rows=%s'):format(tostring(count))) end) busy = false if not ok then print('[docsdb] Probe failed; inspect private database/resource logs, then run docsdb read.') end end, true)

The explicit console check prevents a connected player’s command from doing database writes. Parameters bind values, not SQL identifiers: table/column names remain fixed. The local busy flag prevents this resource’s console probes from interleaving while an awaited call yields; it is not a cross-resource database lock.

3. Start in order and verify results

In the active server.cfg, configure the private connection before starting oxmysql, then start the consumer:

set mysql_connection_string "mysql://fivem_user:[email protected]:3306/fivem?charset=utf8mb4" ensure oxmysql ensure docs_db_probe

Replace the connection template with your development account. In the server console, use refresh if the new resource has not been discovered. Check oxmysql startup and the first error before invoking commands.

Commands, in the server consoleExpected table state
docsdb reset, then docsdb readZero rows; reset deletes only this disposable table’s rows
docsdb commit, then docsdb readcommitted=true; two rows
docsdb reset, then docsdb rollback, then docsdb readDuplicate primary key in the second INSERT; transaction fails and zero rows remain
docsdb commit twice without resetThe repeated INSERT fails; previously committed rows remain
A connected player calls the commandNo database operation

The transaction API  returns a boolean. The wrapper/driver can also report errors; the pcall prevents a rejected await from leaving this probe permanently busy. The duplicate-key error in the rollback test is intentional. Confirm the final table state with docsdb read and, independently, a SQL client. Stop the demo resource when finished. Do not run reset against another resource’s tables.

SQL success is not business success

An UPDATE that matches zero rows can still be a successful SQL statement. A transaction may commit even when an application condition, such as sufficient balance or a current session, was not satisfied. Do not combine a conditional debit and unconditional credit and assume a true transaction boolean makes the transfer safe.

Real purchases require server-owned prices, authenticated identity, concurrency-safe invariant checks, appropriate locking/atomic statements and idempotent request handling. Follow the exact database/driver transaction capabilities and test concurrent requests against your schema. A client-supplied amount or a client-side busy flag is not an authorization boundary. See secure events and async session lifetime.

Migrating legacy database APIs

MySQL.Async.execute is a mysql-async-style interface, not proof that a standalone ghmattimysql installation supplies the same global. The ghmattimysql API  documents resource exports such as exports.ghmattimysql:execute. oxmysql has compatibility aliases, but installation, imports, parameter semantics and return values still need review for the exact consumer version.

Migrate one adapter deliberately: inventory imports/exports, back up schema/data, test callback and await error paths, and verify transaction behavior before switching production. Do not run multiple adapters against assumed interchangeable APIs just to suppress an initialization error.

For MySQL nil, connection refusal, schema mismatch and startup order, use database setup, resource lifecycle and common errors.