قاعدة البيانات
يستخدم FORGE PostgreSQL 15+ كمحرك قاعدة البيانات الوحيد، مستفيداً من ميزاته المتقدمة مثل UUIDs وأعمدة JSONB والبحث النصي الكامل. المشروع المُولّد يتضمن نظام ترحيل وبذر كامل يُبقي مخططك مُصنّفاً وبيانات التطوير قابلة لإعادة الإنتاج.
إعدادات الاتصال
اتصال قاعدة البيانات يُعدّ عبر متغيرات البيئة:
# .env
DATABASE_URL=postgres://forge_user:forge_pass@localhost:5432/forge_db
DATABASE_MAX_CONNECTIONS=10
DATABASE_MIN_CONNECTIONS=2يُنشأ connection pool عند البدء باستخدام SQLx:
use sqlx::postgres::PgPoolOptions;
use sqlx::PgPool;
pub async fn create_pool(database_url: &str) -> PgPool {
PgPoolOptions::new()
.max_connections(10)
.min_connections(2)
.acquire_timeout(std::time::Duration::from_secs(5))
.idle_timeout(std::time::Duration::from_secs(600))
.connect(database_url)
.await
.expect("Failed to create database connection pool")
}نصيحة
SQLx يتحقق من استعلامات SQL في وقت الترجمة مقابل مخطط قاعدة البيانات الفعلي. شغّل cargo sqlx prepare لتوليد بيانات استعلام offline لبناءات CI حيث قاعدة البيانات غير متاحة.
اصطلاحات المخطط
كل جدول في مشروع FORGE المُولّد يتبع هذه الاصطلاحات:
المفاتيح الرئيسية
جميع الجداول تستخدم مفاتيح رئيسية UUID تُولّدها PostgreSQL:
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- ...
);الطوابع الزمنية
كل جدول يتضمن أعمدة created_at و updated_at:
CREATE TABLE users (
-- ...
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);الحذف الناعم
الجداول التي تدعم الحذف الناعم تتضمن عمود deleted_at:
CREATE TABLE users (
-- ...
deleted_at TIMESTAMPTZ
);تحذير
عند استعلام الجداول القابلة للحذف الناعم، ضمّن دائماً WHERE deleted_at IS NULL لاستبعاد السجلات المحذوفة. الـ services المُولّدة تتعامل مع هذا تلقائياً، لكن كن على علم به عند كتابة استعلامات مخصصة.
أعمدة JSONB
المحتوى القابل للترجمة والبيانات الوصفية المرنة تستخدم JSONB:
CREATE TABLE contents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug VARCHAR(255) NOT NULL UNIQUE,
translations JSONB NOT NULL DEFAULT '{}',
metadata JSONB DEFAULT '{}',
-- ...
);ترجمات JSONB تتبع هذه البنية:
{
"en": {
"title": "Welcome",
"body": "Welcome to our site"
},
"ar": {
"title": "مرحبا",
"body": "مرحبا بكم في موقعنا"
}
}الترحيلات
الترحيلات توجد في database/migrations/ ومُرقّمة بالتسلسل:
database/migrations/
├── 00001_create_users_table.sql
├── 00002_create_roles_table.sql
├── 00003_create_permissions_table.sql
├── 00004_create_role_user_table.sql
├── 00005_create_permission_role_table.sql
├── 00006_create_settings_table.sql
├── 00007_create_password_resets_table.sql
├── 00008_create_otp_codes_table.sql
├── 00009_create_media_table.sql
├── 00010_create_audit_logs_table.sql
├── 00011_create_translations_table.sql
├── 00012_create_pages_table.sql
├── 00013_create_faqs_table.sql
├── 00014_create_contents_table.sql
├── 00015_create_menus_table.sql
├── 00016_migrate_media_polymorphic.sql
└── 00017_create_lookups_table.sqlتشغيل الترحيلات
الترحيلات تُنفّذ تلقائياً عند بدء التطبيق:
sqlx::migrate!("./database/migrations")
.run(&pool)
.await
.expect("Failed to run migrations");كتابة ترحيل جديد
أنشئ ملف SQL جديد بالرقم التسلسلي التالي:
-- database/migrations/00018_create_products_table.sql
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL UNIQUE,
translations JSONB NOT NULL DEFAULT '{}',
price DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
-- Index on slug for fast lookups
CREATE INDEX idx_products_slug ON products(slug);
-- Index for active products
CREATE INDEX idx_products_active ON products(is_active) WHERE deleted_at IS NULL;نصيحة
استخدم الفهارس الجزئية (WHERE deleted_at IS NULL) لإبقاء الفهارس صغيرة وفعالة. PostgreSQL ستستخدم هذه الفهارس للاستعلامات التي تتضمن نفس جملة WHERE.
الـ Seeders
الـ seeders تملأ قاعدة البيانات بالبيانات الأولية المطلوبة لعمل التطبيق:
database/seeders/
├── 01_roles.sql # Default roles (admin, user)
├── 02_permissions.sql # All permissions
├── 03_admin_user.sql # Default admin account
├── 04_settings.sql # Application settings
├── 05_pages.sql # Default pages
└── 06_translations.sql # UI translationsمثال seeder لمستخدم admin:
-- database/seeders/03_admin_user.sql
INSERT INTO users (name, email, password, email_verified_at, is_active)
VALUES (
'Admin',
'admin@forge.dev',
-- Argon2 hash for 'password'
'$argon2id$v=19$m=19456,t=2,p=1$...',
now(),
true
)
ON CONFLICT (email) DO NOTHING;
-- Assign admin role
INSERT INTO role_user (user_id, role_id)
SELECT u.id, r.id
FROM users u
CROSS JOIN roles r
WHERE u.email = 'admin@forge.dev'
AND r.name = 'admin'
ON CONFLICT DO NOTHING;استراتيجية الفهرسة
يُولّد FORGE فهارس لأنماط الاستعلام الشائعة:
-- Foreign key indexes (always indexed)
CREATE INDEX idx_role_user_user_id ON role_user(user_id);
CREATE INDEX idx_role_user_role_id ON role_user(role_id);
-- Uniqueness constraints that act as indexes
CREATE UNIQUE INDEX idx_users_email ON users(email);
CREATE UNIQUE INDEX idx_users_mobile ON users(mobile) WHERE mobile IS NOT NULL;
-- Slug lookups
CREATE UNIQUE INDEX idx_contents_slug ON contents(slug);
-- Soft delete filtering
CREATE INDEX idx_users_active ON users(is_active) WHERE deleted_at IS NULL;
-- JSONB indexes for translations
CREATE INDEX idx_contents_translations ON contents USING GIN(translations);
-- Timestamp ordering
CREATE INDEX idx_contents_created_at ON contents(created_at DESC);الاستعلام مع الترجمات
الوصول للحقول المُترجمة باستخدام مُشغّلات JSONB في PostgreSQL:
-- Get Arabic title
SELECT translations->'ar'->>'title' AS title_ar
FROM contents
WHERE slug = 'about-us';
-- Search across translations
SELECT *
FROM contents
WHERE translations->'en'->>'title' ILIKE '%welcome%';
-- Get all translations for a specific language
SELECT translations->'en' AS en_translation
FROM contents
WHERE deleted_at IS NULL;في Rust مع SQLx:
// Query with specific language extraction
let contents = sqlx::query_as!(
ContentWithTranslation,
r#"
SELECT
id,
slug,
translations->$1->>'title' AS "title?",
translations->$1->>'body' AS "body?",
created_at
FROM contents
WHERE deleted_at IS NULL
ORDER BY created_at DESC
"#,
locale
)
.fetch_all(&pool)
.await?;Connection Pooling
التطبيق المُولّد يستخدم connection pool المدمج في SQLx. الإعدادات الرئيسية:
| الإعداد | الافتراضي | الوصف |
|---|---|---|
max_connections | 10 | أقصى عدد من الاتصالات في الـ pool |
min_connections | 2 | أدنى عدد من الاتصالات الخاملة المحفوظة |
acquire_timeout | 5s | أقصى وقت انتظار للحصول على اتصال |
idle_timeout | 600s | إغلاق الاتصالات الخاملة بعد هذه المدة |
تحذير
عيّن max_connections بناءً على إعداد max_connections في PostgreSQL مقسوماً على عدد نسخ التطبيق. مثلاً، إذا كان PostgreSQL يسمح بـ 100 اتصال وتشغّل 5 نسخ، عيّن max_connections إلى 15-18، مع ترك هامش لاتصالات الإدارة.
انظر أيضاً
- الـ Models — بنى Rust التي تُعيّن لجداول قاعدة البيانات
- الـ Services — منطق الأعمال الذي يستعلم قاعدة البيانات
- معالجة الأخطاء — تعيين أخطاء قاعدة البيانات