Drift (سابقًا Moor) هو ORM تفاعلي لـ Flutter و Dart، مبني فوق SQLite مع DSL خاص به للاستعلامات. بخلاف ORM التقليدية، يقوم Drift بتجميع استعلامات Dart إلى SQL في مرحلة البناء، مما يلغي أخطاء وقت التنفيذ. وفقًا لـ Drift Docs, 2024، Drift يولّد حتى 40% أكثر من الكود مقارنة باستعلامات SQL اليدوية، ولكنه يلغي تمامًا كتابة SQL اليدوية، مستبدلاً إياها بنحو Dart آمن من حيث الأنواع.
النقاط الرئيسية
Drift هو ORM لـ Dart و Flutter، المعروف سابقًا باسم Moor. طوره سايمون بيندر في عام 2019 ومنذ ذلك حتى مر بعدة إصدارات رئيسية. يقوم Drift بتجميع استعلامات Dart إلى SQL في مرحلة البناء باستخدام drift_dev و build_runner، مما يوفر أمانًا كاملاً للأنواع ويلغي أخطاء نحو SQL في وقت التنفيذ.
بخلاف Floor، يستخدم Drift DSL (لغة خاصة بالمجال) خاصة به لبناء الاستعلامات — يكتب المطور بـ Dart، ويقوم المولّد بترجمته إلى SQL. يسمح ذلك لبيئة التطوير بتحقق النحو، والإكمال التلقائي لحقول الجداول، وإعادة هيكلة نموذج البيانات دون الخوف من كسر الاستعلامات.
وفقًا لـ Drift (2024)، تُستخدم المكتبة في أكثر من 8000 مشروع Flutter. تدعم جميع المنصات الشائعة: Android عبر sqflite، iOS عبر sqflite، web عبر sqlite3 WASM، سطح المكتب عبر مشغل sqlite3 الأصلي.
Moor تم إعادة تسميته إلى Drift في الإصدار 2.0 (2022). السبب هو تعارض الأسماء مع مشاريع أخرى والرغبة في الابتعاد عن الكود القديم. بقيت API متوافقة: للترحيل، فقط استبدل import من moor إلى drift وحدّث التبعيات.
Drift يوفر: استعلامات Stream مضمنة مع تحديث تلقائي عند تغير البيانات، دعم المعاملات مع التراجع، استعلامات SQL مخصصة عبر rawQuery، نمط DAO لتغليف المنطق، ترحيلات متعددة المنصات والتكامل مع Riverpod و BLoC عبر حزم drift_riverpod و drift_bloc.
Drift يستخدم توليد الكود في مرحلة التجميع. يصف المطور الجداول من خلال توضيحات @DataClass أو فئات Dart تمتد Table. ينشئ المولّد فئات مساعدة: Companion (للحقول القابلة للعدم أثناء الإدراج/التحديث)، DriftDatabase (نقطة الدخول) وتطبيقات DAO.
Drift لا ينفذ استعلامات SQLite مباشرة. بدلاً من ذلك، يكتب المطور بـ Dart: select(tasks).where(tasks.priority.greaterThan(3)).build(). يقوم المولّد بترجمة ذلك إلى SQL، وفي وقت التنفيذ يرسل Drift ببساطة استعلام SQL الجاهز إلى SQLite. يجمع ذلك بين راحة نحو Dart وأداء SQL الأصلي.
Drift يدعم وضعين للاستعلامات: DSL (موصى به) و SQL الخام. استعلامات DSL أكثر أمانًا في الكتابة — يتحقق المجمع من أسماء الحقول، الأنواع والتوافق. SQL الخام ضروري للاستعلامات المعقدة غير المغطاة بـ DSL: دوال النوافذ، CTE المتكررة، إمتدادات SQLite المحددة.
Drift يقدم طريقتين لكتابة الاستعلامات: Dart DSL (أصلي) و SQL الخام (للحالات المعقدة). DSL أفضل لـ 90% من السيناريوهات: أكثر أمانًا، أقرأ ويدعم إعادة الهيكلة. يتم استخدام SQL الخام فقط عندما لا يغطي DSL البناء المطلوب.
| الجانب | Drift DSL | SQL الخام في Drift |
|---|---|---|
| أمان الأنواع | كامل (تجميع) | لا (تنفيذ) |
| الإكمال التلقائي | نعم (IDE) | فقط في ملفات sql |
| إعادة الهيكلة | تلقائية | بحث يدوي عن السلاسل |
| JOIN المعقدة | مدعومة | حرية كاملة |
| دوال النوافذ | محدود | دعم كامل |
| التفاعلية | مضمنة (Stream) | عبر .watch() |
Drift DSL هو الطريقة الرئيسية للعمل. يغطي SELECT، INSERT، UPDATE، DELETE، WHERE، ORDER BY، LIMIT، JOIN والتجميع. لجميع استعلامات CRUD النموذجية، استخدم DSL: أقصر، أكثر أمانًا ويحدث Stream تلقائيًا عند التغييرات.
SQL الخام في Drift ضروري لـ: دوال SQLite المخصصة (FTS5، JSON1)، الاستعلامات الفرعية المعقدة مع EXISTS، INSERT OR REPLACE، UPDATE الشامل مع CASE، بالإضافة إلى الاستعلامات حيث يكون الأداء حاسمًا ولا يولِّد DSL خطة تنفيذ مثلى. يمكن كتابة SQL الخام في ملفات .sql مع دعم التحديد عبر drift_dev.
Drift يستخدم فئات تمتد Table أو توضيح @DataClass. أدناه مثال كامل لنموذج Task مع استعلامات عبر DSL، SQL خام وتحديث تفاعلي. بعد تشغيل build_runner، تكون جميع الفئات المولّدة جاهزة.
الفئة Tasks تمتد Table وتحدد الأعمدة. كل عمود هو تعبير من نوع Column<T>. المعلمات: withDefault() تعين قيمة افتراضية، autoIncrement() تعين الزيادة التلقائية. قاعدة البيانات هي فئة مجردة تمتد $DriftDatabase.
class Tasks extends Table {
IntColumn get id => integer().autoIncrement();
TextColumn get title => text().withDefault(const Constant(''))();
BoolColumn get isCompleted => boolean().withDefault(const Constant(false))();
IntColumn get priority => integer().withDefault(const Constant(0))();
}
@DriftDatabase(tables: [Tasks])
class AppDatabase extends $AppDatabase {
AppDatabase(QueryExecutor e) : super(e);
}
Drift يولِّد الطرق into(tasks).insert()، select(tasks)، update(tasks) و delete(tasks) للجداول. تعود جميع العمليات Future — العمل مع SQLite غير متزامن. لتتبع التغييرات، استخدم .watch() بدلاً من .get().
// إدراج
await into(tasks).insert(TasksCompanion.insert(
title: Value('شراء البقال'),
priority: Value(3),
));
// قراءة بمرشح
final highPriority = await (select(tasks)
..where((t) => t.priority.greaterThan(2))
..orderBy([(t) => OrderingTerm(expression: t.priority, mode: OrderingMode.desc)]))
.get();
// مراقبة تفاعلية
select(tasks).watch().listen((tasksList) {
// tasksList — List, يتحدث عند كل تغيير في الجدول
updateUi(tasksList);
});
للاستعلامات المعقدة، يسمح Drift بكتابة SQL خام مع الحفاظ على التحديد. تأخذ طريقة customSelect سلسلة استعلام وتعيد نتيجة محددة عبر مولِّد الكود. يجمع هذا النهج بين مرونة SQL وأمان أنواع Drift.
final result = await customSelect(
'SELECT title, COUNT(*) as cnt FROM tasks GROUP BY title',
readsFrom: { tasks },
).get();
for (final row in result) {
print('${row.readString("title")}: ${row.readInt("cnt")}');
}
Drift يدعم كلاً من الترحيلات التلقائية (للتغييرات البسيطة) واليدوية (للتحويلات المعقدة). تعين إصدار قاعدة البيانات في منشئ AppDatabase. عندما لا يتطابق الإصدار، يطبق Drift جميع الترحيلات العلقة بالتسلسل.
لإضافة عمود بقيمة افتراضية، يمكن Drift توليد ترحيل تلقائيًا عبر MigrationStrategy. إذا كان التغيير لا يؤثر على البيانات الموجودة (إضافة حقل قابل للعدم)، يمكنك استخدام beforeOpen مع التحقق من الإصدار وتنفيذ ALTER TABLE.
للتغييرات المعقدة (إعادة تسمية جدول، دمج البيانات، تغيير نوع عمود)، يتطلب Drift ترحيل SQL يدويًا. تُحدد الترحيلات عبر معلم migrations في فئة قاعدة البيانات. كل ترحيل هو كائن بأرقام from/to واستعلامات SQL.
Drift يدعم التشغيل في وضع الاختبار عبر NativeDatabase.memory(). تُنشأ قاعدة البيانات في الذاكرة من الصفر قبل كل اختبار وتدمر بعده. للتقليد، استخدم حزمة mocktail مع QueryExecutor مقلّد. يوفر Drift أيضًا DatabaseTestHelper لاختبارات التكامل مع التحقق من الترحيلات والاستعلامات.
Drift يدعم DAO (كائن الوصول إلى البيانات) من خلال فئات مجردة مع توضيح @DriftAccessor. DAO يغلّف الاستعلامات لجدول أو جداول متعددة ويمكن اختباره بشكل منفصل عن قاعدة البيانات. بخلاف الاستعلامات المباشرة عبر Database، يسمح DAO بإعادة استخدام منطق الاستعلامات بين أجزاء مختلفة من التطبيق ويبسّط اختبارات الوحدة.
@DriftDatabase(tables: [Tasks])
class AppDatabase extends $AppDatabase {
AppDatabase(QueryExecutor e) : super(e) {
migrations.add(Migration(1, 2, (m) async {
await m.addColumn(tasks, tasks.dueDate);
await m.createIndex(tasks.idxPriority);
}));
}
}
الأسئلة الشائعة
Drift يستخدم DSL خاص به بدلاً من سلاسل SQL، ما يوفر أمانًا كاملاً للأنواع وإكمالاً تلقائيًا في IDE. Floor يستخدم سلاسل SQL في توضيح @Query. Drift كذلك يدعم المزيد من المنصات (بما في ذلك web) ولديه تفاعلية مضمنة عبر Stream، بينما في Floor يجب تصريح Stream يدويًا.
نعم، Drift يدعم الترحيلات مع الحفاظ على البيانات. لإضافة أعمدة، استخدم addColumn في Migration. للتحويلات المعقدة (إعادة تسمية، دمج)، اكتب SQL خام داخل الترحيل. إذا لم يتم تحديد ترحيل، يقوم Drift بإعادة إنشاء قاعدة البيانات مع فقدان البيانات عندما لا يتطابق المخطط.
Drift يتطلب توليد الكود عبر build_runner و drift_dev. بدون توليد، من المستحيل إنشاء استعلامات محددة. ولكن للمشاريع الصغيرة، يدعم Drift sqlparser — كتابة يدوية لملفات SQL مع تحديد تلقائي، ولكن هذا لا يزال يتطلب خطوة توليد.
للتكامل مع Riverpod، استخدم حزمة drift_riverpod. توفر موفرات لـ Database، DAO واستعلامات Stream. مثال: final tasksProvider = databaseProvider.select((db) => db.select(db.tasks).watch()) — تعاد بناء واجهة المستخدم تلقائيًا عند تغيير البيانات.
Drift ليس لديه تشفير مضمن، ولكنه يدعم توصيل مكتبات sqlite3 مخصصة مع SEE (إمتداد تشفير SQLite). للمنصات المحمولة، استخدم sqflite_sqlcipher كـ QueryExecutor — يعمل Drift مع أي تطبيق SQLite عبر QueryExecutor المجرد.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا