12 poin oleh GN⁺ 2024-12-01 | 2 komentar | Bagikan ke WhatsApp
  • "Bagaimana jika Anda bisa menyimpan data secara persisten dengan aman di Rust, menulis query kompleks dengan mudah, dan bahkan tanpa menulis satu baris SQL pun?"
    • Rust-query adalah library yang dikembangkan untuk mewujudkan hal tersebut

Rust dan database

  • Library database yang ada di Rust saat ini kurang memiliki jaminan pada waktu kompilasi, atau sulit digunakan dan tidak seintuitif SQL
  • Database berperan penting dalam membangun perangkat lunak yang tahan terhadap konflik dan mendukung transaksi atomik
  • SQL adalah protokol standar untuk berinteraksi dengan database, tetapi lebih cocok dihasilkan oleh komputer dan tidak efisien jika ditulis langsung oleh manusia

Pengantar Rust-query

  • rust-query adalah library query database yang terintegrasi secara mendalam dengan sistem tipe Rust
  • Dirancang agar operasi database bisa dilakukan secara natural di Rust

Fitur utama dan keputusan desain

  • Alias tabel eksplisit: menyediakan objek dummy yang merepresentasikan tabel setelah join tabel (let user = User::join(rows);)
  • Keamanan null: nilai opsional dalam query ditangani dengan tipe Option milik Rust
  • Fungsi agregasi yang intuitif: mendukung agregasi yang intuitif per baris tanpa GROUP BY
  • Penelusuran foreign key yang type-safe: memudahkan join implisit berbasis foreign key (track.album().artist().name())
  • Pencarian unik yang type-safe: mengambil baris dengan constraint unik tertentu (mengembalikan Option<Rating>)
  • Skema multi-versi: memungkinkan pengecekan semua perbedaan versi skema secara deklaratif
  • Migrasi yang type-safe: pemrosesan baris dimungkinkan menggunakan kode Rust arbitrer
  • Penanganan konflik unik yang type-safe: mengembalikan tipe error tertentu saat terjadi konflik constraint unik
  • Referensi baris yang terikat pada lifetime transaksi: referensi baris hanya valid selama baris tersebut ada
  • ID baris bertipe yang teren kapsulasi: nomor baris tidak diekspos ke luar API

Query dan penyisipan data

Definisi skema

#[schema]  
enum Schema {  
    User {  
        name: String,  
    },  
    Story {  
        author: User,  
        title: String,  
        content: String,  
    },  
    #[unique(user, story)]  
    Rating {  
        user: User,  
        story: Story,  
        stars: i64,  
    },  
}  
use v0::*;  
  • Skema didefinisikan menggunakan sintaks enum Rust
  • Constraint foreign key dibuat dengan menetapkan nama tabel lain sebagai tipe kolom
  • Tambahkan constraint unik menggunakan atribut #[unique]
  • Makro #[schema] menganalisis definisi dan menghasilkan modul v0

Penyisipan data

fn insert_data(txn: &mut TransactionMut<Schema>) {  
    let alice = txn.insert(User { name: "alice" });  
    let bob = txn.insert(User { name: "bob" });  
  
    let dream = txn.insert(Story {  
        author: alice,  
        title: "My crazy dream",  
        content: "A dinosaur and a bird...",  
    });  
  
    let rating = txn.try_insert(Rating {  
        user: bob,  
        story: dream,  
        stars: 5,  
    }).expect("no rating for this user and story exists yet");  
}  
  • Operasi insert mengembalikan referensi ke baris yang baru disisipkan
  • Untuk insert ke tabel dengan constraint unik, perlu menggunakan try_insert
  • try_insert mengembalikan tipe error tertentu jika terjadi konflik

Query data

fn query_data(txn: &Transaction<Schema>) {  
    let results = txn.query(|rows| {  
        let story = Story::join(rows);  
        let avg_rating = aggregate(|rows| {  
            let rating = Rating::join(rows);  
            rows.filter_on(rating.story(), &story);  
            rows.avg(rating.stars().as_float())  
        });  
        rows.into_vec((story.title(), avg_rating))  
    });  
  
    for (title, avg_rating) in results {  
        println!("story '{title}' has avg rating {avg_rating:?}");  
    }  
}  
  • rows merepresentasikan himpunan baris saat ini dalam query
  • Operasi agregasi dilakukan dengan aggregate
  • Hasil bisa dikumpulkan sebagai vektor tuple atau struct

Evolusi skema dan migrasi

  • Saat membuat versi skema baru, gunakan atribut #[version]

Menambahkan versi skema baru

#[schema]  
#[version(0..=1)]  
enum Schema {  
    User {  
        name: String,  
        #[version(1..)]  
        email: String,  
    },  
    // ... sisa skema ...  
}  
use v1::*;  

Migrasi data

  • Migrasi diperiksa tipenya terhadap skema lama dan baru
  • Data baris dapat diproses menggunakan kode Rust arbitrer (dengan map_dummy)
let m = m.migrate(v1::update::Schema {  
    user: Box::new(|old_user| {  
        Alter::new(v1::update::UserMigration {  
            email: old_user  
                .name()  
                .map_dummy(|name| format!("{name}@example.com")),  
        })  
    }),  
});  

Penutup

  • rust-query menghadirkan pendekatan baru untuk berinteraksi dengan database relasional di Rust:
    • pemeriksaan pada waktu kompilasi
    • query yang dapat dikomposisikan dengan Rust
    • dukungan evolusi skema melalui pemeriksaan tipe
  • Saat ini menggunakan SQLite sebagai satu-satunya backend dan cocok untuk pengembangan aplikasi eksperimental
  • Umpan balik disambut melalui issue di GitHub

2 komentar

 
halfenif 2024-12-02

| Ini cocok untuk dihasilkan oleh komputer dan tidak efisien jika harus ditulis langsung oleh manusia.
Dari sudut pandang orang yang pernah mengerjakan proyek "generasi berikutnya" yang hanya ada di Korea, dengan melibatkan lebih dari 100 developer.

Sangat menarik.

Sebenarnya, sebagian besar developer yang dilibatkan itu adalah para ahli SQL, bukan?

 
GN⁺ 2024-12-01
Komentar Hacker News
  • Kekhawatiran terhadap skema yang didefinisikan aplikasi adalah bahwa skema itu diverifikasi oleh sistem yang salah. Basis data adalah otoritas atas skema, dan semua lapisan aplikasi lain membuat asumsi berdasarkan itu. SQLx di Rust menghasilkan struct berdasarkan tipe basis data dan memverifikasinya saat waktu kompilasi, tetapi tidak menjamin tipe yang sama dengan basis data produksi. Jika kueri dirancang di Postgres v15 lokal dan produksi menjalankan Postgres v12, error runtime bisa terjadi. Skema yang didefinisikan aplikasi memberi rasa aman yang keliru dan membebankan pekerjaan tambahan kepada engineer.

  • SQL memang tidak sempurna, tetapi punya beberapa kelebihan. Kebanyakan orang mengetahui SQL dasar, dan dokumentasi basis data seperti PostgreSQL ditulis dalam SQL. Alat eksternal juga menggunakan SQL, dan perubahan kueri tidak memerlukan tahap kompilasi yang mahal. SQLx menghindari masalah sistem tipe yang memperpanjang waktu kompilasi dengan melakukan type check pada parameter dan membiarkan basis data sendiri memverifikasi kueri. Pada basis data baru, bahasa kueri yang lebih baik mungkin bisa menang, tetapi pada basis data SQL yang sudah ada, SQLx adalah pilihan yang lebih baik.

  • Ada pendapat yang menolak gagasan bahwa SQL seharusnya ditulis oleh komputer. SQL adalah bahasa tingkat tinggi, bahkan lebih tinggi daripada Python atau Rust. SQL dirancang agar mudah dibaca dan digunakan, lalu saat dikompilasi diubah menjadi berbagai prosedur. SQL berada di titik bottleneck pengembangan web, tempat transformasi state terjadi. Karena SQL adalah bahasa tingkat tinggi, optimasinya sulit. SQL adalah utang teknis, tetapi menggunakan SQL 10 kali lebih efisien daripada mengembangkan API yang lebih tepat.

  • Ada pendapat yang menyambut baik eksplorasi terhadap typesafe-db-access di Rust. Library yang ada tidak memberi jaminan saat waktu kompilasi, dan terasa bertele-tele atau canggung seperti SQL. diesel memberikan jaminan saat waktu kompilasi. Dalam perdebatan ORM vs non-ORM, ada preferensi pada query builder yang type-safe, dan diesel termasuk dalam kategori ini. Rust-query tampaknya akan condong ke sisi ORM penuh.

  • Ada pendapat bahwa pendekatan yang menghubungkan skema dan tipe data itu menarik. Fakta bahwa tidak ada enum Schema dalam contoh terasa kurang intuitif. Jika didefinisikan di dalam macro, itu akan lebih jelas.

  • Membingungkan bahwa nomor baris aktual tidak diekspos dalam API library. Di server web, ID baris perlu diteruskan bersama data agar frontend bisa merujuk dan memodifikasi data itu lewat permintaan lain.

  • Ada yang sebagian setuju bahwa SQL seharusnya ditulis oleh komputer, tetapi SQL bukan bahasa yang paling nyaman untuk ditulis oleh code generator. Optimasi rencana yang sederhana pun bisa sepenuhnya mengubah tata letak kueri. Usulan SQL pipe dari Google sedikit memperbaiki hal ini, tetapi tetap memiliki masalah bahasa kueri baru.

  • Ada pendapat bahwa meskipun telah menggunakan SeaQuery, dokumentasinya tidak cukup untuk membuat kueri tingkat lanjut. Kueri dengan tipe yang kuat bisa memperlambat proses pengembangan, sehingga sedang mempertimbangkan untuk kembali ke prepared statement dan binding nilai yang biasa.

  • Migrasi melalui manipulasi pada level baris individual bisa sangat lambat untuk dijalankan. Sebagai contoh, pada tabel dengan 1 miliar baris, pernyataan update biasa bisa memakan waktu hingga satu jam. Update per baris akan memakan waktu lebih lama.