Skip to content

Normalize feedback primary key to (id) for upgraded installs#1801

Open
iplay88keys wants to merge 1 commit intomainfrom
iplay88keys/feedback-migration-upgrade-consistency
Open

Normalize feedback primary key to (id) for upgraded installs#1801
iplay88keys wants to merge 1 commit intomainfrom
iplay88keys/feedback-migration-upgrade-consistency

Conversation

@iplay88keys
Copy link
Copy Markdown
Contributor

@iplay88keys iplay88keys commented May 5, 2026

Summary

Adds migration 000004_feedback_single_pk that converts the feedback table's primary key from the legacy composite (id, user_id) to single (id), so fresh installs and upgraded installs end at the same schema.

Background

Feedback's PK shape diverged in OSS PR #1306 (commit cbc8d57e, first released in v0.7.15):

Version Feedback struct GORM AutoMigrate result
≤ v0.7.14 gorm.Model + UserID gorm:"primaryKey;not null" composite PK (id, user_id)
≥ v0.7.15 (after #1306) gorm.Model + UserID gorm:"not null;index" single-column PK (id)

GORM AutoMigrate is additive-only, so it cannot drop a primary key. Any deployment that was ever migrated under v0.7.14 or earlier kept the composite PK forever, even after the struct was fixed.

The 000001_initial baseline matches the fixed struct's output (id BIGSERIAL PRIMARY KEY), which is correct for fresh installs but no-ops on upgraded installs (the CREATE TABLE IF NOT EXISTS is skipped). Result: upgraded installs still carry the composite PK after running the new migration tooling.

Risks

Duplicate id rows would block the migration. The old composite (id, user_id) only enforces uniqueness of the pair, so it permits two rows with id=1 if their user_id differs. The new single-column (id) requires id itself to be unique. This isn't purely hypothetical for long-lived deployments: throughout the GORM era (and still today, until sqlc closed the gap in v0.9.x) the feedback HTTP handler unmarshalled the entire database.Feedback struct directly from the request body, including the embedded gorm.Model.ID field — so a client sending {"id": 99999, "user_id": "...", ...} could insert with an explicit id. The pre-fix save() helper used ON CONFLICT ... DO UPDATE on the composite PK, and StoreFeedback after PR #1306 used db.Create(); both paths passed a non-zero ID through to Postgres unchanged. The composite PK then allowed the same explicit id to appear under multiple user_id values. The sqlc-era query is hardcoded to omit id from the column list, which is why this gap doesn't grow further on v0.9.x — but pre-v0.9.x history is what's already in the database.

If any such rows exist, ADD CONSTRAINT feedback_pkey PRIMARY KEY (id) will fail with a unique-violation and the migration aborts atomically. This surfaces at startup before traffic, which is the correct failure mode, but operators should run the following against any long-lived database before deploying and dedupe if it returns rows:

SELECT id, COUNT(*) FROM feedback GROUP BY id HAVING COUNT(*) > 1;

Brief table lock during migration. DROP CONSTRAINT and ADD CONSTRAINT PRIMARY KEY each take an AccessExclusiveLock on feedback, blocking all reads and writes for the duration. Adding the new PK requires a full table scan to validate uniqueness and not-null. For typical feedback data sizes this completes in milliseconds; an unusually large table could take seconds. The whole migration runs in a single transaction so the state is always consistent.

No foreign keys reference feedback.id, so there are no cascades or referential-integrity ripples to manage.

Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
@iplay88keys iplay88keys marked this pull request as ready for review May 5, 2026 20:15
Copilot AI review requested due to automatic review settings May 5, 2026 20:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a corrective core migration to make upgraded databases converge on the same feedback schema that fresh installs already get: PRIMARY KEY (id) instead of the legacy composite key. This fits the migration subsystem’s goal of making schema state deterministic across install/upgrade paths.

Changes:

  • Add 000004_feedback_single_pk.up.sql to replace the legacy feedback composite primary key with a single-column primary key on id.
  • Add a documented no-op down migration for this convergence step.
  • Document the migration’s assumptions and its relationship to existing feedback queries/indexes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
go/core/pkg/migrations/core/000004_feedback_single_pk.up.sql Adds the schema change that normalizes feedback PKs to (id).
go/core/pkg/migrations/core/000004_feedback_single_pk.down.sql Adds a no-op rollback file with rationale for leaving the normalized PK in place.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +9
-- No-op: 000004 is a corrective convergence step. Both fresh installs (which
-- already had PRIMARY KEY (id) from 000001) and upgraded installs (which
-- carried the legacy composite PRIMARY KEY (id, user_id) from GORM
-- AutoMigrate) end at PRIMARY KEY (id). Once up has run, there is no way to
-- tell which path the database came from, so a symmetric down would put fresh
-- installs into a composite-PK state they never originally had. Leaving the
-- single-column PK in place on rollback is safer and the application is
-- PK-shape independent (queries do not reference the PK directly).
SELECT 1;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would result in a fresh install env applying this migration on top having the wrong primary key. I think it's fine to leave as a result.

-- Both variants use the default constraint name `feedback_pkey`, so this
-- atomic drop+add converges both paths to the single-column PK without
-- changing column types or affecting sqlc-generated code (queries do not
-- reference the PK; ListFeedback filters on user_id via idx_feedback_user_id).
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EItanya I don't think this is necessary based on the limited usefulness of the feedback feature and the unlikelihood that this will have duplicate ids. The only reason I'm considering it is to have a clearer message for manual fixes in the unlikely event that there is an issue with this upgrade. Another option is we we could just raise it in the release notes if we want, but as before, I think this is unlikely to be an issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants