Use Cases
Practical scenarios for A3SQL. Each section shows what you build, how to build it, and common pitfalls to avoid.
Server admins
Kill/death tracking with persistence
Track kills, deaths, playtime, and score across every mission on your server. JIP players get their stats restored automatically.
Simple kill counter:
// Inside a Killed event handler on the server:
// params ["_unit", "_killer"];
private _uid = getPlayerUID _killer;
private _name = name _killer;
["INSERT OR REPLACE INTO stats (uid, name, kills)
VALUES ($1, $2,
COALESCE((SELECT kills FROM stats WHERE uid = $1), 0) + 1
)", "a3sql", [_uid, _name]] call a3sql_fnc_execute;
Top 10 leaderboard:
_result = ["SELECT name, kills FROM stats ORDER BY kills DESC LIMIT 10"] call a3sql_fnc_selectArray;
Export to CSV for a web panel:
private _csv = ["stats"] call a3sql_fnc_exportCSV;
How: Getting Started, SQL Dialect
Remote administration from Discord
The TCP listener lets external tools query the database while the mission runs. A Discord bot can kick, ban, or show stats without anyone being in-game.
import socket
def query_a3sql(sql):
s = socket.socket()
s.connect(("127.0.0.1", 33306))
s.sendall(b"LOGIN admin yourpassword\n")
s.recv(65536) # [0,"OK","Authenticated"]
s.sendall((sql + "\n").encode())
result = s.recv(65536).decode()
s.close()
return resultWarning: The in-game TCP listener binds to 127.0.0.1. It only accepts connections from the same machine. Use the Standalone Server for remote access.
Ban system with expiry
Store bans in SQL with optional expiry dates. Check on player connect.
CREATE TABLE bans (
uid STRING PRIMARY KEY,
reason STRING,
banned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP
);Check on player connect (in initPlayerServer.sqf):
// initPlayerServer.sqf receives the player as _this select 0
private _player = _this select 0;
private _uid = getPlayerUID _player;
private _result = ["SELECT 1 FROM bans WHERE uid = $1
AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)",
"a3sql", [_uid]] call a3sql_fnc_execute;
if (_result select 0 == 0 && {count (_result select 2) > 0}) then {
// Player is banned, kick them
kick _player;
[format ["Banned player %1 attempted to join", name _player]] remoteExec ["systemChat", 0];
};
How: SQL Dialect, CBA Settings
Performance impact
A3SQL runs in-memory inside the Arma process. For a typical 64-slot server:
| Data size | RAM cost | Save time | Impact on gameplay |
|---|---|---|---|
| 100 MB | ~100 MB | < 1 s | None |
| 1 GB | ~1 GB | 2-5 s | Brief hitch on save |
| 5 GB | ~5 GB | 5-15 s | Noticeable hitch |
The database saves to profile_name/a3sql/ as .bin files. Each save is atomic: a new file is written, then renamed over the old one. A crash during save loses at most the current in-progress write.
How: Production Readiness
Mission makers
Player loadout persistence
Save and restore loadouts across sessions. Players keep their gear when the mission changes.
// Save loadout on disconnect
addMissionEventHandler ["HandleDisconnect", {
params ["_unit"];
private _uid = getPlayerUID _unit;
private _loadout = getUnitLoadout _unit;
["INSERT INTO loadouts (uid, loadout) VALUES ($1, $2)
ON CONFLICT (uid) DO UPDATE SET loadout = $2",
"a3sql", [_uid, _loadout]] call a3sql_fnc_execute;
false // allow disconnect
}];
// Restore loadout on join
addMissionEventHandler ["EntityCreated", {
params ["_entity"];
if (_entity isKindOf "Man" && isPlayer _entity) then {
private _uid = getPlayerUID _entity;
private _result = ["SELECT loadout FROM loadouts WHERE uid = $1",
"a3sql", [_uid]] call a3sql_fnc_execute;
if (_result select 0 == 0 && {count (_result select 2) > 0}) then {
private _loadout = (_result select 2) select 0 select 0;
_entity setUnitLoadout _loadout;
};
};
}];
How: Getting Started, SQL Dialect
Dynamic balance/economy system
Track player currency, vehicle ownership, or base building resources.
CREATE TABLE economy (
uid STRING PRIMARY KEY,
balance INTEGER DEFAULT 0,
last_transaction TIMESTAMP
);// Deduct currency
["UPDATE economy SET balance = balance - $1,
last_transaction = CURRENT_TIMESTAMP
WHERE uid = $2 AND balance >= $1",
"a3sql", [500, getPlayerUID player]] call a3sql_fnc_execute;
// Check balance
_result = ["SELECT balance FROM economy WHERE uid = $1",
"a3sql", [getPlayerUID player]] call a3sql_fnc_execute;
How: SQL Dialect, CBA Settings
Event logging for admin review
Log admin actions, player reports, or suspicious activity for review.
CREATE TABLE admin_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action STRING,
player_uid STRING,
target_uid STRING,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
details STRING
);["INSERT INTO admin_log (action, player_uid, target_uid, details)
VALUES ($1, $2, $3, $4)",
"a3sql", ["kick", "12345678", "87654321", "Team killing"]] call a3sql_fnc_execute;
How: Module Guide
Mod developers
FFI extension for custom SQL
Call your own SQL functions from SQF through the FFI extension. Use this for high-performance operations that bypass the interpreter.
// a3sql_ffi_example.cpp
#include <a3sql/ffi.h>
extern "C" int32_t my_custom_query(
const char* sql, int32_t sql_len,
char* result, int32_t* result_len
) {
// Your custom SQL logic here
// Write result to the output buffer
return 0; // 0 = success
}Warning: The FFI extension must compile against the A3SQL SDK headers. See Development Setup for build instructions.
How: Development Setup, Plugins
Plugin system for custom functions
Register custom SQF functions through the plugin system. Each plugin is a separate PBO that registers its functions with A3SQL.
// plugin_init.cpp
#include <a3sql/plugin.h>
void a3sql_plugin_init() {
a3sql_register_function("my_custom_fn", my_custom_fn_impl);
}How: Plugins, Development Setup
Compatibility testing
Test your mod’s SQL against the A3SQL engine before release. Use the compatibility corpus to catch dialect issues early.
cd tools
uv run sql_corpus/compatibility_test.py --mod my_mod.sqlFAQ
Does A3SQL work with other database mods?
No. A3SQL uses its own in-memory SQLite engine. It does not connect to external databases. The TCP listener and FFI extension provide integration points for external tools.
Can I migrate data between A3SQL versions?
Yes. The database files are in profile_name/a3sql/. Copy the .bin files to the new server. Backward compatibility is maintained within major versions.
What happens if the server crashes?
A3SQL uses atomic writes. A crash during save loses at most the current in-progress write. Previous saves are intact. Enable periodic auto-save through CBA Settings for additional safety.
How do I debug SQL issues?
Enable debug logging in CBA Settings. Check the Arma log file for SQL errors. Use the TCP connector to test queries interactively from a Python or Node.js script.
How: CBA Settings, TCP Connector