Skip to main content
sqlx هي مجموعة أدوات SQL غير متزامنة مكتوبة بالكامل بلغة Rust، تتضمن مجمّع اتصالات مدمجًا ودعمًا لعمليات الترحيل (migrations). ستُنشئ في هذا الدليل جدول todos عبر عملية ترحيل باستخدام sqlx-cli، ثم تُنفّذ استعلامات الإنشاء والقراءة والتحديث والحذف من خلال خادم axum صغير. ويستخدم كل اتصال بروتوكول TLS مع تحقق كامل من الشهادة.

المتطلبات الأساسية

  • Rust الإصدار 1.94 أو أحدث. اختُبر هذا الدليل باستخدام Rust 1.98.1، وsqlx 0.9.0، وsqlx-cli 0.9.0، وaxum 0.8.9.
  • حساب ClickHouse Cloud
  • psql، لإنشاء قاعدة البيانات. ويمكنك بدلًا من ذلك تنفيذ عبارة CREATE DATABASE في وحدة تحكم SQL.

إنشاء خدمة ClickHouse Managed Postgres

في وحدة تحكم ClickHouse Cloud، انقر على New service ثم اختر Postgres. سيصبح المثيل جاهزًا في غضون دقائق قليلة. راجع دليل البدء السريع للاطلاع على شرح مفصّل للخطوات.

الحصول على بيانات الاتصال

انقر على Connect في الشريط الجانبي الأيسر لخدمتك. أبقِ الخيار Directly محددًا وفعّل Use SSL. سينتهي عنوان URL للاتصال في علامة التبويب url الآن بـ sslmode=verify-full&sslrootcert=<service-name>-ca-certificate.pem، وسيكون المنفذ 5432. انقر على Download CA certificate. يمكنك أيضًا تنزيل الشهادة لاحقًا من Settings → CA Certificate. هذه الشهادة فريدة لمثيلك، ما يتيح للمشغّل استخدامها للتحقق من أنه يتصل بخادمك فعلًا. يتصل هذا الدليل مباشرةً بـ Postgres على المنفذ 5432، سواء من التطبيق أو من sqlx-cli. فخادم axum عملية تعمل لفترات طويلة، ويدير PgPool مجمّع الاتصالات الخاص به، لذا لا حاجة إلى وسيط تجميع اتصالات إضافي أمام Postgres. ويجب أن تستخدم عمليات الترحيل الاتصال المباشر دائمًا. إذا كنت تشغّل مثيلات عديدة من التطبيق، فراجع استخدام PgBouncer.

إعداد المشروع

أنشئ مشروعًا وأضف إليه sqlx مع مشغّل Postgres وبيئة تشغيل Tokio والواجهة الخلفية rustls الخاصة بـ TLS، إلى جانب axum وبعض حزم crate المساعدة:
ثبّت sqlx-cli باستخدام الواجهة الخلفية نفسها لـ TLS:
استخدم rustls بدلاً من native-tls على macOSلا تقبل ClickHouse Managed Postgres سوى TLS 1.3. وعلى macOS، تعتمد الواجهة الخلفية native-tls على مكتبة Secure Transport الخاصة بالنظام، وهي لا تدعم TLS 1.3. ويستخدم الأمر cargo install sqlx-cli دون أي خيارات إضافية native-tls، فيفشل بعدها كل أمر بالخطأ error occurred while attempting to establish a TLS connection: One or more parameters passed to a function were not valid. (أو bad protocol version). وينطبق ذلك أيضاً على أي تطبيق مبني باستخدام الميزة tls-native-tls. أما على Linux، فيعتمد native-tls على OpenSSL وينجح في الاتصال، لكنه لا يحمّل سوى الشهادة الأولى من ca-certificate.pem. في المقابل، يدعم rustls بروتوكول TLS 1.3 على جميع المنصات ويحمّل جميع الشهادات الموجودة في الملف.
هل تضيفه إلى تطبيق قائم؟تخطَّ الأمر cargo new. وفي مشروعك، شغّل أمرَي cargo add وcargo install الواردين أعلاه، ثم انتقل إلى تهيئة الاتصال. وإذا كان التطبيق يستخدم sqlx مع SQLite:
  • أزل sqlite من ميزات sqlx في Cargo.toml، واستبدل SqlitePool بـ PgPool.
  • غيّر العناصر النائبة ? إلى $1 و$2 وهكذا.
  • أعد كتابة عمليات الترحيل بصيغة Postgres، مثل استخدام bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY بدلاً من INTEGER PRIMARY KEY AUTOINCREMENT.

تهيئة الاتصال

انقل شهادة CA التي نزّلتها إلى دليل المشروع، وغيّر اسمها إلى ca-certificate.pem. أنشئ قاعدة بيانات للتطبيق. استبدل <PASSWORD> والمضيف بالقيم الظاهرة في نافذة Connect:
أنشئ ملف .env في جذر المشروع، أو حدّث قيمة DATABASE_URL في ملفك الحالي، بحيث تشير إلى قاعدة البيانات guide_rust. يقرأ كلٌّ من التطبيق وsqlx-cli قيمة DATABASE_URL من هذا الملف:
.env
يقرأ sqlx المعاملين sslmode وsslrootcert من URL. ومع verify-full، يتحقق من أن شهادة الخادم موقّعة من الـ CA الخاصة بالمثيل لديك، ومن تطابقها مع اسم المضيف. ويُحدَّد مسار sslrootcert نسبةً إلى الدليل الذي تشغّل منه الأوامر. احرص على إبقاء الملف .env خارج نظام التحكم في الإصدارات، وإذا احتوت كلمة المرور على محارف مثل @ أو / أو #، فرمّزها بترميز النسبة المئوية (percent-encoding).
اضبط دائمًا sslmode=verify-fullعند عدم تحديد sslmode، يستخدم sqlx القيمة prefer، أي أنه يشفّر الاتصال لكنه يقبل أي شهادة، حتى لو ضُبط sslrootcert. أما مع verify-full، فإن غياب شهادة CA أو عدم صحتها يؤدي إلى فشل الاتصال بالخطأ UnknownIssuer، وإذا كان المضيف لا يطابق الشهادة، كأن يكون عنوان IP مثلًا، فيفشل الاتصال بالخطأ NotValidForNameContext (certificate not valid for name في sqlx-cli). كما يؤدي تحديد مسار خاطئ لـ sslrootcert إلى الفشل بالخطأ No such file or directory.

إنشاء الجدول باستخدام عملية ترحيل

أنشئ ملف ترحيل:
افتح الملف الجديد في دليل migrations وأضف إليه تعريف الجدول:
migrations/<timestamp>_create_todos.sql
طبّقه:
يسجّل الأمر sqlx migrate run عمليات الترحيل المطبّقة في جدول _sqlx_migrations، ولا ينفّذ في الاستدعاءات اللاحقة سوى عمليات الترحيل الجديدة. ثبّت الدليل migrations مع الشيفرة الخاصة بك. وإن أردت بدلاً من ذلك تطبيق عمليات الترحيل عند بدء تشغيل التطبيق، فاستدعِ sqlx::migrate!().run(&pool).await? داخل main، شريطة أن يستخدم مجمّع الاتصالات الاتصال المباشر.

بناء واجهة برمجة التطبيقات

استبدل محتوى src/main.rs بخادم axum صغير ينفّذ استعلامًا واحدًا لكل مسار. أما إذا كان لديك تطبيق قائم، فأنشئ مجمّع الاتصالات بالطريقة نفسها المتّبعة في main، ثم أضف البنية Todo (struct) والمعالجات والمسارات إلى الموجّه (router) الخاص بك:
src/main.rs
تُمرَّر القيم دائمًا كمعلمات ($1) باستخدام .bind، ولا تُدرَج أبدًا ضمن string الخاص بـ SQL. ويُحضَّر كل string فريد من SQL مرة واحدة لكل اتصال، ثم يُحفظ في ذاكرة تخزين مؤقت للعبارات تتسع لـ 100 عبارة، ويتولى sqlx إغلاق العبارات عند إخراجها منها.
استعلامات يُتحقَّق منها في وقت التصريفيستخدم هذا الدليل الدالة query_as التي تتحقق من SQL عند تنفيذه، ولذلك لا يحتاج الأمر cargo build إلى قاعدة بيانات. أما الماكرو query! وquery_as! فيتحققان من SQL ومن أنواع الأعمدة مقابل قاعدة البيانات في وقت التصريف. ويقرأ كلاهما DATABASE_URL من الملف .env أثناء الإصدار، لذا احرص على إبقائه مضبوطاً على الاتصال المباشر. وللإصدار دون قاعدة بيانات، كما في بيئة CI مثلاً، شغّل cargo sqlx prepare، ثم ثبّت الدليل .sqlx المُنشأ، واضبط SQLX_OFFLINE=true.

التشغيل والتحقق

شغّل الخادم:
السطر الأول مصدره pg_stat_ssl، وهو يؤكد أن الـ session مشفّرة. في طرفية ثانية، أنشئ مهمتين، ثم علّم الأولى بأنها مكتملة، واحذف الثانية:
يُعيد كل طلب الصف المتأثر. ويقتصر دور الخيار -w '\n' على إضافة سطر جديد بعد كل استجابة:
افتح http://localhost:3117/todos في متصفحك لعرض قائمة المهام المتبقية:
لعرض البيانات في وحدة التحكم، افتح وحدة تحكم SQL من الشريط الجانبي الأيسر لخدمتك، ثم وسّع guide_rust ثم public، وانقر على جدول todos. أما الجدول _sqlx_migrations المجاور له فيحتفظ بسجل عمليات الترحيل. ويحتوي جدول todos على الصف المتبقي:

استخدام PgBouncer

لتوصيل التطبيق عبر PgBouncer المضمَّن، اختر via PgBouncer في نافذة Connect واستخدم المنفذ 6432 في متغير DATABASE_URL الخاص بالتطبيق. ويمكن استخدام شهادة CA نفسها في هذه الحالة أيضًا. فمثلًا، أبقِ ملف .env مضبوطًا على الاتصال المباشر لاستخدامه مع sqlx-cli، واضبط المتغير عند تشغيل التطبيق:
عند الاتصال عبر PgBouncer، يعرض pg_stat_ssl اتصال PgBouncer نفسه بـ Postgres، ولذلك يطبع الفحص القيمة none. ومع ذلك، يظل اتصال تطبيقك بـ PgBouncer خاضعًا للتحقق، إذ تفشل شهادة CA غير الصحيحة بالخطأ UnknownIssuer بالطريقة نفسها. يعمل PgBouncer في وضع تجميع المعاملات، لذا قد تُنفَّذ كل معاملة على اتصال Postgres مختلف. وعند استخدام sqlx:
  • الاتصال يتطلب .extra_float_digits(None)، كما في main.rs. إذ يرسل sqlx افتراضيًا extra_float_digits بوصفها معلمة بدء تشغيل، فيرفض PgBouncer الاتصال بالخطأ unsupported startup parameter: extra_float_digits. وللسبب نفسه، يفشل PgConnectOptions::options بالخطأ unsupported startup parameter in options.
  • العبارات المُحضَّرة تعمل مع الإعدادات الافتراضية. فعندما يُخرج sqlx عبارات من ذاكرة التخزين المؤقت الخاصة به، يحرّرها عبر رسالة Close على مستوى البروتوكول، وهي رسالة يعالجها PgBouncer حتى داخل المعاملات. لا تضبط statement_cache_capacity(0)، فهذا غير ضروري، كما أن sqlx يظل عند تعطيل ذاكرة التخزين المؤقت يُحضّر عبارات مُسمّاة دون أن يغلقها أبدًا.
  • لا تستخدم .persistent(false) مع الاستعلامات التي تُنفَّذ خارج معاملة. ففي هذه الحالة يُحضّر sqlx عبارة غير مُسمّاة ثم ينفّذها في رحلة ذهاب وإياب ثانية، وقد يوجّهها PgBouncer إلى اتصال Postgres مختلف. وعندئذٍ قد يفشل الاستعلام بالخطأ unnamed prepared statement does not exist، أو يُنفّذ دون أي تنبيه عبارةً تخص عميلًا آخر فيُعيد نتائج خاطئة.
  • إعدادات الـ session المضبوطة باستخدام SET، بما في ذلك داخل خطاف after_connect الخاص بمجمّع الاتصالات، لا تبقى مرتبطة باتصالك، وقد تتسرّب إلى عملاء آخرين. لذا استخدم بدلًا منها SET LOCAL داخل معاملة.
  • عمليات الترحيل يجب أن تستخدم الاتصال المباشر. فلا يمكن لـ sqlx-cli ولا لماكرو query! الاتصال عبر PgBouncer، لأنها ترسل extra_float_digits دائمًا. كما يحصل sqlx::migrate! على قفل استشاري على مستوى الـ session (pg_advisory_lock)، وعند المرور عبر PgBouncer قد يصل أمر فكّ القفل إلى اتصال Postgres مختلف. فيبقى القفل عندئذٍ محتجزًا على أحد الاتصالات المُجمَّعة، وتتعلّق عمليات الترحيل اللاحقة، حتى تلك التي تجري عبر الاتصال المباشر، إلى أن يُغلق PgBouncer ذلك الاتصال.

الخطوات التالية