Romania XPlayZM.CSBlackDevil.COM # Zombie Plague UltimateX [ Respawn + FDL + VIP 22-12PM + POINTS] ® 11/32
Unknown ❤ RESPAWN.ZAFEST.RO | VIP FREE | FREE FOR ALL ❤ 0/32
France EMIRATES.KiNGS 亗 GAMEAREA ||͇̿P͇̿U͇̿B͇̿L͇̿I͇̿C͇̿| ✪ 32/32
Promoveaza serverul
125 lines 6.9 KiB Markdown Source Rendered Raw Permalink History
---
title: "How to Use the SQLite Module in AMX Mod X"
excerpt: "Enable the SQLite module in AMX Mod X, where the .sq3 file lives, when SQLite beats MySQL, and how to avoid the locking problems."
category: "installation"
tags: "amxmodx, sqlite"
image: ""
read_time: "8 min read"
---
<p>The SQLite module gives AMX Mod X a real SQL database with no server, no credentials, and no network. It uses the same <code>sqlx</code> API as MySQL, so the same plugin code works against either backend — only the connection tuple changes. For a single game server that needs to persist points, ranks, shop purchases or settings, SQLite is often the correct choice and MySQL is over-engineering.</p>

<h2>When SQLite is the right call</h2>
<table>
<thead><tr><th>Situation</th><th>Use</th></tr></thead>
<tbody>
<tr><td>One server, data local to it</td><td><strong>SQLite</strong></td></tr>
<tr><td>Several servers sharing ranks/bans</td><td>MySQL</td></tr>
<tr><td>A web panel reads the data live</td><td>MySQL</td></tr>
<tr><td>You want zero moving parts</td><td><strong>SQLite</strong></td></tr>
<tr><td>Heavy concurrent writes from many processes</td><td>MySQL</td></tr>
</tbody>
</table>
<p>SQLite is also a strictly better choice than <code>nvault</code> for anything structured. nVault is a flat key/value store with no queries and a well-earned reputation for corruption; see <a href="/tutorials/fix-nvault-corrupted">nVault corruption</a>.</p>

<h2>1. Enable the module</h2>
<p><code>addons/amxmodx/configs/modules.ini</code>:</p>
<pre>fun
engine
fakemeta
cstrike
csx
hamsandwich
sqlite</pre>
<p>Check the file is there and is 32-bit:</p>
<pre>ls -la /home/steam/hlds/cstrike/addons/amxmodx/modules/sqlite_amxx_i386.so
file /home/steam/hlds/cstrike/addons/amxmodx/modules/sqlite_amxx_i386.so</pre>
<p>You do <strong>not</strong> need to install SQLite on the host. The module carries its own engine. Restart the server — modules load at startup, not on map change.</p>

<h2>2. Where the database file lives</h2>
<p>The SQLite module puts databases under:</p>
<pre>cstrike/addons/amxmodx/data/sqlite/</pre>
<p>The directory must exist and must be writable by the user running HLDS. If it is not, every query fails and — depending on the plugin — you get silence, not an error.</p>
<pre>mkdir -p /home/steam/hlds/cstrike/addons/amxmodx/data/sqlite
chown -R steam:steam /home/steam/hlds/cstrike/addons/amxmodx/data
chmod 755 /home/steam/hlds/cstrike/addons/amxmodx/data/sqlite</pre>

<h2>3. Connect from a plugin</h2>
<p>Same <code>sqlx</code> API as MySQL. The only difference is the tuple: for SQLite the "database" is the file name and the host/user/pass are ignored.</p>
<pre>#include &lt;amxmodx&gt;
#include &lt;sqlx&gt;

new Handle:g_tuple

public plugin_init()
{
    register_plugin("SQLite Example", "1.0", "you")

    // For SQLite the 4th argument is the database file (created if absent)
    g_tuple = SQL_MakeDbTuple("", "", "", "server_stats")

    SQL_ThreadQuery(g_tuple, "OnTableReady",
        "CREATE TABLE IF NOT EXISTS points ( \
             steamid TEXT PRIMARY KEY, \
             name    TEXT, \
             points  INTEGER DEFAULT 0 \
         )")
}

public OnTableReady(failstate, Handle:query, error[], errnum, data[], size, Float:queuetime)
{
    if (failstate != TQUERY_SUCCESS)
        log_amx("SQLite error %d: %s", errnum, error)
}</pre>
<p>Set <code>amx_sql_type</code> to <code>sqlite</code> in <code>configs/sql.cfg</code> if you use a plugin that reads the backend from there:</p>
<pre>amx_sql_type "sqlite"
amx_sql_db   "server_stats"</pre>
<p>That produces <code>data/sqlite/server_stats.sq3</code>.</p>

<h2>4. Writing rows safely</h2>
<p>Always escape values that come from a player. Names contain quotes, semicolons and worse, and SQLite is as injectable as MySQL:</p>
<pre>new name[64], escaped[128], steamid[35], q[256]
get_user_name(id, name, charsmax(name))
get_user_authid(id, steamid, charsmax(steamid))

SQL_QuoteString(Empty_Handle, escaped, charsmax(escaped), name)

formatex(q, charsmax(q),
    "INSERT OR REPLACE INTO points (steamid, name, points) VALUES ('%s', '%s', %d)",
    steamid, escaped, points)

SQL_ThreadQuery(g_tuple, "OnSaved", q)</pre>
<p><code>INSERT OR REPLACE</code> is SQLite's upsert; MySQL uses <code>ON DUPLICATE KEY UPDATE</code>. This is the one place the two backends genuinely differ in a plugin, and it is why "SQLite and MySQL are drop-in interchangeable" is only 95% true.</p>

<h2>5. Inspecting the database</h2>
<p>Install the <code>sqlite3</code> CLI on the host (this is for <em>you</em>, not for the module):</p>
<pre>sudo apt install -y sqlite3
cd /home/steam/hlds/cstrike/addons/amxmodx/data/sqlite
sqlite3 server_stats.sq3

sqlite&gt; .tables
sqlite&gt; SELECT * FROM points ORDER BY points DESC LIMIT 10;
sqlite&gt; .quit</pre>
<p>Do not run write queries against the file while the server is running. SQLite locks the whole database on write, and a long external transaction will make the game server's queries fail.</p>

<h2>Common errors</h2>
<ul>
<li><strong><code>database is locked</code></strong> — two writers. Usually you left a <code>sqlite3</code> shell open with an uncommitted transaction, or two game servers point at the same <code>.sq3</code> file. One server, one file.</li>
<li><strong><code>unable to open database file</code></strong> — <code>data/sqlite/</code> does not exist, or is not writable by the HLDS user. Check with <code>sudo -u steam touch data/sqlite/test</code>.</li>
<li><strong><code>Module failed to load</code></strong> — the <code>.so</code> is missing or is not 32-bit. See <a href="/tutorials/fix-amxx-module-failed-to-load">the module load fix</a>.</li>
<li><strong><code>no such table: points</code></strong> — the plugin queried before its <code>CREATE TABLE</code> callback completed. Threaded queries are asynchronous; chain the first read off the create callback rather than firing both from <code>plugin_init()</code>.</li>
<li><strong>Data disappears after a restart</strong> — you are writing to a path inside a container without a volume, or the file is being recreated because the plugin's <code>CREATE TABLE</code> is missing <code>IF NOT EXISTS</code> and failing silently.</li>
</ul>

<h2>Backups</h2>
<p>The whole database is one file, which is the best thing about SQLite:</p>
<pre>sqlite3 server_stats.sq3 ".backup '/home/steam/backups/stats-$(date +%F).sq3'"</pre>
<p>Use <code>.backup</code>, not <code>cp</code> — copying a file that is being written produces a corrupt database. Put that in cron.</p>

<h2>Verification</h2>
<ol>
<li><code>amxx modules</code> lists SQLite as <code>running</code>.</li>
<li>The <code>.sq3</code> file appears in <code>data/sqlite/</code> after the first map.</li>
<li><code>sqlite3 file.sq3 ".tables"</code> shows your plugin's tables.</li>
<li>A value written in-game survives a map change and a full restart.</li>
</ol>
<p>If you later outgrow one server, the same plugin code moves to <a href="/tutorials/install-mysql-module-amxx">the MySQL module</a> with a different tuple and an upsert rewrite.</p>