Rust dan SQLx 2026: Query yang Diverifikasi Saat Kompilasi dan Pertanyaan Wawancara
Pelajari cara SQLx 0.9 memverifikasi query SQL saat kompilasi, konfigurasi sqlx.toml, dan persiapan wawancara teknis untuk posisi backend Rust.

SQLx 0.9 mengubah cara pengembang Rust berinteraksi dengan database. Versi terbaru ini menangkap kesalahan SQL saat kompilasi, bukan saat aplikasi berjalan di produksi. Dirilis pada Mei 2026, SQLx 0.9 memperkenalkan sistem konfigurasi baru melalui sqlx.toml, fleksibilitas runtime, dan keamanan query yang lebih ketat melalui trait SqlSafeStr.
Berbeda dengan ORM yang menghasilkan SQL dari struct Rust, SQLx memverifikasi query SQL yang ditulis manual terhadap database aktif saat kompilasi. Kesalahan ketik pada nama kolom akan menggagalkan proses build, bukan menyebabkan error di produksi.
Verifikasi Query Saat Kompilasi di SQLx 0.9
Macro query! memeriksa setiap statement SQL terhadap skema database selama cargo build. Fitur ini mendeteksi kesalahan ketik, ketidakcocokan tipe, dan kolom yang hilang sebelum kode dijalankan.
use sqlx::{FromRow, PgPool};
#[derive(FromRow)]
pub struct User {
pub id: i32,
pub email: String,
pub created_at: chrono::DateTime<chrono::Utc>,
}
pub async fn get_user_by_email(pool: &PgPool, email: &str) -> Result<Option<User>, sqlx::Error> {
// Diverifikasi saat kompilasi: nama kolom, tipe, dan struktur return
sqlx::query_as!(
User,
r#"
SELECT id, email, created_at
FROM users
WHERE email = $1
"#,
email
)
.fetch_optional(pool)
.await
}Macro query_as! memetakan baris hasil langsung ke struct User. Jika tabel users tidak memiliki kolom created_at, kompilasi akan gagal dengan pesan error yang jelas menunjuk ke baris yang bermasalah.
Sistem Konfigurasi sqlx.toml
SQLx 0.9 memperkenalkan file sqlx.toml yang memusatkan konfigurasi database, override tipe, dan pengaturan multi-database. Pendekatan ini menggantikan variabel environment yang tersebar dan menyederhanakan pipeline CI.
# sqlx.toml
[common]
database_url_var = "DATABASE_URL"
[macros]
default_type_override.uuid = "uuid::Uuid"
default_type_override.timestamptz = "chrono::DateTime<chrono::Utc>"
[sqlite]
extensions = ["uuid", "crypto"]Override tipe menghilangkan casting berulang. Setiap kolom UUID secara otomatis dipetakan ke uuid::Uuid, dan setiap timestamptz ke chrono::DateTime tanpa anotasi per-query.
SqlSafeStr: Mencegah SQL Injection pada Level Tipe
SQLx 0.9 memperkuat keamanan query dengan mewajibkan SqlSafeStr untuk parameter string. Secara default, hanya &'static str yang mengimplementasikan trait ini, memblokir string dinamis yang berpotensi membawa payload injection.
use sqlx::{AssertSqlSafe, PgPool};
// Nama tabel statis: langsung dikompilasi
const TABLE: &str = "products";
pub async fn search_products(
pool: &PgPool,
user_query: &str,
) -> Result<Vec<Product>, sqlx::Error> {
// Aman: $1 adalah nilai parameter, bukan SQL yang diinterpolasi
sqlx::query_as!(
Product,
r#"SELECT * FROM products WHERE name ILIKE '%' || $1 || '%'"#,
user_query
)
.fetch_all(pool)
.await
}
// Tabel dinamis dari config: bungkus dengan AssertSqlSafe setelah validasi
pub async fn count_table(pool: &PgPool, table: &str) -> Result<i64, sqlx::Error> {
// Validasi nama tabel terhadap allowlist sebelum assert safety
let allowed = ["users", "products", "orders"];
if !allowed.contains(&table) {
return Err(sqlx::Error::Configuration("Invalid table".into()));
}
let query = format!("SELECT COUNT(*) as count FROM {}", table);
let row: (i64,) = sqlx::query_as(&AssertSqlSafe(query))
.fetch_one(pool)
.await?;
Ok(row.0)
}Input pengguna melewati parameterized query dengan placeholder $1. SQL dinamis dari sumber terpercaya memerlukan pembungkusan eksplisit dengan AssertSqlSafe setelah validasi.
Siap menguasai wawancara Rust Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Migrasi Database dengan sqlx-cli
Tool sqlx-cli mengelola migrasi skema dengan file SQL berversi. Migrasi dijalankan secara berurutan, dan tool ini melacak migrasi yang sudah diterapkan.
# Install CLI (0.9.x tidak lagi memerlukan --locked)
cargo install sqlx-cli --features postgres
# Buat migrasi baru
sqlx migrate add create_users_table
# Jalankan migrasi pending
sqlx migrate run
# Cek status migrasi
sqlx migrate infoSetiap migrasi membuat sepasang file di direktori migrations/:
-- migrations/20260912120000_create_users_table.up.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users(email);-- migrations/20260912120000_create_users_table.down.sql
DROP TABLE IF EXISTS users;File up.sql dijalankan saat migrate run, dan down.sql saat migrate revert. Menyimpan kedua file memungkinkan rollback deployment yang gagal.
Mode Offline untuk Pipeline CI
Pemeriksaan saat kompilasi memerlukan koneksi database selama build. Untuk environment CI tanpa akses database, SQLx meng-cache metadata query di direktori .sqlx/.
# Generate cache query secara lokal (memerlukan DATABASE_URL)
cargo sqlx prepare
# Commit direktori .sqlx/
git add .sqlx/
git commit -m "Update SQLx query cache"Di CI, build menggunakan metadata yang di-cache:
# .github/workflows/ci.yml
jobs:
build:
runs-on: ubuntu-latest
env:
SQLX_OFFLINE: true
steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --releaseVariabel environment SQLX_OFFLINE=true memberitahu SQLx untuk menggunakan metadata yang di-cache alih-alih terhubung ke database. Pendekatan ini menjaga CI tetap cepat dan menghindari kredensial database di environment build.
Connection Pooling dengan PgPool
SQLx menyediakan connection pooling bawaan. PgPool mengelola kumpulan koneksi, menggunakannya kembali di seluruh request alih-alih membuka koneksi baru setiap kali.
use sqlx::postgres::PgPoolOptions;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
let pool = PgPoolOptions::new()
.max_connections(10)
.min_connections(2)
.acquire_timeout(Duration::from_secs(3))
.idle_timeout(Duration::from_secs(600))
.connect(&std::env::var("DATABASE_URL").expect("DATABASE_URL required"))
.await?;
// Pool adalah Clone dan Send, aman untuk dibagikan antar task
let app_state = AppState { db: pool };
// Mulai web server dengan app_state...
Ok(())
}Atur max_connections berdasarkan batas koneksi database dan jumlah instance aplikasi. Aturan umum: database_max_connections / number_of_instances.
Pertanyaan Wawancara: SQLx dan Akses Database di Rust
Wawancara teknis untuk posisi backend Rust sering menggali pemahaman tentang penanganan database. Pertanyaan-pertanyaan ini menguji pemahaman tentang jaminan compile-time SQLx dan pola async.
T: Bagaimana SQLx memverifikasi query saat kompilasi?
Macro query! terhubung ke database yang berjalan selama cargo build. Macro ini mem-parsing SQL, mengirimkannya ke database untuk perencanaan (tanpa eksekusi), dan memverifikasi bahwa nama kolom ada, tipe cocok, dan query valid secara sintaks. Query planner database yang melakukan validasi, sehingga edge case spesifik Postgres, MySQL, atau SQLite dapat terdeteksi.
T: Apa yang terjadi jika skema database berubah setelah kompilasi?
Binary yang dikompilasi berisi query plan dari waktu build. Jika skema berubah (kolom diganti nama, tipe diubah), query akan gagal saat runtime dengan error decode atau column-not-found. Tim memitigasi ini dengan menjalankan migrasi sebelum deployment dan rebuild setelah perubahan skema. Di produksi, blue-green deployment memastikan binary baru dan skema baru diluncurkan bersamaan.
T: Kapan memilih SQLx dibanding Diesel atau SeaORM?
SQLx cocok untuk proyek di mana pengembang menginginkan kontrol penuh atas SQL tanpa mempelajari DSL. Diesel menghasilkan SQL dari kode Rust dan mendeteksi perubahan skema saat kompilasi melalui file schema.rs. SeaORM menyediakan API gaya ActiveRecord dengan migrasi. SQLx paling cocok ketika tim menulis SQL kompleks (CTE, window function, fitur spesifik database) dan menginginkan pemeriksaan compile-time tanpa abstraksi.
T: Jelaskan tradeoff antara query! dan query_as!
Macro query! mengembalikan tipe record anonim dengan field yang cocok dengan kolom SELECT. Macro query_as! memetakan hasil ke struct bernama yang mengimplementasikan FromRow. Gunakan query! untuk query sekali pakai di mana mendefinisikan struct menambah overhead. Gunakan query_as! ketika tipe hasil muncul di banyak tempat atau perlu mengimplementasikan trait.
// query! mengembalikan record anonim
let row = sqlx::query!("SELECT id, name FROM users WHERE id = $1", user_id)
.fetch_one(pool)
.await?;
let name: String = row.name; // Akses dengan nama field
// query_as! memetakan ke struct
let user: User = sqlx::query_as!(User, "SELECT id, name FROM users WHERE id = $1", user_id)
.fetch_one(pool)
.await?;T: Bagaimana menangani transaksi di SQLx?
Mulai transaksi dengan pool.begin(), jalankan query pada objek transaksi, kemudian panggil commit() atau biarkan drop untuk rollback.
pub async fn create_order_with_items(
pool: &PgPool,
order: NewOrder,
items: Vec<OrderItem>,
) -> Result<i32, sqlx::Error> {
let mut tx = pool.begin().await?;
let order_id: (i32,) = sqlx::query_as(
"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id"
)
.bind(order.user_id)
.bind(order.total)
.fetch_one(&mut *tx)
.await?;
for item in items {
sqlx::query(
"INSERT INTO order_items (order_id, product_id, quantity) VALUES ($1, $2, $3)"
)
.bind(order_id.0)
.bind(item.product_id)
.bind(item.quantity)
.execute(&mut *tx)
.await?;
}
tx.commit().await?;
Ok(order_id.0)
}Jika query gagal, transaksi otomatis rollback ketika tx di-drop. Rollback eksplisit jarang diperlukan.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Pola Produksi untuk SQLx 0.9
Pola-pola ini menangani skenario produksi umum: menangani kolom nullable, tipe custom, dan operasi batch.
Kolom Nullable dan Tipe Option
#[derive(FromRow)]
pub struct UserProfile {
pub id: i32,
pub email: String,
pub avatar_url: Option<String>, // Kolom nullable dipetakan ke Option
pub bio: Option<String>,
}
pub async fn update_profile(
pool: &PgPool,
user_id: i32,
avatar_url: Option<&str>,
bio: Option<&str>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
UPDATE users
SET avatar_url = COALESCE($2, avatar_url),
bio = COALESCE($3, bio),
updated_at = NOW()
WHERE id = $1
"#,
user_id,
avatar_url,
bio
)
.execute(pool)
.await?;
Ok(())
}Batch Insert dengan UNNEST
Untuk bulk insert, UNNEST menghindari multiple round trip:
pub async fn insert_tags(pool: &PgPool, tags: &[String]) -> Result<Vec<i32>, sqlx::Error> {
let ids: Vec<(i32,)> = sqlx::query_as(
r#"
INSERT INTO tags (name)
SELECT * FROM UNNEST($1::text[])
ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
RETURNING id
"#
)
.bind(tags)
.fetch_all(pool)
.await?;
Ok(ids.into_iter().map(|(id,)| id).collect())
}Ringkasan SQLx di Rust
- Macro query! memverifikasi SQL saat kompilasi terhadap database aktif, mendeteksi kesalahan ketik dan ketidakcocokan tipe sebelum runtime
- SQLx 0.9 memperkenalkan sqlx.toml untuk override tipe, konfigurasi multi-database, dan loading ekstensi SQLite
- SqlSafeStr mencegah SQL injection dengan membatasi string query ke nilai statis kecuali dibungkus eksplisit dengan AssertSqlSafe
- Gunakan
cargo sqlx prepareuntuk meng-cache metadata query untuk build offline di CI - PgPool menangani connection pooling dengan limit, timeout, dan pengaturan idle yang dapat dikonfigurasi
- Migrasi disimpan dalam file SQL yang dikelola oleh sqlx-cli, dengan pasangan up/down untuk dukungan rollback
- Untuk persiapan wawancara Rust, latih penjelasan verifikasi compile-time, tradeoff query! vs query_as!, dan pola penanganan transaksi
Bisakah kamu menemukan bug di Rust?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 12 September 2026
Tag
Bagikan
Artikel terkait

Smart Pointer Rust Dijelaskan: Box, Rc, Arc, dan RefCell di 2026
Smart pointer Rust Box, Rc, Arc, dan RefCell dijelaskan dengan contoh 2026 yang dapat dikompilasi, tabel keputusan, dan pertanyaan wawancara yang umum.

Rust Traits dan Generics 2026: Trait Upcasting, AsyncFn dan Pola Tingkat Lanjut
Panduan lengkap Rust traits dan generics dengan fitur terbaru edisi 2024: trait upcasting, AsyncFn closures, RPITIT, serta pola tingkat lanjut yang diuji dalam wawancara nyata.

Memahami Ownership dan Borrowing di Rust: Panduan Lengkap untuk Developer
Pelajari konsep ownership, borrowing, dan lifetime di Rust secara mendalam. Artikel ini membahas aturan kepemilikan, referensi, serta pola-pola umum yang sering muncul dalam wawancara teknis.