Rust وPostgreSQL: كتابة استعلامات فعالة بدون ألم

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

Rust وPostgreSQL: ثنائي يجعل كودك أسرع

\n\n

هل تساءلت يومًا لماذا ينتقل المزيد والمزيد من مطوري backend إلى Rust؟ الإجابة بسيطة: Rust يمنحك التحكم في الذاكرة والسرعة، وPostgreSQL يمنحك التحكم في البيانات. معًا يشكلان ثنائيًا قادرًا على تحمل مئات الآلاف من الاستعلامات في الثانية دون "تباطؤ" أو تسريبات.

\n\n

في هذا الدليل، سنشرح كيفية التوفيق بين Rust وPostgreSQL. ستتعلم كيفية إعداد الاتصال، تنفيذ الاستعلامات، تجنب حقن SQL، وبناء تطبيقات غير متزامنة. هيا بنا!

\n\n

لماذا Rust وPostgreSQL تحديدًا؟

\n\n

Rust ليس مجرد لغة عصرية. نظام الملكية (ownership) و borrow checker يضمنان عدم حصولك على null pointer أو مراجع معلقة. وPostgreSQL ليس مجرد قاعدة بيانات. إنها أداة قوية تدعم JSON، البحث النصي الكامل، والمعاملات المعقدة.

\n\n

عندما تدمج Rust وPostgreSQL، تحصل على:

\n
    \n
  • الأمان — لن يسمح لك Rust بإرسال SQL غير صحيح عن طريق الخطأ.
  • \n
  • الأداء — تعمل برامج التشغيل غير المتزامنة على مستوى لغة C.
  • \n
  • التحكم — أنت من يقرر متى وكيف تسحب البيانات.
  • \n
\n\n

تثبيت وإعداد البيئة

\n\n

قبل كتابة الكود، تأكد من تثبيت ما يلي:

\n
    \n
  • Rust (أحدث إصدار مستقر) — rustup update
  • \n
  • PostgreSQL (محليًا أو عبر Docker) — docker run --name pg -e POSTGRES_PASSWORD=pass -p 5432:5432 -d postgres
  • \n
  • مكتبة libpq-dev (لنظام Linux) أو ما يعادلها على Mac/Windows
  • \n
\n\n

أنشئ مشروعًا جديدًا:

\n\n
cargo new rust-postgres-guide\ncd rust-postgres-guide
\n\n

اختيار برنامج التشغيل: tokio-postgres مقابل sqlx

\n\n

هناك طريقتان رئيسيتان للعمل مع PostgreSQL في Rust:

\n
    \n
  • tokio-postgres — برنامج تشغيل غير متزامن منخفض المستوى. يمنحك تحكمًا كاملاً.
  • \n
  • sqlx — برنامج تشغيل غير متزامن عالي المستوى مع التحقق من الاستعلامات في وقت الترجمة.
  • \n
\n\n

للمشاريع المتقدمة، أوصي باستخدام sqlx — فهو أكثر ملاءمة وأمانًا. ولكن للتحكم الأقصى، استخدم tokio-postgres.

\n\n

أضف التبعيات إلى Cargo.toml:

\n\n
[dependencies]\ntokio = { version = "1", features = ["full"] }\nsqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres"] }\nserde = { version = "1", features = ["derive"] }\n
\n\n

الاتصال الأول: لقاء Rust وPostgreSQL

\n\n

لنكتب مثالًا بسيطًا يتصل بقاعدة البيانات ويعيد إصدار PostgreSQL.

\n\n
use sqlx::postgres::PgPoolOptions;\nuse std::error::Error;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn Error>> {\n    // سلسلة الاتصال: postgres://user:password@host:port/db\n    let database_url = "postgres://postgres:pass@localhost:5432/postgres";\n    \n    // إنشاء مجموعة اتصالات (reusable connections)\n    let pool = PgPoolOptions::new()\n        .max_connections(5)\n        .connect(database_url)\n        .await?;\n\n    // تنفيذ الاستعلام\n    let row: (String,) = sqlx::query_as("SELECT version()")\n        .fetch_one(&pool)\n        .await?;\n\n    println!("PostgreSQL version: {}", row.0);\n    Ok(())\n}
\n\n

شغّل الكود: cargo run. إذا كان كل شيء مضبوطًا بشكل صحيح، سترى إصدار PostgreSQL. لاحظ PgPoolOptions — مجموعة الاتصالات إلزامية للإنتاج. فهي تعيد استخدام الاتصالات ولا تنشئها من جديد لكل استعلام.

\n\n

إنشاء الجداول وعمليات CRUD

\n\n

الآن لنقم بإنشاء جدول users وتنفيذ العمليات الأساسية: الإدراج، القراءة، التحديث، الحذف.

\n\n

الترحيل: إنشاء الجدول

\n\n

في sqlx، الترحيلات هي ملفات SQL منفصلة. أنشئ مجلد migrations وملفًا باسم 20240101000000_create_users.sql بالمحتوى التالي:

\n\n
CREATE TABLE IF NOT EXISTS users (\n    id SERIAL PRIMARY KEY,\n    name VARCHAR(255) NOT NULL,\n    email VARCHAR(255) UNIQUE NOT NULL,\n    created_at TIMESTAMPTZ DEFAULT NOW()\n);
\n\n

ثم قم بتشغيل الترحيلات من الكود:

\n\n
sqlx::migrate!().run(&pool).await?;
\n\n

سيقوم sqlx بتتبع الترحيلات المنفذة وتشغيل الترحيلات الجديدة فقط.

\n\n

إدراج البيانات (Create)

\n\n
use sqlx::Row;\n\n#[derive(sqlx::FromRow)]\nstruct User {\n    id: i32,\n    name: String,\n    email: String,\n}\n\nasync fn create_user(pool: &PgPool, name: &str, email: &str) -> Result {\n    let user = sqlx::query_as::<_, User>(\n        "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email"\n    )\n    .bind(name)\n    .bind(email)\n    .fetch_one(pool)\n    .await?;\n    \n    Ok(user)\n}
\n\n

لاحظ استخدام $1 و $2 — هذه هي العناصر النائبة (placeholders) في PostgreSQL. يقوم sqlx تلقائيًا بالتحقق من تطابق الأنواع في وقت الترجمة.

\n\n

قراءة البيانات (Read)

\n\n
async fn get_user_by_email(pool: &PgPool, email: &str) -> Result, sqlx::Error> {\n    let user = sqlx::query_as::<_, User>(\n        "SELECT id, name, email FROM users WHERE email = $1"\n    )\n    .bind(email)\n    .fetch_optional(pool)\n    .await?;\n    \n    Ok(user)\n}
\n\n

استخدام fetch_optional يعيد None إذا لم يتم العثور على المستخدم، مما يمنع الأخطاء غير المتوقعة.

\n\n

تحديث البيانات (Update)

\n\n
async fn update_user_name(pool: &PgPool, id: i32, new_name: &str) -> Result {\n    let result = sqlx::query(\n        "UPDATE users SET name = $1 WHERE id = $2"\n    )\n    .bind(new_name)\n    .bind(id)\n    .execute(pool)\n    .await?;\n    \n    Ok(result.rows_affected())\n}
\n\n

تعيد rows_affected() عدد الصفوف التي تم تحديثها — مفيد للتحقق من نجاح العملية.

\n\n

حذف البيانات (Delete)

\n\n
async fn delete_user(pool: &PgPool, id: i32) -> Result {\n    let result = sqlx::query(\"DELETE FROM users WHERE id = $1\")\n        .bind(id)\n        .execute(pool)\n        .await?;\n    \n    Ok(result.rows_affected() > 0)\n}
\n\n

الاستعلامات غير المتزامنة: السرعة الحقيقية

\n\n

الميزة الرئيسية لـ Rust وPostgreSQL هي القدرة على تنفيذ آلاف الاستعلامات في وقت واحد دون حظر. يستخدم sqlx tokio لهذا الغرض.

\n\n

مثال على تنفيذ استعلامات متوازية:

\n\n
use futures::future::join_all;\n\nasync fn fetch_multiple_users(pool: &PgPool, ids: Vec) -> Result, sqlx::Error> {\n    let queries: Vec<_> = ids.into_iter().map(|id| {\n        sqlx::query_as::<_, User>(\"SELECT * FROM users WHERE id = $1\")\n            .bind(id)\n            .fetch_one(pool)\n    }).collect();\n    \n    let results = join_all(queries).await;\n    \n    // معالجة الأخطاء\n    results.into_iter().collect()\n}
\n\n

يستخدم هذا الكود join_all من مكتبة futures لتشغيل جميع الاستعلامات في وقت واحد. في الخلفية، يستخدم tokio تجمع خيوط (thread pool) لتنفيذها بالتوازي.

\n\n

الأمان: حماية من حقن SQL

\n\n

أحد أكبر مزايا sqlx هو التحقق من الاستعلامات في وقت الترجمة. إذا حاولت تمرير نوع خاطئ، سيرفض الكود الترجمة.

\n\n

مثال على استعلام آمن:

\n\n
// آمن — يتم التحقق من الأنواع في وقت الترجمة\nlet user = sqlx::query_as::<_, User>(\n    \"SELECT * FROM users WHERE email = $1\"\n)\n.bind(user_input)  // user_input: String\n.fetch_one(pool)\n.await?;\n\n// خطير — لا تفعل هذا أبدًا!\n// let query = format!(\"SELECT * FROM users WHERE email = '{user_input}'\");\n// sqlx::query(&query)...
\n\n

تذكر: لا تقم أبدًا بتنسيق الاستعلامات يدويًا. استخدم دائمًا العناصر النائبة ($1, $2) وطريقة .bind().

\n\n

تحسين الأداء: نصائح للمحترفين

\n\n
    \n
  1. استخدم تجمع الاتصالات (Connection Pool) — كما في المثال أعلاه. لا تنشئ اتصالاً جديدًا لكل استعلام.
  2. \n
  3. استخدم الفهارس (Indexes) — أضف فهارس للأعمدة التي تستخدمها في WHERE و JOIN.
  4. \n
  5. تجنب N+1 queries — استخدم JOIN أو استعلامات مجمعة بدلاً من استعلامات متعددة.
  6. \n
  7. استخدم الاستعلامات المُعدة (Prepared Statements) — يقوم sqlx بذلك تلقائيًا.
  8. \n
  9. راقب الأداء — استخدم EXPLAIN ANALYZE في PostgreSQL لتحليل الاستعلامات البطيئة.
  10. \n
\n\n

الخلاصة: Rust وPostgreSQL — مستقبل تطوير backend

\n\n

في هذا الدليل، تعلمنا كيفية إعداد الاتصال بين Rust وPostgreSQL، تنفيذ عمليات CRUD، العمل مع الاستعلامات غير المتزامنة، وحماية التطبيق من حقن SQL. يوفر Rust الأمان والسرعة، بينما يوفر PostgreSQL الموثوقية والمرونة.

\n\n

ابدأ اليوم — أنشئ مشروعًا جديدًا، أضف sqlx، واكتب أول استعلام لك. ستلاحظ الفرق في الأداء فورًا!

\n\n

هل لديك أسئلة؟ اكتبها في التعليقات. ولا تنسَ مشاركة هذا الدليل مع زملائك المطورين!

المدونات

توصيات الكتب