Migration پایگاه داده

Migrationهای پایگاه داده اسکریپت‌های نسخه‌بندی‌شده‌ای هستند که تغییرات schema پایگاه داده شما را در طول زمان پیگیری می‌کنند. به‌جای اجرای دستی دستورات SQL در هر environment، migrationها تضمین می‌کنند که هر پایگاه داده — development، staging، production — به‌صورت خودکار همگام (sync) باقی بماند.

فینچ migrationهای اعمال‌شده را در جدولی به نام wa_migration که به‌صورت خودکار ایجاد می‌کند (برای هر پایگاه داده) پیگیری می‌کند، و دو دستور app-runtime مرتبط اما مجزا ارائه می‌دهد: migrate برای MySQL، و migrate_sqlite برای SQLite. هر دو دستور app-runtime هستند — یعنی درون نمونه واقعی FinchApp شما اجرا می‌شوند، نه به‌صورت یک flag ساده در سطح بالای finch. برای اینکه ببینید این دو لایه دستور چگونه به هم مرتبط هستند، به Finch CLI مراجعه کنید.

دستورات Migration

این دستورات را از طریق finch run --args="..." / finch serve --args="..."، یا مستقیماً با dart run lib/app.dart ... اجرا کنید:

دستور گزینه مخفف توضیح
migrate --init -i اعمال تمام migrationهای معلق MySQL، به‌ترتیب
migrate --init --sqlite اعمال migrationهای معلق MySQL و SQLite با هم، در یک فراخوانی
migrate --rollback -r برگرداندن آخرین migration اعمال‌شده MySQL
migrate --list -l نمایش فایل‌های migration مربوط به MySQL و وضعیت اعمال‌شدن آن‌ها
migrate_sqlite --init / --rollback / --list -i / -r / -l همان عملیات‌ها، محدود به فقط SQLite

مهم: flag به نام --sqlite روی دستور migrate فقط روی --init تأثیر می‌گذارد — و به‌عنوان یک قابلیت راحتی، migrationهای معلق هر دو پایگاه داده را با هم اجرا می‌کند. این flag توسط --rollback و --list نادیده گرفته می‌شود؛ migrate --rollback --sqlite همچنان یک migration MySQL را برمی‌گرداند. برای rollback یا نمایش فهرست migrationهای SQLite، همیشه از دستور اختصاصی migrate_sqlite استفاده کنید.

ساخت یک فایل migration جدید (برخلاف اعمال کردن آن) در عوض یک دستور CLI سطح بالای finch است — به Creating a Migration File در ادامه مراجعه کنید.

راه‌اندازی اولیه

برای اعمال تمام migrationها روی یک پایگاه داده تازه، --init را اجرا کنید:

# MySQL only
finch run --args="migrate --init"

# MySQL and SQLite together
finch run --args="migrate --init --and migrate_sqlite --init"
# or, equivalently, in one migrate call:
finch run --args="migrate --init --sqlite"

# SQLite only
finch run --args="migrate_sqlite --init"

فینچ در اولین اجرا (برای هر پایگاه داده) یک جدول wa_migration ایجاد می‌کند تا مشخص کند کدام فایل‌ها قبلاً اجرا شده‌اند. فراخوانی‌های بعدی --init فقط فایل‌هایی را اعمال می‌کنند که هنوز اجرا نشده‌اند.

Creating a Migration File

ساخت یک فایل migration جدید یک دستور CLI سطح بالای finch است (نیازی به اجرای برنامه ندارد، چون صرفاً یک فایل را scaffold می‌کند):

finch migrate --create --name add_books_table
# Creates something like: migrations/z1700000000000_add_books_table_migration.sql

finch migrate --create --name add_books_table --sqlite
# Creates the SQLite equivalent under pathMigrationSQLite

نام فایل با پیشوند z<millisecondsSinceEpoch>_ شروع می‌شود (حرف z در ابتدا باعث می‌شود فایل‌های دارای پیشوند timestamp، بعد از هر فایل غیر-migration در مرتب‌سازی قرار بگیرند) و نامی که پاس داده‌اید را slugify می‌کند. این فایل در دایرکتوری pathMigrationMySQL (یا pathMigrationSQLite برای SQLite) قرار می‌گیرد که هر دو در FinchConfigs پیکربندی می‌شوند:

FinchConfigs configs = FinchConfigs(
  pathMigrationMySQL:  pathTo('./migrations'),
  pathMigrationSQLite: pathTo('./migrations_sqlite'),
);

اینکه --create یک فایل .sql تولید کند یا یک boilerplate آماده از کلاس Dart به نام DartMigration، توسط تنظیم mysql_migrate.type / sqlite_migrate.type در pubspec.yaml شما کنترل می‌شود (پیش‌فرض sql است) — به pubspec.yaml Configuration مراجعه کنید.

فرمت فایل Migration

هر فایل migration با پسوند .sql دارای دو بخش است: migration رو-به-جلو (## NEW VERSION) و rollback (## ROLL BACK):

-- 2024-01-15 12:00:00.000
-- MySQL Migration File
-- Name: add_books_table
-- ## NEW VERSION:

CREATE TABLE IF NOT EXISTS `books` (
  `id`             INT          NOT NULL AUTO_INCREMENT,
  `title`          VARCHAR(255) NOT NULL,
  `author`         VARCHAR(255) NOT NULL,
  `published_date` DATE         NOT NULL,
  `category_id`    INT          NULL,
  PRIMARY KEY (`id`)
);

-- ## ROLL BACK:

DROP TABLE IF EXISTS `books`;

هر migration (فایلی یا مبتنی بر Dart) درون یک تراکنش پایگاه‌داده اجرا می‌شود: اگر هر یک از دستورات با خطا مواجه شود، تراکنش به‌صورت خودکار rollback می‌شود و آن migration در wa_migration ثبت نمی‌شود، بنابراین نسخه اصلاح‌شده همان فایل در فراخوانی بعدی --init اعمال خواهد شد.

Migrationهای مبتنی بر Dart

به‌جای فایل‌های SQL، می‌توانید migrationها را در Dart با extend کردن DartMigration تعریف کنید. SQL مربوط به up()/down() را با addSql() بسازید، و target را برای انتخاب MigrationTarget.mysql یا MigrationTarget.sqlite تنظیم کنید:

import 'package:finch/mysql.dart';

class M1CreateUsers extends DartMigration {
  @override
  MigrationTarget get target => MigrationTarget.sqlite;

  M1CreateUsers() : super('m1_create_users');

  @override
  void up() {
    addSql('''
      CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        email TEXT
      );
    ''');
  }

  @override
  void down() {
    addSql('DROP TABLE IF EXISTS users;');
  }
}

migrationها را روی نمونه FinchApp ثبت کنید. نامی که به super(...) پاس داده می‌شود باید در میان همه migrationهای ثبت‌شده یکتا باشد — این همان مقداری است که در wa_migration ثبت می‌شود، بنابراین تغییر نام آن در آینده باعث می‌شود فینچ فکر کند آن migration هرگز اعمال نشده است:

final app = FinchApp(configs: configs)
  ..registerDartMigration([
    M1CreateUsers(),
    M2InsertUsers(),
  ]);

پس از ثبت، migrate --init/--rollback/--list و معادل‌های migrate_sqlite آن‌ها، migrationهای Dart متناظر با آن target را علاوه بر هر فایل .sql موجود در دایرکتوری migrations اجرا می‌کنند — می‌توانید هر دو سبک را آزادانه در یک پروژه ترکیب کنید.

بازگردانی

# Undo the most recently applied MySQL migration
finch run --args="migrate --rollback"

# Undo the most recently applied SQLite migration — must use migrate_sqlite
finch run --args="migrate_sqlite --rollback"

# Undo the two most recent MySQL migrations
finch run --args="migrate --rollback 2"

rollback، SQL موجود در بخش ## ROLL BACK: آخرین فایل اعمال‌شده (یا متد down() آخرین migration اعمال‌شده Dart) را اجرا می‌کند.

نمایش وضعیت Migrationها

finch run --args="migrate --list"        # MySQL
finch run --args="migrate_sqlite --list" # SQLite

این دستور جدولی چاپ می‌کند که هر فایل migration، وضعیت اجراشدن آن، و زمان اجرای آن را نشان می‌دهد.

اجرای Migrationها هنگام راه‌اندازی

migrationها را به‌صورت خودکار به‌عنوان بخشی از دستور startup خود اجرا کنید — این دقیقاً همان کاری است که image داکر پروژه نمونه انجام می‌دهد (به Docker for Finch مراجعه کنید):

# In production startup (e.g., Docker CMD)
finch serve --args="migrate --init --and migrate_sqlite --init"

--and چندین دستور app-runtime را در یک رشته --args واحد زنجیر می‌کند، بنابراین هر دو پایگاه داده قبل از اینکه سرور شروع به پاسخ‌دهی به درخواست‌ها کند، migrate می‌شوند.

همیشه قبل از اجرای migrationها از پایگاه داده production خود پشتیبان (backup) بگیرید.