Skip to content

fix(migrations): work around GinIndex+OpClass codegen regression in Django 5.x#926

Open
cmorinupgrade wants to merge 2 commits into
codecov:mainfrom
cmorinupgrade:fix/gin-opclass-django5-migration-codegen
Open

fix(migrations): work around GinIndex+OpClass codegen regression in Django 5.x#926
cmorinupgrade wants to merge 2 commits into
codecov:mainfrom
cmorinupgrade:fix/gin-opclass-django5-migration-codegen

Conversation

@cmorinupgrade
Copy link
Copy Markdown

@cmorinupgrade cmorinupgrade commented May 21, 2026

Problem

GinIndex(OpClass(Upper(field), name="gin_trgm_ops")) generates invalid SQL under Django 5.x. The operator class ends up inside the expression parens instead of after them:

-- Generated (broken):
CREATE INDEX ... USING gin ((UPPER("name") gin_trgm_ops))

-- Correct:
CREATE INDEX ... USING gin ((UPPER("name")) gin_trgm_ops)

PostgreSQL rejects this with syntax error at or near "gin_trgm_ops", causing the worker to crash-loop on startup when it tries to run migrations.

Two migrations are affected:

  • core/0056_branch_name_trgm_idx
  • core/0080_repository_repos_name_trgm_idx

Both were generated against Django 4.2 and worked fine then, but fail on Django 5.x.

Fix

Replace RiskyAddIndex with SeparateDatabaseAndState:

  • database_operations: RiskyRunSQL with the correct hardcoded SQL (preserves the risky-skip behaviour via SKIP_RISKY_MIGRATION_STEPS)
  • state_operations: plain AddIndex to keep Django's model state in sync

The SQL used is exactly what the migration docstrings already document as the intended DDL. IF NOT EXISTS makes it safe to re-run.

Testing

Verified against PostgreSQL 14 with Django 5.x — migrations apply cleanly.


Note

Medium Risk
Touches production database migrations by replacing ORM-generated index DDL with raw RunSQL, so mistakes could break migration application or leave schema/state mismatched, though the change is limited to two index creations and uses IF NOT EXISTS/reversible SQL.

Overview
Works around a Django 5.x SQL generation regression for GinIndex(OpClass(...)) by changing two trigram index migrations to use SeparateDatabaseAndState.

The DB step now runs explicit CREATE INDEX IF NOT EXISTS ... gin_trgm_ops via RiskyRunSQL (preserving SKIP_RISKY_MIGRATION_STEPS behavior), while the migration state still records the corresponding AddIndex so Django’s model state remains consistent.

Reviewed by Cursor Bugbot for commit cc7d9f1. Bugbot is set up for automated code reviews on this repo. Configure here.

…jango 5.x

GinIndex(OpClass(Upper(field), name=opclass)) generates invalid SQL under
Django 5.x: the operator class ends up inside the expression parens instead
of after them, causing a PostgreSQL syntax error on CREATE INDEX.

Migrations 0056 and 0080 both hit this pattern. Fix both by switching to
SeparateDatabaseAndState: database_operations runs the correct SQL via
RiskyRunSQL (preserving the risky-skip behaviour), state_operations uses
AddIndex to keep Django model state in sync.
@cmorinupgrade cmorinupgrade requested a review from a team as a code owner May 21, 2026 15:09
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e7a8b64. Configure here.

Comment thread libs/shared/shared/django_apps/core/migrations/0056_branch_name_trgm_idx.py Outdated
Branch.name has db_column="branch", so the index must use UPPER("branch")
not UPPER("name"). Caught by Cursor Bugbot on PR review.
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.

1 participant