Replace GORM + AutoMigrate with golang-migrate + sqlc#1561
Conversation
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
…ranch Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
|
Important UI Tests need review – Review now🟡 UI Tests: 5 visual and accessibility changes must be accepted as baselines |
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
… install Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
There was a problem hiding this comment.
Pull request overview
This PR replaces GORM's AutoMigrate with golang-migrate (versioned SQL migrations) and sqlc (compile-time type-safe query generation). The migration system manages two independent tracks (core and vector) with atomic cross-track rollback, multi-instance safety via PostgreSQL advisory locks, and automatic dirty-state recovery. Two new CI checks enforce migration immutability and sqlc generation freshness. The changes include 12 database tables migrated to versioned SQL schema, new migration runner with advisory lock support, comprehensive test coverage for cross-track safety and rollback scenarios, and type updates (int→int64, gorm.DeletedAt→*time.Time) throughout the models and handlers.
Changes:
- Migration system: Added golang-migrate runner with advisory locks, dirty-state recovery, and cross-track rollback logic
- Database layer: Replaced GORM with sqlc for type-safe queries; added pgx connection pooling with pgvector type registration
- Schema migrations: Created versioned SQL files for both core (2 migrations) and vector (3 migrations) tracks with proper idempotency guards
- Type updates: Changed model fields from
inttoint64andgorm.DeletedAtto*time.Timeto match sqlc generation patterns - CI enforcement: Added workflows to prevent migration file modification and ensure sqlc-generated code stays in sync with source queries
Reviewed changes
Copilot reviewed 69 out of 70 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| go/core/pkg/migrations/runner.go | Migration execution logic with advisory locks, rollback, and dirty-state recovery |
| go/core/pkg/migrations/cross_track_test.go | Static analysis tests enforcing migration guard patterns and cross-track table ownership |
| go/core/pkg/migrations/core/ & vector/ | Versioned SQL migration files with IF NOT EXISTS/IF EXISTS guards |
| go/core/internal/database/connect.go | Database connection with pgvector type registration and retry logic |
| go/core/internal/database/client_postgres.go | New sqlc-based client replacing GORM-based manager |
| go/api/database/models.go | Clean model types without GORM tags; updated field types for sqlc compatibility |
| go/core/pkg/app/app.go | Integration point calling migration runner before pool connection |
| .github/workflows/ | CI checks for migration immutability and sqlc generation sync |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…grade failure Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
| result = append(result, &parsed) | ||
| } | ||
| return result, nil | ||
| } |
There was a problem hiding this comment.
Feedback.MessageID changed from uint to *int64 and Feedback.ID changed from gorm.Model (embedded uint ID) to explicit int64. These are breaking type changes for any code referencing these fields. Have all callers been updated? (The generated sqlc code and client_postgres.go look correct, just want to confirm nothing outside this PR relies on the old types.)
There was a problem hiding this comment.
Both of these changes were done to align the types with the database. GORM handled the mismatch in the past, but the new types are generated from sqlc, so I updated the API model to match.
All of the internal callers are up-to-date and I have not found evidence that anything outside of this PR relies on those old types.
|
Comment left by Claude on behalf of @EItanya Re: Makefile The Makefile adds Is the migrate image actually used anywhere in deployment, or is it purely an operator tool for manual |
All of that was removed when I changed to in-app migrations in 7034b1c. I believe there could be benefit to having a standalone migrator binary/image, but we can re-evaluate that when the need arises. |
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Description
Warning
Upgrading from a version earlier than v0.8.0 is not supported. You must upgrade to v0.8.0+ before upgrading to this version.
Important
Recommended upgrade path: Take a snapshot/backup of your database before upgrading. A fresh install is the cleanest upgrade path — back up your data, install this version on a fresh database, and restore if needed. In-place upgrades from GORM are supported and tested, but a backup ensures you can recover from any unexpected issues.
GORM's
AutoMigrateis additive-only, leaves no migration history, and has no rollback path. This PR replaces it with versioned SQL migrations (golang-migrate) and compile-time type-safe query generation (sqlc).Two new CI checks are added:
What this changes for operators
CREATE TABLE IF NOT EXISTS, so all data is preserved--database-vector-enabled=true, the runner verifies that the pgvector extension is available before running any migrations. This prevents a failed vector migration from triggering a rollback that could affect core tables.Architecture
Migration SQL files live in
go/core/pkg/migrations/undercore/andvector/subdirectories. These SQL files are the stable contract for downstream consumers.app.Startaccepts an optionalMigrationRunnercallback. Passingniluses the default (migrations.RunUpwith the embeddedmigrations.FS). Downstream consumers can pass a custom runner to take full ownership of the migration process:Multi-instance safety
golang-migrate uses a session-level PostgreSQL advisory lock — only one instance runs migrations at a time. Others block, then find
ErrNoChange. The lock releases automatically if the process crashes. If a crash leaves a dirty migration state, the next startup detects it and rolls back before retrying.Static analysis enforcement
CI tests in
cross_track_test.goenforce migration safety policies without needing a database:TestNoCrossTrackDDLALTER TABLEorCREATE INDEX ONa table owned by another trackTestMigrationGuardsIF NOT EXISTS; down migrations must useIF EXISTSTesting
E2E tests
E2E tests were run against a Kind cluster in three scenarios — all passed:
main— baseline GORM schema, e2e tests passmainto this branch — migrations 000001 + 000002 applied, e2e tests passAdditionally,
main's e2e tests were run against the branch's schema (after migration 000002 appliedSET NOT NULLonfeedback.is_positiveandlg_checkpoint.version) to confirm backward compatibility. All 15 tests passed.Schema validation
pg_dump --schema-onlywas captured after each scenario and compared:Fresh main vs upgrade (expected differences only):
feedback.is_positive→NOT NULLlg_checkpoint.version→NOT NULLmemory.id→DEFAULT gen_random_uuid()schema_migrationstable addedvector_schema_migrationstable addedUpgrade vs fresh branch: Functionally identical. The only differences are column ordering within tables (GORM's struct-field ordering vs the migration file ordering) which has no runtime impact.
Data safety testing
Tested upgrade failure scenarios against an external PostgreSQL (non-pgvector) database to verify data protection: