From eee5c0ac0b89ed55dbb9fe02008291680ad6d6bb Mon Sep 17 00:00:00 2001 From: ianshaw Date: Wed, 31 Dec 2025 18:06:42 -0800 Subject: [PATCH] =?UTF-8?q?feat(migrations):=20=E6=94=B9=E8=BF=9B=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E5=92=8C=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA=E5=92=8C?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增强迁移校验和不匹配的错误信息,提供具体解决方案 - 添加 migrations/README.md 文档说明迁移最佳实践 - 明确迁移不可变原则和正确的修改流程 --- .../internal/repository/migrations_runner.go | 10 +- backend/migrations/017_add_gemini_tier_id.sql | 7 +- backend/migrations/README.md | 178 ++++++++++++++++++ 3 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 backend/migrations/README.md diff --git a/backend/internal/repository/migrations_runner.go b/backend/internal/repository/migrations_runner.go index e556b9ce..39b633b1 100644 --- a/backend/internal/repository/migrations_runner.go +++ b/backend/internal/repository/migrations_runner.go @@ -127,7 +127,15 @@ func applyMigrationsFS(ctx context.Context, db *sql.DB, fsys fs.FS) error { if existing != checksum { // 校验和不匹配意味着迁移文件在应用后被修改,这是危险的。 // 正确的做法是创建新的迁移文件来进行变更。 - return fmt.Errorf("migration %s checksum mismatch (db=%s file=%s)", name, existing, checksum) + return fmt.Errorf( + "migration %s checksum mismatch (db=%s file=%s)\n"+ + "This means the migration file was modified after being applied to the database.\n"+ + "Solutions:\n"+ + " 1. Revert to original: git log --oneline -- migrations/%s && git checkout -- migrations/%s\n"+ + " 2. For new changes, create a new migration file instead of modifying existing ones\n"+ + "Note: Modifying applied migrations breaks the immutability principle and can cause inconsistencies across environments.", + name, existing, checksum, name, name, + ) } continue // 迁移已应用且校验和匹配,跳过 } diff --git a/backend/migrations/017_add_gemini_tier_id.sql b/backend/migrations/017_add_gemini_tier_id.sql index 17d9440d..0388a412 100644 --- a/backend/migrations/017_add_gemini_tier_id.sql +++ b/backend/migrations/017_add_gemini_tier_id.sql @@ -26,10 +26,5 @@ UPDATE accounts SET credentials = credentials - 'tier_id' WHERE platform = 'gemini' AND type = 'oauth' - AND jsonb_typeof(credentials) = 'object' - AND credentials->>'tier_id' = 'LEGACY' - AND ( - credentials->>'oauth_type' = 'code_assist' - OR (credentials->>'oauth_type' IS NULL AND credentials->>'project_id' IS NOT NULL) - ); + AND credentials->>'oauth_type' = 'code_assist'; -- +goose StatementEnd diff --git a/backend/migrations/README.md b/backend/migrations/README.md new file mode 100644 index 00000000..3fe328e6 --- /dev/null +++ b/backend/migrations/README.md @@ -0,0 +1,178 @@ +# Database Migrations + +## Overview + +This directory contains SQL migration files for database schema changes. The migration system uses SHA256 checksums to ensure migration immutability and consistency across environments. + +## Migration File Naming + +Format: `NNN_description.sql` +- `NNN`: Sequential number (e.g., 001, 002, 003) +- `description`: Brief description in snake_case + +Example: `017_add_gemini_tier_id.sql` + +## Migration File Structure + +```sql +-- +goose Up +-- +goose StatementBegin +-- Your forward migration SQL here +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +-- Your rollback migration SQL here +-- +goose StatementEnd +``` + +## Important Rules + +### ⚠️ Immutability Principle + +**Once a migration is applied to ANY environment (dev, staging, production), it MUST NOT be modified.** + +Why? +- Each migration has a SHA256 checksum stored in the `schema_migrations` table +- Modifying an applied migration causes checksum mismatch errors +- Different environments would have inconsistent database states +- Breaks audit trail and reproducibility + +### ✅ Correct Workflow + +1. **Create new migration** + ```bash + # Create new file with next sequential number + touch migrations/018_your_change.sql + ``` + +2. **Write Up and Down migrations** + - Up: Apply the change + - Down: Revert the change (should be symmetric with Up) + +3. **Test locally** + ```bash + # Apply migration + make migrate-up + + # Test rollback + make migrate-down + ``` + +4. **Commit and deploy** + ```bash + git add migrations/018_your_change.sql + git commit -m "feat(db): add your change" + ``` + +### ❌ What NOT to Do + +- ❌ Modify an already-applied migration file +- ❌ Delete migration files +- ❌ Change migration file names +- ❌ Reorder migration numbers + +### 🔧 If You Accidentally Modified an Applied Migration + +**Error message:** +``` +migration 017_add_gemini_tier_id.sql checksum mismatch (db=abc123... file=def456...) +``` + +**Solution:** +```bash +# 1. Find the original version +git log --oneline -- migrations/017_add_gemini_tier_id.sql + +# 2. Revert to the commit when it was first applied +git checkout -- migrations/017_add_gemini_tier_id.sql + +# 3. Create a NEW migration for your changes +touch migrations/018_your_new_change.sql +``` + +## Migration System Details + +- **Checksum Algorithm**: SHA256 of trimmed file content +- **Tracking Table**: `schema_migrations` (filename, checksum, applied_at) +- **Runner**: `internal/repository/migrations_runner.go` +- **Auto-run**: Migrations run automatically on service startup + +## Best Practices + +1. **Keep migrations small and focused** + - One logical change per migration + - Easier to review and rollback + +2. **Write reversible migrations** + - Always provide a working Down migration + - Test rollback before committing + +3. **Use transactions** + - Wrap DDL statements in transactions when possible + - Ensures atomicity + +4. **Add comments** + - Explain WHY the change is needed + - Document any special considerations + +5. **Test in development first** + - Apply migration locally + - Verify data integrity + - Test rollback + +## Example Migration + +```sql +-- +goose Up +-- +goose StatementBegin +-- Add tier_id field to Gemini OAuth accounts for quota tracking +UPDATE accounts +SET credentials = jsonb_set( + credentials, + '{tier_id}', + '"LEGACY"', + true +) +WHERE platform = 'gemini' + AND type = 'oauth' + AND credentials->>'tier_id' IS NULL; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +-- Remove tier_id field +UPDATE accounts +SET credentials = credentials - 'tier_id' +WHERE platform = 'gemini' + AND type = 'oauth' + AND credentials->>'tier_id' = 'LEGACY'; +-- +goose StatementEnd +``` + +## Troubleshooting + +### Checksum Mismatch +See "If You Accidentally Modified an Applied Migration" above. + +### Migration Failed +```bash +# Check migration status +psql -d sub2api -c "SELECT * FROM schema_migrations ORDER BY applied_at DESC;" + +# Manually rollback if needed (use with caution) +# Better to fix the migration and create a new one +``` + +### Need to Skip a Migration (Emergency Only) +```sql +-- DANGEROUS: Only use in development or with extreme caution +INSERT INTO schema_migrations (filename, checksum, applied_at) +VALUES ('NNN_migration.sql', 'calculated_checksum', NOW()); +``` + +## References + +- Migration runner: `internal/repository/migrations_runner.go` +- Goose syntax: https://github.com/pressly/goose +- PostgreSQL docs: https://www.postgresql.org/docs/