Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Checklist: mysql_samp vs MySQL R41-4

Coverage of the MySQL R41-4 (BlueG / maddinat0r) Pawn API by **mysql_samp 1.1.1**. Source of truth: [`include/mysql_samp.inc.in`](include/mysql_samp.inc.in) and [`src/lib.rs`](src/lib.rs).
Coverage of the MySQL R41-4 (BlueG / maddinat0r) Pawn API by **mysql_samp**. Source of truth: [`include/mysql_samp.inc.in`](include/mysql_samp.inc.in) and [`src/lib.rs`](src/lib.rs). Current plugin version lives in [`Cargo.toml`](Cargo.toml).

## Connection

Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# mysql_samp

> MySQL plugin for SA-MP written in Rust — by [NullSablex](https://github.com/NullSablex)
> MySQL plugin for SA-MP and Open Multiplayer, written in Rust — by [NullSablex](https://github.com/NullSablex)

![License](https://img.shields.io/badge/license-GPL--3.0-blue)
![SA-MP](https://img.shields.io/badge/SA--MP-0.3.7+-orange)
Expand Down Expand Up @@ -42,8 +42,8 @@ The same binary loads on SA-MP and on Open Multiplayer — natively as a compone
plugins mysql_samp.so
```
(or `mysql_samp.dll` on Windows)
- **Open Multiplayer (native, recommended)** — list the binary under `components` in `config.json`. It will be loaded via `ComponentEntryPoint`, with access to `ICore`, `ITimersComponent` and the other native APIs.
- **Open Multiplayer (legacy)** — still supported. Register the binary under `legacy_plugins` in `config.json` if you prefer the SA-MP compatibility path. Same binary, no extra build flags.
- **Open Multiplayer (native, recommended)** — drop the binary into the `components/` folder. open.mp auto-discovers it on start and loads it via `ComponentEntryPoint`, with access to `ICore`, `ITimersComponent` and the other native APIs. No `config.json` entry required.
- **Open Multiplayer (legacy)** — same binary works as a legacy plugin. Drop it into `plugins/` and add it to `legacy_plugins` in `config.json` (this one DOES need to be declared, otherwise open.mp skips legacy plugins).

> [!IMPORTANT]
> No `libmysqlclient` or other system library is required. The plugin is self-contained.
Expand Down Expand Up @@ -86,6 +86,8 @@ public OnGameModeExit() {
}
```

Browse the [examples/](examples/) folder for self-contained `.pwn` scripts covering connection setup, threaded queries, ORM, TLS and error handling. The plugin natives (`mysql_*`, `cache_*`, `orm_*`) and the `OnQueryError` forward are identical across SA-MP and Open Multiplayer, so every example builds and runs on both — the only thing that differs between servers is the installation path documented above.

## Documentation

The full plugin documentation lives in [docs/](docs/):
Expand Down
4 changes: 2 additions & 2 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ Source of truth: [`include/mysql_samp.inc.in`](https://github.com/NullSablex/mys
## Constants

```pawn
#define MYSQL_SAMP_VERSION "1.1.1"
#define MYSQL_SAMP_VERSION "X.Y.Z"
```

The literal above is regenerated by `build.rs` from `CARGO_PKG_VERSION` on every build, so it always tracks the actual plugin version.
The literal is regenerated by `build.rs` from `CARGO_PKG_VERSION` on every build, so the value shipped in `mysql_samp.inc` always tracks the version declared in [`Cargo.toml`](https://github.com/NullSablex/mysql_samp/blob/master/Cargo.toml). This page does not hardcode the current number on purpose — check `Cargo.toml` or the include itself.

### Connection options

Expand Down
2 changes: 1 addition & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ plugins mysql_samp.so

### Open Multiplayer — native mode (recommended)

Drop the binary into `components/` (open.mp folder) and register it under `components` in `config.json`. The plugin is loaded via `ComponentEntryPoint` and gets access to `ICore`, `ITimersComponent` and the other native APIs.
Drop the binary into the `components/` folder. open.mp auto-discovers and loads it via `ComponentEntryPoint` on start, with access to `ICore`, `ITimersComponent` and the other native APIs. No `config.json` entry is required — native components are discovered by scanning the folder, not by declaration.

### Open Multiplayer — legacy mode

Expand Down
57 changes: 57 additions & 0 deletions examples/01_basic_connection.pwn
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 01_basic_connection.pwn — open a connection at OnGameModeInit, close it on exit.
//
// Two variants are shown:
// 1. The minimal call (default port 3306, no SSL, auto_reconnect on).
// 2. The same call routed through mysql_options_new() so you can tweak port,
// connect timeout, auto-reconnect, etc.

#include <a_samp>
#include <mysql_samp>

#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "samp"
#define MYSQL_PASSWORD "secret"
#define MYSQL_DATABASE "samp_server"

new g_MysqlConn = 0;

public OnGameModeInit()
{
// --- Variant 1: no options ----------------------------------------------
// g_MysqlConn = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);

// --- Variant 2: with options --------------------------------------------
new opts = mysql_options_new();
mysql_options_set_int(opts, MYSQL_OPT_PORT, 3306);
mysql_options_set_int(opts, MYSQL_OPT_CONNECT_TIMEOUT, 5); // seconds
mysql_options_set_int(opts, MYSQL_OPT_AUTO_RECONNECT, 1);

g_MysqlConn = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, opts);

if (g_MysqlConn == 0)
{
new err[256];
mysql_error(0, err); // 0 = global error slot (set when connect itself fails)
printf("[mysql] connect failed: %s", err);
return 1;
}

// Optional: ask the server how it's doing.
new status[256];
if (mysql_status(g_MysqlConn, status))
{
printf("[mysql] connected. %s", status);
}

return 1;
}

public OnGameModeExit()
{
if (g_MysqlConn != 0)
{
mysql_close(g_MysqlConn);
g_MysqlConn = 0;
}
return 1;
}
69 changes: 69 additions & 0 deletions examples/02_threaded_query.pwn
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 02_threaded_query.pwn — non-blocking SELECT with a callback, FIFO-ordered.
//
// mysql_query() runs the query on a worker thread and dispatches the result
// to the named callback. Inside the callback the result is the *active cache*
// — read it with the cache_* natives. The cache is freed automatically when
// the callback returns (use cache_save() to keep it longer).
//
// Format spec for the variadic args: each letter maps to one extra param.
// "d" int, "f" float, "s" string. Order must match the callback signature.

#include <a_samp>
#include <mysql_samp>

#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "samp"
#define MYSQL_PASSWORD "secret"
#define MYSQL_DATABASE "samp_server"

new g_MysqlConn = 0;

public OnGameModeInit()
{
g_MysqlConn = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);
return 1;
}

public OnPlayerConnect(playerid)
{
new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, sizeof(name));

new query[256];
mysql_format(g_MysqlConn, query, sizeof(query),
"SELECT id, money, score FROM players WHERE name = '%e' LIMIT 1",
name);

// Pass playerid through to the callback so we know which session to apply
// the result to. FIFO ensures the result lands before any later query for
// the same player.
mysql_query(g_MysqlConn, query, "OnPlayerLoad", "d", playerid);
return 1;
}

forward OnPlayerLoad(playerid);
public OnPlayerLoad(playerid)
{
if (cache_get_row_count() == 0)
{
printf("[mysql] new player, no row yet (id=%d)", playerid);
return 1;
}

new playerDbId = cache_get_value_name_int(0, "id");
new money = cache_get_value_name_int(0, "money");
new score = cache_get_value_name_int(0, "score");

GivePlayerMoney(playerid, money);
SetPlayerScore(playerid, score);

printf("[mysql] loaded player %d (db_id=%d, money=%d, score=%d)",
playerid, playerDbId, money, score);
return 1;
}

public OnGameModeExit()
{
if (g_MysqlConn != 0) mysql_close(g_MysqlConn);
return 1;
}
57 changes: 57 additions & 0 deletions examples/03_parallel_query.pwn
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// 03_parallel_query.pwn — mysql_pquery: parallel, no ordering guarantee.
//
// Use mysql_pquery when the result does NOT depend on a previously queued
// query for the same row. Typical fit: independent UPDATEs, write-only logging,
// or read-only lookups whose order doesn't matter.
//
// If you fire mysql_pquery() for the same key (e.g. same playerid) multiple
// times in a row and need ordering, switch to mysql_query() instead.

#include <a_samp>
#include <mysql_samp>

#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "samp"
#define MYSQL_PASSWORD "secret"
#define MYSQL_DATABASE "samp_server"

new g_MysqlConn = 0;

public OnGameModeInit()
{
g_MysqlConn = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);
return 1;
}

// Fire-and-forget log entry. We don't care when it lands, only that it lands.
LogPlayerAction(playerid, const action[])
{
new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, sizeof(name));

new query[256];
mysql_format(g_MysqlConn, query, sizeof(query),
"INSERT INTO action_log (player, action, ts) VALUES ('%e', '%e', NOW())",
name, action);

// Empty callback string = run and discard the result.
mysql_pquery(g_MysqlConn, query);
}

public OnPlayerDeath(playerid, killerid, reason)
{
LogPlayerAction(playerid, "death");
return 1;
}

public OnPlayerSpawn(playerid)
{
LogPlayerAction(playerid, "spawn");
return 1;
}

public OnGameModeExit()
{
if (g_MysqlConn != 0) mysql_close(g_MysqlConn);
return 1;
}
86 changes: 86 additions & 0 deletions examples/04_escape_and_format.pwn
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// 04_escape_and_format.pwn — building queries safely.
//
// mysql_format specifiers:
// %d int
// %f float
// %s string, auto-escaped (preferred for user input)
// %e alias of %s, explicit "escape"
// %r string, raw (NO escape) — use only with values YOU control
// %% literal percent sign
//
// If you absolutely need to escape a string outside mysql_format, use
// mysql_escape_string. It is a pure function: no connection required.

#include <a_samp>
#include <mysql_samp>

#define MYSQL_HOST "127.0.0.1"
#define MYSQL_USER "samp"
#define MYSQL_PASSWORD "secret"
#define MYSQL_DATABASE "samp_server"

new g_MysqlConn = 0;

public OnGameModeInit()
{
g_MysqlConn = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE);
return 1;
}

// Example 1: a SELECT with an escaped user-supplied name.
SearchPlayerByName(const input[])
{
new query[256];
mysql_format(g_MysqlConn, query, sizeof(query),
"SELECT id, score FROM players WHERE name = '%e' LIMIT 1",
input);
mysql_query(g_MysqlConn, query, "OnSearchResult");
}

forward OnSearchResult();
public OnSearchResult()
{
if (cache_get_row_count() == 0) return printf("[mysql] not found");
new id = cache_get_value_name_int(0, "id");
new score = cache_get_value_name_int(0, "score");
printf("[mysql] id=%d score=%d", id, score);
return 1;
}

// Example 2: an UPDATE mixing ints, floats and strings.
PersistPlayer(playerid, const note[])
{
new Float:x, Float:y, Float:z;
GetPlayerPos(playerid, x, y, z);

new query[512];
mysql_format(g_MysqlConn, query, sizeof(query),
"UPDATE players SET pos_x=%f, pos_y=%f, pos_z=%f, score=%d, note='%e' WHERE id=%d",
x, y, z, GetPlayerScore(playerid), note, playerid);
mysql_pquery(g_MysqlConn, query);
}

// Example 3: %r — raw, no escape. Only for trusted, hard-coded values.
TruncateActionLog()
{
new query[128];
mysql_format(g_MysqlConn, query, sizeof(query),
"DELETE FROM %r WHERE ts < NOW() - INTERVAL %d DAY",
"action_log", 30);
mysql_pquery(g_MysqlConn, query);
}

// Example 4: escape outside mysql_format (rare, but available).
ManualEscape()
{
new src[] = "O'Brien";
new dest[64];
mysql_escape_string(src, dest);
printf("[mysql] escaped: %s", dest); // O\'Brien
}

public OnGameModeExit()
{
if (g_MysqlConn != 0) mysql_close(g_MysqlConn);
return 1;
}
Loading
Loading