Knight docs
EN ID
GitHub

MySQL

Hubungkan ke MySQL, jalankan query sinkron dan asinkron, baca hasil dengan cache_*.

Knight menyertakan driver MySQL. Cara pakainya sama seperti SQLite: buka koneksi, jalankan query, baca baris. Query asinkron mengembalikan lewat callback bernama.

Note
Drivernya github.com/go-sql-driver/mysql. Gunakan DSN standar, mis. user:pass@tcp(127.0.0.1:3306/dbname?parseTime=true.

1. Buka koneksi

knight
system::start() {
    let db = mysql_open("root:secret@tcp(127.0.0.1:3306/knight?parseTime=true");
    if (db == null) {
        Log("Koneksi MySQL gagal");
        return;
    }
    Log("Handle MySQL: {db}");

    // Buat tabel (sinkron aman untuk setup sekali jalan).
    mysql_tquery(db, "CREATE TABLE IF NOT EXISTS accounts (name VARCHAR(24) PRIMARY KEY, hash VARCHAR(255), score INT DEFAULT 0)", "onReady");
};

function onReady(result, err) {
    if (err != "") { Log("error setup: " + err); }
    else { Log("database siap"); }
};
Warning
mysql_open mengembalikan handle (integer), atau null bila gagal. Simpan di level modul agar tidak membuka koneksi untuk setiap query.

2. Jalankan query

mysql_tquery menjalankan query di worker thread dan memanggil fungsi bernama dengan (result, err) saat selesai.

knight
function onLoadScore(result, err) {
    if (err != "") {
        Log("query gagal: " + err);
        return;
    }
    let rows = cache_get_row_count(result);
    if (rows == 0) {
        Log("akun tidak ditemukan");
        return;
    }
    let score = cache_get_value_name_int(result, 0, "score");
    let name  = cache_get_value_name(result, 0, "name");
    Log("{name} punya skor {score}");
}

player::command("/load") {
    mysql_tquery(DB, "SELECT name, score FROM accounts WHERE name = 'CJ'", "onLoadScore");
};

3. Jalankan penulisan

Gunakan mysql_tquery untuk INSERT/UPDATE/DELETE juga. Callback tetap menerima (result, err); periksa err.

knight
function onSaved(result, err) {
    if (err != "") { Log("gagal simpan: " + err); return; }
    Log("tersimpan");
}

player::command("/save", int score) {
    let q = mysql_format(DB, "INSERT INTO accounts (name, score) VALUES ('%e', %d) ON DUPLICATE KEY UPDATE score = %d",
                          PlayerName(playerid), score, score);
    mysql_tquery(DB, q, "onSaved");
};
Warning
mysql_tquery dan mysql_pquery tidak mengikat argumen tambahan. Susun query dengan mysql_format dan placeholder agar input ter-escape.

4. Placeholder mysql_format

PlaceholderArti
%sString, di-escape dan diberi kutip
%eString, di-escape tanpa kutip (untuk LIKE ... '%%e%%')
%dInteger
%fFloat
knight
let q = mysql_format(DB, "SELECT * FROM accounts WHERE name = '%s'", "CJ");

5. Membaca hasil

FungsiHasil
cache_get_row_count(result)Jumlah baris
cache_get_field_count(result)Jumlah kolom
cache_get_value_name(result, row, "col")Nilai string
cache_get_value_name_int(result, row, "col")Nilai integer
cache_get_value_name_float(result, row, "col")Nilai float
cache_get_value_index_int(result, row, col)Integer berdasarkan indeks kolom
cache_insert_id()Insert id terakhir
cache_delete(result)Bebaskan hasil
knight
function onAll(result, err) {
    if (err != "") { return; }
    let n = cache_get_row_count(result);
    for (let i = 0; i < n; i += 1) {
        Log(cache_get_value_name(result, i, "name"));
    }
}
Tip
Iterasi baris dengan loop for biasa memakai cache_get_row_count(result). Selalu panggil cache_delete(result) setelah selesai.

6. Error

knight
let errno = mysql_errno(DB);
if (errno != 0) {
    Log("mysql error {errno}");
}

7. Tutup

knight
system::stop() {
    mysql_close(DB);
};

8. Connection pooling

Setiap handle menjaga pool kecil (8 koneksi terbuka, 4 idle). Pakai ulang satu handle, jangan buka koneksi baru untuk setiap query.

9. SQL injection

Warning
Jangan pernah menyusun SQL dari penggabungan input pengguna. Selalu gunakan mysql_format (%s/%e/%d/%f) atau placeholder ? pada query sinkron.