🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
376 lines
14 KiB
Plaintext
Executable File
376 lines
14 KiB
Plaintext
Executable File
MIGRATIONS(7) RSX Framework Manual MIGRATIONS(7)
|
|
|
|
NAME
|
|
migrations - Database migration system with raw SQL enforcement
|
|
|
|
SYNOPSIS
|
|
php artisan make:migration:safe <name>
|
|
php artisan migrate
|
|
php artisan migrate:status
|
|
|
|
DESCRIPTION
|
|
The RSX framework enforces a forward-only migration strategy using raw SQL
|
|
statements. Laravel's Schema builder is prohibited to ensure clarity,
|
|
auditability, and prevent hidden behaviors.
|
|
|
|
The migrate command automatically handles snapshot protection in development
|
|
mode - no manual steps required.
|
|
|
|
PHILOSOPHY
|
|
1. Forward-only migrations - No rollbacks, no down() methods
|
|
2. Raw SQL only - Direct MySQL statements, no abstractions
|
|
3. Fail loud - Migrations must succeed or fail with clear errors
|
|
4. Automatic safety - Development mode creates snapshots automatically
|
|
|
|
MIGRATION RULES
|
|
|
|
Schema Builder Prohibition
|
|
All migrations MUST use DB::statement() with raw SQL. The following are prohibited:
|
|
|
|
- Schema::create()
|
|
- Schema::table()
|
|
- Schema::drop()
|
|
- Schema::dropIfExists()
|
|
- Schema::rename()
|
|
- Blueprint class usage
|
|
- $table-> method chains
|
|
|
|
The migration validator automatically checks for these patterns and will prevent
|
|
migrations from running if violations are found.
|
|
|
|
Required Table Structure
|
|
ALL tables MUST have:
|
|
|
|
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
|
|
|
|
This is non-negotiable. Every table needs this exact ID column (SIGNED for
|
|
easier future migrations).
|
|
|
|
Data Type Standards - What You Need to Know
|
|
The framework automatically normalizes data types during migration, so you can
|
|
use simpler types and let the system handle optimization:
|
|
|
|
What You Can Use (System Auto-Converts):
|
|
- INT -> automatically becomes BIGINT
|
|
- TEXT -> automatically becomes LONGTEXT
|
|
- FLOAT -> automatically becomes DOUBLE
|
|
- Any charset -> automatically becomes UTF8MB4
|
|
- created_at/updated_at -> automatically added with proper defaults
|
|
- created_by/updated_by -> automatically added
|
|
- deleted_by -> automatically added for soft-delete tables
|
|
|
|
What You MUST Be Careful About:
|
|
- Foreign key columns - Must match the referenced column type exactly
|
|
Example: If users.id is BIGINT, then orders.user_id must be BIGINT
|
|
- TINYINT(1) - Preserved for boolean values, won't be converted
|
|
- Column names ending in _id are assumed to be foreign keys
|
|
|
|
Recommended for Simplicity:
|
|
- Just use INT for integers (becomes BIGINT automatically)
|
|
- Just use TEXT for long content (becomes LONGTEXT automatically)
|
|
- Just use FLOAT for decimals (becomes DOUBLE automatically)
|
|
- Don't add created_at/updated_at (added automatically)
|
|
- Don't add created_by/updated_by (added automatically)
|
|
|
|
down() Method Removal
|
|
The migration system automatically removes down() methods from migration files.
|
|
Migrations are forward-only - database changes should never be reversed.
|
|
|
|
AUTOMATIC NORMALIZATION
|
|
|
|
What migrate:normalize_schema Does For You
|
|
After migrations run, the normalize_schema command automatically:
|
|
|
|
1. Type Conversions:
|
|
- INT columns -> BIGINT (except TINYINT(1) for booleans)
|
|
- BIGINT UNSIGNED -> BIGINT SIGNED
|
|
- TEXT -> LONGTEXT
|
|
- FLOAT -> DOUBLE
|
|
- All text columns -> UTF8MB4 character set
|
|
|
|
2. Required Columns Added:
|
|
- created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3)
|
|
- updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE
|
|
- created_by INT(11) NULL
|
|
- updated_by INT(11) NULL
|
|
- deleted_by INT(11) NULL (only for soft-delete tables)
|
|
|
|
3. Indexes Added:
|
|
- INDEX on created_at
|
|
- INDEX on updated_at
|
|
- INDEX on site_id (for models extending Rsx_Site_Model_Abstract)
|
|
- INDEX on id+version (for Versionable models)
|
|
|
|
4. Model-Specific Columns:
|
|
- site_id BIGINT - for models extending Rsx_Site_Model_Abstract
|
|
- version INT(11) DEFAULT 1 - for Versionable/Ajaxable models
|
|
|
|
5. Precision Upgrades:
|
|
- All DATETIME/TIMESTAMP columns -> precision (3) for milliseconds
|
|
|
|
This means you can write simpler migrations and let the system handle the
|
|
optimization and standardization. The only time you need to be explicit about
|
|
types is when creating foreign key columns that must match their referenced
|
|
column exactly.
|
|
|
|
VALIDATION SYSTEM
|
|
|
|
Automatic Validation
|
|
When running migrations in development mode, the system automatically:
|
|
|
|
1. Validates all pending migrations for Schema builder usage
|
|
2. Removes down() methods if present
|
|
3. Reports violations with colored output and remediation advice
|
|
4. Stops at the first violation to allow correction
|
|
|
|
Validation Output
|
|
When a violation is detected, you'll see:
|
|
|
|
[ERROR] Migration Validation Failed
|
|
|
|
File: 2025_09_30_create_example_table.php
|
|
Line: 28
|
|
|
|
Violation: Found forbidden Schema builder usage: Schema::create
|
|
|
|
Code Preview:
|
|
----------------------------------------
|
|
Schema::create('users', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('name');
|
|
});
|
|
----------------------------------------
|
|
|
|
Remediation: Use DB::statement("CREATE TABLE...") instead
|
|
|
|
Example:
|
|
DB::statement('CREATE TABLE users (
|
|
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255),
|
|
created_at TIMESTAMP NULL DEFAULT NULL
|
|
)');
|
|
|
|
MIGRATION WORKFLOW
|
|
|
|
Development Mode (RSX_MODE=development)
|
|
Simply run:
|
|
|
|
php artisan migrate
|
|
|
|
The command automatically:
|
|
1. Creates a database snapshot before running migrations
|
|
2. Runs all pending migrations with validation
|
|
3. Runs schema quality checks
|
|
4. On success: commits changes, regenerates constants, recompiles bundles
|
|
5. On failure: automatically rolls back to snapshot
|
|
|
|
Failed migrations restore the database to its pre-migration state. Fix your
|
|
migration files and run "php artisan migrate" again.
|
|
|
|
Debug/Production Mode (RSX_MODE=debug or production)
|
|
Run:
|
|
|
|
php artisan migrate
|
|
|
|
In these modes:
|
|
- No snapshot protection (source code is read-only)
|
|
- Migrations run directly against the database
|
|
- Schema normalization still runs
|
|
- Constants and bundles are NOT regenerated (already done in development)
|
|
|
|
Ensure migrations are thoroughly tested in development before running in
|
|
debug/production modes.
|
|
|
|
Framework-Only Migrations
|
|
To run only framework migrations (system/database/migrations):
|
|
|
|
php artisan migrate --framework-only
|
|
|
|
This skips snapshot protection regardless of mode.
|
|
|
|
MIGRATION EXAMPLES
|
|
|
|
Creating a Table (Simple Version - Recommended)
|
|
public function up()
|
|
{
|
|
// You can write this simple version - system auto-normalizes types
|
|
DB::statement("
|
|
CREATE TABLE products (
|
|
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, -- becomes BIGINT
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT, -- becomes LONGTEXT
|
|
price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
|
stock_quantity INT NOT NULL DEFAULT 0, -- becomes BIGINT
|
|
is_active TINYINT(1) NOT NULL DEFAULT 1, -- stays TINYINT(1)
|
|
category_id INT NULL, -- becomes BIGINT
|
|
INDEX idx_category (category_id),
|
|
INDEX idx_active (is_active)
|
|
-- No need for created_at/updated_at - added automatically
|
|
)
|
|
");
|
|
}
|
|
|
|
Creating a Table (Explicit Version - If You Prefer)
|
|
public function up()
|
|
{
|
|
// Or be explicit about types if you prefer
|
|
DB::statement("
|
|
CREATE TABLE products (
|
|
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
description LONGTEXT,
|
|
price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
|
stock_quantity BIGINT NOT NULL DEFAULT 0,
|
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
|
category_id BIGINT NULL,
|
|
created_at TIMESTAMP NULL DEFAULT NULL,
|
|
updated_at TIMESTAMP NULL DEFAULT NULL,
|
|
INDEX idx_category (category_id),
|
|
INDEX idx_active (is_active),
|
|
INDEX idx_created (created_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
");
|
|
}
|
|
|
|
Adding Columns
|
|
public function up()
|
|
{
|
|
DB::statement("ALTER TABLE users ADD COLUMN age BIGINT NULL AFTER email");
|
|
DB::statement("ALTER TABLE users ADD INDEX idx_age (age)");
|
|
}
|
|
|
|
Modifying Columns
|
|
public function up()
|
|
{
|
|
// Change column type
|
|
DB::statement("ALTER TABLE products MODIFY COLUMN price DECIMAL(12,2)");
|
|
|
|
// Rename column
|
|
DB::statement("ALTER TABLE users CHANGE COLUMN username user_name VARCHAR(100)");
|
|
|
|
// Add default value
|
|
DB::statement("ALTER TABLE posts ALTER COLUMN status SET DEFAULT 'draft'");
|
|
}
|
|
|
|
Managing Indexes
|
|
public function up()
|
|
{
|
|
// Add index
|
|
DB::statement("CREATE INDEX idx_email ON users (email)");
|
|
|
|
// Add unique index
|
|
DB::statement("CREATE UNIQUE INDEX idx_unique_slug ON posts (slug)");
|
|
|
|
// Add composite index
|
|
DB::statement("CREATE INDEX idx_user_status ON orders (user_id, status)");
|
|
|
|
// Drop index
|
|
DB::statement("DROP INDEX idx_old_index ON table_name");
|
|
}
|
|
|
|
Foreign Keys (IMPORTANT - Match Types Exactly)
|
|
public function up()
|
|
{
|
|
// CRITICAL: Foreign key columns must match referenced column type
|
|
// If users.id is BIGINT, orders.user_id must also be BIGINT
|
|
|
|
// First, ensure the column has correct type (if not already created)
|
|
DB::statement("ALTER TABLE orders ADD COLUMN user_id BIGINT NULL");
|
|
|
|
// Then add the foreign key constraint
|
|
DB::statement("
|
|
ALTER TABLE orders
|
|
ADD CONSTRAINT orders_user_fk
|
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
ON DELETE CASCADE
|
|
");
|
|
|
|
// To drop a foreign key
|
|
DB::statement("ALTER TABLE orders DROP FOREIGN KEY orders_user_fk");
|
|
}
|
|
|
|
// Note: After normalization, all id columns are BIGINT, so foreign keys
|
|
// should always use BIGINT to avoid type mismatches
|
|
|
|
Data Migrations
|
|
public function up()
|
|
{
|
|
// Simple update
|
|
DB::statement("UPDATE users SET role = 'member' WHERE role IS NULL");
|
|
|
|
// Complex migration with temporary column
|
|
DB::statement("ALTER TABLE orders ADD COLUMN total_new DECIMAL(10,2)");
|
|
DB::statement("UPDATE orders SET total_new = quantity * price");
|
|
DB::statement("ALTER TABLE orders DROP COLUMN total");
|
|
DB::statement("ALTER TABLE orders CHANGE total_new total DECIMAL(10,2)");
|
|
}
|
|
|
|
ERROR MESSAGES
|
|
|
|
"Migration validation failed: Schema builder usage detected"
|
|
Your migration uses Laravel's Schema builder. Rewrite using DB::statement()
|
|
with raw SQL.
|
|
|
|
"Unauthorized migrations detected!"
|
|
Migration files exist that weren't created via make:migration:safe.
|
|
Recreate them using the proper command.
|
|
|
|
"Snapshot-based migrations require Docker environment"
|
|
You're in development mode but not running in Docker. Either:
|
|
- Run in Docker, or
|
|
- Set RSX_MODE=debug in .env to skip snapshot protection
|
|
|
|
AUTOMATIC ROLLBACK (Development Mode)
|
|
|
|
In development mode, if a migration fails:
|
|
1. The error is displayed
|
|
2. Database is automatically restored to pre-migration state
|
|
3. You can fix the migration and run "php artisan migrate" again
|
|
|
|
This happens automatically - no manual intervention required.
|
|
|
|
SECURITY CONSIDERATIONS
|
|
|
|
SQL Injection
|
|
When using dynamic values in migrations, always use parameter binding:
|
|
|
|
CORRECT:
|
|
DB::statement("UPDATE users SET status = ? WHERE created_at < ?", ['active', '2025-01-01']);
|
|
|
|
WRONG:
|
|
DB::statement("UPDATE users SET status = '$status' WHERE created_at < '$date'");
|
|
|
|
Production Safety
|
|
- Always test migrations in development first
|
|
- Keep migrations small and focused
|
|
- Never reference models or services in migrations
|
|
- Migrations must be self-contained and idempotent where possible
|
|
|
|
DEBUGGING
|
|
|
|
Viewing Pending Migrations
|
|
php artisan migrate:status
|
|
|
|
Testing a Migration
|
|
In development mode, just run:
|
|
php artisan migrate
|
|
|
|
If it fails, the database is automatically restored. Fix the migration
|
|
and try again.
|
|
|
|
Common Issues
|
|
- "Class not found" - Don't reference models in migrations
|
|
- "Syntax error" - Check your SQL syntax, test in MySQL client first
|
|
- "Foreign key constraint" - Ensure referenced table/column exists
|
|
- "Duplicate column" - Check if column already exists before adding
|
|
|
|
SEE ALSO
|
|
rsx:man database - Database system overview
|
|
rsx:man coding_standards - General coding standards
|
|
rsx:man error_handling - Error handling patterns
|
|
|
|
AUTHORS
|
|
RSX Framework Team
|
|
|
|
RSX Framework January 2026 MIGRATIONS(7)
|