| name | database-change-management |
| description | Plan and implement safe database schema changes including migrations, indexes, and backfills. Use when creating tables, adding columns, optimizing queries, or managing Eloquent relationships. EXCLUSIVE to database-admin agent. |
| allowed-tools | Read, Edit, Bash, Grep, Glob, Write |
Database Change Management
Exclusive to: database-admin agent
Validation Loop (MANDATORY)
Every migration MUST pass this verification sequence:
php artisan migrate # Run migration
php artisan migrate:rollback # Verify rollback works
php artisan migrate # Re-run migration
composer test # All tests still pass
Do NOT complete until all steps succeed.
Instructions
- Audit existing migrations and models for current schema
- Design migration with reversible
down()method - Run
migrateandrollbackto validate locally - Update Eloquent model ($fillable, $casts, relationships)
- Document any required backfills or deployment order
Safe Migration Patterns
Adding Columns
// ✅ Safe: nullable or with default
$table->string('field')->nullable();
$table->boolean('active')->default(true);
// ❌ Unsafe: NOT NULL without default
$table->string('field');
Adding Indexes
// Index for WHERE/ORDER BY columns
$table->index('user_id');
$table->index(['status', 'created_at']);
Zero-Downtime Strategy
- Add — Add nullable column
- Backfill — Populate data in chunks
- Enforce — Make column required
Backfill Pattern
Model::query()
->whereNull('new_field')
->chunkById(1000, function ($items) {
foreach ($items as $item) {
$item->update(['new_field' => $value]);
}
});
Eloquent Relationships
One-to-Many
// User has many Posts
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
Many-to-Many
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class)
->withTimestamps();
}
Query Optimization
Eager Loading
// ❌ N+1 Problem
foreach (Post::all() as $post) {
echo $post->user->name;
}
// ✅ Eager Load
Post::with('user')->get();
Index Strategy
| Query Pattern | Index |
|---|---|
WHERE user_id = ? |
index('user_id') |
WHERE status = ? AND date > ? |
index(['status', 'date']) |
Common Pitfalls
- ❌ NOT NULL without default on existing table
- ❌ Dropping columns without backup
- ❌ Missing indexes on foreign keys
- ❌ Missing
down()method
Verification
php artisan migrate
php artisan migrate:rollback
php artisan migrate
Examples
- "Add an index to speed up dashboard query"
- "Add a nullable column then backfill safely"