Skip to content

fix(db): preserve expression-based indexes in autogenerate - #144

Merged
antosubash merged 1 commit into
mainfrom
feature/feat-autogenerate-silently-ix-users-user-email-lower-sqlite-drkss
May 13, 2026
Merged

fix(db): preserve expression-based indexes in autogenerate#144
antosubash merged 1 commit into
mainfrom
feature/feat-autogenerate-silently-ix-users-user-email-lower-sqlite-drkss

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Summary

  • SQLAlchemy 2.0 can't reflect functional indexes under SQLite, so Alembic autogenerate silently skips them — leaving dev SQLite DBs without indexes prod Postgres has. Adds a framework-level process_revision_directives hook that re-injects expression-based indexes into CreateTableOp directives (with dedup so it's a no-op on Postgres where autogen emits them correctly).
  • Wires the hook into both the in-repo host/migrations/env.py and the smpy new scaffold template, so every host — existing or freshly scaffolded — gets the fix.
  • Ships catch-up migration 41cf2c53660e that back-fills ix_users_user_email_lower on already-migrated DBs, idempotent via if_not_exists / if_exists.

Closes #140.

Test plan

  • uv run pytest framework/db/tests/test_migrations.py — 4 new tests in TestProcessRevisionDirectives cover injection, no-op on non-create-table revisions, no double-emit on Postgres, and no-op for plain column indexes.
  • make lint (ruff + ty) — green on all touched files; 300-line file-cap check passes.
  • End-to-end: deleted host/app.db, ran alembic upgrade head on SQLite, confirmed ix_users_user_email_lower is present in sqlite_master.
  • Re-running alembic revision --autogenerate against head produces an empty migration (no spurious diff from the hook).

Closes #140. SQLAlchemy 2.0 can't reflect functional indexes (e.g.
`CREATE INDEX ... ON t (lower(email))`) under the SQLite dialect, so
Alembic autogenerate silently skips them — leaving SQLite dev DBs
without an index that production Postgres has.

- Add `make_process_revision_directives(metadata)` in
  `simple_module_db.migrations`: walks each generated `MigrationScript`
  and, for any `CreateTableOp` whose table has expression-based indexes
  in the target metadata, appends a matching `CreateIndexOp` (and the
  reverse `DropIndexOp` in the downgrade). Dedups against already-emitted
  ops so the hook is a no-op on Postgres.
- Wire the hook into `host/migrations/env.py` and the scaffold template
  so every existing and `smpy new`-generated host gets it.
- Add catch-up migration `41cf2c53660e` that back-fills
  `ix_users_user_email_lower` on databases already past
  `3bf3f9db7f7f`. Uses `if_not_exists` / `if_exists` so it's a no-op
  on installs that somehow already have the index.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 87f455a
Status: ✅  Deploy successful!
Preview URL: https://2f58bc5f.simple-module-python.pages.dev
Branch Preview URL: https://feature-feat-autogenerate-si.simple-module-python.pages.dev

View logs

@antosubash
antosubash merged commit ef2a54e into main May 13, 2026
12 checks passed
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.

Autogenerate silently drops ix_users_user_email_lower on SQLite — dev/prod schema diverges

1 participant