Queries¶
Every query in mysql_samp is non-blocking: the statement runs on a worker thread and the callback is invoked on a later tick. The server never freezes waiting for the database.
mysql_query — FIFO-ordered¶
native bool:mysql_query(connId, const query[], const callback[] = "", const format[] = "", {Float,_}:...);
| Parameter | Type | Description |
|---|---|---|
connId |
int | Connection id returned by mysql_connect |
query |
string | Raw SQL statement (already escaped — use mysql_format to build it safely) |
callback |
string | Pawn public to invoke when the result is ready. Empty ("") means fire-and-forget |
format |
string | One char per variadic parameter (d/i = int, f = float, s = string) |
... |
variadic | Values forwarded to the callback in the order they appear |
Returns: true if the query was queued, false if connId is unknown.
Ordering guarantee: callbacks are delivered in submission order, even when the underlying queries finish out of order. A slow query blocks the dispatch of later callbacks until it completes.
mysql_query(g_mysql, "SELECT * FROM players WHERE level > 5", "OnHighLevelPlayers");
forward OnHighLevelPlayers();
public OnHighLevelPlayers()
{
new rows = cache_get_row_count();
printf("high-level players: %d", rows);
}
Passing parameters to the callback¶
The format string declares the types; the variadics carry the actual values. The callback sees the parameters in left-to-right order.
mysql_format(g_mysql, query, sizeof(query),
"SELECT * FROM players WHERE id = %d", target_id);
mysql_query(g_mysql, query, "OnPlayerLoaded", "d", playerid);
forward OnPlayerLoaded(playerid);
public OnPlayerLoaded(playerid)
{
if (cache_get_row_count() > 0)
{
new name[MAX_PLAYER_NAME];
cache_get_value_name(0, "name", name);
printf("player %d: %s", playerid, name);
}
}
Callback format characters:
| Char | Pawn type | Notes |
|---|---|---|
d, i |
int | Both aliases work |
f |
float | |
s |
string | Pushed as an AMX string allocation |
Any other character logs a warning at submission time and is skipped.
Fire and forget¶
Omit the callback to discard the result:
A failure still fires OnQueryError.
mysql_pquery — parallel, no ordering¶
native bool:mysql_pquery(connId, const query[], const callback[] = "", const format[] = "", {Float,_}:...);
Same signature as mysql_query, same callback semantics, but no ordering guarantee. Callbacks fire as soon as each query finishes; the dispatcher places parallel results behind ordered ones in the same tick.
Use mysql_pquery when the gamemode does not care which callback runs first.
mysql_pquery(g_mysql, "UPDATE stats SET kills = kills + 1 WHERE id = 1");
mysql_pquery(g_mysql, "INSERT INTO logs (action) VALUES ('kill')");
mysql_pquery(g_mysql, "SELECT * FROM rewards WHERE id = 1", "OnRewards");
Choosing between mysql_query and mysql_pquery¶
| Concern | mysql_query |
mysql_pquery |
|---|---|---|
| Threading | One worker thread per query | One worker thread per query |
| Callback order | FIFO (submission order) | First done, first dispatched |
| Typical use | SELECT chains that depend on order | UPDATE/INSERT and independent reads |
| Reordering cost | Yes — results buffer until the next sequence is available | None |
Both natives create one OS thread per query — the cost of a thread spawn is roughly the cost of one TCP round-trip, well below the cost of a real MySQL query.
mysql_tick — backwards compatibility¶
Drains the threaded-query queue manually. You do not need to call this. Since rust-samp v3.0.0, the unified on_tick hook is driven by ProcessTick on SA-MP and by an ITimersComponent 5 ms timer on Open Multiplayer native mode; the plugin processes pending results automatically. mysql_tick is kept only so old gamemodes that called it explicitly still compile and behave.
mysql_format¶
printf-style query builder with automatic SQL escaping on %s / %e. Returns the byte length of the rendered string (after truncation, if any). The connId parameter is currently unused but kept for API compatibility — pass the connection you intend to run the query on.
| Specifier | Type | Behavior |
|---|---|---|
%d, %i |
int | Decimal integer |
%f |
float | 4 decimal places ({:.4}) |
%s, %e |
string | SQL-escaped (safe to drop into a quoted string literal) |
%r |
string | Raw, not escaped — only for trusted constants |
%% |
literal | Single % |
Any other %x is left as-is in the output and a single warning is logged for the whole call.
Truncation¶
If the rendered string is longer than max_len - 1 (the -1 reserves a slot for the AMX NUL terminator), the plugin truncates at the nearest UTF-8 char boundary, logs a warning and still returns the length actually written. Old behavior aborted the native; the new behavior is deterministic and survives oversized inputs gracefully.
Example¶
new query[256];
new name[24] = "O'Brien";
mysql_format(g_mysql, query, sizeof(query),
"SELECT * FROM players WHERE name = '%s'", name);
// Result: SELECT * FROM players WHERE name = 'O\'Brien'
mysql_query(g_mysql, query, "OnPlayerFound");
Combining types:
new query[256];
mysql_format(g_mysql, query, sizeof(query),
"INSERT INTO scores (player_id, score, name) VALUES (%d, %f, '%s')",
playerid, 99.5, "Player1");
Rule of thumb. Use
%sfor every string that originated outside your code. Use%ronly for compile-time constants such as table names. Never pass user input through%r.
mysql_escape_string¶
Pure escape function — no connection required because the escape rules are connection-independent under UTF-8 (which the plugin forces on every pool connection). Returns true on success, false only if writing into dest fails.
Characters escaped: \0, \n, \r, \\, ', ", \x1a (Ctrl-Z). Every other byte (including \t, control bytes, and multi-byte UTF-8) passes through unchanged.
new escaped[128];
new input[] = "It's a \"test\"";
mysql_escape_string(input, escaped);
// escaped = "It\'s a \"test\""
Never call mysql_escape_string on a string and then pass the result through %s — %s escapes again and you end up with \\' instead of \'.
Inspecting the queue¶
Returns in_flight + pending_ordered.len() — queries currently spawned on a worker thread plus queries whose results arrived but whose callbacks are blocked behind an earlier sequence.
Useful for graceful shutdown loops: keep ticking until mysql_unprocessed_queries() == 0.
Result limits¶
| Limit | Value | Behavior on overflow |
|---|---|---|
| Rows per result | 100 000 | Drained but discarded; a warning is logged once per oversized query |
| Saved caches | 1 024 | cache_save() returns 0 and logs a warning |
| ORM string buffer | 4 096 chars | orm_addvar_string returns false |