diff --git a/server/cmd/migrate/main.go b/server/cmd/migrate/main.go index dfac877..ae96592 100644 --- a/server/cmd/migrate/main.go +++ b/server/cmd/migrate/main.go @@ -12,6 +12,7 @@ package main import ( + "context" "fmt" "log" "os" @@ -44,6 +45,9 @@ func main() { if err := store.MigrateUp(db, driver); err != nil { log.Fatalf("migrate up: %v", err) } + if err := store.ApplyCodesLibMigrations(context.Background(), db, driver); err != nil { + log.Fatalf("migrate up (codes lib): %v", err) + } log.Println("migrate: up — done") case "down": diff --git a/server/go.mod b/server/go.mod index 275c355..72a2448 100644 --- a/server/go.mod +++ b/server/go.mod @@ -17,6 +17,7 @@ require ( github.com/testcontainers/testcontainers-go v0.43.0 github.com/testcontainers/testcontainers-go/modules/mysql v0.43.0 github.com/testcontainers/testcontainers-go/modules/redis v0.43.0 + github.com/wangjia/codes v0.0.0-00010101000000-000000000000 golang.org/x/crypto v0.52.0 golang.org/x/term v0.43.0 google.golang.org/grpc v1.81.1 diff --git a/server/internal/store/codes_lib_migrate_test.go b/server/internal/store/codes_lib_migrate_test.go new file mode 100644 index 0000000..1a6e786 --- /dev/null +++ b/server/internal/store/codes_lib_migrate_test.go @@ -0,0 +1,41 @@ +package store_test + +import ( + "context" + "testing" + + "github.com/wangjia/pangolin/server/internal/config" + "github.com/wangjia/pangolin/server/internal/store" +) + +// TestApplyCodesLibMigrations verifies the shared codes library's tables are +// created on top of a fully-migrated pangolin sqlite DB (post-000020, the +// legacy tables are renamed away so the lib's `codes` table name is free). +func TestApplyCodesLibMigrations(t *testing.T) { + ctx := context.Background() + db, err := store.Open(&config.Config{Driver: "sqlite", DSN: ":memory:"}) + if err != nil { + t.Fatalf("open: %v", err) + } + defer db.Close() + if err := store.MigrateUp(db, "sqlite"); err != nil { + t.Fatalf("MigrateUp: %v", err) + } + + if err := store.ApplyCodesLibMigrations(ctx, db, "sqlite"); err != nil { + t.Fatalf("ApplyCodesLibMigrations: %v", err) + } + // 幂等:重复调用不报错(库自带 codes_schema_migrations 追踪表)。 + if err := store.ApplyCodesLibMigrations(ctx, db, "sqlite"); err != nil { + t.Fatalf("ApplyCodesLibMigrations 2nd: %v", err) + } + + for _, tbl := range []string{"codes", "codes_batches", "codes_audit_log", "codes_schema_migrations"} { + var name string + if err := db.QueryRow( + `SELECT name FROM sqlite_master WHERE type='table' AND name=?`, tbl, + ).Scan(&name); err != nil { + t.Errorf("lib table %q missing: %v", tbl, err) + } + } +} diff --git a/server/internal/store/migrate.go b/server/internal/store/migrate.go index 3d8d428..a30fa3e 100644 --- a/server/internal/store/migrate.go +++ b/server/internal/store/migrate.go @@ -1,6 +1,7 @@ package store import ( + "context" "database/sql" "errors" "fmt" @@ -11,6 +12,7 @@ import ( migratemysql "github.com/golang-migrate/migrate/v4/database/mysql" migratesqlite "github.com/golang-migrate/migrate/v4/database/sqlite" "github.com/golang-migrate/migrate/v4/source/iofs" + libcodes "github.com/wangjia/codes" "github.com/wangjia/pangolin/server/internal/db" "github.com/wangjia/pangolin/server/migrations" @@ -24,6 +26,18 @@ func MigrateUp(database *sql.DB, driver string) error { }) } +// ApplyCodesLibMigrations creates/updates the shared codes library's own +// tables (codes / codes_batches / codes_audit_log, tracked by the lib's +// codes_schema_migrations). Runs AFTER pangolin's golang-migrate set — 000020 +// must have renamed the legacy `codes` table away first. Idempotent. +func ApplyCodesLibMigrations(ctx context.Context, database *sql.DB, driver string) error { + d := libcodes.DialectMySQL + if db.Normalize(driver) == "sqlite" { + d = libcodes.DialectSQLite + } + return libcodes.ApplyMigrations(ctx, database, d) +} + // MigrateDown rolls back all applied migrations. ErrNoChange = success. func MigrateDown(database *sql.DB, driver string) error { return runMigration(database, driver, func(m *migrate.Migrate) error { diff --git a/server/migrations/sqlite/000020_codes_lib_legacy_rename.down.sql b/server/migrations/sqlite/000020_codes_lib_legacy_rename.down.sql index 32cdbcf..cc0b779 100644 --- a/server/migrations/sqlite/000020_codes_lib_legacy_rename.down.sql +++ b/server/migrations/sqlite/000020_codes_lib_legacy_rename.down.sql @@ -1,2 +1,4 @@ +DROP INDEX idx_legacy_codes_status; ALTER TABLE legacy_code_batches RENAME TO code_batches; ALTER TABLE legacy_codes RENAME TO codes; +CREATE INDEX idx_codes_status ON codes (status); diff --git a/server/migrations/sqlite/000020_codes_lib_legacy_rename.up.sql b/server/migrations/sqlite/000020_codes_lib_legacy_rename.up.sql index 99d5069..2d60a57 100644 --- a/server/migrations/sqlite/000020_codes_lib_legacy_rename.up.sql +++ b/server/migrations/sqlite/000020_codes_lib_legacy_rename.up.sql @@ -1,3 +1,8 @@ -- 同 mysql 版注释;sqlite ≥3.25 的 RENAME 会自动更新其他表 FK 引用。 ALTER TABLE codes RENAME TO legacy_codes; ALTER TABLE code_batches RENAME TO legacy_code_batches; +-- sqlite 的索引名是库级全局命名空间,ALTER TABLE RENAME 不会连带重命名索引: +-- idx_codes_status 仍挂在 legacy_codes 上但保留旧名,会跟 codes 库自己迁移里 +-- 同名的 idx_codes_status(建在它的新 codes 表上)冲突。这里随表改名一并重建索引名。 +DROP INDEX idx_codes_status; +CREATE INDEX idx_legacy_codes_status ON legacy_codes (status);