12 पॉइंट द्वारा GN⁺ 2024-12-01 | 2 टिप्पणियां | WhatsApp पर शेयर करें
  • "अगर Rust में डेटा को सुरक्षित रूप से स्थायी रूप से store किया जा सके, complex queries आसानी से लिखी जा सकें, और SQL की एक भी पंक्ति लिखनी न पड़े तो कैसा होगा?"
    • Rust-query इसी को संभव बनाने के लिए विकसित की गई एक library है

Rust और डेटाबेस

  • Rust की मौजूदा database libraries में compile-time guarantees की कमी है, या उनका उपयोग झंझटभरा है और वे SQL जितनी intuitive नहीं हैं
  • डेटाबेस collision-avoidance software बनाने और atomic transactions को support करने में महत्वपूर्ण भूमिका निभाते हैं
  • SQL डेटाबेस के साथ interaction के लिए standard protocol है, लेकिन यह computer द्वारा generate किए जाने के लिए अधिक उपयुक्त है और मनुष्यों द्वारा सीधे लिखने के लिए अक्षम है

Rust-query परिचय

  • rust-query Rust के type system के साथ गहराई से integrated database query library है
  • इसे इस तरह design किया गया है कि Rust में native की तरह database operations किए जा सकें

मुख्य फीचर्स और design decisions

  • explicit table alias: table join के बाद उस table को दर्शाने वाला dummy object प्रदान करता है (let user = User::join(rows);)
  • Null safety: query के optional values को Rust के Option type से handle किया जाता है
  • intuitive aggregate functions: GROUP BY के बिना row-level intuitive aggregation support
  • type-safe foreign key traversal: foreign key के आधार पर implicit join आसानी से किया जा सकता है (track.album().artist().name())
  • type-safe unique lookup: किसी विशेष unique constraint वाले row को lookup करना (Option<Rating> return)
  • multi-version schema: declarative तरीके से सभी schema version differences की जांच संभव
  • type-safe migration: arbitrary Rust code का उपयोग करके rows को process किया जा सकता है
  • type-safe unique conflict handling: unique constraint conflict होने पर specific error type return
  • transaction lifetime से बंधे row references: row reference केवल तब तक valid हैं जब तक row मौजूद है
  • encapsulated typed row ID: row number API के बाहर expose नहीं किया जाता

क्वेरी और डेटा insertion

schema definition

#[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::*;  
  • schema को Rust के enum syntax का उपयोग करके define किया जाता है
  • foreign key constraints किसी दूसरे table name को column type के रूप में निर्दिष्ट करके बनाए जाते हैं
  • #[unique] attribute का उपयोग करके unique constraint जोड़ा जाता है
  • #[schema] macro definition का analysis करके v0 module generate करता है

डेटा insertion

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");  
}  
  • insertion operation नई insert की गई row का reference return करती है
  • unique constraint वाले table में insert करते समय try_insert का उपयोग आवश्यक है
  • try_insert conflict होने पर specific error type return करता है

डेटा query

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 query में current row set को दर्शाता है
  • aggregate का उपयोग करके aggregation operation किया जाता है
  • results को tuple या struct के vector के रूप में collect किया जा सकता है

schema evolution और migration

  • नया schema version बनाते समय #[version] attribute का उपयोग किया जाता है

नया schema version जोड़ना

#[schema]  
#[version(0..=1)]  
enum Schema {  
    User {  
        name: String,  
        #[version(1..)]  
        email: String,  
    },  
    // ... 나머지 스키마 ...  
}  
use v1::*;  

डेटा migration

  • migration को पुराने और नए schema दोनों के लिए type-check किया जाता है
  • row data को arbitrary Rust code से process किया जा सकता है (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")),  
        })  
    }),  
});  

समापन

  • rust-query Rust में relational database के साथ interaction का एक नया approach पेश करता है:
    • compile-time checks
    • Rust के साथ composable queries
    • type checking के माध्यम से schema evolution support
  • फिलहाल यह SQLite को एकमात्र backend के रूप में उपयोग करता है और experimental applications के विकास के लिए उपयुक्त है
  • GitHub issues के जरिए feedback का स्वागत है

2 टिप्पणियां

 
halfenif 2024-12-02

| यह ऐसी चीज़ है जिसे कंप्यूटर द्वारा जनरेट करना अधिक उपयुक्त है, और इंसानों के लिए सीधे लिखना अक्षम है
कोरिया में ही मौजूद, 100 से अधिक डेवलपर्स लगाए जाने वाले 'next-generation' प्रोजेक्ट करने की स्थिति से देखें तो।

काफ़ी दिलचस्प है।

असल में, लगाए जाने वाले ज़्यादातर डेवलपर्स SQL विशेषज्ञ ही होते हैं, है न?

 
GN⁺ 2024-12-01
Hacker News राय
  • application-defined schema को लेकर चिंता यह है कि इसे गलत system द्वारा verify किया जाता है। database ही schema का authoritative source है, और बाकी सभी application layers उसी के आधार पर assumptions बनाती हैं। Rust का SQLx database types के आधार पर structs generate करके compile time पर verify करता है, लेकिन यह production database के समान types की गारंटी नहीं देता। अगर आप local Postgres v15 पर query design करें और production में Postgres v12 चला रहे हों, तो runtime error हो सकता है। application-defined schema गलत sense of safety देता है और engineers पर अतिरिक्त काम डालता है.

  • SQL परफेक्ट नहीं है, लेकिन इसके कुछ फायदे हैं। ज़्यादातर लोग basic SQL जानते हैं, और PostgreSQL जैसे databases की documentation SQL में लिखी होती है। external tools भी SQL का इस्तेमाल करते हैं, और query बदलने पर महंगे compile step की ज़रूरत नहीं पड़ती। SQLx parameters को type-check करता है और database को खुद query verify करने देता है, जिससे compile time बढ़ाने वाली type system समस्याओं से बचा जा सकता है। नए databases में बेहतर query language जीत सकती है, लेकिन मौजूदा SQL databases में SQLx बेहतर विकल्प है.

  • इस राय से असहमति है कि SQL कंप्यूटर को लिखना चाहिए। SQL एक high-level language है, और Python या Rust से भी ज्यादा high-level है। SQL को पढ़ने और इस्तेमाल करने में आसान बनाने के लिए design किया गया है, और compile होने पर यह कई procedures में बदलता है। SQL web development का bottleneck point है, और state mutation यहीं होती है। SQL high-level language होने की वजह से optimize करना मुश्किल है। SQL technical debt है, लेकिन ज्यादा उपयुक्त API develop करने की तुलना में SQL का इस्तेमाल करना 10 गुना ज्यादा efficient है.

  • Rust में typesafe-db-access की exploration देखकर खुशी है। मौजूदा libraries compile-time guarantees नहीं देतीं, और SQL की तरह verbose या awkward लगती हैं। diesel compile-time guarantees देता है। ORM बनाम non-ORM बहस में typesafe query builder को prefer किया जाता है, और diesel इसी category में आता है। Rust-query का झुकाव full ORM की तरफ लगता है.

  • schema और data types को जोड़ने वाला approach दिलचस्प लगता है। example में Schema enum का न होना intuitive नहीं है। अगर इसे macro के अंदर define किया जाता, तो यह ज्यादा स्पष्ट होता.

  • library API में actual row numbers expose न होना confusing है। web server में data के साथ row ID pass किया जा सके, ताकि frontend उसे दूसरी requests में refer और modify कर सके.

  • इस राय से आंशिक सहमति है कि SQL कंप्यूटर को लिखना चाहिए, लेकिन SQL code generators के लिए लिखने के हिसाब से सबसे सुविधाजनक language नहीं है। साधारण plan optimization भी query layout को पूरी तरह बदल सकता है। Google का SQL pipe proposal थोड़ा बेहतर है, लेकिन उसमें नई query language वाली समस्याएँ अब भी हैं.

  • SeaQuery इस्तेमाल किया है, लेकिन advanced queries generate करने के लिए documentation पर्याप्त नहीं लगती। strongly-typed queries development process को धीमा कर सकती हैं, इसलिए पारंपरिक prepared statements और value binding पर वापस जाने पर विचार किया जा रहा है.

  • individual row level manipulation के जरिए migration चलाना बहुत धीमा हो सकता है। उदाहरण के लिए, 1 अरब rows वाली table में सामान्य update statement को एक घंटे तक लग सकता है। row-by-row updates में इससे भी ज्यादा समय लगेगा.