12 puan yazan GN⁺ 2024-12-01 | 2 yorum | WhatsApp'ta paylaş
  • "Rust'ta verileri güvenli biçimde kalıcı olarak saklayabilseniz, karmaşık sorguları kolayca yazabilseniz ve tek satır SQL yazmak zorunda kalmasanız nasıl olurdu?"
    • Rust-query bunu gerçekleştirmek için geliştirilmiş bir kütüphanedir

Rust ve veritabanı

  • Rust'ın mevcut veritabanı kütüphaneleri ya derleme zamanında yeterli güvence sunmuyor ya da kullanımı zahmetli ve SQL kadar sezgisel değil
  • Veritabanları, çakışmayı önleyen yazılımlar kurmada ve atomik transaction desteği sağlamada önemli rol oynar
  • SQL, veritabanıyla etkileşim kurmak için standart protokoldür; ancak bilgisayarın üretmesine daha uygundur ve insanların doğrudan yazması verimsizdir

Rust-query'ye giriş

  • rust-query, Rust'ın tip sistemiyle derin biçimde entegre bir veritabanı sorgu kütüphanesidir
  • Rust'ta veritabanı işlemlerini yerelmiş gibi gerçekleştirebilmek için tasarlanmıştır

Başlıca özellikler ve tasarım kararları

  • Açık tablo takma adları: Tablo birleştirmesinden sonra ilgili tabloyu temsil eden dummy nesnesi sağlar (let user = User::join(rows);)
  • Null güvenliği: Sorgudaki isteğe bağlı değerler Rust'ın Option tipiyle ele alınır
  • Sezgisel toplulaştırma fonksiyonları: GROUP BY olmadan satır düzeyinde sezgisel toplulaştırma desteği
  • Tip güvenli foreign key gezinmesi: Foreign key'e dayalı örtük join işlemlerini kolaylaştırır (track.album().artist().name())
  • Tip güvenli benzersiz sorgulama: Belirli bir unique constraint'e sahip satırları sorgular (Option<Rating> döndürür)
  • Çok sürümlü şema: Bildirimsel biçimde tüm şema sürümü farkları doğrulanabilir
  • Tip güvenli migration: Satırlar keyfi Rust koduyla işlenebilir
  • Tip güvenli unique çakışma işleme: Unique constraint çakışmasında belirli bir hata tipi döndürür
  • Transaction ömrüne bağlı satır referansları: Satır referansları yalnızca satır mevcut olduğu sürece geçerlidir
  • Kapsüllenmiş tipli satır ID'leri: Satır numaraları API dışına açılmaz

Sorgular ve veri ekleme

Şema tanımı

#[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::*;  
  • Şema, Rust'ın enum sözdizimi kullanılarak tanımlanır
  • Foreign key kısıtları, sütun tipi olarak başka tablo adları belirtilerek oluşturulur
  • Unique constraint eklemek için #[unique] niteliği kullanılır
  • #[schema] makrosu tanımı analiz ederek v0 modülünü oluşturur

Veri ekleme

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");  
}  
  • Ekleme işlemi, yeni eklenen satırın referansını döndürür
  • Unique constraint bulunan tablolara ekleme yaparken try_insert kullanılması gerekir
  • try_insert, çakışma durumunda belirli bir hata tipi döndürür

Veri sorgulama

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, sorgudaki mevcut satır kümesini temsil eder
  • Toplulaştırma işlemleri aggregate kullanılarak gerçekleştirilir
  • Sonuçlar, tuple veya struct vektörü olarak toplanabilir

Şema evrimi ve migration

  • Yeni bir şema sürümü oluştururken #[version] niteliği kullanılır

Yeni şema sürümü ekleme

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

Veri migration'ı

  • Migration'lar hem eski hem de yeni şema için tip denetiminden geçer
  • Satır verileri keyfi Rust koduyla işlenebilir (map_dummy kullanılarak)
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")),  
        })  
    }),  
});  

Sonuç

  • rust-query, Rust'ta ilişkisel veritabanlarıyla etkileşim için yeni bir yaklaşım sunuyor:
    • derleme zamanı denetimi
    • Rust ile birleştirilebilir sorgular
    • tip denetimi üzerinden şema evrimi desteği
  • Şu anda tek backend olarak SQLite kullanıyor ve deneysel uygulama geliştirme için uygun
  • Geri bildirimler GitHub issue'ları üzerinden memnuniyetle karşılanıyor

2 yorum

 
halfenif 2024-12-02

| Bilgisayarın üretmesi daha uygun ve insanların doğrudan yazması verimsiz.
Kore'ye özgü, 100'den fazla geliştiricinin投入 edildiği "yeni nesil" projeleri yapan biri olarak.

Oldukça ilginç.

Aslında görevlendirilen geliştiricilerin çoğu SQL uzmanı sayılır.

 
GN⁺ 2024-12-01
Hacker News görüşleri
  • Uygulama tanımlı şemalara yönelik kaygı, bunların yanlış sistem tarafından doğrulanmasıdır. Şemanın otoritesi veritabanıdır ve diğer tüm uygulama katmanları buna dayanarak varsayımlarda bulunur. Rust’ın SQLx’i veritabanı tiplerine dayalı struct’lar üretip bunları derleme zamanında doğrular, ancak bunun üretim veritabanıyla aynı tipleri garanti ettiği anlamına gelmez. Sorguyu yerelde Postgres v15 üzerinde tasarlayıp üretimde Postgres v12 çalıştırırsanız çalışma zamanında hata alabilirsiniz. Uygulama tanımlı şemalar yanlış bir güven hissi verir ve mühendislere ek iş yükü bindirir.

  • SQL kusursuz değil, ancak bazı avantajları var. Çoğu kişi temel SQL bilir ve PostgreSQL gibi veritabanlarının dokümantasyonu SQL ile yazılmıştır. Harici araçlar da SQL kullanır ve sorgu değiştiğinde pahalı bir derleme aşaması gerekmez. SQLx, parametreleri tip kontrolünden geçirip sorgunun veritabanının kendisi tarafından doğrulanmasını sağlayarak derleme sürelerini artıran tip sistemi sorunlarından kaçınır. Yeni veritabanlarında daha iyi bir sorgu dili öne çıkabilir, ancak mevcut SQL veritabanlarında SQLx daha iyi bir seçimdir.

  • SQL’i bilgisayarların yazması gerektiği görüşüne karşı çıkanlar var. SQL, Python ya da Rust’tan daha yüksek seviyeli bir dildir. Okunması ve kullanılması kolay olacak şekilde tasarlanmıştır ve derleme sırasında çeşitli prosedürlere dönüştürülür. SQL, web geliştirmedeki darboğaz noktasında yer alır; durum değişikliklerinin gerçekleştiği yer burasıdır. SQL yüksek seviyeli bir dil olduğu için optimize edilmesi zordur. SQL bir teknik borçtur; ama SQL kullanmak, daha uygun bir API geliştirmekten 10 kat daha verimlidir.

  • Rust’ta type-safe veritabanı erişimi üzerine yapılan keşifler memnuniyet verici bulunuyor. Mevcut kütüphaneler derleme zamanı garantileri sunmuyor ve SQL kadar ayrıntılı ya da hantal kalıyor. diesel ise derleme zamanı garantileri sağlıyor. ORM ve non-ORM tartışmasında type-safe sorgu oluşturucular tercih ediliyor ve diesel bu kategoriye giriyor. Rust-query ise tam ORM tarafına kayıyor gibi görünüyor.

  • Şema ile veri tiplerini bağlayan yaklaşım ilgi çekici bulunuyor. Örnekte Schema enum’unun yer almaması sezgisel gelmiyor. Bunun makro içinde tanımlanması daha açık olurdu.

  • Kütüphane API’sinde gerçek satır numaralarının görünmemesi kafa karıştırıcı. Bir web sunucusunda, frontend’in başka isteklerle veriyi referans alıp değiştirebilmesi için satır ID’sinin veriyle birlikte iletilmesi gerekir.

  • SQL’i bilgisayarların yazması gerektiği görüşüne kısmen katılanlar var, ancak SQL kod üreticilerinin yazması için en elverişli dil değil. Basit bir plan optimizasyonu bile sorgunun düzenini tamamen değiştirebilir. Google’ın SQL pipe önerisi bunu biraz iyileştiriyor, ancak yeni bir sorgu dilinin sorunlarını hâlâ taşıyor.

  • SeaQuery kullanılmış, ancak gelişmiş sorgular üretmek için dokümantasyonun yeterli olmadığı düşünülüyor. Güçlü tipli sorgular geliştirme sürecini yavaşlatabildiği için, klasik prepared statement ve value binding yaklaşımına geri dönmek değerlendiriliyor.

  • Tek tek satır düzeyinde yapılan migration’lar çok yavaş çalışabilir. Örneğin 1 milyar satırlık bir tabloda sıradan bir UPDATE ifadesi bir saate kadar sürebilir. Satır bazında güncelleme ise daha da uzun sürecektir.