Standardize settings file naming and relocate documentation files Fix code quality violations from rsx:check Reorganize user_management directory into logical subdirectories Move Quill Bundle to core and align with Tom Select pattern Simplify Site Settings page to focus on core site information Complete Phase 5: Multi-tenant authentication with login flow and site selection Add route query parameter rule and synchronize filename validation logic Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs Implement filename convention rule and resolve VS Code auto-rename conflict Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns Implement RPC server architecture for JavaScript parsing WIP: Add RPC server infrastructure for JS parsing (partial implementation) Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation Add JQHTML-CLASS-01 rule and fix redundant class names Improve code quality rules and resolve violations Remove legacy fatal error format in favor of unified 'fatal' error type Filter internal keys from window.rsxapp output Update button styling and comprehensive form/modal documentation Add conditional fly-in animation for modals Fix non-deterministic bundle compilation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
349 lines
13 KiB
Plaintext
Executable File
349 lines
13 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:begin
|
|
php artisan migrate [--production]
|
|
php artisan migrate:commit
|
|
php artisan migrate:rollback
|
|
|
|
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.
|
|
|
|
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. Snapshot safety - Development requires database snapshots before migrating
|
|
|
|
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 non-production 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:
|
|
|
|
❌ 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
|
|
)');
|
|
|
|
Bypassing Validation
|
|
In production mode (--production flag or APP_ENV=production), validation is
|
|
skipped. This should only be used when absolutely necessary.
|
|
|
|
MIGRATION WORKFLOW
|
|
|
|
Development Workflow
|
|
1. Create snapshot: php artisan migrate:begin
|
|
2. Create migration: php artisan make:migration:safe <name>
|
|
3. Write migration using raw SQL
|
|
4. Run migrations: php artisan migrate
|
|
5. If successful: php artisan migrate:commit
|
|
6. If failed: System auto-rollbacks to snapshot
|
|
|
|
Production Workflow
|
|
1. Create migration: php artisan make:migration:safe <name>
|
|
2. Write migration using raw SQL
|
|
3. Test thoroughly in development/staging
|
|
4. Run migrations: php artisan migrate --production
|
|
|
|
Note: No snapshot protection in production mode. Ensure migrations are
|
|
thoroughly tested before running in production.
|
|
|
|
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.
|
|
|
|
"Migration mode not active!"
|
|
You're in development mode and haven't created a snapshot. Run:
|
|
php artisan migrate:begin
|
|
|
|
"Unauthorized migrations detected!"
|
|
Migration files exist that weren't created via make:migration:safe.
|
|
Recreate them using the proper command.
|
|
|
|
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/staging 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
|
|
1. Create snapshot: php artisan migrate:begin
|
|
2. Run migration: php artisan migrate
|
|
3. If it fails, automatic rollback occurs
|
|
4. Fix the migration file
|
|
5. Try again: php artisan migrate
|
|
|
|
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 September 2025 MIGRATIONS(7) |