تخيل سلة إعادة تدوير وضعت عليها بوضوح ملصقًا يقول "للزجاجات البلاستيكية فقط"، ولكن عندما يلقي شخص ما قطعة ورق داخلها، فإنها تقبلها بصمت دون كلمة احتجاج واحدة؟
هذه هي التجربة المثيرة للقشعريرة التي يمر بها المطورون عندما يقابلون نظام أنواع SQLite لأول مرة.
إذا كنت معتادًا على أسلوب موظف الجمارك الصارم في PostgreSQL (حيث يتم رفض الأنواع غير الصحيحة مباشرة من الدخول)، فإن عفوية وسهولة SQLite قد تجعلك تشك في حياتك.
والأكثر رعبًا من ذلك، عندما تريد تعديل بنية جدول، ستخبرك قاعدة البيانات بالتالي:
"لا يمكن تعديل الجدول مباشرة. يرجى بناء منزل جديد، ونقل الأثاث إليه، ثم تفجير المنزل القديم."
تحت الغطاء، تمتلك SQLite 5 فئات تخزين فقط
بغض النظر عن أسماء الأنواع الفخمة التي تعلن عنها في CREATE TABLE مثل (VARCHAR(255)، BIGINT، DECIMAL)، فإن SQLite تحت الغطاء لا تتعرف إلا على فئات التخزين الخمس التالية:
| فئة التخزين | الوصف |
|---|---|
| NULL | قيمة فارغة |
| INTEGER | عدد صحيح (يشغل تلقائيًا من 1 إلى 8 بايت اعتمادًا على حجم القيمة) |
| REAL | رقم عشري عائم (ثابت 8 بايت) |
| TEXT | سلسلة نصية (ترميز UTF-8 افتراضي) |
| BLOB | كائن ثنائي كبير (يتم تخزينه تمامًا كما تم إدخاله) |
الأنواع التي تحددها في الأعمدة هي مجرد "توصية" لـ SQLite وليست "قواعد إلزامية".
يطلق على هذا اسم "Type Affinity" (أفيونيتي النوع). ستحاول
SQLiteتحويل بياناتك إلى النوع الموصى به، ولكن إذا لم تتمكن من ذلك، فستقوم بإدخال البيانات الأصلية على أي حال دون إظهار أي خطأ.
يمكنك الإعلان عن عمود age INTEGER في SQLite ثم إدخال السلسلة النصية 'ثمانية عشر عامًا للأبد'؛ وسوف تقبله بكل سرور.
ثلاثة فخاخ أنواع هي الأسهل في الوقوع بها
الفخ 1: غياب نوع Boolean الأصلي
لا تحتوي SQLite على نوع boolean أصلي. يمكن تمثيل قيمتي True و False بالأعداد الصحيحة 1 و 0 فقط.
عندما تسترجع البيانات من SQLite باستخدام Node.js، ستحصل على الرقم 1 أو 0 وليس true أو false.
إذا قمت مباشرة بإجراء فحص مثل if (user.is_admin === true)، فلن يكون صحيحًا أبدًا.
الفخ 2: غياب نوع Date/Time
لا تحتوي SQLite على نوع للتاريخ/الوقت. لا يمكنك تخزين الوقت إلا بالطرق التالية:
| طريقة التخزين | مثال | الميزات والعيوب |
|---|---|---|
| TEXT (سلسلة ISO-8601) | '2026-05-19T18:00:00Z' |
الأكثر توصية، وضوح عالٍ، وتحويل سلس عند الانتقال إلى PostgreSQL مستقبلاً |
| INTEGER (طابع Unix الزمني) | 1747656000 |
يشغل مساحة صغيرة، ولكنه غير مقروء للبشر |
لا تخزن التواريخ مطلقًا بتنسيقات مخصصة عشوائية مثل 19/5/2026 6:00 مساءً، وإلا فسيكون ترحيل البيانات مستقبلاً كارثة حقيقية.
الفخ 3: إدخال سلسلة نصية في عمود INTEGER لن يؤدي لحدوث خطأ
في PostgreSQL يؤدي إدخال سلسلة نصية في عمود INTEGER إلى حدوث خطأ على الفور. ولكن SQLite ستحاول التحويل بصمت فقط، وعند الفشل، ستقبل البيانات كما هي.
هذا يعني أن البيانات الملوثة قد تتسلل بصمت إلى قاعدة بياناتك حتى يأتي يوم يتوقف فيه برنامجك فجأة بسبب تلقي نوع غير متوقع، وعندها فقط تكتشف المشكلة.
البرمجة الدفاعية: مواجهة قاعدة بيانات عفوية بموقف صارم
في مواجهة عفوية وسهولة SQLite، يجب عليك بناء آليات دفاع صارمة في تطوير تطبيقات Node.js:
| مستوى الدفاع | الأداة | الدور |
|---|---|---|
| حارس وقت الترجمة | TypeScript | التقاط الأنواع غير الصحيحة في مرحلة كتابة الكود |
| التحقق من مدخلات API | Zod | التحقق من صحة البيانات الواردة بصرامة (ضمان أن يكون age رقمًا دائمًا) |
| تحويل الأنواع الداخلي | Prisma / Drizzle ORM | معالجة فروق الأنواع تلقائيًا بين SQLite و PostgreSQL |
إن نقل "حارس بوابة التحقق من البيانات" من مستوى قاعدة البيانات إلى مستوى تطبيق
Node.jsهو استراتيجية رئيسية للاستفادة من سرعة تطوير SQLite مع ضمان قابلية التوسع مستقبلاً.
عند استخدام ORM، طالما أنك تعلن عن type: 'boolean' في كودك، فإن الـ ORM يقوم تلقائيًا بتحويله إلى 1/0 عند الحفظ في SQLite ويحوله مجددًا إلى true/false عند القراءة، مما يخفي فروق الأنواع الأساسية تمامًا.
ALTER TABLE غير مكتمل: ما يمكن وما لا يمكن تعديله
دعم SQLite لتعديل بنية الجداول محدود للغاية:
| العملية | مدعومة |
|---|---|
إضافة عمود (ADD COLUMN) |
نعم |
إعادة تسمية عمود (RENAME COLUMN) |
نعم |
حذف عمود (DROP COLUMN) |
نعم (في الإصدارات الحديثة) |
إعادة تسمية جدول (RENAME TO) |
نعم |
| تغيير نوع العمود | لا |
إضافة/إزالة قيود UNIQUE و NOT NULL |
لا |
| تعديل المفتاح الأساسي | لا |
| تعديل المفتاح الأجنبي | لا |
بمجرد أن تحتاج إلى إجراء أي من التعديلات "غير المدعومة"، تتطلب منك
SQLiteتنفيذ استراتيجية "إعادة الإنشاء والنقل".
أربع خطوات لإعادة الإنشاء والنقل: طريقة ترقية جداول SQLite
نظرًا لعدم إمكانية التعديل المباشر، فإن الطريقة القياسية الموصى بها في الوثائق الرسمية هي بناء منزل جديد، ونقل الأثاث، وتفجير المنزل القديم، وتعليق اللوحة الجديدة:
| الخطوة | الإجراء | الوصف |
|---|---|---|
| 1 | إنشاء جدول جديد | إنشاء CREATE TABLE users_new (...) بالبنية الصحيحة |
| 2 | نسخ البيانات | INSERT INTO users_new SELECT ... FROM users |
| 3 | حذف الجدول القديم | DROP TABLE users |
| 4 | إعادة تسمية الجدول | ALTER TABLE users_new RENAME TO users |
يجب تنفيذ هذه الخطوات الأربع دفعة واحدة ومن دون انقطاع؛ أي انقطاع في الطاقة أو تعطل للتطبيق في منتصف الطريق سيؤدي إلى فقدان البيانات.
ضمان عدم فقدان البيانات أثناء الترقية: خطان للدفاع عن الأمان
خط الدفاع 1: الدفاع المادي، نسخ الملف مباشرة
تعتبر SQLite في جوهرها مجرد ملف واحد. قبل تنفيذ أي تغييرات في المخطط، يكفي ببساطة نسخ ملف .db ليكون نسخة احتياطية.
const fs = require('fs');
fs.copyFileSync('my_project.db', 'my_project_backup.db');
إذا سارت الأمور بشكل سيء، فإن استبدال الملف يستعيد كل شيء في لمحة بصر. هذه ميزة لا توفرها قواعد البيانات الكبيرة الأخرى.
خط الدفاع 2: تغليف المعاملة (Transaction), آلة الزمن لقاعدة البيانات
قم بتغليف جميع خطوات الترحيل داخل معاملة Transaction واحدة؛ وإذا فشلت أي خطوة، فسيتم التراجع تلقائيًا (Rollback) عن العملية بأكملها وكأن شيئًا لم يكن.
const Database = require('better-sqlite3');
const db = new Database('my_project.db');
const migrateData = db.transaction(() => {
db.prepare(`
CREATE TABLE users_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL DEFAULT 18
)
`).run();
db.prepare(`
INSERT INTO users_new (id, name, age)
SELECT id, name, COALESCE(age, 18) FROM users
`).run();
db.prepare('DROP TABLE users').run();
db.prepare('ALTER TABLE users_new RENAME TO users').run();
});
try {
migrateData();
console.log('اكتملت ترقية الجدول');
} catch (error) {
console.error('فشلت الترقية، تم استعادة البيانات بأمان:', error.message);
}
"ملف النسخ الاحتياطي + ربط المعاملة" هو الوسادة الهوائية لترحيل قاعدة بياناتك.
التحكم في عفوية SQLite بموقف صارم
تحكم في عفوية وسهولة
SQLiteباستخدام بنية صارمة لطبقة التطبيق للاستمتاع بميزة سرعة التطوير الفائقة مع تجنب الديون التقنية مستقبلاً.
يعتبر نظام الأنواع في SQLite مرنًا وعفويًا للغاية، كما أن ALTER TABLE لديه العديد من القيود.
ولكن طالما أنك تقوم بتطبيق فحص أنواع TypeScript + تحقق Zod + تجريد الـ ORM في جهة Node.js بجدية، وتدمج ذلك مع استراتيجية الأمان المتمثلة في النسخ الاحتياطي المادي + المعاملات الصارمة، فيمكنك الاستمتاع بكفاءة التطوير الرائعة التي توفرها SQLite مع تمهيد الطريق للانتقال السلس إلى PostgreSQL مستقبلاً.