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
| Placeholder | Arti |
|---|---|
%s | String, di-escape dan diberi kutip |
%e | String, di-escape tanpa kutip (untuk LIKE ... '%%e%%') |
%d | Integer |
%f | Float |
knight
let q = mysql_format(DB, "SELECT * FROM accounts WHERE name = '%s'", "CJ");
5. Membaca hasil
| Fungsi | Hasil |
|---|---|
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.